<?php
/**
 * Rest API functions
 *
 * @package Lazy Blocks
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class LazyBlocks_Rest
 */
class LazyBlocks_Rest extends WP_REST_Controller {
	/**
	 * Namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'lazy-blocks/v';

	/**
	 * Version.
	 *
	 * @var string
	 */
	protected $version = '1';

	/**
	 * LazyBlocks_Rest constructor.
	 */
	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * Register rest routes.
	 */
	public function register_routes() {
		$namespace = $this->namespace . $this->version;

		// Get Lazy Block Editor Preview.
		register_rest_route(
			$namespace,
			'/block-render/',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'get_block' ),
				'permission_callback' => array( $this, 'get_block_permission' ),
			)
		);

		// Get Lazy Block Data.
		register_rest_route(
			$namespace,
			'/get-block-data/',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_block_data' ),
				'permission_callback' => array( $this, 'get_block_data_permission' ),
			)
		);

		// Update Lazy Block Data.
		register_rest_route(
			$namespace,
			'/update-block-data/',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_block_data' ),
				'permission_callback' => array( $this, 'update_block_data_permission' ),
			)
		);

		// Get WP post types.
		register_rest_route(
			$namespace,
			'/get-post-types/',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_post_types' ),
				'permission_callback' => array( $this, 'get_post_types_permission' ),
			)
		);

		// Get Lazy Block Editor Preview.
		register_rest_route(
			$namespace,
			'/block-builder-preview/',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'block_builder_preview' ),
				'permission_callback' => array( $this, 'block_builder_preview_permission' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to read blocks.
	 *
	 * @since 2.8.0
	 * @access public
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response|true
	 */
	public function get_block_permission( $request ) {
		global $post;

		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;

		if ( 0 < $post_id ) {
			// phpcs:ignore
			$post = get_post( $post_id );

			if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
				return $this->error( 'lazy_block_cannot_read', esc_html__( 'Sorry, you are not allowed to read Gutenberg blocks of this post.', 'lazy-blocks' ), true );
			}
		} elseif ( ! current_user_can( 'edit_posts' ) ) {
			return $this->error( 'lazy_block_cannot_read', esc_html__( 'Sorry, you are not allowed to read Gutenberg blocks as this user.', 'lazy-blocks' ), true );
		}

		return true;
	}

	/**
	 * Get read block data permissions.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response|true
	 */
	public function get_block_data_permission( $request ) {
		global $post;

		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;

		if ( 0 < $post_id ) {
			// phpcs:ignore
			$post = get_post( $post_id );
			if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
				return $this->error( 'lazy_block_data_cannot_read', esc_html__( 'Sorry, you are not allowed to read Gutenberg blocks data.', 'lazy-blocks' ), true );
			}
		} elseif ( ! current_user_can( 'edit_posts' ) ) {
			return $this->error( 'lazy_block_data_cannot_read', esc_html__( 'Sorry, you are not allowed to read Gutenberg blocks data as this user.', 'lazy-blocks' ), true );
		}

		return true;
	}

	/**
	 * Get edit block data permissions.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response|true
	 */
	public function update_block_data_permission( $request ) {
		return $this->get_block_data_permission( $request );
	}

	/**
	 * Get read wp post types permissions.
	 *
	 * @return WP_REST_Response|true
	 */
	public function get_post_types_permission() {
		if ( ! current_user_can( 'edit_posts' ) ) {
			return $this->error( 'lazy_block_data_cannot_read', esc_html__( 'Sorry, you are not allowed to read Gutenberg blocks data as this user.', 'lazy-blocks' ), true );
		}

		return true;
	}

	/**
	 * Get block builder preview permissions.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response|true
	 */
	public function block_builder_preview_permission( $request ) {
		$base_permission = $this->get_block_data_permission( $request );

		if ( is_wp_error( $base_permission ) || true !== $base_permission ) {
			return $base_permission;
		}

		// Check if the block uses PHP output method and requires unfiltered_html capability.
		$block = $request->get_param( 'block' );
		if (
			isset( $block['code_output_method'] ) &&
			'php' === $block['code_output_method'] &&
			! current_user_can( 'unfiltered_html' )
		) {
			return $this->error( 'lazy_block_cannot_execute_php', esc_html__( 'Not allowed to execute PHP code.', 'lazy-blocks' ), true );
		}

		return true;
	}

	/**
	 * Returns block output from block's registered render_callback.
	 *
	 * @since 2.8.0
	 * @access public
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response
	 */
	public function get_block( $request ) {
		global $post;

		$post_id          = $request->get_param( 'post_id' ) ? intval( $request->get_param( 'post_id' ) ) : 0;
		$render_location  = $request->get_param( 'render_location' );
		$block_name       = $request->get_param( 'name' );
		$block_attributes = $request->get_param( 'attributes' );
		$context          = $request->get_param( 'context' );

		// add global data to fix meta data output in preview.
		global $lzb_preview_block_data;
		$lzb_preview_block_data = array(
			'post_id'          => $post_id,
			'render_location'  => $render_location,
			'block_name'       => $block_name,
			'block_attributes' => $block_attributes,
			'context'          => $context,
		);

		if ( 0 < $post_id ) {
			// phpcs:ignore
			$post = get_post( $post_id );

			// Set up postdata since this will be needed if post_id was set.
			setup_postdata( $post );
		}
		$block = lazyblocks()->blocks()->get_block( $block_name );

		if ( ! $block ) {
			return $this->error( 'lazy_block_invalid', esc_html__( 'Invalid block.', 'lazy-blocks' ) );
		}

		$block_result = lazyblocks()->blocks()->render_callback( $block_attributes, $context, null, $render_location, $block );

		if ( isset( $block_result ) && null !== $block_result ) {
			return $this->success( $block_result );
		} else {
			return $this->error( 'lazy_block_no_render_callback', esc_html__( 'Render callback is not specified.', 'lazy-blocks' ) );
		}
	}

	/**
	 * Update block data.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response
	 */
	public function update_block_data( $request ) {
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
		$data    = isset( $request['data'] ) ? $request['data'] : false;
		$meta    = array();

		if ( 0 < $post_id && $data ) {
			$meta_prefix = 'lazyblocks_';

			// add 'lazyblocks_' prefix.
			foreach ( $data as $k => $val ) {
				$meta[ $meta_prefix . $k ] = $val;
			}

			lazyblocks()->blocks()->save_meta_boxes( $post_id, $meta );

			// Clear blocks cache after updating block data.
			lazyblocks()->blocks()->clear_blocks_cache();
		}

		return $this->success( $meta );
	}

	/**
	 * Get block data.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response
	 */
	public function get_block_data( $request ) {
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
		$meta    = array();

		if ( 0 < $post_id ) {
			$post_meta   = lazyblocks()->blocks()->get_meta_boxes( $post_id );
			$meta_prefix = 'lazyblocks_';

			// remove 'lazyblocks_' prefix.
			foreach ( $post_meta as $k => $val ) {
				if ( substr( $k, 0, strlen( $meta_prefix ) ) === $meta_prefix ) {
					$meta[ substr( $k, strlen( $meta_prefix ) ) ] = $val;
				}
			}
		}

		return $this->success( $meta );
	}

	/**
	 * Get WP post types.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response
	 */
	public function get_post_types( $request ) {
		$args     = isset( $request['args'] ) ? $request['args'] : array();
		$output   = isset( $request['output'] ) ? $request['output'] : 'names';
		$operator = isset( $request['operator'] ) ? $request['operator'] : 'and';

		$result = get_post_types( $args, $output, $operator );

		return $this->success( $result );
	}

	/**
	 * Returns block output from block's registered custom_render.
	 *
	 * @access public
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response
	 */
	public function block_builder_preview( $request ) {
		$block      = $request->get_param( 'block' );
		$context    = $request->get_param( 'context' );
		$attributes = $request->get_param( 'attributes' );

		global $lzb_block_builder_preview;
		$lzb_block_builder_preview = true;

		// Prepare clean block data for marshal method.
		// Add 'lazyblocks_' prefix to all block attributes.
		$block_data = array();

		foreach ( $block as $k => $val ) {
			$block_data[ 'lazyblocks_' . $k ] = $val;
		}

		$block_data = lazyblocks()->blocks()->marshal_block_data( $block_data );

		// Prepare standard lazyblock attributes.
		$attributes['blockId']          = 'preview-id';
		$attributes['blockUniqueClass'] = 'lazyblock-preview-class';
		$attributes['anchor']           = '';
		$attributes['className']        = '';
		$attributes['align']            = '';
		$attributes['lazyblock']        = array(
			'slug' => $block_data['slug'],
		);

		/**
		 * The WP_Block_Supports class requires a block_to_render check because of its dependency chain.
		 * When get_block_wrapper_attributes runs, it calls apply_block_supports.
		 * This method then needs self::$block_to_render['blockName'], which causes an error if block_to_render is null.
		 *
		 * The issue only appears on the block designer page.
		 * When calling the REST method, the block hasn't been registered in the system yet, which causes errors.
		 * We fix this by assigning a block to the block_to_render property,
		 * Which allows the block attributes to display correctly in the block designer preview.
		 */
		if ( class_exists( 'WP_Block_Supports' ) ) {
			$block_data['blockName'] = $block_data['blockName'] ?? $block_data['slug'];

			WP_Block_Supports::$block_to_render = $block_data;
		}

		try {
			$block_result = lazyblocks()->blocks()->render_callback( $attributes, null, null, $context, $block_data );
		} catch ( Throwable $e ) {
			return $this->error( 'lazy_block_render_failed', $e->getMessage() . PHP_EOL . PHP_EOL . $e->getTraceAsString() );
		}

		if ( isset( $block_result ) && null !== $block_result ) {
			return $this->success( $block_result );
		} else {
			return $this->error( 'lazy_block_no_render_callback', esc_html__( 'Render callback is not specified.', 'lazy-blocks' ) );
		}
	}

	/**
	 * Success rest.
	 *
	 * @param mixed $response response data.
	 *
	 * @return WP_REST_Response
	 */
	public function success( $response ) {
		return new WP_REST_Response(
			array(
				'success'  => true,
				'response' => $response,
			),
			200
		);
	}

	/**
	 * Error rest.
	 *
	 * @param mixed   $code       error code.
	 * @param mixed   $response   response data.
	 * @param boolean $true_error use true error response to stop the code processing.
	 * @return mixed
	 */
	public function error( $code, $response, $true_error = false ) {
		if ( $true_error ) {
			return new WP_Error( $code, $response, array( 'status' => 401 ) );
		}

		return new WP_REST_Response(
			array(
				'error'      => true,
				'success'    => false,
				'error_code' => $code,
				'message'    => $response,
			),
			401
		);
	}
}
new LazyBlocks_Rest();
