angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-rest-autosaves-controller.php (12961B)


      1 <?php
      2 /**
      3  * REST API: WP_REST_Autosaves_Controller class.
      4  *
      5  * @package WordPress
      6  * @subpackage REST_API
      7  * @since 5.0.0
      8  */
      9 
     10 /**
     11  * Core class used to access autosaves via the REST API.
     12  *
     13  * @since 5.0.0
     14  *
     15  * @see WP_REST_Revisions_Controller
     16  * @see WP_REST_Controller
     17  */
     18 class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
     19 
     20 	/**
     21 	 * Parent post type.
     22 	 *
     23 	 * @since 5.0.0
     24 	 * @var string
     25 	 */
     26 	private $parent_post_type;
     27 
     28 	/**
     29 	 * Parent post controller.
     30 	 *
     31 	 * @since 5.0.0
     32 	 * @var WP_REST_Controller
     33 	 */
     34 	private $parent_controller;
     35 
     36 	/**
     37 	 * Revision controller.
     38 	 *
     39 	 * @since 5.0.0
     40 	 * @var WP_REST_Controller
     41 	 */
     42 	private $revisions_controller;
     43 
     44 	/**
     45 	 * The base of the parent controller's route.
     46 	 *
     47 	 * @since 5.0.0
     48 	 * @var string
     49 	 */
     50 	private $parent_base;
     51 
     52 	/**
     53 	 * Constructor.
     54 	 *
     55 	 * @since 5.0.0
     56 	 *
     57 	 * @param string $parent_post_type Post type of the parent.
     58 	 */
     59 	public function __construct( $parent_post_type ) {
     60 		$this->parent_post_type = $parent_post_type;
     61 		$post_type_object       = get_post_type_object( $parent_post_type );
     62 		$parent_controller      = $post_type_object->get_rest_controller();
     63 
     64 		if ( ! $parent_controller ) {
     65 			$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
     66 		}
     67 
     68 		$this->parent_controller    = $parent_controller;
     69 		$this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
     70 		$this->namespace            = 'wp/v2';
     71 		$this->rest_base            = 'autosaves';
     72 		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
     73 	}
     74 
     75 	/**
     76 	 * Registers the routes for autosaves.
     77 	 *
     78 	 * @since 5.0.0
     79 	 *
     80 	 * @see register_rest_route()
     81 	 */
     82 	public function register_routes() {
     83 		register_rest_route(
     84 			$this->namespace,
     85 			'/' . $this->parent_base . '/(?P<id>[\d]+)/' . $this->rest_base,
     86 			array(
     87 				'args'   => array(
     88 					'parent' => array(
     89 						'description' => __( 'The ID for the parent of the autosave.' ),
     90 						'type'        => 'integer',
     91 					),
     92 				),
     93 				array(
     94 					'methods'             => WP_REST_Server::READABLE,
     95 					'callback'            => array( $this, 'get_items' ),
     96 					'permission_callback' => array( $this, 'get_items_permissions_check' ),
     97 					'args'                => $this->get_collection_params(),
     98 				),
     99 				array(
    100 					'methods'             => WP_REST_Server::CREATABLE,
    101 					'callback'            => array( $this, 'create_item' ),
    102 					'permission_callback' => array( $this, 'create_item_permissions_check' ),
    103 					'args'                => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
    104 				),
    105 				'schema' => array( $this, 'get_public_item_schema' ),
    106 			)
    107 		);
    108 
    109 		register_rest_route(
    110 			$this->namespace,
    111 			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
    112 			array(
    113 				'args'   => array(
    114 					'parent' => array(
    115 						'description' => __( 'The ID for the parent of the autosave.' ),
    116 						'type'        => 'integer',
    117 					),
    118 					'id'     => array(
    119 						'description' => __( 'The ID for the autosave.' ),
    120 						'type'        => 'integer',
    121 					),
    122 				),
    123 				array(
    124 					'methods'             => WP_REST_Server::READABLE,
    125 					'callback'            => array( $this, 'get_item' ),
    126 					'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ),
    127 					'args'                => array(
    128 						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
    129 					),
    130 				),
    131 				'schema' => array( $this, 'get_public_item_schema' ),
    132 			)
    133 		);
    134 
    135 	}
    136 
    137 	/**
    138 	 * Get the parent post.
    139 	 *
    140 	 * @since 5.0.0
    141 	 *
    142 	 * @param int $parent_id Supplied ID.
    143 	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
    144 	 */
    145 	protected function get_parent( $parent_id ) {
    146 		return $this->revisions_controller->get_parent( $parent_id );
    147 	}
    148 
    149 	/**
    150 	 * Checks if a given request has access to get autosaves.
    151 	 *
    152 	 * @since 5.0.0
    153 	 *
    154 	 * @param WP_REST_Request $request Full details about the request.
    155 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
    156 	 */
    157 	public function get_items_permissions_check( $request ) {
    158 		$parent = $this->get_parent( $request['id'] );
    159 		if ( is_wp_error( $parent ) ) {
    160 			return $parent;
    161 		}
    162 
    163 		if ( ! current_user_can( 'edit_post', $parent->ID ) ) {
    164 			return new WP_Error(
    165 				'rest_cannot_read',
    166 				__( 'Sorry, you are not allowed to view autosaves of this post.' ),
    167 				array( 'status' => rest_authorization_required_code() )
    168 			);
    169 		}
    170 
    171 		return true;
    172 	}
    173 
    174 	/**
    175 	 * Checks if a given request has access to create an autosave revision.
    176 	 *
    177 	 * Autosave revisions inherit permissions from the parent post,
    178 	 * check if the current user has permission to edit the post.
    179 	 *
    180 	 * @since 5.0.0
    181 	 *
    182 	 * @param WP_REST_Request $request Full details about the request.
    183 	 * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
    184 	 */
    185 	public function create_item_permissions_check( $request ) {
    186 		$id = $request->get_param( 'id' );
    187 
    188 		if ( empty( $id ) ) {
    189 			return new WP_Error(
    190 				'rest_post_invalid_id',
    191 				__( 'Invalid item ID.' ),
    192 				array( 'status' => 404 )
    193 			);
    194 		}
    195 
    196 		return $this->parent_controller->update_item_permissions_check( $request );
    197 	}
    198 
    199 	/**
    200 	 * Creates, updates or deletes an autosave revision.
    201 	 *
    202 	 * @since 5.0.0
    203 	 *
    204 	 * @param WP_REST_Request $request Full details about the request.
    205 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    206 	 */
    207 	public function create_item( $request ) {
    208 
    209 		if ( ! defined( 'DOING_AUTOSAVE' ) ) {
    210 			define( 'DOING_AUTOSAVE', true );
    211 		}
    212 
    213 		$post = get_post( $request['id'] );
    214 
    215 		if ( is_wp_error( $post ) ) {
    216 			return $post;
    217 		}
    218 
    219 		$prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
    220 		$prepared_post->ID = $post->ID;
    221 		$user_id           = get_current_user_id();
    222 
    223 		if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
    224 			// Draft posts for the same author: autosaving updates the post and does not create a revision.
    225 			// Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
    226 			$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
    227 		} else {
    228 			// Non-draft posts: create or update the post autosave.
    229 			$autosave_id = $this->create_post_autosave( (array) $prepared_post );
    230 		}
    231 
    232 		if ( is_wp_error( $autosave_id ) ) {
    233 			return $autosave_id;
    234 		}
    235 
    236 		$autosave = get_post( $autosave_id );
    237 		$request->set_param( 'context', 'edit' );
    238 
    239 		$response = $this->prepare_item_for_response( $autosave, $request );
    240 		$response = rest_ensure_response( $response );
    241 
    242 		return $response;
    243 	}
    244 
    245 	/**
    246 	 * Get the autosave, if the ID is valid.
    247 	 *
    248 	 * @since 5.0.0
    249 	 *
    250 	 * @param WP_REST_Request $request Full details about the request.
    251 	 * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
    252 	 */
    253 	public function get_item( $request ) {
    254 		$parent_id = (int) $request->get_param( 'parent' );
    255 
    256 		if ( $parent_id <= 0 ) {
    257 			return new WP_Error(
    258 				'rest_post_invalid_id',
    259 				__( 'Invalid post parent ID.' ),
    260 				array( 'status' => 404 )
    261 			);
    262 		}
    263 
    264 		$autosave = wp_get_post_autosave( $parent_id );
    265 
    266 		if ( ! $autosave ) {
    267 			return new WP_Error(
    268 				'rest_post_no_autosave',
    269 				__( 'There is no autosave revision for this post.' ),
    270 				array( 'status' => 404 )
    271 			);
    272 		}
    273 
    274 		$response = $this->prepare_item_for_response( $autosave, $request );
    275 		return $response;
    276 	}
    277 
    278 	/**
    279 	 * Gets a collection of autosaves using wp_get_post_autosave.
    280 	 *
    281 	 * Contains the user's autosave, for empty if it doesn't exist.
    282 	 *
    283 	 * @since 5.0.0
    284 	 *
    285 	 * @param WP_REST_Request $request Full details about the request.
    286 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    287 	 */
    288 	public function get_items( $request ) {
    289 		$parent = $this->get_parent( $request['id'] );
    290 		if ( is_wp_error( $parent ) ) {
    291 			return $parent;
    292 		}
    293 
    294 		$response  = array();
    295 		$parent_id = $parent->ID;
    296 		$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
    297 
    298 		foreach ( $revisions as $revision ) {
    299 			if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
    300 				$data       = $this->prepare_item_for_response( $revision, $request );
    301 				$response[] = $this->prepare_response_for_collection( $data );
    302 			}
    303 		}
    304 
    305 		return rest_ensure_response( $response );
    306 	}
    307 
    308 
    309 	/**
    310 	 * Retrieves the autosave's schema, conforming to JSON Schema.
    311 	 *
    312 	 * @since 5.0.0
    313 	 *
    314 	 * @return array Item schema data.
    315 	 */
    316 	public function get_item_schema() {
    317 		if ( $this->schema ) {
    318 			return $this->add_additional_fields_schema( $this->schema );
    319 		}
    320 
    321 		$schema = $this->revisions_controller->get_item_schema();
    322 
    323 		$schema['properties']['preview_link'] = array(
    324 			'description' => __( 'Preview link for the post.' ),
    325 			'type'        => 'string',
    326 			'format'      => 'uri',
    327 			'context'     => array( 'edit' ),
    328 			'readonly'    => true,
    329 		);
    330 
    331 		$this->schema = $schema;
    332 
    333 		return $this->add_additional_fields_schema( $this->schema );
    334 	}
    335 
    336 	/**
    337 	 * Creates autosave for the specified post.
    338 	 *
    339 	 * From wp-admin/post.php.
    340 	 *
    341 	 * @since 5.0.0
    342 	 *
    343 	 * @param array $post_data Associative array containing the post data.
    344 	 * @return mixed The autosave revision ID or WP_Error.
    345 	 */
    346 	public function create_post_autosave( $post_data ) {
    347 
    348 		$post_id = (int) $post_data['ID'];
    349 		$post    = get_post( $post_id );
    350 
    351 		if ( is_wp_error( $post ) ) {
    352 			return $post;
    353 		}
    354 
    355 		$user_id = get_current_user_id();
    356 
    357 		// Store one autosave per author. If there is already an autosave, overwrite it.
    358 		$old_autosave = wp_get_post_autosave( $post_id, $user_id );
    359 
    360 		if ( $old_autosave ) {
    361 			$new_autosave                = _wp_post_revision_data( $post_data, true );
    362 			$new_autosave['ID']          = $old_autosave->ID;
    363 			$new_autosave['post_author'] = $user_id;
    364 
    365 			// If the new autosave has the same content as the post, delete the autosave.
    366 			$autosave_is_different = false;
    367 
    368 			foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
    369 				if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
    370 					$autosave_is_different = true;
    371 					break;
    372 				}
    373 			}
    374 
    375 			if ( ! $autosave_is_different ) {
    376 				wp_delete_post_revision( $old_autosave->ID );
    377 				return new WP_Error(
    378 					'rest_autosave_no_changes',
    379 					__( 'There is nothing to save. The autosave and the post content are the same.' ),
    380 					array( 'status' => 400 )
    381 				);
    382 			}
    383 
    384 			/** This filter is documented in wp-admin/post.php */
    385 			do_action( 'wp_creating_autosave', $new_autosave );
    386 
    387 			// wp_update_post() expects escaped array.
    388 			return wp_update_post( wp_slash( $new_autosave ) );
    389 		}
    390 
    391 		// Create the new autosave as a special post revision.
    392 		return _wp_put_post_revision( $post_data, true );
    393 	}
    394 
    395 	/**
    396 	 * Prepares the revision for the REST response.
    397 	 *
    398 	 * @since 5.0.0
    399 	 *
    400 	 * @param WP_Post         $post    Post revision object.
    401 	 * @param WP_REST_Request $request Request object.
    402 	 * @return WP_REST_Response Response object.
    403 	 */
    404 	public function prepare_item_for_response( $post, $request ) {
    405 
    406 		$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
    407 
    408 		$fields = $this->get_fields_for_response( $request );
    409 
    410 		if ( in_array( 'preview_link', $fields, true ) ) {
    411 			$parent_id          = wp_is_post_autosave( $post );
    412 			$preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
    413 			$preview_query_args = array();
    414 
    415 			if ( false !== $parent_id ) {
    416 				$preview_query_args['preview_id']    = $parent_id;
    417 				$preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
    418 			}
    419 
    420 			$response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
    421 		}
    422 
    423 		$context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
    424 		$response->data = $this->add_additional_fields_to_object( $response->data, $request );
    425 		$response->data = $this->filter_response_by_context( $response->data, $context );
    426 
    427 		/**
    428 		 * Filters a revision returned from the REST API.
    429 		 *
    430 		 * Allows modification of the revision right before it is returned.
    431 		 *
    432 		 * @since 5.0.0
    433 		 *
    434 		 * @param WP_REST_Response $response The response object.
    435 		 * @param WP_Post          $post     The original revision object.
    436 		 * @param WP_REST_Request  $request  Request used to generate the response.
    437 		 */
    438 		return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
    439 	}
    440 
    441 	/**
    442 	 * Retrieves the query params for the autosaves collection.
    443 	 *
    444 	 * @since 5.0.0
    445 	 *
    446 	 * @return array Collection parameters.
    447 	 */
    448 	public function get_collection_params() {
    449 		return array(
    450 			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
    451 		);
    452 	}
    453 }