HEX
Server: Apache
System: Linux d5123.usc1.stableserver.net 5.14.0-570.17.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Sat May 24 12:53:17 EDT 2025 x86_64
User: d5123 (1001)
PHP: 8.4.21
Disabled: NONE
Upload Files
File: /home/d5123/myboofola_com/wp-content/plugins/unsplash/php/class-rest-controller.php
<?php
/**
 * REST API Controller class.
 *
 * @package Unsplash.
 */

namespace Unsplash;

use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Server;
use WP_REST_Response;
use WP_Error;

/**
 * REST API Controller.
 */
class Rest_Controller extends WP_REST_Controller {

	/**
	 * Plugin instance.
	 *
	 * @var Plugin
	 */
	public $plugin;

	/**
	 * Post type.
	 *
	 * @var string
	 */
	protected $post_type;

	/**
	 * Constructor.
	 *
	 * @param Plugin $plugin Instance of the plugin abstraction.
	 */
	public function __construct( $plugin ) {
		$this->plugin    = $plugin;
		$this->namespace = 'unsplash/v1';
		$this->rest_base = 'photos';
		$this->post_type = 'attachment';
	}

	/**
	 * Initiate the class.
	 */
	public function init() {
		add_action( 'rest_api_init', [ $this, 'register_routes' ] );
	}

	/**
	 * Registers the routes for the Unsplash API.
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			[
				[
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => [ $this, 'get_items' ],
					'permission_callback' => [ $this, 'get_items_permissions_check' ],
					'args'                => $this->get_collection_params(),
				],
				'schema' => [ $this, 'get_item_schema' ],
			]
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\w-]+)',
			[
				'args'   => [
					'id' => [
						'description' => esc_html__( 'Unsplash image ID.', 'unsplash' ),
						'type'        => 'string',
					],
				],
				[
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => [ $this, 'get_item' ],
					'permission_callback' => [ $this, 'get_item_permissions_check' ],
					'args'                => [
						'context' => $this->get_context_param( [ 'default' => 'view' ] ),
					],
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/import/(?P<id>[\w-]+)',
			[
				'args'   => [
					'id'          => [
						'description' => esc_html__( 'Unsplash image ID.', 'unsplash' ),
						'type'        => 'string',
					],
					'parent'      => [
						'description' => esc_html__( 'Parent post ID.', 'unsplash' ),
						'type'        => 'integer',
						'default'     => 0,
					],
					'alt'         => [
						'description' => esc_html__( 'Image alt text.', 'unsplash' ),
						'type'        => 'string',
					],
					'title'       => [
						'description' => esc_html__( 'Image title.', 'unsplash' ),
						'type'        => 'string',
					],
					'description' => [
						'description' => esc_html__( 'Image description.', 'unsplash' ),
						'type'        => 'string',
					],
					'caption'     => [
						'description' => esc_html__( 'Image caption.', 'unsplash' ),
						'type'        => 'string',
					],
				],
				[
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => [ $this, 'get_import' ],
					'permission_callback' => [ $this, 'create_item_permissions_check' ],
					'args'                => [
						'context' => $this->get_context_param( [ 'default' => 'view' ] ),
					],
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/post-process/(?P<id>[\d]+)',
			[
				'args'   => [
					'id' => [
						'description'       => esc_html__( 'WordPress attachment ID.', 'unsplash' ),
						'type'              => 'integer',
						'validate_callback' => [ $this, 'validate_get_attachment' ],
					],
				],
				[
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => [ $this, 'post_process' ],
					'permission_callback' => [ $this, 'create_item_permissions_check' ],
					'args'                => [
						'context' => $this->get_context_param( [ 'default' => 'view' ] ),
					],
				],
				'schema' => [ $this, 'get_public_item_schema' ],
			]
		);
	}

	/**
	 * Retrieve a page of photos.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response Single page of photo results.
	 */
	public function get_items( $request ) {
		$page        = $request->get_param( 'page' );
		$per_page    = $request->get_param( 'per_page' );
		$order_by    = $request->get_param( 'order_by' );
		$search      = $request->get_param( 'search' );
		$orientation = $request->get_param( 'orientation' );
		$collections = $request->get_param( 'collections' );
		$photos      = [];
		if ( $search ) {
			$api_response = $this->plugin->api->search( $search, $page, $per_page, $orientation, $collections );
		} else {
			$api_response = $this->plugin->api->all( $page, $per_page, $order_by );
		}

		if ( is_wp_error( $api_response ) ) {
			return $this->rest_ensure_response( $api_response, $request );
		}
		$results   = $api_response->get_results();
		$max_pages = $api_response->get_total_pages();
		$total     = $api_response->get_total_object();
		$cached    = (int) $api_response->get_cached();

		foreach ( $results as $index => $photo ) {
			if ( $this->is_ajax_request( $request ) ) {
				$photo = $this->set_unique_media_id( $photo, $index, $page, $per_page );
			}

			$data     = $this->prepare_item_for_response( $photo, $request );
			$photos[] = $this->prepare_response_for_collection( $data );
		}

		$response = rest_ensure_response( $photos );
		$response->header( 'X-WP-Total', (int) $total );
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
		$response->header( 'X-WP-Unsplash-Cache-Hit', $cached );

		return $this->rest_ensure_response( $response, $request );
	}

