Source: pro/classes/controller/rest.php

<?php

namespace wpbuddy\rich_snippets\pro;

use wpbuddy\rich_snippets\Rich_Snippet;
use wpbuddy\rich_snippets\Snippets_Model;
use wpbuddy\rich_snippets\View;
use wpbuddy\rich_snippets\WPBuddy_Model;
use function wpbuddy\rich_snippets\rich_snippets;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly


/**
 * Class Rest.
 *
 * Here for any REST things.
 *
 * @package wpbuddy\rich_snippets
 *
 * @since   2.19.0
 */
class Rest_Controller extends \wpbuddy\rich_snippets\Rest_Controller {

	/**
	 * Get the singleton instance.
	 *
	 * Creates a new instance of the class if it does not exists.
	 *
	 * @return Rest_Controller
	 *
	 * @since 2.19.0
	 */
	public static function instance() {

		if ( null === self::$_instance ) {
			self::$_instance = new self;
		}

		return self::$_instance;
	}

	/**
	 * Initializes admin stuff
	 *
	 * @since 2.19.0
	 */
	public static function init() {
		$instance = self::instance();

		parent::init();

		add_filter( 'wpbuddy/rich_snippets/rest/activate_plugin', [ $instance, 'activate_plugin_filter' ], 10, 2 );

		add_filter( 'wpbuddy/rich_snippets/rest/property/html/actions', [
			Admin_Snippets_Controller::instance(),
			'property_actions'
		] );

		# register REST fields
		$instance->register_rest_fields();
	}


	/**
	 * Registers the REST fields needed.
	 *
	 * @since 2.14.0
	 */
	protected function register_rest_fields() {
		register_rest_field( 'wpb-rs-global', 'schemas', [
			'get_callback'    => [ $this, 'get_schema_field' ],
			'schema'          => [
				'type'        => 'object',
				'description' => __( 'Schemas saved on a post.', 'rich-snippets-schema' ),
				'context'     => [ 'view', 'edit' ],
				'arg_options' => array(
					'sanitize_callback' => [ $this, 'sanitize_schema_field' ],
				),
			],
			'update_callback' => [ $this, 'update_schema_field' ],
		] );

		register_rest_field( 'wpb-rs-global', 'position', [
			'get_callback'    => [ $this, 'get_position_field' ],
			'schema'          => [
				'type'        => 'object',
				'description' => __( 'The position of the schema.', 'rich-snippets-schema' ),
				'context'     => [ 'view', 'edit' ],
				'arg_options' => array(
					'sanitize_callback' => [ $this, 'sanitize_position_field' ],
				),
			],
			'update_callback' => [ $this, 'update_position_field' ],
		] );
	}


	/**
	 * Returns the position saved to a post.
	 *
	 * @param array $object
	 * @param string $field_name
	 * @param \WP_REST_Request $request
	 * @param string $object_type
	 *
	 * @return string JSON encoded representation of the ruleset.
	 * @since 2.14.0
	 */
	public function get_position_field( $object, $field_name, $request, $object_type ) {

		return Rules_Model::get_ruleset( $object['id'] )->__toString();
	}

