[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-autosaves-controller.php (source)

   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_Revisions_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->rest_base            = 'autosaves';
  71          $this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
  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       * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
 400       *
 401       * @param WP_Post         $item    Post revision object.
 402       * @param WP_REST_Request $request Request object.
 403       * @return WP_REST_Response Response object.
 404       */
 405  	public function prepare_item_for_response( $item, $request ) {
 406          // Restores the more descriptive, specific name for use within this method.
 407          $post     = $item;
 408          $response = $this->revisions_controller->prepare_item_for_response( $post, $request );
 409  
 410          $fields = $this->get_fields_for_response( $request );
 411  
 412          if ( in_array( 'preview_link', $fields, true ) ) {
 413              $parent_id          = wp_is_post_autosave( $post );
 414              $preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
 415              $preview_query_args = array();
 416  
 417              if ( false !== $parent_id ) {
 418                  $preview_query_args['preview_id']    = $parent_id;
 419                  $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
 420              }
 421  
 422              $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
 423          }
 424  
 425          $context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
 426          $response->data = $this->add_additional_fields_to_object( $response->data, $request );
 427          $response->data = $this->filter_response_by_context( $response->data, $context );
 428  
 429          /**
 430           * Filters a revision returned from the REST API.
 431           *
 432           * Allows modification of the revision right before it is returned.
 433           *
 434           * @since 5.0.0
 435           *
 436           * @param WP_REST_Response $response The response object.
 437           * @param WP_Post          $post     The original revision object.
 438           * @param WP_REST_Request  $request  Request used to generate the response.
 439           */
 440          return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
 441      }
 442  
 443      /**
 444       * Retrieves the query params for the autosaves collection.
 445       *
 446       * @since 5.0.0
 447       *
 448       * @return array Collection parameters.
 449       */
 450  	public function get_collection_params() {
 451          return array(
 452              'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 453          );
 454      }
 455  }


Generated: Sat Apr 27 01:00:02 2024 Cross-referenced by PHPXref 0.7.1