	/**
	 * Retrieve a page of photo.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response Single page of photo results.
	 */
	public function get_item( $request ) {
		$id = $request->get_param( 'id' );

		$api_response = $this->plugin->api->get( $id );
		if ( is_wp_error( $api_response ) ) {
			return $this->rest_ensure_response( $api_response, $request );
		}
		$results = $api_response->get_results();
		$cached  = (int) $api_response->get_cached();
		$photos  = $this->prepare_item_for_response( $results, $request );

		$response = rest_ensure_response( $photos );
		$response->header( 'X-WP-Unsplash-Cache-Hit', $cached );

		return $this->rest_ensure_response( $response, $request );
	}

	/**
	 * Import image into WP.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response|WP_Error Single page of photo results.
	 */
	public function get_import( $request ) {
		$id = $request->get_param( 'id' );

		$api_response = $this->plugin->api->get( $id );
		if ( is_wp_error( $api_response ) ) {
			return $api_response;
		}
		$results     = $api_response->get_results();
		$credentials = $this->plugin->settings->get_credentials();
		$utm_source  = $credentials['utmSource'];
		$image       = new Image( $results, $utm_source );
		$image->set_field( 'alt', $request->get_param( 'alt' ) );
		$image->set_field( 'title', $request->get_param( 'title' ) );
		$image->set_field( 'description', $request->get_param( 'description' ) );
		$image->set_field( 'caption', $request->get_param( 'caption' ) );

		$importer      = new Import( $id, $image, $request->get_param( 'parent' ) );
		$attachment_id = $importer->process();
		// @codeCoverageIgnoreStart
		if ( is_wp_error( $attachment_id ) ) {
			return $attachment_id;
		}
		// @codeCoverageIgnoreEnd
		$this->plugin->api->download( $id );
		$response = $this->prepare_item_for_response( $results, $request );
		$response = rest_ensure_response( $response );
		$response->set_status( 301 );
		$response->header( 'X-WP-Upload-Attachment-ID', $attachment_id );
		$response->header( 'Location', add_query_arg( 'context', 'edit', rest_url( sprintf( '%s/%s/%d', 'wp/v2', 'media', $attachment_id ) ) ) );

		return $this->rest_ensure_response( $response, $request );
	}

	/**
	 * Process image.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response|WP_Error Single page of photo results.
	 */
	public function post_process( $request ) {
		$attachment_id = $request->get_param( 'id' );
		$retry         = (bool) $request->get_param( 'retry' );
		try {
			// @codeCoverageIgnoreStart
			if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
				require_once ABSPATH . 'wp-admin/includes/media.php';
				require_once ABSPATH . 'wp-admin/includes/file.php';
				require_once ABSPATH . 'wp-admin/includes/image.php';
			}
			// @codeCoverageIgnoreEnd

			if ( $retry ) {
				// On retry, try to scale back images generated.
				add_filter( 'intermediate_image_sizes_advanced', [ $this, 'intermediate_image_sizes_advanced' ] );
				add_filter( 'big_image_size_threshold', '__return_zero' );
			}
			$meta     = (array) get_post_meta( $attachment_id, 'unsplash_attachment_metadata', true );
			$file     = get_attached_file( $attachment_id );
			$new_meta = wp_generate_attachment_metadata( $attachment_id, $file );
			unset( $meta['sizes'], $new_meta['image_meta'] );
			$new_meta  = wp_parse_args( $new_meta, $meta );
			$processed = wp_update_attachment_metadata( $attachment_id, $new_meta );
			$data      = [
				'processed' => $processed,
				'retry'     => $retry,
			];
			if ( $retry ) {
				// Return filters.
				remove_filter( 'intermediate_image_sizes_advanced', [ $this, 'intermediate_image_sizes_advanced' ] );
				remove_filter( 'big_image_size_threshold', '__return_zero' );
			}
		} catch ( \Exception $e ) {
			$response = new WP_Error(
				'rest_unsplash_single_photo_process',
				/* translators: %d: attachment id */
				sprintf( esc_html__( 'Unable to process image attachment %d.', 'unsplash' ), $attachment_id ),
				[
					'attachment_id' => $attachment_id,
					'status'        => '400',
				]
			);
			$this->plugin->trigger_warning( $e->getMessage() );
			return $response;
		}

