Diff: STRATO-apps/wordpress_03/app/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
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
+
$obj = get_post_type_object( $post_type );
37
+
$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
38
+
$this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2';
39
+
}
40
+
41
+
/**
42
+
* Registers the controllers routes.
43
+
*
44
+
* @since 5.8.0
45
+
* @since 6.1.0 Endpoint for fallback template content.
46
+
*/
47
+
public function register_routes() {
48
+
// Lists all templates.
49
+
register_rest_route(
50
+
$this->namespace,
51
+
'/' . $this->rest_base,
52
+
array(
53
+
array(
54
+
'methods' => WP_REST_Server::READABLE,
55
+
'callback' => array( $this, 'get_items' ),
56
+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
57
+
'args' => $this->get_collection_params(),
58
+
),
59
+
array(
60
+
'methods' => WP_REST_Server::CREATABLE,
61
+
'callback' => array( $this, 'create_item' ),
62
+
'permission_callback' => array( $this, 'create_item_permissions_check' ),
63
+
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
64
+
),
65
+
'schema' => array( $this, 'get_public_item_schema' ),
66
+
)
67
+
);
68
+
69
+
// Get fallback template content.
70
+
register_rest_route(
71
+
$this->namespace,
72
+
'/' . $this->rest_base . '/lookup',
73
+
array(
74
+
array(
75
+
'methods' => WP_REST_Server::READABLE,
76
+
'callback' => array( $this, 'get_template_fallback' ),
77
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
78
+
'args' => array(
79
+
'slug' => array(
80
+
'description' => __( 'The slug of the template to get the fallback for' ),
81
+
'type' => 'string',
82
+
'required' => true,
83
+
),
84
+
'is_custom' => array(
85
+
'description' => __( 'Indicates if a template is custom or part of the template hierarchy' ),
86
+
'type' => 'boolean',
87
+
),
88
+
'template_prefix' => array(
89
+
'description' => __( 'The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`' ),
90
+
'type' => 'string',
91
+
),
92
+
),
93
+
),
94
+
)
95
+
);
96
+
97
+
// Lists/updates a single template based on the given id.
98
+
register_rest_route(
99
+
$this->namespace,
100
+
// The route.
101
+
sprintf(
102
+
'/%s/(?P<id>%s%s)',
103
+
$this->rest_base,
104
+
/*
105
+
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
106
+
* Excludes invalid directory name characters: `/:<>*?"|`.
107
+
*/
108
+
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
109
+
// Matches the template name.
110
+
'[\/\w%-]+'
111
+
),
112
+
array(
113
+
'args' => array(
114
+
'id' => array(
115
+
'description' => __( 'The id of a template' ),
116
+
'type' => 'string',
117
+
'sanitize_callback' => array( $this, '_sanitize_template_id' ),
118
+
),
119
+
),
120
+
array(
121
+
'methods' => WP_REST_Server::READABLE,
122
+
'callback' => array( $this, 'get_item' ),
123
+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
124
+
'args' => array(
125
+
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
126
+
),
127
+
),
128
+
array(
129
+
'methods' => WP_REST_Server::EDITABLE,
130
+
'callback' => array( $this, 'update_item' ),
131
+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
132
+
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
133
+
),
134
+
array(
135
+
'methods' => WP_REST_Server::DELETABLE,
136
+
'callback' => array( $this, 'delete_item' ),
137
+
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
138
+
'args' => array(
139
+
'force' => array(
140
+
'type' => 'boolean',
141
+
'default' => false,
142
+
'description' => __( 'Whether to bypass Trash and force deletion.' ),
143
+
),
144
+
),
145
+
),
146
+
'schema' => array( $this, 'get_public_item_schema' ),
147
+
)
148
+
);
149
+
}
150
+
151
+
/**
152
+
* Returns the fallback template for the given slug.
153
+
*
154
+
* @since 6.1.0
155
+
* @since 6.3.0 Ignore empty templates.
156
+
*
157
+
* @param WP_REST_Request $request The request instance.
158
+
* @return WP_REST_Response|WP_Error
159
+
*/
160
+
public function get_template_fallback( $request ) {
161
+
$hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] );
162
+
163
+
do {
164
+
$fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
165
+
array_shift( $hierarchy );
166
+
} while ( ! empty( $hierarchy ) && empty( $fallback_template->content ) );
167
+
168
+
// To maintain original behavior, return an empty object rather than a 404 error when no template is found.
169
+
$response = $fallback_template ? $this->prepare_item_for_response( $fallback_template, $request ) : new stdClass();
170
+
171
+
return rest_ensure_response( $response );
172
+
}
173
+
174
+
/**
175
+
* Checks if the user has permissions to make the request.
176
+
*
177
+
* @since 5.8.0
178
+
*
179
+
* @param WP_REST_Request $request Full details about the request.
180
+
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
181
+
*/
182
+
protected function permissions_check( $request ) {
183
+
/*
184
+
* Verify if the current user has edit_theme_options capability.
185
+
* This capability is required to edit/view/delete templates.
186
+
*/
187
+
if ( ! current_user_can( 'edit_theme_options' ) ) {
188
+
return new WP_Error(
189
+
'rest_cannot_manage_templates',
190
+
__( 'Sorry, you are not allowed to access the templates on this site.' ),
191
+
array(
192
+
'status' => rest_authorization_required_code(),
193
+
)
194
+
);
195
+
}
196
+
197
+
return true;
198
+
}
199
+
200
+
/**
201
+
* Requesting this endpoint for a template like 'twentytwentytwo//home'
202
+
* requires using a path like /wp/v2/templates/twentytwentytwo//home. There
203
+
* are special cases when WordPress routing corrects the name to contain
204
+
* only a single slash like 'twentytwentytwo/home'.
205
+
*
206
+
* This method doubles the last slash if it's not already doubled. It relies
207
+
* on the template ID format {theme_name}//{template_slug} and the fact that
208
+
* slugs cannot contain slashes.
209
+
*
210
+
* @since 5.9.0
211
+
* @see https://core.trac.wordpress.org/ticket/54507
212
+
*
213
+
* @param string $id Template ID.
214
+
* @return string Sanitized template ID.
215
+
*/
216
+
public function _sanitize_template_id( $id ) {
217
+
$id = urldecode( $id );
218
+
219
+
$last_slash_pos = strrpos( $id, '/' );
220
+
if ( false === $last_slash_pos ) {
221
+
return $id;
222
+
}
223
+
224
+
$is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/';
225
+
if ( $is_double_slashed ) {
226
+
return $id;
227
+
}
228
+
return (
229
+
substr( $id, 0, $last_slash_pos )
230
+
. '/'
231
+
. substr( $id, $last_slash_pos )
232
+
);
233
+
}
234
+
235
+
/**
236
+
* Checks if a given request has access to read templates.
237
+
*
238
+
* @since 5.8.0
239
+
* @since 6.6.0 Allow users with edit_posts capability to read templates.
240
+
*
241
+
* @param WP_REST_Request $request Full details about the request.
242
+
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
243
+
*/
244
+
public function get_items_permissions_check( $request ) {
245
+
if ( current_user_can( 'edit_posts' ) ) {
246
+
return true;
247
+
}
248
+
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
249
+
if ( current_user_can( $post_type->cap->edit_posts ) ) {
250
+
return true;
251
+
}
252
+
}
253
+
254
+
return new WP_Error(
255
+
'rest_cannot_manage_templates',
256
+
__( 'Sorry, you are not allowed to access the templates on this site.' ),
257
+
array(
258
+
'status' => rest_authorization_required_code(),
259
+
)
260
+
);
261
+
}
262
+
263
+
/**
264
+
* Returns a list of templates.
265
+
*
266
+
* @since 5.8.0
267
+
*
268
+
* @param WP_REST_Request $request The request instance.
269
+
* @return WP_REST_Response
270
+
*/
271
+
public function get_items( $request ) {
272
+
if ( $request->is_method( 'HEAD' ) ) {
273
+
// Return early as this handler doesn't add any response headers.
274
+
return new WP_REST_Response( array() );
275
+
}
276
+
277
+
$query = array();
278
+
if ( isset( $request['wp_id'] ) ) {
279
+
$query['wp_id'] = $request['wp_id'];
280
+
}
281
+
if ( isset( $request['area'] ) ) {
282
+
$query['area'] = $request['area'];
283
+
}
284
+
if ( isset( $request['post_type'] ) ) {
285
+
$query['post_type'] = $request['post_type'];
286
+
}
287
+
288
+
$templates = array();
289
+
foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
290
+
$data = $this->prepare_item_for_response( $template, $request );
291
+
$templates[] = $this->prepare_response_for_collection( $data );
292
+
}
293
+
294
+
return rest_ensure_response( $templates );
295
+
}
296
+
297
+
/**
298
+
* Checks if a given request has access to read a single template.
299
+
*
300
+
* @since 5.8.0
301
+
* @since 6.6.0 Allow users with edit_posts capability to read individual templates.
302
+
*
303
+
* @param WP_REST_Request $request Full details about the request.
304
+
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
305
+
*/
306
+
public function get_item_permissions_check( $request ) {
307
+
if ( current_user_can( 'edit_posts' ) ) {
308
+
return true;
309
+
}
310
+
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
311
+
if ( current_user_can( $post_type->cap->edit_posts ) ) {
312
+
return true;
313
+
}
314
+
}
315
+
316
+
return new WP_Error(
317
+
'rest_cannot_manage_templates',
318
+
__( 'Sorry, you are not allowed to access the templates on this site.' ),
319
+
array(
320
+
'status' => rest_authorization_required_code(),
321
+
)
322
+
);
323
+
}
324
+
325
+
/**
326
+
* Returns the given template
327
+
*
328
+
* @since 5.8.0
329
+
*
330
+
* @param WP_REST_Request $request The request instance.
331
+
* @return WP_REST_Response|WP_Error
332
+
*/
333
+
public function get_item( $request ) {
334
+
if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) {
335
+
$template = get_block_file_template( $request['id'], $this->post_type );
336
+
} else {
337
+
$template = get_block_template( $request['id'], $this->post_type );
338
+
}
339
+
340
+
if ( ! $template ) {
341
+
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
342
+
}
343
+
344
+
return $this->prepare_item_for_response( $template, $request );
345
+
}
346
+
347
+
/**
348
+
* Checks if a given request has access to write a single template.
349
+
*
350
+
* @since 5.8.0
351
+
*
352
+
* @param WP_REST_Request $request Full details about the request.
353
+
* @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
354
+
*/
355
+
public function update_item_permissions_check( $request ) {
356
+
return $this->permissions_check( $request );
357
+
}
358
+
359
+
/**
360
+
* Updates a single template.
361
+
*
362
+
* @since 5.8.0
363
+
*
364
+
* @param WP_REST_Request $request Full details about the request.
365
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
366
+
*/
367
+
public function update_item( $request ) {
368
+
$template = get_block_template( $request['id'], $this->post_type );
369
+
if ( ! $template ) {
370
+
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
371
+
}
372
+
373
+
$post_before = get_post( $template->wp_id );
374
+
375
+
if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
376
+
wp_delete_post( $template->wp_id, true );
377
+
$request->set_param( 'context', 'edit' );
378
+
379
+
$template = get_block_template( $request['id'], $this->post_type );
380
+
$response = $this->prepare_item_for_response( $template, $request );
381
+
382
+
return rest_ensure_response( $response );
383
+
}
384
+
385
+
$changes = $this->prepare_item_for_database( $request );
386
+
387
+
if ( is_wp_error( $changes ) ) {
388
+
return $changes;
389
+
}
390
+
391
+
if ( 'custom' === $template->source ) {
392
+
$update = true;
393
+
$result = wp_update_post( wp_slash( (array) $changes ), false );
394
+
} else {
395
+
$update = false;
396
+
$post_before = null;
397
+
$result = wp_insert_post( wp_slash( (array) $changes ), false );
398
+
}
399
+
400
+
if ( is_wp_error( $result ) ) {
401
+
if ( 'db_update_error' === $result->get_error_code() ) {
402
+
$result->add_data( array( 'status' => 500 ) );
403
+
} else {
404
+
$result->add_data( array( 'status' => 400 ) );
405
+
}
406
+
return $result;
407
+
}
408
+
409
+
$template = get_block_template( $request['id'], $this->post_type );
410
+
$fields_update = $this->update_additional_fields_for_object( $template, $request );
411
+
if ( is_wp_error( $fields_update ) ) {
412
+
return $fields_update;
413
+
}
414
+
415
+
$request->set_param( 'context', 'edit' );
416
+
417
+
$post = get_post( $template->wp_id );
418
+
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
419
+
do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );
420
+
421
+
wp_after_insert_post( $post, $update, $post_before );
422
+
423
+
$response = $this->prepare_item_for_response( $template, $request );
424
+
425
+
return rest_ensure_response( $response );
426
+
}
427
+
428
+
/**
429
+
* Checks if a given request has access to create a template.
430
+
*
431
+
* @since 5.8.0
432
+
*
433
+
* @param WP_REST_Request $request Full details about the request.
434
+
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
435
+
*/
436
+
public function create_item_permissions_check( $request ) {
437
+
return $this->permissions_check( $request );
438
+
}
439
+
440
+
/**
441
+
* Creates a single template.
442
+
*
443
+
* @since 5.8.0
444
+
*
445
+
* @param WP_REST_Request $request Full details about the request.
446
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
447
+
*/
448
+
public function create_item( $request ) {
449
+
$prepared_post = $this->prepare_item_for_database( $request );
450
+
451
+
if ( is_wp_error( $prepared_post ) ) {
452
+
return $prepared_post;
453
+
}
454
+
455
+
$prepared_post->post_name = $request['slug'];
456
+
$post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true );
457
+
if ( is_wp_error( $post_id ) ) {
458
+
if ( 'db_insert_error' === $post_id->get_error_code() ) {
459
+
$post_id->add_data( array( 'status' => 500 ) );
460
+
} else {
461
+
$post_id->add_data( array( 'status' => 400 ) );
462
+
}
463
+
464
+
return $post_id;
465
+
}
466
+
$posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type );
467
+
if ( ! count( $posts ) ) {
468
+
return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) );
469
+
}
470
+
$id = $posts[0]->id;
471
+
$post = get_post( $post_id );
472
+
$template = get_block_template( $id, $this->post_type );
473
+
$fields_update = $this->update_additional_fields_for_object( $template, $request );
474
+
if ( is_wp_error( $fields_update ) ) {
475
+
return $fields_update;
476
+
}
477
+
478
+
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
479
+
do_action( "rest_after_insert_{$this->post_type}", $post, $request, true );
480
+
481
+
wp_after_insert_post( $post, false, null );
482
+
483
+
$response = $this->prepare_item_for_response( $template, $request );
484
+
$response = rest_ensure_response( $response );
485
+
486
+
$response->set_status( 201 );
487
+
$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) );
488
+
489
+
return $response;
490
+
}
491
+
492
+
/**
493
+
* Checks if a given request has access to delete a single template.
494
+
*
495
+
* @since 5.8.0
496
+
*
497
+
* @param WP_REST_Request $request Full details about the request.
498
+
* @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
499
+
*/
500
+
public function delete_item_permissions_check( $request ) {
501
+
return $this->permissions_check( $request );
502
+
}
503
+
504
+
/**
505
+
* Deletes a single template.
506
+
*
507
+
* @since 5.8.0
508
+
*
509
+
* @param WP_REST_Request $request Full details about the request.
510
+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
511
+
*/
512
+
public function delete_item( $request ) {
513
+
$template = get_block_template( $request['id'], $this->post_type );
514
+
if ( ! $template ) {
515
+
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
516
+
}
517
+
if ( 'custom' !== $template->source ) {
518
+
return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
519
+
}
520
+
521
+
$id = $template->wp_id;
522
+
$force = (bool) $request['force'];
523
+
524
+
$request->set_param( 'context', 'edit' );
525
+
526
+
// If we're forcing, then delete permanently.
527
+
if ( $force ) {
528
+
$previous = $this->prepare_item_for_response( $template, $request );
529
+
$result = wp_delete_post( $id, true );
530
+
$response = new WP_REST_Response();
531
+
$response->set_data(
532
+
array(
533
+
'deleted' => true,
534
+
'previous' => $previous->get_data(),
535
+
)
536
+
);
537
+
} else {
538
+
// Otherwise, only trash if we haven't already.
539
+
if ( 'trash' === $template->status ) {
540
+
return new WP_Error(
541
+
'rest_template_already_trashed',
542
+
__( 'The template has already been deleted.' ),
543
+
array( 'status' => 410 )
544
+
);
545
+
}
546
+
547
+
/*
548
+
* (Note that internally this falls through to `wp_delete_post()`
549
+
* if the Trash is disabled.)
550
+
*/
551
+
$result = wp_trash_post( $id );
552
+
$template->status = 'trash';
553
+
$response = $this->prepare_item_for_response( $template, $request );
554
+
}
555
+
556
+
if ( ! $result ) {
557
+
return new WP_Error(
558
+
'rest_cannot_delete',
559
+
__( 'The template cannot be deleted.' ),
560
+
array( 'status' => 500 )
561
+
);
562
+
}
563
+
564
+
return $response;
565
+
}
566
+
567
+
/**
568
+
* Prepares a single template for create or update.
569
+
*
570
+
* @since 5.8.0
571
+
*
572
+
* @param WP_REST_Request $request Request object.
573
+
* @return stdClass|WP_Error Changes to pass to wp_update_post.
574
+
*/
575
+
protected function prepare_item_for_database( $request ) {
576
+
$template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
577
+
$changes = new stdClass();
578
+
if ( null === $template ) {
579
+
$changes->post_type = $this->post_type;
580
+
$changes->post_status = 'publish';
581
+
$changes->tax_input = array(
582
+
'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(),
583
+
);
584
+
} elseif ( 'custom' !== $template->source ) {
585
+
$changes->post_name = $template->slug;
586
+
$changes->post_type = $this->post_type;
587
+
$changes->post_status = 'publish';
588
+
$changes->tax_input = array(
589
+
'wp_theme' => $template->theme,
590
+
);
591
+
$changes->meta_input = array(
592
+
'origin' => $template->source,
593
+
);
594
+
} else {
595
+
$changes->post_name = $template->slug;
596
+
$changes->ID = $template->wp_id;
597
+
$changes->post_status = 'publish';
598
+
}
599
+
if ( isset( $request['content'] ) ) {
600
+
if ( is_string( $request['content'] ) ) {
601
+
$changes->post_content = $request['content'];
602
+
} elseif ( isset( $request['content']['raw'] ) ) {
603
+
$changes->post_content = $request['content']['raw'];
604
+
}
605
+
} elseif ( null !== $template && 'custom' !== $template->source ) {
606
+
$changes->post_content = $template->content;
607
+
}
608
+
if ( isset( $request['title'] ) ) {
609
+
if ( is_string( $request['title'] ) ) {
610
+
$changes->post_title = $request['title'];
611
+
} elseif ( ! empty( $request['title']['raw'] ) ) {
612
+
$changes->post_title = $request['title']['raw'];
613
+
}
614
+
} elseif ( null !== $template && 'custom' !== $template->source ) {
615
+
$changes->post_title = $template->title;
616
+
}
617
+
if ( isset( $request['description'] ) ) {
618
+
$changes->post_excerpt = $request['description'];
619
+
} elseif ( null !== $template && 'custom' !== $template->source ) {
620
+
$changes->post_excerpt = $template->description;
621
+
}
622
+
623
+
if ( 'wp_template' === $this->post_type && isset( $request['is_wp_suggestion'] ) ) {
624
+
$changes->meta_input = wp_parse_args(
625
+
array(
626
+
'is_wp_suggestion' => $request['is_wp_suggestion'],
627
+
),
628
+
$changes->meta_input = array()
629
+
);
630
+
}
631
+
632
+
if ( 'wp_template_part' === $this->post_type ) {
633
+
if ( isset( $request['area'] ) ) {
634
+
$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
635
+
} elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
636
+
$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
637
+
} elseif ( empty( $template->area ) ) {
638
+
$changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
639
+
}
640
+
}
641
+
642
+
if ( ! empty( $request['author'] ) ) {
643
+
$post_author = (int) $request['author'];
644
+
645
+
if ( get_current_user_id() !== $post_author ) {
646
+
$user_obj = get_userdata( $post_author );
647
+
648
+
if ( ! $user_obj ) {
649
+
return new WP_Error(
650
+
'rest_invalid_author',
651
+
__( 'Invalid author ID.' ),
652
+
array( 'status' => 400 )
653
+
);
654
+
}
655
+
}
656
+
657
+
$changes->post_author = $post_author;
658
+
}
659
+
660
+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
661
+
return apply_filters( "rest_pre_insert_{$this->post_type}", $changes, $request );
662
+
}
663
+
664
+
/**
665
+
* Prepare a single template output for response
666
+
*
667
+
* @since 5.8.0
668
+
* @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support.
669
+
* @since 6.3.0 Added `modified` property to the response.
670
+
*
671
+
* @param WP_Block_Template $item Template instance.
672
+
* @param WP_REST_Request $request Request object.
673
+
* @return WP_REST_Response Response object.
674
+
*/
675
+
public function prepare_item_for_response( $item, $request ) {
676
+
// Don't prepare the response body for HEAD requests.
677
+
if ( $request->is_method( 'HEAD' ) ) {
678
+
return new WP_REST_Response( array() );
679
+
}
680
+
681
+
/*
682
+
* Resolve pattern blocks so they don't need to be resolved client-side
683
+
* in the editor, improving performance.
684
+
*/
685
+
$blocks = parse_blocks( $item->content );
686
+
$blocks = resolve_pattern_blocks( $blocks );
687
+
$item->content = serialize_blocks( $blocks );
688
+
689
+
// Restores the more descriptive, specific name for use within this method.
690
+
$template = $item;
691
+
692
+
$fields = $this->get_fields_for_response( $request );
693
+
694
+
// Base fields for every template.
695
+
$data = array();
696
+
697
+
if ( rest_is_field_included( 'id', $fields ) ) {
698
+
$data['id'] = $template->id;
699
+
}
700
+
701
+
if ( rest_is_field_included( 'theme', $fields ) ) {
702
+
$data['theme'] = $template->theme;
703
+
}
704
+
705
+
if ( rest_is_field_included( 'content', $fields ) ) {
706
+
$data['content'] = array();
707
+
}
708
+
if ( rest_is_field_included( 'content.raw', $fields ) ) {
709
+
$data['content']['raw'] = $template->content;
710
+
}
711
+
712
+
if ( rest_is_field_included( 'content.block_version', $fields ) ) {
713
+
$data['content']['block_version'] = block_version( $template->content );
714
+
}
715
+
716
+
if ( rest_is_field_included( 'slug', $fields ) ) {
717
+
$data['slug'] = $template->slug;
718
+
}
719
+
720
+
if ( rest_is_field_included( 'source', $fields ) ) {
721
+
$data['source'] = $template->source;
722
+
}
723
+
724
+
if ( rest_is_field_included( 'origin', $fields ) ) {
725
+
$data['origin'] = $template->origin;
726
+
}
727
+
728
+
if ( rest_is_field_included( 'type', $fields ) ) {
729
+
$data['type'] = $template->type;
730
+
}
731
+
732
+
if ( rest_is_field_included( 'description', $fields ) ) {
733
+
$data['description'] = $template->description;
734
+
}
735
+
736
+
if ( rest_is_field_included( 'title', $fields ) ) {
737
+
$data['title'] = array();
738
+
}
739
+
740
+
if ( rest_is_field_included( 'title.raw', $fields ) ) {
741
+
$data['title']['raw'] = $template->title;
742
+
}
743
+
744
+
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
745
+
if ( $template->wp_id ) {
746
+
/** This filter is documented in wp-includes/post-template.php */
747
+
$data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id );
748
+
} else {
749
+
$data['title']['rendered'] = $template->title;
750
+
}
751
+
}
752
+
753
+
if ( rest_is_field_included( 'status', $fields ) ) {
754
+
$data['status'] = $template->status;
755
+
}
756
+
757
+
if ( rest_is_field_included( 'wp_id', $fields ) ) {
758
+
$data['wp_id'] = (int) $template->wp_id;
759
+
}
760
+
761
+
if ( rest_is_field_included( 'has_theme_file', $fields ) ) {
762
+
$data['has_theme_file'] = (bool) $template->has_theme_file;
763
+
}
764
+
765
+
if ( rest_is_field_included( 'is_custom', $fields ) && 'wp_template' === $template->type ) {
766
+
$data['is_custom'] = $template->is_custom;
767
+
}
768
+
769
+
if ( rest_is_field_included( 'author', $fields ) ) {
770
+
$data['author'] = (int) $template->author;
771
+
}
772
+
773
+
if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
774
+
$data['area'] = $template->area;
775
+
}
776
+
777
+
if ( rest_is_field_included( 'modified', $fields ) ) {
778
+
$data['modified'] = mysql_to_rfc3339( $template->modified );
779
+
}
780
+
781
+
if ( rest_is_field_included( 'author_text', $fields ) ) {
782
+
$data['author_text'] = self::get_wp_templates_author_text_field( $template );
783
+
}
784
+
785
+
if ( rest_is_field_included( 'original_source', $fields ) ) {
786
+
$data['original_source'] = self::get_wp_templates_original_source_field( $template );
787
+
}
788
+
789
+
if ( rest_is_field_included( 'plugin', $fields ) ) {
790
+
$registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug );
791
+
if ( $registered_template ) {
792
+
$data['plugin'] = $registered_template->plugin;
793
+
}
794
+
}
795
+
796
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
797
+
$data = $this->add_additional_fields_to_object( $data, $request );
798
+
$data = $this->filter_response_by_context( $data, $context );
799
+
800
+
// Wrap the data in a response object.
801
+
$response = rest_ensure_response( $data );
802
+
803
+
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
804
+
$links = $this->prepare_links( $template->id );
805
+
$response->add_links( $links );
806
+
if ( ! empty( $links['self']['href'] ) ) {
807
+
$actions = $this->get_available_actions();
808
+
$self = $links['self']['href'];
809
+
foreach ( $actions as $rel ) {
810
+
$response->add_link( $rel, $self );
811
+
}
812
+
}
813
+
}
814
+
815
+
return $response;
816
+
}
817
+
818
+
/**
819
+
* Returns the source from where the template originally comes from.
820
+
*
821
+
* @since 6.5.0
822
+
*
823
+
* @param WP_Block_Template $template_object Template instance.
824
+
* @return string Original source of the template one of theme, plugin, site, or user.
825
+
*/
826
+
private static function get_wp_templates_original_source_field( $template_object ) {
827
+
if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
828
+
/*
829
+
* Added by theme.
830
+
* Template originally provided by a theme, but customized by a user.
831
+
* Templates originally didn't have the 'origin' field so identify
832
+
* older customized templates by checking for no origin and a 'theme'
833
+
* or 'custom' source.
834
+
*/
835
+
if ( $template_object->has_theme_file &&
836
+
( 'theme' === $template_object->origin || (
837
+
empty( $template_object->origin ) && in_array(
838
+
$template_object->source,
839
+
array(
840
+
'theme',
841
+
'custom',
842
+
),
843
+
true
844
+
) )
845
+
)
846
+
) {
847
+
return 'theme';
848
+
}
849
+
850
+
// Added by plugin.
851
+
if ( 'plugin' === $template_object->origin ) {
852
+
return 'plugin';
853
+
}
854
+
855
+
/*
856
+
* Added by site.
857
+
* Template was created from scratch, but has no author. Author support
858
+
* was only added to templates in WordPress 5.9. Fallback to showing the
859
+
* site logo and title.
860
+
*/
861
+
if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
862
+
return 'site';
863
+
}
864
+
}
865
+
866
+
// Added by user.
867
+
return 'user';
868
+
}
869
+
870
+
/**
871
+
* Returns a human readable text for the author of the template.
872
+
*
873
+
* @since 6.5.0
874
+
*
875
+
* @param WP_Block_Template $template_object Template instance.
876
+
* @return string Human readable text for the author.
877
+
*/
878
+
private static function get_wp_templates_author_text_field( $template_object ) {
879
+
$original_source = self::get_wp_templates_original_source_field( $template_object );
880
+
switch ( $original_source ) {
881
+
case 'theme':
882
+
$theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
883
+
return empty( $theme_name ) ? $template_object->theme : $theme_name;
884
+
case 'plugin':
885
+
if ( ! function_exists( 'get_plugins' ) ) {
886
+
require_once ABSPATH . 'wp-admin/includes/plugin.php';
887
+
}
888
+
if ( isset( $template_object->plugin ) ) {
889
+
$plugins = wp_get_active_and_valid_plugins();
890
+
891
+
foreach ( $plugins as $plugin_file ) {
892
+
$plugin_basename = plugin_basename( $plugin_file );
893
+
// Split basename by '/' to get the plugin slug.
894
+
list( $plugin_slug, ) = explode( '/', $plugin_basename );
895
+
896
+
if ( $plugin_slug === $template_object->plugin ) {
897
+
$plugin_data = get_plugin_data( $plugin_file );
898
+
899
+
if ( ! empty( $plugin_data['Name'] ) ) {
900
+
return $plugin_data['Name'];
901
+
}
902
+
903
+
break;
904
+
}
905
+
}
906
+
}
907
+
908
+
/*
909
+
* Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
910
+
* compatibility with templates that were registered before the plugin attribute was added.
911
+
*/
912
+
$plugins = get_plugins();
913
+
$plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) );
914
+
if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
915
+
return $plugins[ $plugin_basename ]['Name'];
916
+
}
917
+
return isset( $template_object->plugin ) ?
918
+
$template_object->plugin :
919
+
$template_object->theme;
920
+
case 'site':
921
+
return get_bloginfo( 'name' );
922
+
case 'user':
923
+
$author = get_user_by( 'id', $template_object->author );
924
+
if ( ! $author ) {
925
+
return __( 'Unknown author' );
926
+
}
927
+
return $author->get( 'display_name' );
928
+
}
929
+
930
+
// Fail-safe to return a string should the original source ever fall through.
931
+
return '';
932
+
}
933
+
934
+
935
+
/**
936
+
* Prepares links for the request.
937
+
*
938
+
* @since 5.8.0
939
+
*
940
+
* @param integer $id ID.
941
+
* @return array Links for the given post.
942
+
*/
943
+
protected function prepare_links( $id ) {
944
+
$links = array(
945
+
'self' => array(
946
+
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ),
947
+
),
948
+
'collection' => array(
949
+
'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ),
950
+
),
951
+
'about' => array(
952
+
'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
953
+
),
954
+
);
955
+
956
+
if ( post_type_supports( $this->post_type, 'revisions' ) ) {
957
+
$template = get_block_template( $id, $this->post_type );
958
+
if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) {
959
+
$revisions = wp_get_latest_revision_id_and_total_count( $template->wp_id );
960
+
$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
961
+
$revisions_base = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $id );
962
+
963
+
$links['version-history'] = array(
964
+
'href' => rest_url( $revisions_base ),
965
+
'count' => $revisions_count,
966
+
);
967
+
968
+
if ( $revisions_count > 0 ) {
969
+
$links['predecessor-version'] = array(
970
+
'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ),
971
+
'id' => $revisions['latest_id'],
972
+
);
973
+
}
974
+
}
975
+
}
976
+
977
+
return $links;
978
+
}
979
+
980
+
/**
981
+
* Get the link relations available for the post and current user.
982
+
*
983
+
* @since 5.8.0
984
+
*
985
+
* @return string[] List of link relations.
986
+
*/
987
+
protected function get_available_actions() {
988
+
$rels = array();
989
+
990
+
$post_type = get_post_type_object( $this->post_type );
991
+
992
+
if ( current_user_can( $post_type->cap->publish_posts ) ) {
993
+
$rels[] = 'https://api.w.org/action-publish';
994
+
}
995
+
996
+
if ( current_user_can( 'unfiltered_html' ) ) {
997
+
$rels[] = 'https://api.w.org/action-unfiltered-html';
998
+
}
999
+
1000
+
return $rels;
1001
+
}
1002
+
1003
+
/**
1004
+
* Retrieves the query params for the posts collection.
1005
+
*
1006
+
* @since 5.8.0
1007
+
* @since 5.9.0 Added `'area'` and `'post_type'`.
1008
+
*
1009
+
* @return array Collection parameters.
1010
+
*/
1011
+
public function get_collection_params() {
1012
+
return array(
1013
+
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
1014
+
'wp_id' => array(
1015
+
'description' => __( 'Limit to the specified post id.' ),
1016
+
'type' => 'integer',
1017
+
),
1018
+
'area' => array(
1019
+
'description' => __( 'Limit to the specified template part area.' ),
1020
+
'type' => 'string',
1021
+
),
1022
+
'post_type' => array(
1023
+
'description' => __( 'Post type to get the templates for.' ),
1024
+
'type' => 'string',
1025
+
),
1026
+
);
1027
+
}
1028
+
1029
+
/**
1030
+
* Retrieves the block type' schema, conforming to JSON Schema.
1031
+
*
1032
+
* @since 5.8.0
1033
+
* @since 5.9.0 Added `'area'`.
1034
+
*
1035
+
* @return array Item schema data.
1036
+
*/
1037
+
public function get_item_schema() {
1038
+
if ( $this->schema ) {
1039
+
return $this->add_additional_fields_schema( $this->schema );
1040
+
}
1041
+
1042
+
$schema = array(
1043
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
1044
+
'title' => $this->post_type,
1045
+
'type' => 'object',
1046
+
'properties' => array(
1047
+
'id' => array(
1048
+
'description' => __( 'ID of template.' ),
1049
+
'type' => 'string',
1050
+
'context' => array( 'embed', 'view', 'edit' ),
1051
+
'readonly' => true,
1052
+
),
1053
+
'slug' => array(
1054
+
'description' => __( 'Unique slug identifying the template.' ),
1055
+
'type' => 'string',
1056
+
'context' => array( 'embed', 'view', 'edit' ),
1057
+
'required' => true,
1058
+
'minLength' => 1,
1059
+
'pattern' => '[a-zA-Z0-9_\%-]+',
1060
+
),
1061
+
'theme' => array(
1062
+
'description' => __( 'Theme identifier for the template.' ),
1063
+
'type' => 'string',
1064
+
'context' => array( 'embed', 'view', 'edit' ),
1065
+
),
1066
+
'type' => array(
1067
+
'description' => __( 'Type of template.' ),
1068
+
'type' => 'string',
1069
+
'context' => array( 'embed', 'view', 'edit' ),
1070
+
),
1071
+
'source' => array(
1072
+
'description' => __( 'Source of template' ),
1073
+
'type' => 'string',
1074
+
'context' => array( 'embed', 'view', 'edit' ),
1075
+
'readonly' => true,
1076
+
),
1077
+
'origin' => array(
1078
+
'description' => __( 'Source of a customized template' ),
1079
+
'type' => 'string',
1080
+
'context' => array( 'embed', 'view', 'edit' ),
1081
+
'readonly' => true,
1082
+
),
1083
+
'content' => array(
1084
+
'description' => __( 'Content of template.' ),
1085
+
'type' => array( 'object', 'string' ),
1086
+
'default' => '',
1087
+
'context' => array( 'embed', 'view', 'edit' ),
1088
+
'properties' => array(
1089
+
'raw' => array(
1090
+
'description' => __( 'Content for the template, as it exists in the database.' ),
1091
+
'type' => 'string',
1092
+
'context' => array( 'view', 'edit' ),
1093
+
),
1094
+
'block_version' => array(
1095
+
'description' => __( 'Version of the content block format used by the template.' ),
1096
+
'type' => 'integer',
1097
+
'context' => array( 'edit' ),
1098
+
'readonly' => true,
1099
+
),
1100
+
),
1101
+
),
1102
+
'title' => array(
1103
+
'description' => __( 'Title of template.' ),
1104
+
'type' => array( 'object', 'string' ),
1105
+
'default' => '',
1106
+
'context' => array( 'embed', 'view', 'edit' ),
1107
+
'properties' => array(
1108
+
'raw' => array(
1109
+
'description' => __( 'Title for the template, as it exists in the database.' ),
1110
+
'type' => 'string',
1111
+
'context' => array( 'view', 'edit', 'embed' ),
1112
+
),
1113
+
'rendered' => array(
1114
+
'description' => __( 'HTML title for the template, transformed for display.' ),
1115
+
'type' => 'string',
1116
+
'context' => array( 'view', 'edit', 'embed' ),
1117
+
'readonly' => true,
1118
+
),
1119
+
),
1120
+
),
1121
+
'description' => array(
1122
+
'description' => __( 'Description of template.' ),
1123
+
'type' => 'string',
1124
+
'default' => '',
1125
+
'context' => array( 'embed', 'view', 'edit' ),
1126
+
),
1127
+
'status' => array(
1128
+
'description' => __( 'Status of template.' ),
1129
+
'type' => 'string',
1130
+
'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ),
1131
+
'default' => 'publish',
1132
+
'context' => array( 'embed', 'view', 'edit' ),
1133
+
),
1134
+
'wp_id' => array(
1135
+
'description' => __( 'Post ID.' ),
1136
+
'type' => 'integer',
1137
+
'context' => array( 'embed', 'view', 'edit' ),
1138
+
'readonly' => true,
1139
+
),
1140
+
'has_theme_file' => array(
1141
+
'description' => __( 'Theme file exists.' ),
1142
+
'type' => 'bool',
1143
+
'context' => array( 'embed', 'view', 'edit' ),
1144
+
'readonly' => true,
1145
+
),
1146
+
'author' => array(
1147
+
'description' => __( 'The ID for the author of the template.' ),
1148
+
'type' => 'integer',
1149
+
'context' => array( 'view', 'edit', 'embed' ),
1150
+
),
1151
+
'modified' => array(
1152
+
'description' => __( "The date the template was last modified, in the site's timezone." ),
1153
+
'type' => 'string',
1154
+
'format' => 'date-time',
1155
+
'context' => array( 'view', 'edit' ),
1156
+
'readonly' => true,
1157
+
),
1158
+
'author_text' => array(
1159
+
'type' => 'string',
1160
+
'description' => __( 'Human readable text for the author.' ),
1161
+
'readonly' => true,
1162
+
'context' => array( 'view', 'edit', 'embed' ),
1163
+
),
1164
+
'original_source' => array(
1165
+
'description' => __( 'Where the template originally comes from e.g. \'theme\'' ),
1166
+
'type' => 'string',
1167
+
'readonly' => true,
1168
+
'context' => array( 'view', 'edit', 'embed' ),
1169
+
'enum' => array(
1170
+
'theme',
1171
+
'plugin',
1172
+
'site',
1173
+
'user',
1174
+
),
1175
+
),
1176
+
),
1177
+
);
1178
+
1179
+
if ( 'wp_template' === $this->post_type ) {
1180
+
$schema['properties']['is_custom'] = array(
1181
+
'description' => __( 'Whether a template is a custom template.' ),
1182
+
'type' => 'bool',
1183
+
'context' => array( 'embed', 'view', 'edit' ),
1184
+
'readonly' => true,
1185
+
);
1186
+
$schema['properties']['plugin'] = array(
1187
+
'type' => 'string',
1188
+
'description' => __( 'Plugin that registered the template.' ),
1189
+
'readonly' => true,
1190
+
'context' => array( 'view', 'edit', 'embed' ),
1191
+
);
1192
+
}
1193
+
1194
+
if ( 'wp_template_part' === $this->post_type ) {
1195
+
$schema['properties']['area'] = array(
1196
+
'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ),
1197
+
'type' => 'string',
1198
+
'context' => array( 'embed', 'view', 'edit' ),
1199
+
);
1200
+
}
1201
+
1202
+
$this->schema = $schema;
1203
+
1204
+
return $this->add_additional_fields_schema( $this->schema );
1205
+
}
1206
+
}
1207
+