	/**
	 * Registers the REST routes.
	 *
	 * @since 2.0.0
	 */
	protected function register_routes() {
		parent::register_routes();

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'overwrite_form', array(
			'methods'             => \WP_REST_Server::READABLE,
			'callback'            => array( self::instance(), 'get_overwrite_form' ),
			'permission_callback' => function ( $request ) {

				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission',
					current_user_can( 'manage_options' ),
					$request
				);
			},
			'args'                => array(
				'post_id'         => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {

						# check if post exists
						return is_string( get_post_status( absint( $param ) ) );
					},
					'sanitize_callback' => function ( $param ) {

						return absint( $param );
					},
				),
				'snippet_post_id' => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {

						# check if post exists
						return is_string( get_post_status( absint( $param ) ) );
					},
					'sanitize_callback' => function ( $param ) {

						return absint( $param );
					},
				),
			),
		) );

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'overwrite', array(
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => array( self::instance(), 'overwrite_snippet_values' ),
			'permission_callback' => function ( $request ) {

				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission',
					current_user_can( 'manage_options' ),
					$request
				);
			},
			'args'                => array(
				'post_id'              => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {

						# check if post exists
						return is_string( get_post_status( absint( $param ) ) );
					},
					'sanitize_callback' => function ( $param ) {

						return absint( $param );
					},
				),
				'main_snippet_post_id' => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {
						return is_string( get_post_status( intval( $param ) ) );
					},
					'sanitize_callback' => function ( $param ) {

						return intval( $param );
					},
				),
				'main_snippet_id'      => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {

						# check if post exists
						return is_string( $param );
					},
					'sanitize_callback' => function ( $param ) {

						return sanitize_text_field( $param );
					},
				),
			),
		) );

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'feature-request/vote', array(
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => array( self::instance(), 'support_feature_vote' ),
			'permission_callback' => function ( $request ) {

				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission',
					current_user_can( 'manage_options' ),
					$request
				);
			},
			'args'                => array(
				'comment_id' => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {

						$comment_id = intval( $param );

						return ! empty( $comment_id );
					},
					'sanitize_callback' => function ( $param ) {

						return intval( $param );
					},
				),
				'direction'  => array(
					'required'          => true,
					'validate_callback' => function ( $param, $request, $key ) {

						return in_array( $param, array( 'up', 'down' ) );
					},
					'sanitize_callback' => function ( $param ) {

						return sanitize_text_field( $param );
					},
				),
			),
		) );

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'feature-request', array(
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => array( self::instance(), 'support_feature_add' ),
			'permission_callback' => function ( $request ) {

				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission',
					current_user_can( 'manage_options' ),
					$request
				);
			},
			'args'                => array(
				'content' => array(
					'required'          => true,
					'sanitize_callback' => function ( $param ) {

						return sanitize_textarea_field( $param );
					},
				),
			),
		) );

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'deactivate-license', array(
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => array( self::instance(), 'deactivate_license' ),
			'permission_callback' => function ( $request ) {

				/**
				 * REST deactivate license permission filter.
				 *
				 * Allows to modify the capability for the REST access on deactivating the license.
				 *
				 * @hook  wpbuddy/rich_snippets/rest/permission/deactivate_license
				 *
				 * @param {string} $capability The capability for the REST access. Default: manage_options.
				 * @param {WP_Rest_Request} $request
				 *
				 * @returns {string} The capability for the REST persmission.
				 *
				 * @since 2.0.0
				 */
				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission/deactivate_license',
					current_user_can( 'manage_options' ),
					$request
				);
			},
		) );

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'rating/dismiss', array(
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => array( self::instance(), 'rating_dismiss' ),
			'permission_callback' => function ( $request ) {

				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission/rating/dismiss',
					current_user_can( 'manage_options' ),
					$request
				);
			},
		) );

		register_rest_route( 'wpbuddy/rich_snippets/v1', 'schema/export', array(
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => array( self::instance(), 'schema_export' ),
			'permission_callback' => function ( $request ) {

				return apply_filters(
					'wpbuddy/rich_snippets/rest/permission',
					current_user_can( 'manage_options' ),
					$request
				);
			},
		) );
	}


	/**
	 * Outputs the overwrite-form for a snippet.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return mixed|\WP_REST_Response
	 *
	 * @since 2.2.0
	 */
	public function get_overwrite_form( $request ) {

		$snippet_post_id = $request->get_param( 'snippet_post_id' );
		$post_id         = $request->get_param( 'post_id' );

		$rich_snippets = Snippets_Model::get_snippets( $snippet_post_id );

		if ( count( $rich_snippets ) <= 0 ) {
			return new \WP_Error( 'get_overwrite_form',
				__( 'Could not find snippets on this post.', 'rich-snippets-schema' ) );
		}

		$rich_snippet = array_values( $rich_snippets )[0];

		new Fields_Model();

		ob_start();
		View::admin_snippets_overwrite_form( get_post( $snippet_post_id ), $rich_snippet, $post_id );

		return rest_ensure_response( array(
			'form' => ob_get_clean(),
		) );
	}


	/**
	 * Saves overwrite-data to a post.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return mixed|\WP_REST_Response
	 *
	 * @since 2.2.0
	 */
	public function overwrite_snippet_values( $request ) {

		$post_id         = $request->get_param( 'post_id' );
		$snippet_post_id = $request->get_param( 'main_snippet_post_id' );
		$main_snippet_id = $request->get_param( 'main_snippet_id' );

		/**
		 * Back Compat
		 */
		$data = get_post_meta( $post_id, '_wpb_rs_overwrite_data', true );
		if ( is_array( $data ) && array_key_exists( $main_snippet_id, $data ) ) {
			unset( $data[ $main_snippet_id ] );
			update_post_meta( $post_id, '_wpb_rs_overwrite_data', $data );
		}

		/**
		 * New Format
		 */
		global $wpdb;

		# delete all old values
		$wpdb->query( $wpdb->prepare(
			"DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key LIKE %s",
			$post_id,
			'%' . $wpdb->esc_like( 'snippet_' . $snippet_post_id ) . '%'
		) );

		$snippet = call_user_func( function ( $params, $pid ) {

			$helper = Helper_Model::instance();

			$r = [];
			foreach ( $params as $param_name => $param_value ) {
				if ( 0 !== stripos( $param_name, 'snippet_' . $pid ) ) {
					continue;
				}

				/**
				 * Allowed HTML filter for overwritten input fields.
				 *
				 * Allows to change what HTML types are allowed on input fields.
				 *
				 * @hook  wpbuddy/rich_snippets/rest/overwrite_field/allowed_html
				 *
				 * @param {array} $allowed_Html Array of allowed HTML tags. @see wp_kses() function in WordPress.
				 * @param {string} $param_name The HTML name of the current field.
				 * @param {string} $param_value The value of the current field.
				 * @param {array} $params Parameters for this process (post-iD, snippet ID, etc.)
				 *
				 * @returns {array} Array of allowed HTML tags. @see wp_kses() function in WordPress.
				 *
				 * @since 2.17.0
				 */
				$a_html = apply_filters( 'wpbuddy/rich_snippets/rest/overwrite_field/allowed_html', [
					'h1'     => [],
					'h2'     => [],
					'h3'     => [],
					'h4'     => [],
					'h5'     => [],
					'h6'     => [],
					'br'     => [],
					'ol'     => [],
					'ul'     => [],
					'li'     => [],
					'a'      => [
						'href' => array(),
					],
					'p'      => [],
					'div'    => [],
					'b'      => [],
					'strong' => [],
					'i'      => [],
					'em'     => [],
				], $param_name, $param_value, $params );


				$r[ sanitize_text_field( $param_name ) ] = $helper->filter_html( $param_value, $a_html );
			}

			return $r;
		}, $request->get_params(), $snippet_post_id );

		# save all new values
		foreach ( $snippet as $prop_name => $prop_value ) {
			add_post_meta( $post_id, $prop_name, $prop_value, true );
		}

		return rest_ensure_response( true );
	}


	/**
	 * Vote for a feature.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return mixed|\WP_REST_Response
	 * @since 2.3.0
	 *
	 */
	public function support_feature_vote( $request ) {

		$response = WPBuddy_Model::request(
			'/wpbuddy/rich_snippets_manager/v1/support/feature/vote',
			[
				'method' => 'POST',
				'body'   => $request->get_params(),
			],
			false,
			true
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return rest_ensure_response( [ 'success' => true ] );
	}


	/**
	 * Add a new feature.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return mixed|\WP_REST_Response
	 * @since 2.3.0
	 *
	 */
	public function support_feature_add( $request ) {

		$response = WPBuddy_Model::request(
			'/wp/v2/comments',
			[
				'method' => 'POST',
				'body'   => [
					'content' => $request->get_param( 'content' ),
					'post'    => defined( 'WPB_RS_REMOTE' ) ? 1 : 443,
				],
			],
			false,
			true
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return rest_ensure_response( [ 'success' => true ] );
	}


	/**
	 * Deactivates a license.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return \WP_REST_Response
	 * @since 2.5.0
	 *
	 */
	public function deactivate_license( $request ) {

		$response = WPBuddy_Model::request(
			'/v3/deactivate-license',
			[ 'method' => 'POST' ],
			false,
			true
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$deactivated = isset( $response->deactivated ) ? $response->deactivated : false;

		$redirect_url = '';

		if ( $deactivated ) {
			if ( ! function_exists( '\deactivate_plugins' ) && is_file( ABSPATH . 'wp-admin/includes/plugin.php' ) ) {
				include ABSPATH . 'wp-admin/includes/plugin.php';
			}

			deactivate_plugins( rich_snippets()->get_plugin_file() );

			$redirect_url = admin_url( 'index.php' );
		}

		return rest_ensure_response( [
			'deactivated'  => $deactivated,
			'redirect_url' => $redirect_url
		] );
	}

	/**
	 * Dismisses the rating notice.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return \WP_REST_Response
	 * @since 2.9.0
	 *
	 */
	public function rating_dismiss( $request ) {

		return rest_ensure_response( [
			'updated' => update_option( 'wpb_rs/rating_dismissed_timestamp', time(), true ),
		] );
	}


	/**
	 * Returns a SNIP in JSON format for exporting.
	 *
	 * @param \WP_REST_Request $request
	 *
	 * @return \WP_REST_Response|\WP_Error
	 * @since 2.13.0
	 *
	 */
	public function schema_export( $request ) {

		$form_data = $request->get_body_params();

		if ( ! isset( $form_data['snippets'] ) ) {
			return new \WP_Error(
				'wpbuddy/rich_snippets/rest/export',
				__( 'Missing field "snippets".', 'rich-snippets-schema' )
			);
		}

		if ( ! is_array( $form_data['snippets'] ) ) {
			return new \WP_Error(
				'wpbuddy/rich_snippets/rest/export',
				__( 'Field "snippets" should be an array of values.', 'rich-snippets-schema' )
			);
		}

		$snippets = Snippets_Model::sanitize_and_generate_snippets( $form_data['snippets'] );

		$snippets = array_map( function ( $snippet ) {
			/**
			 * @var Rich_Snippet $snippet
			 */
			$snippet->_is_export = true;
			$snippet->prepare_for_export();

			return $snippet;
		}, $snippets );

		if ( isset( $form_data['wpb_rs_position_rule'] ) && is_array( $form_data['wpb_rs_position_rule'] ) ) {
			$ruleset = Rules_Model::sanitize_and_convert_to_ruleset( $form_data['wpb_rs_position_rule'] );
			if ( $ruleset->has_rulegroups() ) {
				foreach ( $snippets as $snip_id => $snippet ) {
					$rules = $ruleset->__toString();

					$snippets[ $snip_id ]->{'@ruleset'} = json_decode( $rules );
				}
			}
		}

		return rest_ensure_response( $snippets );
	}

	/**
	 * Activation of the plugin.
	 *
	 * @param bool $verified
	 * @param string $purchase_code
	 *
	 * @return bool
	 * @since 2.19.5
	 */
	public function activate_plugin_filter( $verified, $purchase_code ) {

		$response = WPBuddy_Model::request(
			'/v3/validate/',
			array(
				'method'  => 'POST',
				'body'    => array(
					'purchase_code' => $purchase_code,
				),
				'timeout' => 20,
			),
			false,
			true
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$verified = isset( $response->verified ) && $response->verified;

		update_option( base64_decode( 'd3BiX3JzL3ZlcmlmaWVk' ), $verified, true );
		update_option( 'd3BiX3JzL3ZlcmlmaWVk', $verified, true );

		if ( isset( $response->purchase_code ) ) {
			update_option( 'wpb_rs/purchase_code', $response->purchase_code, false );
		}

		return $verified;
	}
}