home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / rest-api / endpoints / class-wp-rest-revisions-controller.php < prev    next >
Encoding:
PHP Script  |  2017-10-04  |  16.6 KB  |  567 lines

  1. <?php
  2. /**
  3.  * REST API: WP_REST_Revisions_Controller class
  4.  *
  5.  * @package WordPress
  6.  * @subpackage REST_API
  7.  * @since 4.7.0
  8.  */
  9.  
  10. /**
  11.  * Core class used to access revisions via the REST API.
  12.  *
  13.  * @since 4.7.0
  14.  *
  15.  * @see WP_REST_Controller
  16.  */
  17. class WP_REST_Revisions_Controller extends WP_REST_Controller {
  18.  
  19.     /**
  20.      * Parent post type.
  21.      *
  22.      * @since 4.7.0
  23.      * @var string
  24.      */
  25.     private $parent_post_type;
  26.  
  27.     /**
  28.      * Parent controller.
  29.      *
  30.      * @since 4.7.0
  31.      * @var WP_REST_Controller
  32.      */
  33.     private $parent_controller;
  34.  
  35.     /**
  36.      * The base of the parent controller's route.
  37.      *
  38.      * @since 4.7.0
  39.      * @var string
  40.      */
  41.     private $parent_base;
  42.  
  43.     /**
  44.      * Constructor.
  45.      *
  46.      * @since 4.7.0
  47.      *
  48.      * @param string $parent_post_type Post type of the parent.
  49.      */
  50.     public function __construct( $parent_post_type ) {
  51.         $this->parent_post_type = $parent_post_type;
  52.         $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
  53.         $this->namespace = 'wp/v2';
  54.         $this->rest_base = 'revisions';
  55.         $post_type_object = get_post_type_object( $parent_post_type );
  56.         $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
  57.     }
  58.  
  59.     /**
  60.      * Registers routes for revisions based on post types supporting revisions.
  61.      *
  62.      * @since 4.7.0
  63.      *
  64.      * @see register_rest_route()
  65.      */
  66.     public function register_routes() {
  67.  
  68.         register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array(
  69.             'args' => array(
  70.                 'parent' => array(
  71.                     'description' => __( 'The ID for the parent of the object.' ),
  72.                     'type'        => 'integer',
  73.                 ),
  74.             ),
  75.             array(
  76.                 'methods'             => WP_REST_Server::READABLE,
  77.                 'callback'            => array( $this, 'get_items' ),
  78.                 'permission_callback' => array( $this, 'get_items_permissions_check' ),
  79.                 'args'                => $this->get_collection_params(),
  80.             ),
  81.             'schema' => array( $this, 'get_public_item_schema' ),
  82.         ) );
  83.  
  84.         register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array(
  85.             'args' => array(
  86.                 'parent' => array(
  87.                     'description' => __( 'The ID for the parent of the object.' ),
  88.                     'type'        => 'integer',
  89.                 ),
  90.                 'id' => array(
  91.                     'description' => __( 'Unique identifier for the object.' ),
  92.                     'type'        => 'integer',
  93.                 ),
  94.             ),
  95.             array(
  96.                 'methods'             => WP_REST_Server::READABLE,
  97.                 'callback'            => array( $this, 'get_item' ),
  98.                 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  99.                 'args'                => array(
  100.                     'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  101.                 ),
  102.             ),
  103.             array(
  104.                 'methods'             => WP_REST_Server::DELETABLE,
  105.                 'callback'            => array( $this, 'delete_item' ),
  106.                 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  107.                 'args'                => array(
  108.                     'force' => array(
  109.                         'type'        => 'boolean',
  110.                         'default'     => false,
  111.                         'description' => __( 'Required to be true, as revisions do not support trashing.' ),
  112.                     ),
  113.                 ),
  114.             ),
  115.             'schema' => array( $this, 'get_public_item_schema' ),
  116.         ));
  117.  
  118.     }
  119.  
  120.     /**
  121.      * Get the parent post, if the ID is valid.
  122.      *
  123.      * @since 4.7.2
  124.      *
  125.      * @param int $id Supplied ID.
  126.      * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
  127.      */
  128.     protected function get_parent( $parent ) {
  129.         $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
  130.         if ( (int) $parent <= 0 ) {
  131.             return $error;
  132.         }
  133.  
  134.         $parent = get_post( (int) $parent );
  135.         if ( empty( $parent ) || empty( $parent->ID ) || $this->parent_post_type !== $parent->post_type ) {
  136.             return $error;
  137.         }
  138.  
  139.         return $parent;
  140.     }
  141.  
  142.     /**
  143.      * Checks if a given request has access to get revisions.
  144.      *
  145.      * @since 4.7.0
  146.      *
  147.      * @param WP_REST_Request $request Full data about the request.
  148.      * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  149.      */
  150.     public function get_items_permissions_check( $request ) {
  151.         $parent = $this->get_parent( $request['parent'] );
  152.         if ( is_wp_error( $parent ) ) {
  153.             return $parent;
  154.         }
  155.  
  156.         $parent_post_type_obj = get_post_type_object( $parent->post_type );
  157.         if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
  158.             return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
  159.         }
  160.  
  161.         return true;
  162.     }
  163.  
  164.     /**
  165.      * Get the revision, if the ID is valid.
  166.      *
  167.      * @since 4.7.2
  168.      *
  169.      * @param int $id Supplied ID.
  170.      * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
  171.      */
  172.     protected function get_revision( $id ) {
  173.         $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) );
  174.         if ( (int) $id <= 0 ) {
  175.             return $error;
  176.         }
  177.  
  178.         $revision = get_post( (int) $id );
  179.         if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) {
  180.             return $error;
  181.         }
  182.  
  183.         return $revision;
  184.     }
  185.  
  186.     /**
  187.      * Gets a collection of revisions.
  188.      *
  189.      * @since 4.7.0
  190.      *
  191.      * @param WP_REST_Request $request Full data about the request.
  192.      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  193.      */
  194.     public function get_items( $request ) {
  195.         $parent = $this->get_parent( $request['parent'] );
  196.         if ( is_wp_error( $parent ) ) {
  197.             return $parent;
  198.         }
  199.  
  200.         $revisions = wp_get_post_revisions( $request['parent'] );
  201.  
  202.         $response = array();
  203.         foreach ( $revisions as $revision ) {
  204.             $data = $this->prepare_item_for_response( $revision, $request );
  205.             $response[] = $this->prepare_response_for_collection( $data );
  206.         }
  207.         return rest_ensure_response( $response );
  208.     }
  209.  
  210.     /**
  211.      * Checks if a given request has access to get a specific revision.
  212.      *
  213.      * @since 4.7.0
  214.      *
  215.      * @param WP_REST_Request $request Full data about the request.
  216.      * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
  217.      */
  218.     public function get_item_permissions_check( $request ) {
  219.         return $this->get_items_permissions_check( $request );
  220.     }
  221.  
  222.     /**
  223.      * Retrieves one revision from the collection.
  224.      *
  225.      * @since 4.7.0
  226.      *
  227.      * @param WP_REST_Request $request Full data about the request.
  228.      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  229.      */
  230.     public function get_item( $request ) {
  231.         $parent = $this->get_parent( $request['parent'] );
  232.         if ( is_wp_error( $parent ) ) {
  233.             return $parent;
  234.         }
  235.  
  236.         $revision = $this->get_revision( $request['id'] );
  237.         if ( is_wp_error( $revision ) ) {
  238.             return $revision;
  239.         }
  240.  
  241.         $response = $this->prepare_item_for_response( $revision, $request );
  242.         return rest_ensure_response( $response );
  243.     }
  244.  
  245.     /**
  246.      * Checks if a given request has access to delete a revision.
  247.      *
  248.      * @since 4.7.0
  249.      *
  250.      * @param  WP_REST_Request $request Full details about the request.
  251.      * @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
  252.      */
  253.     public function delete_item_permissions_check( $request ) {
  254.         $parent = $this->get_parent( $request['parent'] );
  255.         if ( is_wp_error( $parent ) ) {
  256.             return $parent;
  257.         }
  258.  
  259.         $revision = $this->get_revision( $request['id'] );
  260.         if ( is_wp_error( $revision ) ) {
  261.             return $revision;
  262.         }
  263.  
  264.         $response = $this->get_items_permissions_check( $request );
  265.         if ( ! $response || is_wp_error( $response ) ) {
  266.             return $response;
  267.         }
  268.  
  269.         $post_type = get_post_type_object( 'revision' );
  270.         return current_user_can( $post_type->cap->delete_post, $revision->ID );
  271.     }
  272.  
  273.     /**
  274.      * Deletes a single revision.
  275.      *
  276.      * @since 4.7.0
  277.      *
  278.      * @param WP_REST_Request $request Full details about the request.
  279.      * @return true|WP_Error True on success, or WP_Error object on failure.
  280.      */
  281.     public function delete_item( $request ) {
  282.         $revision = $this->get_revision( $request['id'] );
  283.         if ( is_wp_error( $revision ) ) {
  284.             return $revision;
  285.         }
  286.  
  287.         $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
  288.  
  289.         // We don't support trashing for revisions.
  290.         if ( ! $force ) {
  291.             /* translators: %s: force=true */
  292.             return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Revisions do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
  293.         }
  294.  
  295.         $previous = $this->prepare_item_for_response( $revision, $request );
  296.  
  297.         $result = wp_delete_post( $request['id'], true );
  298.  
  299.         /**
  300.          * Fires after a revision is deleted via the REST API.
  301.          *
  302.          * @since 4.7.0
  303.          *
  304.          * @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully)
  305.          *                        or false (failure). If the revision was moved to the trash, $result represents
  306.          *                        its new state; if it was deleted, $result represents its state before deletion.
  307.          * @param WP_REST_Request $request The request sent to the API.
  308.          */
  309.         do_action( 'rest_delete_revision', $result, $request );
  310.  
  311.         if ( ! $result ) {
  312.             return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
  313.         }
  314.  
  315.         $response = new WP_REST_Response();
  316.         $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
  317.         return $response;
  318.     }
  319.  
  320.     /**
  321.      * Prepares the revision for the REST response.
  322.      *
  323.      * @since 4.7.0
  324.      *
  325.      * @param WP_Post         $post    Post revision object.
  326.      * @param WP_REST_Request $request Request object.
  327.      * @return WP_REST_Response Response object.
  328.      */
  329.     public function prepare_item_for_response( $post, $request ) {
  330.         $GLOBALS['post'] = $post;
  331.  
  332.         setup_postdata( $post );
  333.  
  334.         $schema = $this->get_item_schema();
  335.  
  336.         $data = array();
  337.  
  338.         if ( ! empty( $schema['properties']['author'] ) ) {
  339.             $data['author'] = (int) $post->post_author;
  340.         }
  341.  
  342.         if ( ! empty( $schema['properties']['date'] ) ) {
  343.             $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
  344.         }
  345.  
  346.         if ( ! empty( $schema['properties']['date_gmt'] ) ) {
  347.             $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
  348.         }
  349.  
  350.         if ( ! empty( $schema['properties']['id'] ) ) {
  351.             $data['id'] = $post->ID;
  352.         }
  353.  
  354.         if ( ! empty( $schema['properties']['modified'] ) ) {
  355.             $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
  356.         }
  357.  
  358.         if ( ! empty( $schema['properties']['modified_gmt'] ) ) {
  359.             $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
  360.         }
  361.  
  362.         if ( ! empty( $schema['properties']['parent'] ) ) {
  363.             $data['parent'] = (int) $post->post_parent;
  364.         }
  365.  
  366.         if ( ! empty( $schema['properties']['slug'] ) ) {
  367.             $data['slug'] = $post->post_name;
  368.         }
  369.  
  370.         if ( ! empty( $schema['properties']['guid'] ) ) {
  371.             $data['guid'] = array(
  372.                 /** This filter is documented in wp-includes/post-template.php */
  373.                 'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
  374.                 'raw'      => $post->guid,
  375.             );
  376.         }
  377.  
  378.         if ( ! empty( $schema['properties']['title'] ) ) {
  379.             $data['title'] = array(
  380.                 'raw'      => $post->post_title,
  381.                 'rendered' => get_the_title( $post->ID ),
  382.             );
  383.         }
  384.  
  385.         if ( ! empty( $schema['properties']['content'] ) ) {
  386.  
  387.             $data['content'] = array(
  388.                 'raw'      => $post->post_content,
  389.                 /** This filter is documented in wp-includes/post-template.php */
  390.                 'rendered' => apply_filters( 'the_content', $post->post_content ),
  391.             );
  392.         }
  393.  
  394.         if ( ! empty( $schema['properties']['excerpt'] ) ) {
  395.             $data['excerpt'] = array(
  396.                 'raw'      => $post->post_excerpt,
  397.                 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ),
  398.             );
  399.         }
  400.  
  401.         $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
  402.         $data = $this->add_additional_fields_to_object( $data, $request );
  403.         $data = $this->filter_response_by_context( $data, $context );
  404.         $response = rest_ensure_response( $data );
  405.  
  406.         if ( ! empty( $data['parent'] ) ) {
  407.             $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) );
  408.         }
  409.  
  410.         /**
  411.          * Filters a revision returned from the API.
  412.          *
  413.          * Allows modification of the revision right before it is returned.
  414.          *
  415.          * @since 4.7.0
  416.          *
  417.          * @param WP_REST_Response $response The response object.
  418.          * @param WP_Post          $post     The original revision object.
  419.          * @param WP_REST_Request  $request  Request used to generate the response.
  420.          */
  421.         return apply_filters( 'rest_prepare_revision', $response, $post, $request );
  422.     }
  423.  
  424.     /**
  425.      * Checks the post_date_gmt or modified_gmt and prepare any post or
  426.      * modified date for single post output.
  427.      *
  428.      * @since 4.7.0
  429.      *
  430.      * @param string      $date_gmt GMT publication time.
  431.      * @param string|null $date     Optional. Local publication time. Default null.
  432.      * @return string|null ISO8601/RFC3339 formatted datetime, otherwise null.
  433.      */
  434.     protected function prepare_date_response( $date_gmt, $date = null ) {
  435.         if ( '0000-00-00 00:00:00' === $date_gmt ) {
  436.             return null;
  437.         }
  438.  
  439.         if ( isset( $date ) ) {
  440.             return mysql_to_rfc3339( $date );
  441.         }
  442.  
  443.         return mysql_to_rfc3339( $date_gmt );
  444.     }
  445.  
  446.     /**
  447.      * Retrieves the revision's schema, conforming to JSON Schema.
  448.      *
  449.      * @since 4.7.0
  450.      *
  451.      * @return array Item schema data.
  452.      */
  453.     public function get_item_schema() {
  454.         $schema = array(
  455.             '$schema'    => 'http://json-schema.org/draft-04/schema#',
  456.             'title'      => "{$this->parent_post_type}-revision",
  457.             'type'       => 'object',
  458.             // Base properties for every Revision.
  459.             'properties' => array(
  460.                 'author'          => array(
  461.                     'description' => __( 'The ID for the author of the object.' ),
  462.                     'type'        => 'integer',
  463.                     'context'     => array( 'view', 'edit', 'embed' ),
  464.                 ),
  465.                 'date'            => array(
  466.                     'description' => __( "The date the object was published, in the site's timezone." ),
  467.                     'type'        => 'string',
  468.                     'format'      => 'date-time',
  469.                     'context'     => array( 'view', 'edit', 'embed' ),
  470.                 ),
  471.                 'date_gmt'        => array(
  472.                     'description' => __( 'The date the object was published, as GMT.' ),
  473.                     'type'        => 'string',
  474.                     'format'      => 'date-time',
  475.                     'context'     => array( 'view', 'edit' ),
  476.                 ),
  477.                 'guid'            => array(
  478.                     'description' => __( 'GUID for the object, as it exists in the database.' ),
  479.                     'type'        => 'string',
  480.                     'context'     => array( 'view', 'edit' ),
  481.                 ),
  482.                 'id'              => array(
  483.                     'description' => __( 'Unique identifier for the object.' ),
  484.                     'type'        => 'integer',
  485.                     'context'     => array( 'view', 'edit', 'embed' ),
  486.                 ),
  487.                 'modified'        => array(
  488.                     'description' => __( "The date the object was last modified, in the site's timezone." ),
  489.                     'type'        => 'string',
  490.                     'format'      => 'date-time',
  491.                     'context'     => array( 'view', 'edit' ),
  492.                 ),
  493.                 'modified_gmt'    => array(
  494.                     'description' => __( 'The date the object was last modified, as GMT.' ),
  495.                     'type'        => 'string',
  496.                     'format'      => 'date-time',
  497.                     'context'     => array( 'view', 'edit' ),
  498.                 ),
  499.                 'parent'          => array(
  500.                     'description' => __( 'The ID for the parent of the object.' ),
  501.                     'type'        => 'integer',
  502.                     'context'     => array( 'view', 'edit', 'embed' ),
  503.                     ),
  504.                 'slug'            => array(
  505.                     'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
  506.                     'type'        => 'string',
  507.                     'context'     => array( 'view', 'edit', 'embed' ),
  508.                 ),
  509.             ),
  510.         );
  511.  
  512.         $parent_schema = $this->parent_controller->get_item_schema();
  513.  
  514.         if ( ! empty( $parent_schema['properties']['title'] ) ) {
  515.             $schema['properties']['title'] = $parent_schema['properties']['title'];
  516.         }
  517.  
  518.         if ( ! empty( $parent_schema['properties']['content'] ) ) {
  519.             $schema['properties']['content'] = $parent_schema['properties']['content'];
  520.         }
  521.  
  522.         if ( ! empty( $parent_schema['properties']['excerpt'] ) ) {
  523.             $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt'];
  524.         }
  525.  
  526.         if ( ! empty( $parent_schema['properties']['guid'] ) ) {
  527.             $schema['properties']['guid'] = $parent_schema['properties']['guid'];
  528.         }
  529.  
  530.         return $this->add_additional_fields_schema( $schema );
  531.     }
  532.  
  533.     /**
  534.      * Retrieves the query params for collections.
  535.      *
  536.      * @since 4.7.0
  537.      *
  538.      * @return array Collection parameters.
  539.      */
  540.     public function get_collection_params() {
  541.         return array(
  542.             'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  543.         );
  544.     }
  545.  
  546.     /**
  547.      * Checks the post excerpt and prepare it for single post output.
  548.      *
  549.      * @since 4.7.0
  550.      *
  551.      * @param string  $excerpt The post excerpt.
  552.      * @param WP_Post $post    Post revision object.
  553.      * @return string Prepared excerpt or empty string.
  554.      */
  555.     protected function prepare_excerpt_response( $excerpt, $post ) {
  556.  
  557.         /** This filter is documented in wp-includes/post-template.php */
  558.         $excerpt = apply_filters( 'the_excerpt', $excerpt, $post );
  559.  
  560.         if ( empty( $excerpt ) ) {
  561.             return '';
  562.         }
  563.  
  564.         return $excerpt;
  565.     }
  566. }
  567.