		return $this->rest_ensure_response( $data, $request );
	}

	/**
	 * If enable to generate all image sizes, just try and generate core default image sizes.
	 *
	 * @param array $sizes Current image sizes.
	 *
	 * @return array
	 */
	public function intermediate_image_sizes_advanced( $sizes ) {
		return array_intersect_key( $sizes, array_flip( [ 'thumbnail', 'medium', 'medium_large', 'large' ] ) );
	}

	/**
	 * Format response for AJAX.
	 *
	 * @param  mixed           $response Response data.
	 * @param  WP_REST_Request $request  Request.
	 * @return  WP_REST_Response          Repsonse object.
	 */
	public function rest_ensure_response( $response, WP_REST_Request $request ) {
		if ( $this->is_ajax_request( $request ) ) {
			if ( is_wp_error( $response ) ) {
				$wp_error = $response;
				$response = array( 'success' => false );
				$result   = array();
				foreach ( $wp_error->errors as $code => $messages ) {
					$data = ( isset( $wp_error->error_data[ $code ] ) ) ? $wp_error->error_data[ $code ] : [];
					foreach ( $messages as $message ) {
						$result[] = array(
							'code'    => $code,
							'message' => $message,
							'data'    => $data,
						);
					}
				}
				$response['data'] = $result;
			}
		}
		return rest_ensure_response( $response );
	}

	/**
	 * Checks if a given request has access to get a specific item.
	 *
	 * @see   https://github.com/WordPress/WordPress/blob/c85c8f5235356cbf65680a3201e9ee4161803c0b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php#L128-L149
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 *
	 * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$post_type = get_post_type_object( $this->post_type );

		if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				esc_html__( 'Sorry, you are not allowed to edit posts in this post type.', 'unsplash' ),
				[ 'status' => rest_authorization_required_code() ]
			);
		}

		return current_user_can( 'edit_posts' );
	}

	/**
	 * Checks if a given request has access to get a specific item.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		return $this->get_items_permissions_check( $request );
	}

	/**
	 * Checks if a given request has access to create items.
	 *
	 * @see   https://github.com/WordPress/WordPress/blob/c85c8f5235356cbf65680a3201e9ee4161803c0b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php#L515-L567
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_Error|bool True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {

		$post_type = get_post_type_object( $this->post_type );

		if ( ! current_user_can( $post_type->cap->create_posts ) ) {
			return new WP_Error(
				'rest_cannot_create',
				esc_html__( 'Sorry, you are not allowed to create posts as this user.', 'unsplash' ),
				[ 'status' => rest_authorization_required_code() ]
			);
		}

		if ( ! current_user_can( 'upload_files' ) ) {
			return new WP_Error(
				'rest_cannot_create',
				esc_html__( 'Sorry, you are not allowed to upload media on this site.', 'unsplash' ),
				[ 'status' => 400 ]
			);
		}

		return true;
	}

	/**
	 * Prepares a single photo output for response.
	 *
	 * @param array|Object    $photo Photo object.
	 * @param WP_REST_Request $request Request object.
	 *
	 * @return array|WP_Error|WP_REST_Response Array if its an AJAX request,
	 * WP_Error if an error occurs, otherwise a REST response object.
	 */
	public function prepare_item_for_response( $photo, $request ) {
		if ( $this->is_ajax_request( $request ) ) {
			return $this->plugin->wp_prepare_attachment_for_js( $photo );
		}

		$fields     = $this->get_fields_for_response( $request );
		$schema     = $this->get_item_schema();
		$properties = $schema['properties'];
		$data       = [];

		foreach ( $photo as $field => $value ) {
			if ( in_array( $field, $fields, true ) ) {
				$value = rest_sanitize_value_from_schema( $photo[ $field ], $properties[ $field ] );
				if ( is_wp_error( $value ) ) {
					return $value;
				}
				$data[ $field ] = $value;
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		return rest_ensure_response( $data );
	}

	/**
	 * Override default collection params to add Unsplash fields.
	 *
	 * @return array
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context']['default'] = 'view';

		$query_params['per_page']['maximum'] = 30;

		$query_params['order_by'] = [
			'description' => esc_html__( 'How to sort the photos.', 'unsplash' ),
			'type'        => 'string',
			'default'     => 'latest',
			'enum'        => [ 'latest', 'oldest', 'popular' ],
		];

		$query_params['orientation'] = [
			'default'     => null,
			'enum'        => [ 'landscape', 'portrait', 'squarish' ],
			'description' => esc_html__( 'Filter search results by photo orientation.', 'unsplash' ),
			'type'        => 'string',
		];

		$query_params['collections'] = [
			'default'           => null,
			'type'              => 'string',
			'description'       => esc_html__( 'Collection ID(‘s) to narrow search. If multiple, comma-separated.', 'unsplash' ),
			'validate_callback' => [ static::class, 'validate_get_search_param' ],
		];

		return $query_params;
	}

	/**
	 * Validate parameters for get_search().
	 *
	 * @param string          $value   Parameter value.
	 * @param WP_REST_Request $request Request.
	 * @param string          $param   Parameter name.
	 * @return bool True if valid, false if not.
	 */
	public static function validate_get_search_param( $value, $request, $param ) {
		if ( 'collections' === $param ) {
			// Only digits are accepted. If there are multiple IDs, they must be comma delimited.
			return (bool) preg_match( '/^(\d+(,\d+)*)$/', $value );
		}

		return true;
	}

	/**
	 * Check if attachment exists.
	 *
	 * @param int $param Attachment ID.
	 * @return bool|WP_Error
	 */
	public function validate_get_attachment( $param ) {
		$attachment = get_post( (int) $param );

		if ( empty( $attachment ) ) {
			return new WP_Error(
				'rest_post_invalid_id',
				esc_html__( 'Invalid attachment ID.', 'unsplash' ),
				array( 'status' => 400 )
			);
		}

		if ( get_post_type( $attachment ) !== $this->post_type ) {
			return new WP_Error(
				'rest_invalid_post_type_id',
				esc_html__( 'Invalid attachment ID.', 'unsplash' ),
				array( 'status' => 400 )
			);
		}

		return true;
	}

	/**
	 * Retrieves the photo type's schema, conforming to JSON Schema.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( property_exists( self::class, 'schema' ) && null !== $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}
		// @todo Add in all required fields.

		$schema = [
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'type',
			'type'       => 'photo',
			'properties' => [
				'id'              => [
					'description' => esc_html__( 'Unique identifier for the object.', 'unsplash' ),
					'type'        => 'string',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'created_at'      => [
					'description' => esc_html__( 'The date the object was published.', 'unsplash' ),
					'type'        => [ 'string', 'null' ],
					'format'      => 'date-time',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'updated_at'      => [
					'description' => esc_html__( 'The date the object was last modified.', 'unsplash' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'alt_description' => [
					'description' => esc_html__( 'Alternative text to display when attachment is not displayed.', 'unsplash' ),
					'type'        => 'string',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'description'     => [
					'description' => esc_html__( 'Description for the object, as it exists in the database.', 'unsplash' ),
					'type'        => 'string',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'color'           => [
					'description' => esc_html__( 'Color for the object, as it exists in the database.', 'unsplash' ),
					'type'        => 'string',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'height'          => [
					'description' => esc_html__( 'Height for the object.', 'unsplash' ),
					'type'        => 'integer',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'width'           => [
					'description' => esc_html__( 'Width for the object.', 'unsplash' ),
					'type'        => 'integer',
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
				'urls'            => [
					'description' => esc_html__( 'List of url for default image sizes for the object.', 'unsplash' ),
					'type'        => 'object',
					'properties'  => [],
					'context'     => [ 'view', 'edit', 'embed' ],
					'readonly'    => true,
				],
			],
		];

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Determine if a request is an AJAX one.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return bool
	 */
	public function is_ajax_request( $request ) {
		return 'XMLHttpRequest' === $request->get_header( 'X-Requested-With' );
	}

	/**
	 * Set a unique ID on the photo so that it does not clash with other media objects in the media library.
	 *
	 * @param array $photo    Photo attributes.
	 * @param int   $index    Index of $photo in current page.
	 * @param int   $page     Current page.
	 * @param int   $per_page Number of photos per page.
	 *
	 * @return array Photo with updated ID.
	 */
	public function set_unique_media_id( $photo, $index, $page, $per_page ) {
		/*
		 * The media selector uses the image ID to sort the list of images received from the API, so an
		 * incremental ID is generated and set on the photo so that they can be ordered correctly.
		 *
		 * The 'unsplash-' prefix is added to prevent any attachment ID collisions in the media selector and
		 * will be stripped when media objects are being compared.
		 */
		$photo['unsplash_order'] = ( $index + ( ( $page - 1 ) * $per_page ) );

		return $photo;
	}
}