<?php
/**
 * Preview Token Class
 *
 * @package HeadlessWP
 */

namespace HeadlessWP\Preview;

use HeadlessWP\BaseToken;
use HeadlessWP\JWT\JWT;

/**
 * Class with static methods to generate and parse capability tokens.
 */
class PreviewToken extends BaseToken {

	/**
	 * Preview TOKEN Payload
	 *
	 * @var array
	 */
	private static $payload = null;

	/**
	 * Add actions and filters.
	 */
	public static function setup() {
		$payload       = self::get_payload_from_token();
		self::$payload = $payload;

		// Filter that modifies user capabilities during runtime.
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST && $payload ) {
			add_filter( 'user_has_cap', [ self::class, 'user_has_cap' ], 20, 4 );
		}
	}

	/**
	 * Validate a token using args.
	 *
	 * @param array $args The arguments
	 */
	public static function check_capability( $args ) {
		$payload = (array) self::$payload;

		// Do not authenticate if the token was not generated by Frontity.
		if ( '10up-headless-wp' !== $payload['generator'] ) {
			return false;
		}
		// Do not authenticate if the token is not for the preview.
		if ( 'preview' !== $payload['type'] ) {
			return false;
		}
		// Prevent using the token for requests that are not GET.
		if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
			return false;
		}
		// Prevent using the token for requests that are GET, but use the
		// `_method` query to override the method.
		if ( isset( $_GET['_method'] ) && 'GET' !== $_GET['_method'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			return false;
		}
		// Prevent using the token for requests that are GET, but use the
		// `X-HTTP-Method-Override` header to override the method.
		if ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) && 'GET' !== $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) {
			return false;
		}

		$post_id   = $payload['post_id'];
		$post_type = $payload['post_type'];

		// Allowed capabilities when the token is type 'preview'. You also need to
		// have permission to 'edit_post' or 'delete_post' for preview posts.
		$capabilities = [
			'read_post'   => $post_id,
			'edit_post'   => $post_id,
			'delete_post' => $post_id,
		];

		// Prior to WordPress 5.5.1, capabilities should be specified with `page`
		// for pages, so we are adding them as well to support older versions of
		// WordPress.
		if ( 'page' === $post_type ) {
			$capabilities = array_merge(
				$capabilities,
				[
					'read_page'   => $post_id,
					'edit_page'   => $post_id,
					'delete_page' => $post_id,
				]
			);
		}

		// Use key-value to check capabilities with an associated ID.
		if ( count( $args ) === 3 ) {
			// Get capability and ID.
			list( $cap, $_, $id ) = $args;
			// Find that capability in the capabilities array.
			return isset( $capabilities[ $cap ] ) && $capabilities[ $cap ] === $id;
		}

		// If it is a global capability, check if it is included as value.
		list( $cap ) = $args;
		return in_array( $cap, $capabilities, true );
	}

	/**
	 * Modify user capabilities on run time.
	 *
	 * @param array $allcaps All capabilities.
	 * @param array $caps Capabilities.
	 * @param array $args Arguments.
	 */
	public static function user_has_cap( $allcaps, $caps, $args ) {
		// Add capability if it is allowed in the token.
		if ( self::check_capability( $args ) ) {
			foreach ( $caps as $cap ) {
				$allcaps[ $cap ] = true;
			}
		}

		// Return capabilities.
		return $allcaps;
	}
}
