[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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  
  70          $revisions_controller = $post_type_object->get_revisions_rest_controller();
  71          if ( ! $revisions_controller ) {
  72              $revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
  73          }
  74          $this->revisions_controller = $revisions_controller;
  75          $this->rest_base            = 'autosaves';
  76          $this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
  77          $this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
  78      }
  79  
  80      /**
  81       * Registers the routes for autosaves.
  82       *
  83       * @since 5.0.0
  84       *
  85       * @see register_rest_route()
  86       */
  87  	public function register_routes() {
  88          register_rest_route(
  89              $this->namespace,
  90              '/' . $this->parent_base . '/(?P<id>[\d]+)/' . $this->rest_base,
  91              array(
  92                  'args'   => array(
  93                      'parent' => array(
  94                          'description' => __( 'The ID for the parent of the autosave.' ),
  95                          'type'        => 'integer',
  96                      ),
  97                  ),
  98                  array(
  99                      'methods'             => WP_REST_Server::READABLE,
 100                      'callback'            => array( $this, 'get_items' ),
 101                      'permission_callback' => array( $this, 'get_items_permissions_check' ),
 102                      'args'                => $this->get_collection_params(),
 103                  ),
 104                  array(
 105                      'methods'             => WP_REST_Server::CREATABLE,
 106                      'callback'            => array( $this, 'create_item' ),
 107                      'permission_callback' => array( $this, 'create_item_permissions_check' ),
 108                      'args'                => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
 109                  ),
 110                  'schema' => array( $this, 'get_public_item_schema' ),
 111              )
 112          );
 113  
 114          register_rest_route(
 115              $this->namespace,
 116              '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
 117              array(
 118                  'args'   => array(
 119                      'parent' => array(
 120                          'description' => __( 'The ID for the parent of the autosave.' ),
 121                          'type'        => 'integer',
 122                      ),
 123                      'id'     => array(
 124                          'description' => __( 'The ID for the autosave.' ),
 125                          'type'        => 'integer',
 126                      ),
 127                  ),
 128                  array(
 129                      'methods'             => WP_REST_Server::READABLE,
 130                      'callback'            => array( $this, 'get_item' ),
 131                      'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ),
 132                      'args'                => array(
 133                          'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 134                      ),
 135                  ),
 136                  'schema' => array( $this, 'get_public_item_schema' ),
 137              )
 138          );
 139      }
 140  
 141      /**
 142       * Get the parent post.
 143       *
 144       * @since 5.0.0
 145       *
 146       * @param int $parent_id Supplied ID.
 147       * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
 148       */
 149  	protected function get_parent( $parent_id ) {
 150          return $this->revisions_controller->get_parent( $parent_id );
 151      }
 152  
 153      /**
 154       * Checks if a given request has access to get autosaves.
 155       *
 156       * @since 5.0.0
 157       *
 158       * @param WP_REST_Request $request Full details about the request.
 159       * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
 160       */
 161  	public function get_items_permissions_check( $request ) {
 162          $parent = $this->get_parent( $request['id'] );
 163          if ( is_wp_error( $parent ) ) {
 164              return $parent;
 165          }
 166  
 167          if ( ! current_user_can( 'edit_post', $parent->ID ) ) {
 168              return new WP_Error(
 169                  'rest_cannot_read',
 170                  __( 'Sorry, you are not allowed to view autosaves of this post.' ),
 171                  array( 'status' => rest_authorization_required_code() )
 172              );
 173          }
 174  
 175          return true;
 176      }
 177  
 178      /**
 179       * Checks if a given request has access to create an autosave revision.
 180       *
 181       * Autosave revisions inherit permissions from the parent post,
 182       * check if the current user has permission to edit the post.
 183       *
 184       * @since 5.0.0
 185       *
 186       * @param WP_REST_Request $request Full details about the request.
 187       * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
 188       */
 189  	public function create_item_permissions_check( $request ) {
 190          $id = $request->get_param( 'id' );
 191  
 192          if ( empty( $id ) ) {
 193              return new WP_Error(
 194                  'rest_post_invalid_id',
 195                  __( 'Invalid item ID.' ),
 196                  array( 'status' => 404 )
 197              );
 198          }
 199  
 200          return $this->parent_controller->update_item_permissions_check( $request );
 201      }
 202  
 203      /**
 204       * Creates, updates or deletes an autosave revision.
 205       *
 206       * @since 5.0.0
 207       *
 208       * @param WP_REST_Request $request Full details about the request.
 209       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 210       */
 211  	public function create_item( $request ) {
 212  
 213          if ( ! defined( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) {
 214              define( 'DOING_AUTOSAVE', true );
 215          }
 216  
 217          $post = $this->get_parent( $request['id'] );
 218  
 219          if ( is_wp_error( $post ) ) {
 220              return $post;
 221          }
 222  
 223          $prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
 224          $prepared_post->ID = $post->ID;
 225          $user_id           = get_current_user_id();
 226  
 227          // We need to check post lock to ensure the original author didn't leave their browser tab open.
 228          if ( ! function_exists( 'wp_check_post_lock' ) ) {
 229              require_once  ABSPATH . 'wp-admin/includes/post.php';
 230          }
 231  
 232          $post_lock = wp_check_post_lock( $post->ID );
 233          $is_draft  = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;
 234  
 235          if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock ) {
 236              /*
 237               * Draft posts for the same author: autosaving updates the post and does not create a revision.
 238               * Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
 239               */
 240              $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
 241          } else {
 242              // Non-draft posts: create or update the post autosave. Pass the meta data.
 243              $autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
 244          }
 245  
 246          if ( is_wp_error( $autosave_id ) ) {
 247              return $autosave_id;
 248          }
 249  
 250          $autosave = get_post( $autosave_id );
 251          $request->set_param( 'context', 'edit' );
 252  
 253          $response = $this->prepare_item_for_response( $autosave, $request );
 254          $response = rest_ensure_response( $response );
 255  
 256          return $response;
 257      }
 258  
 259      /**
 260       * Get the autosave, if the ID is valid.
 261       *
 262       * @since 5.0.0
 263       *
 264       * @param WP_REST_Request $request Full details about the request.
 265       * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
 266       */
 267  	public function get_item( $request ) {
 268          $parent_id = (int) $request->get_param( 'parent' );
 269  
 270          if ( $parent_id <= 0 ) {
 271              return new WP_Error(
 272                  'rest_post_invalid_id',
 273                  __( 'Invalid post parent ID.' ),
 274                  array( 'status' => 404 )
 275              );
 276          }
 277  
 278          $autosave = wp_get_post_autosave( $parent_id );
 279  
 280          if ( ! $autosave ) {
 281              return new WP_Error(
 282                  'rest_post_no_autosave',
 283                  __( 'There is no autosave revision for this post.' ),
 284                  array( 'status' => 404 )
 285              );
 286          }
 287  
 288          $response = $this->prepare_item_for_response( $autosave, $request );
 289          return $response;
 290      }
 291  
 292      /**
 293       * Gets a collection of autosaves using wp_get_post_autosave.
 294       *
 295       * Contains the user's autosave, for empty if it doesn't exist.
 296       *
 297       * @since 5.0.0
 298       *
 299       * @param WP_REST_Request $request Full details about the request.
 300       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 301       */
 302  	public function get_items( $request ) {
 303          $parent = $this->get_parent( $request['id'] );
 304          if ( is_wp_error( $parent ) ) {
 305              return $parent;
 306          }
 307  
 308          $response  = array();
 309          $parent_id = $parent->ID;
 310          $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
 311  
 312          foreach ( $revisions as $revision ) {
 313              if ( str_contains( $revision->post_name, "{$parent_id}-autosave" ) ) {
 314                  $data       = $this->prepare_item_for_response( $revision, $request );
 315                  $response[] = $this->prepare_response_for_collection( $data );
 316              }
 317          }
 318  
 319          return rest_ensure_response( $response );
 320      }
 321  
 322  
 323      /**
 324       * Retrieves the autosave's schema, conforming to JSON Schema.
 325       *
 326       * @since 5.0.0
 327       *
 328       * @return array Item schema data.
 329       */
 330  	public function get_item_schema() {
 331          if ( $this->schema ) {
 332              return $this->add_additional_fields_schema( $this->schema );
 333          }
 334  
 335          $schema = $this->revisions_controller->get_item_schema();
 336  
 337          $schema['properties']['preview_link'] = array(
 338              'description' => __( 'Preview link for the post.' ),
 339              'type'        => 'string',
 340              'format'      => 'uri',
 341              'context'     => array( 'edit' ),
 342              'readonly'    => true,
 343          );
 344  
 345          $this->schema = $schema;
 346  
 347          return $this->add_additional_fields_schema( $this->schema );
 348      }
 349  
 350      /**
 351       * Creates autosave for the specified post.
 352       *
 353       * From wp-admin/post.php.
 354       *
 355       * @since 5.0.0
 356       * @since 6.4.0 The `$meta` parameter was added.
 357       *
 358       * @param array $post_data Associative array containing the post data.
 359       * @param array $meta      Associative array containing the post meta data.
 360       * @return mixed The autosave revision ID or WP_Error.
 361       */
 362  	public function create_post_autosave( $post_data, array $meta = array() ) {
 363  
 364          $post_id = (int) $post_data['ID'];
 365          $post    = get_post( $post_id );
 366  
 367          if ( is_wp_error( $post ) ) {
 368              return $post;
 369          }
 370  
 371          // Only create an autosave when it is different from the saved post.
 372          $autosave_is_different = false;
 373          $new_autosave          = _wp_post_revision_data( $post_data, true );
 374  
 375          foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
 376              if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
 377                  $autosave_is_different = true;
 378                  break;
 379              }
 380          }
 381  
 382          // Check if meta values have changed.
 383          if ( ! empty( $meta ) ) {
 384              $revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
 385              foreach ( $revisioned_meta_keys as $meta_key ) {
 386                  // get_metadata_raw is used to avoid retrieving the default value.
 387                  $old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
 388                  $new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';
 389  
 390                  if ( $new_meta !== $old_meta ) {
 391                      $autosave_is_different = true;
 392                      break;
 393                  }
 394              }
 395          }
 396  
 397          $user_id = get_current_user_id();
 398  
 399          // Store one autosave per author. If there is already an autosave, overwrite it.
 400          $old_autosave = wp_get_post_autosave( $post_id, $user_id );
 401  
 402          if ( ! $autosave_is_different && $old_autosave ) {
 403              // Nothing to save, return the existing autosave.
 404              return $old_autosave->ID;
 405          }
 406  
 407          if ( $old_autosave ) {
 408              $new_autosave['ID']          = $old_autosave->ID;
 409              $new_autosave['post_author'] = $user_id;
 410  
 411              /** This filter is documented in wp-admin/post.php */
 412              do_action( 'wp_creating_autosave', $new_autosave );
 413  
 414              // wp_update_post() expects escaped array.
 415              $revision_id = wp_update_post( wp_slash( $new_autosave ) );
 416          } else {
 417              // Create the new autosave as a special post revision.
 418              $revision_id = _wp_put_post_revision( $post_data, true );
 419          }
 420  
 421          if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
 422              return $revision_id;
 423          }
 424  
 425          // Attached any passed meta values that have revisions enabled.
 426          if ( ! empty( $meta ) ) {
 427              foreach ( $revisioned_meta_keys as $meta_key ) {
 428                  if ( isset( $meta[ $meta_key ] ) ) {
 429                      update_metadata( 'post', $revision_id, $meta_key, wp_slash( $meta[ $meta_key ] ) );
 430                  }
 431              }
 432          }
 433  
 434          return $revision_id;
 435      }
 436  
 437      /**
 438       * Prepares the revision for the REST response.
 439       *
 440       * @since 5.0.0
 441       * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
 442       *
 443       * @param WP_Post         $item    Post revision object.
 444       * @param WP_REST_Request $request Request object.
 445       * @return WP_REST_Response Response object.
 446       */
 447  	public function prepare_item_for_response( $item, $request ) {
 448          // Restores the more descriptive, specific name for use within this method.
 449          $post = $item;
 450  
 451          $response = $this->revisions_controller->prepare_item_for_response( $post, $request );
 452          $fields   = $this->get_fields_for_response( $request );
 453  
 454          if ( in_array( 'preview_link', $fields, true ) ) {
 455              $parent_id          = wp_is_post_autosave( $post );
 456              $preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
 457              $preview_query_args = array();
 458  
 459              if ( false !== $parent_id ) {
 460                  $preview_query_args['preview_id']    = $parent_id;
 461                  $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
 462              }
 463  
 464              $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
 465          }
 466  
 467          $context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
 468          $response->data = $this->add_additional_fields_to_object( $response->data, $request );
 469          $response->data = $this->filter_response_by_context( $response->data, $context );
 470  
 471          /**
 472           * Filters a revision returned from the REST API.
 473           *
 474           * Allows modification of the revision right before it is returned.
 475           *
 476           * @since 5.0.0
 477           *
 478           * @param WP_REST_Response $response The response object.
 479           * @param WP_Post          $post     The original revision object.
 480           * @param WP_REST_Request  $request  Request used to generate the response.
 481           */
 482          return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
 483      }
 484  
 485      /**
 486       * Retrieves the query params for the autosaves collection.
 487       *
 488       * @since 5.0.0
 489       *
 490       * @return array Collection parameters.
 491       */
 492  	public function get_collection_params() {
 493          return array(
 494              'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 495          );
 496      }
 497  }


Generated : Thu Apr 25 08:20:02 2024 Cross-referenced by PHPXref