ru-se.com

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

class-wp-rest-templates-controller.php (17760B)


      1 <?php
      2 /**
      3  * REST API: WP_REST_Templates_Controller class
      4  *
      5  * @package    WordPress
      6  * @subpackage REST_API
      7  * @since 5.8.0
      8  */
      9 
     10 /**
     11  * Base Templates REST API Controller.
     12  *
     13  * @since 5.8.0
     14  *
     15  * @see WP_REST_Controller
     16  */
     17 class WP_REST_Templates_Controller extends WP_REST_Controller {
     18 
     19 	/**
     20 	 * Post type.
     21 	 *
     22 	 * @since 5.8.0
     23 	 * @var string
     24 	 */
     25 	protected $post_type;
     26 
     27 	/**
     28 	 * Constructor.
     29 	 *
     30 	 * @since 5.8.0
     31 	 *
     32 	 * @param string $post_type Post type.
     33 	 */
     34 	public function __construct( $post_type ) {
     35 		$this->post_type = $post_type;
     36 		$this->namespace = 'wp/v2';
     37 		$obj             = get_post_type_object( $post_type );
     38 		$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
     39 	}
     40 
     41 	/**
     42 	 * Registers the controllers routes.
     43 	 *
     44 	 * @since 5.8.0
     45 	 */
     46 	public function register_routes() {
     47 		// Lists all templates.
     48 		register_rest_route(
     49 			$this->namespace,
     50 			'/' . $this->rest_base,
     51 			array(
     52 				array(
     53 					'methods'             => WP_REST_Server::READABLE,
     54 					'callback'            => array( $this, 'get_items' ),
     55 					'permission_callback' => array( $this, 'get_items_permissions_check' ),
     56 					'args'                => $this->get_collection_params(),
     57 				),
     58 				array(
     59 					'methods'             => WP_REST_Server::CREATABLE,
     60 					'callback'            => array( $this, 'create_item' ),
     61 					'permission_callback' => array( $this, 'create_item_permissions_check' ),
     62 					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
     63 				),
     64 				'schema' => array( $this, 'get_public_item_schema' ),
     65 			)
     66 		);
     67 
     68 		// Lists/updates a single template based on the given id.
     69 		register_rest_route(
     70 			$this->namespace,
     71 			'/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
     72 			array(
     73 				array(
     74 					'methods'             => WP_REST_Server::READABLE,
     75 					'callback'            => array( $this, 'get_item' ),
     76 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
     77 					'args'                => array(
     78 						'id' => array(
     79 							'description' => __( 'The id of a template' ),
     80 							'type'        => 'string',
     81 						),
     82 					),
     83 				),
     84 				array(
     85 					'methods'             => WP_REST_Server::EDITABLE,
     86 					'callback'            => array( $this, 'update_item' ),
     87 					'permission_callback' => array( $this, 'update_item_permissions_check' ),
     88 					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
     89 				),
     90 				array(
     91 					'methods'             => WP_REST_Server::DELETABLE,
     92 					'callback'            => array( $this, 'delete_item' ),
     93 					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
     94 					'args'                => array(
     95 						'force' => array(
     96 							'type'        => 'boolean',
     97 							'default'     => false,
     98 							'description' => __( 'Whether to bypass Trash and force deletion.' ),
     99 						),
    100 					),
    101 				),
    102 				'schema' => array( $this, 'get_public_item_schema' ),
    103 			)
    104 		);
    105 	}
    106 
    107 	/**
    108 	 * Checks if the user has permissions to make the request.
    109 	 *
    110 	 * @since 5.8.0
    111 	 *
    112 	 * @param WP_REST_Request $request Full details about the request.
    113 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
    114 	 */
    115 	protected function permissions_check( $request ) {
    116 		// Verify if the current user has edit_theme_options capability.
    117 		// This capability is required to edit/view/delete templates.
    118 		if ( ! current_user_can( 'edit_theme_options' ) ) {
    119 			return new WP_Error(
    120 				'rest_cannot_manage_templates',
    121 				__( 'Sorry, you are not allowed to access the templates on this site.' ),
    122 				array(
    123 					'status' => rest_authorization_required_code(),
    124 				)
    125 			);
    126 		}
    127 
    128 		return true;
    129 	}
    130 
    131 	/**
    132 	 * Checks if a given request has access to read templates.
    133 	 *
    134 	 * @since 5.8.0
    135 	 *
    136 	 * @param WP_REST_Request $request Full details about the request.
    137 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
    138 	 */
    139 	public function get_items_permissions_check( $request ) {
    140 		return $this->permissions_check( $request );
    141 	}
    142 
    143 	/**
    144 	 * Returns a list of templates.
    145 	 *
    146 	 * @since 5.8.0
    147 	 *
    148 	 * @param WP_REST_Request $request The request instance.
    149 	 * @return WP_REST_Response
    150 	 */
    151 	public function get_items( $request ) {
    152 		$query = array();
    153 		if ( isset( $request['wp_id'] ) ) {
    154 			$query['wp_id'] = $request['wp_id'];
    155 		}
    156 		if ( isset( $request['area'] ) ) {
    157 			$query['area'] = $request['area'];
    158 		}
    159 
    160 		$templates = array();
    161 		foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
    162 			$data        = $this->prepare_item_for_response( $template, $request );
    163 			$templates[] = $this->prepare_response_for_collection( $data );
    164 		}
    165 
    166 		return rest_ensure_response( $templates );
    167 	}
    168 
    169 	/**
    170 	 * Checks if a given request has access to read a single template.
    171 	 *
    172 	 * @since 5.8.0
    173 	 *
    174 	 * @param WP_REST_Request $request Full details about the request.
    175 	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
    176 	 */
    177 	public function get_item_permissions_check( $request ) {
    178 		return $this->permissions_check( $request );
    179 	}
    180 
    181 	/**
    182 	 * Returns the given template
    183 	 *
    184 	 * @since 5.8.0
    185 	 *
    186 	 * @param WP_REST_Request $request The request instance.
    187 	 * @return WP_REST_Response|WP_Error
    188 	 */
    189 	public function get_item( $request ) {
    190 		$template = get_block_template( $request['id'], $this->post_type );
    191 
    192 		if ( ! $template ) {
    193 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
    194 		}
    195 
    196 		return $this->prepare_item_for_response( $template, $request );
    197 	}
    198 
    199 	/**
    200 	 * Checks if a given request has access to write a single template.
    201 	 *
    202 	 * @since 5.8.0
    203 	 *
    204 	 * @param WP_REST_Request $request Full details about the request.
    205 	 * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
    206 	 */
    207 	public function update_item_permissions_check( $request ) {
    208 		return $this->permissions_check( $request );
    209 	}
    210 
    211 	/**
    212 	 * Updates a single template.
    213 	 *
    214 	 * @since 5.8.0
    215 	 *
    216 	 * @param WP_REST_Request $request Full details about the request.
    217 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    218 	 */
    219 	public function update_item( $request ) {
    220 		$template = get_block_template( $request['id'], $this->post_type );
    221 		if ( ! $template ) {
    222 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
    223 		}
    224 
    225 		$changes = $this->prepare_item_for_database( $request );
    226 
    227 		if ( 'custom' === $template->source ) {
    228 			$result = wp_update_post( wp_slash( (array) $changes ), true );
    229 		} else {
    230 			$result = wp_insert_post( wp_slash( (array) $changes ), true );
    231 		}
    232 		if ( is_wp_error( $result ) ) {
    233 			return $result;
    234 		}
    235 
    236 		$template      = get_block_template( $request['id'], $this->post_type );
    237 		$fields_update = $this->update_additional_fields_for_object( $template, $request );
    238 		if ( is_wp_error( $fields_update ) ) {
    239 			return $fields_update;
    240 		}
    241 
    242 		return $this->prepare_item_for_response(
    243 			get_block_template( $request['id'], $this->post_type ),
    244 			$request
    245 		);
    246 	}
    247 
    248 	/**
    249 	 * Checks if a given request has access to create a template.
    250 	 *
    251 	 * @since 5.8.0
    252 	 *
    253 	 * @param WP_REST_Request $request Full details about the request.
    254 	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
    255 	 */
    256 	public function create_item_permissions_check( $request ) {
    257 		return $this->permissions_check( $request );
    258 	}
    259 
    260 	/**
    261 	 * Creates a single template.
    262 	 *
    263 	 * @since 5.8.0
    264 	 *
    265 	 * @param WP_REST_Request $request Full details about the request.
    266 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    267 	 */
    268 	public function create_item( $request ) {
    269 		$changes            = $this->prepare_item_for_database( $request );
    270 		$changes->post_name = $request['slug'];
    271 		$result             = wp_insert_post( wp_slash( (array) $changes ), true );
    272 		if ( is_wp_error( $result ) ) {
    273 			return $result;
    274 		}
    275 		$posts = get_block_templates( array( 'wp_id' => $result ), $this->post_type );
    276 		if ( ! count( $posts ) ) {
    277 			return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ) );
    278 		}
    279 		$id            = $posts[0]->id;
    280 		$template      = get_block_template( $id, $this->post_type );
    281 		$fields_update = $this->update_additional_fields_for_object( $template, $request );
    282 		if ( is_wp_error( $fields_update ) ) {
    283 			return $fields_update;
    284 		}
    285 
    286 		return $this->prepare_item_for_response(
    287 			get_block_template( $id, $this->post_type ),
    288 			$request
    289 		);
    290 	}
    291 
    292 	/**
    293 	 * Checks if a given request has access to delete a single template.
    294 	 *
    295 	 * @since 5.8.0
    296 	 *
    297 	 * @param WP_REST_Request $request Full details about the request.
    298 	 * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
    299 	 */
    300 	public function delete_item_permissions_check( $request ) {
    301 		return $this->permissions_check( $request );
    302 	}
    303 
    304 	/**
    305 	 * Deletes a single template.
    306 	 *
    307 	 * @since 5.8.0
    308 	 *
    309 	 * @param WP_REST_Request $request Full details about the request.
    310 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    311 	 */
    312 	public function delete_item( $request ) {
    313 		$template = get_block_template( $request['id'], $this->post_type );
    314 		if ( ! $template ) {
    315 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
    316 		}
    317 		if ( 'custom' !== $template->source ) {
    318 			return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
    319 		}
    320 
    321 		$id    = $template->wp_id;
    322 		$force = (bool) $request['force'];
    323 
    324 		// If we're forcing, then delete permanently.
    325 		if ( $force ) {
    326 			$previous = $this->prepare_item_for_response( $template, $request );
    327 			wp_delete_post( $id, true );
    328 			$response = new WP_REST_Response();
    329 			$response->set_data(
    330 				array(
    331 					'deleted'  => true,
    332 					'previous' => $previous->get_data(),
    333 				)
    334 			);
    335 
    336 			return $response;
    337 		}
    338 
    339 		// Otherwise, only trash if we haven't already.
    340 		if ( 'trash' === $template->status ) {
    341 			return new WP_Error(
    342 				'rest_template_already_trashed',
    343 				__( 'The template has already been deleted.' ),
    344 				array( 'status' => 410 )
    345 			);
    346 		}
    347 
    348 		wp_trash_post( $id );
    349 		$template->status = 'trash';
    350 		return $this->prepare_item_for_response( $template, $request );
    351 	}
    352 
    353 	/**
    354 	 * Prepares a single template for create or update.
    355 	 *
    356 	 * @since 5.8.0
    357 	 *
    358 	 * @param WP_REST_Request $request Request object.
    359 	 * @return stdClass Changes to pass to wp_update_post.
    360 	 */
    361 	protected function prepare_item_for_database( $request ) {
    362 		$template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
    363 		$changes  = new stdClass();
    364 		if ( null === $template ) {
    365 			$changes->post_type   = $this->post_type;
    366 			$changes->post_status = 'publish';
    367 			$changes->tax_input   = array(
    368 				'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
    369 			);
    370 		} elseif ( 'custom' !== $template->source ) {
    371 			$changes->post_name   = $template->slug;
    372 			$changes->post_type   = $this->post_type;
    373 			$changes->post_status = 'publish';
    374 			$changes->tax_input   = array(
    375 				'wp_theme' => $template->theme,
    376 			);
    377 		} else {
    378 			$changes->post_name   = $template->slug;
    379 			$changes->ID          = $template->wp_id;
    380 			$changes->post_status = 'publish';
    381 		}
    382 		if ( isset( $request['content'] ) ) {
    383 			$changes->post_content = $request['content'];
    384 		} elseif ( null !== $template && 'custom' !== $template->source ) {
    385 			$changes->post_content = $template->content;
    386 		}
    387 		if ( isset( $request['title'] ) ) {
    388 			$changes->post_title = $request['title'];
    389 		} elseif ( null !== $template && 'custom' !== $template->source ) {
    390 			$changes->post_title = $template->title;
    391 		}
    392 		if ( isset( $request['description'] ) ) {
    393 			$changes->post_excerpt = $request['description'];
    394 		} elseif ( null !== $template && 'custom' !== $template->source ) {
    395 			$changes->post_excerpt = $template->description;
    396 		}
    397 
    398 		return $changes;
    399 	}
    400 
    401 	/**
    402 	 * Prepare a single template output for response
    403 	 *
    404 	 * @since 5.8.0
    405 	 *
    406 	 * @param WP_Block_Template $template Template instance.
    407 	 * @param WP_REST_Request   $request Request object.
    408 	 * @return WP_REST_Response $data
    409 	 */
    410 	public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
    411 		$result = array(
    412 			'id'             => $template->id,
    413 			'theme'          => $template->theme,
    414 			'content'        => array( 'raw' => $template->content ),
    415 			'slug'           => $template->slug,
    416 			'source'         => $template->source,
    417 			'type'           => $template->type,
    418 			'description'    => $template->description,
    419 			'title'          => array(
    420 				'raw'      => $template->title,
    421 				'rendered' => $template->title,
    422 			),
    423 			'status'         => $template->status,
    424 			'wp_id'          => $template->wp_id,
    425 			'has_theme_file' => $template->has_theme_file,
    426 		);
    427 
    428 		if ( 'wp_template_part' === $template->type ) {
    429 			$result['area'] = $template->area;
    430 		}
    431 
    432 		$result = $this->add_additional_fields_to_object( $result, $request );
    433 
    434 		$response = rest_ensure_response( $result );
    435 		$links    = $this->prepare_links( $template->id );
    436 		$response->add_links( $links );
    437 		if ( ! empty( $links['self']['href'] ) ) {
    438 			$actions = $this->get_available_actions();
    439 			$self    = $links['self']['href'];
    440 			foreach ( $actions as $rel ) {
    441 				$response->add_link( $rel, $self );
    442 			}
    443 		}
    444 
    445 		return $response;
    446 	}
    447 
    448 
    449 	/**
    450 	 * Prepares links for the request.
    451 	 *
    452 	 * @since 5.8.0
    453 	 *
    454 	 * @param integer $id ID.
    455 	 * @return array Links for the given post.
    456 	 */
    457 	protected function prepare_links( $id ) {
    458 		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
    459 
    460 		$links = array(
    461 			'self'       => array(
    462 				'href' => rest_url( trailingslashit( $base ) . $id ),
    463 			),
    464 			'collection' => array(
    465 				'href' => rest_url( $base ),
    466 			),
    467 			'about'      => array(
    468 				'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
    469 			),
    470 		);
    471 
    472 		return $links;
    473 	}
    474 
    475 	/**
    476 	 * Get the link relations available for the post and current user.
    477 	 *
    478 	 * @since 5.8.0
    479 	 *
    480 	 * @return array List of link relations.
    481 	 */
    482 	protected function get_available_actions() {
    483 		$rels = array();
    484 
    485 		$post_type = get_post_type_object( $this->post_type );
    486 
    487 		if ( current_user_can( $post_type->cap->publish_posts ) ) {
    488 			$rels[] = 'https://api.w.org/action-publish';
    489 		}
    490 
    491 		if ( current_user_can( 'unfiltered_html' ) ) {
    492 			$rels[] = 'https://api.w.org/action-unfiltered-html';
    493 		}
    494 
    495 		return $rels;
    496 	}
    497 
    498 	/**
    499 	 * Retrieves the query params for the posts collection.
    500 	 *
    501 	 * @since 5.8.0
    502 	 *
    503 	 * @return array Collection parameters.
    504 	 */
    505 	public function get_collection_params() {
    506 		return array(
    507 			'context' => $this->get_context_param(),
    508 			'wp_id'   => array(
    509 				'description' => __( 'Limit to the specified post id.' ),
    510 				'type'        => 'integer',
    511 			),
    512 		);
    513 	}
    514 
    515 	/**
    516 	 * Retrieves the block type' schema, conforming to JSON Schema.
    517 	 *
    518 	 * @since 5.8.0
    519 	 *
    520 	 * @return array Item schema data.
    521 	 */
    522 	public function get_item_schema() {
    523 		if ( $this->schema ) {
    524 			return $this->add_additional_fields_schema( $this->schema );
    525 		}
    526 
    527 		$schema = array(
    528 			'$schema'    => 'http://json-schema.org/draft-04/schema#',
    529 			'title'      => $this->post_type,
    530 			'type'       => 'object',
    531 			'properties' => array(
    532 				'id'             => array(
    533 					'description' => __( 'ID of template.' ),
    534 					'type'        => 'string',
    535 					'context'     => array( 'embed', 'view', 'edit' ),
    536 					'readonly'    => true,
    537 				),
    538 				'slug'           => array(
    539 					'description' => __( 'Unique slug identifying the template.' ),
    540 					'type'        => 'string',
    541 					'context'     => array( 'embed', 'view', 'edit' ),
    542 					'required'    => true,
    543 					'minLength'   => 1,
    544 					'pattern'     => '[a-zA-Z_\-]+',
    545 				),
    546 				'theme'          => array(
    547 					'description' => __( 'Theme identifier for the template.' ),
    548 					'type'        => 'string',
    549 					'context'     => array( 'embed', 'view', 'edit' ),
    550 				),
    551 				'source'         => array(
    552 					'description' => __( 'Source of template' ),
    553 					'type'        => 'string',
    554 					'context'     => array( 'embed', 'view', 'edit' ),
    555 					'readonly'    => true,
    556 				),
    557 				'content'        => array(
    558 					'description' => __( 'Content of template.' ),
    559 					'type'        => array( 'object', 'string' ),
    560 					'default'     => '',
    561 					'context'     => array( 'embed', 'view', 'edit' ),
    562 				),
    563 				'title'          => array(
    564 					'description' => __( 'Title of template.' ),
    565 					'type'        => array( 'object', 'string' ),
    566 					'default'     => '',
    567 					'context'     => array( 'embed', 'view', 'edit' ),
    568 				),
    569 				'description'    => array(
    570 					'description' => __( 'Description of template.' ),
    571 					'type'        => 'string',
    572 					'default'     => '',
    573 					'context'     => array( 'embed', 'view', 'edit' ),
    574 				),
    575 				'status'         => array(
    576 					'description' => __( 'Status of template.' ),
    577 					'type'        => 'string',
    578 					'default'     => 'publish',
    579 					'context'     => array( 'embed', 'view', 'edit' ),
    580 				),
    581 				'wp_id'          => array(
    582 					'description' => __( 'Post ID.' ),
    583 					'type'        => 'integer',
    584 					'context'     => array( 'embed', 'view', 'edit' ),
    585 					'readonly'    => true,
    586 				),
    587 				'has_theme_file' => array(
    588 					'description' => __( 'Theme file exists.' ),
    589 					'type'        => 'bool',
    590 					'context'     => array( 'embed', 'view', 'edit' ),
    591 					'readonly'    => true,
    592 				),
    593 			),
    594 		);
    595 
    596 		$this->schema = $schema;
    597 
    598 		return $this->add_additional_fields_schema( $this->schema );
    599 	}
    600 }