[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Font_Families_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 6.5.0 8 */ 9 10 /** 11 * Font Families Controller class. 12 * 13 * @since 6.5.0 14 */ 15 class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller { 16 17 /** 18 * The latest version of theme.json schema supported by the controller. 19 * 20 * @since 6.5.0 21 * @var int 22 */ 23 const LATEST_THEME_JSON_VERSION_SUPPORTED = 3; 24 25 /** 26 * Whether the controller supports batching. 27 * 28 * @since 6.5.0 29 * @var false 30 */ 31 protected $allow_batch = false; 32 33 /** 34 * Checks if a given request has access to font families. 35 * 36 * @since 6.5.0 37 * 38 * @param WP_REST_Request $request Full details about the request. 39 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 40 */ 41 public function get_items_permissions_check( $request ) { 42 $post_type = get_post_type_object( $this->post_type ); 43 44 if ( ! current_user_can( $post_type->cap->read ) ) { 45 return new WP_Error( 46 'rest_cannot_read', 47 __( 'Sorry, you are not allowed to access font families.' ), 48 array( 'status' => rest_authorization_required_code() ) 49 ); 50 } 51 52 return true; 53 } 54 55 /** 56 * Checks if a given request has access to a font family. 57 * 58 * @since 6.5.0 59 * 60 * @param WP_REST_Request $request Full details about the request. 61 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 62 */ 63 public function get_item_permissions_check( $request ) { 64 $post = $this->get_post( $request['id'] ); 65 if ( is_wp_error( $post ) ) { 66 return $post; 67 } 68 69 if ( ! current_user_can( 'read_post', $post->ID ) ) { 70 return new WP_Error( 71 'rest_cannot_read', 72 __( 'Sorry, you are not allowed to access this font family.' ), 73 array( 'status' => rest_authorization_required_code() ) 74 ); 75 } 76 77 return true; 78 } 79 80 /** 81 * Validates settings when creating or updating a font family. 82 * 83 * @since 6.5.0 84 * 85 * @param string $value Encoded JSON string of font family settings. 86 * @param WP_REST_Request $request Request object. 87 * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. 88 */ 89 public function validate_font_family_settings( $value, $request ) { 90 $settings = json_decode( $value, true ); 91 92 // Check settings string is valid JSON. 93 if ( null === $settings ) { 94 return new WP_Error( 95 'rest_invalid_param', 96 /* translators: %s: Parameter name: "font_family_settings". */ 97 sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ), 98 array( 'status' => 400 ) 99 ); 100 } 101 102 $schema = $this->get_item_schema()['properties']['font_family_settings']; 103 $required = $schema['required']; 104 105 if ( isset( $request['id'] ) ) { 106 // Allow sending individual properties if we are updating an existing font family. 107 unset( $schema['required'] ); 108 109 // But don't allow updating the slug, since it is used as a unique identifier. 110 if ( isset( $settings['slug'] ) ) { 111 return new WP_Error( 112 'rest_invalid_param', 113 /* translators: %s: Name of parameter being updated: font_family_settings[slug]". */ 114 sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ), 115 array( 'status' => 400 ) 116 ); 117 } 118 } 119 120 // Check that the font face settings match the theme.json schema. 121 $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' ); 122 123 if ( is_wp_error( $has_valid_settings ) ) { 124 $has_valid_settings->add_data( array( 'status' => 400 ) ); 125 return $has_valid_settings; 126 } 127 128 // Check that none of the required settings are empty values. 129 foreach ( $required as $key ) { 130 if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { 131 return new WP_Error( 132 'rest_invalid_param', 133 /* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */ 134 sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ), 135 array( 'status' => 400 ) 136 ); 137 } 138 } 139 140 return true; 141 } 142 143 /** 144 * Sanitizes the font family settings when creating or updating a font family. 145 * 146 * @since 6.5.0 147 * 148 * @param string $value Encoded JSON string of font family settings. 149 * @return array Decoded array of font family settings. 150 */ 151 public function sanitize_font_family_settings( $value ) { 152 // Settings arrive as stringified JSON, since this is a multipart/form-data request. 153 $settings = json_decode( $value, true ); 154 $schema = $this->get_item_schema()['properties']['font_family_settings']['properties']; 155 156 // Sanitize settings based on callbacks in the schema. 157 foreach ( $settings as $key => $value ) { 158 $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; 159 $settings[ $key ] = call_user_func( $sanitize_callback, $value ); 160 } 161 162 return $settings; 163 } 164 165 /** 166 * Creates a single font family. 167 * 168 * @since 6.5.0 169 * 170 * @param WP_REST_Request $request Full details about the request. 171 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 172 */ 173 public function create_item( $request ) { 174 $settings = $request->get_param( 'font_family_settings' ); 175 176 // Check that the font family slug is unique. 177 $query = new WP_Query( 178 array( 179 'post_type' => $this->post_type, 180 'posts_per_page' => 1, 181 'name' => $settings['slug'], 182 'update_post_meta_cache' => false, 183 'update_post_term_cache' => false, 184 ) 185 ); 186 if ( ! empty( $query->posts ) ) { 187 return new WP_Error( 188 'rest_duplicate_font_family', 189 /* translators: %s: Font family slug. */ 190 sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ), 191 array( 'status' => 400 ) 192 ); 193 } 194 195 return parent::create_item( $request ); 196 } 197 198 /** 199 * Deletes a single font family. 200 * 201 * @since 6.5.0 202 * 203 * @param WP_REST_Request $request Full details about the request. 204 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 205 */ 206 public function delete_item( $request ) { 207 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 208 209 // We don't support trashing for font families. 210 if ( ! $force ) { 211 return new WP_Error( 212 'rest_trash_not_supported', 213 /* translators: %s: force=true */ 214 sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), 215 array( 'status' => 501 ) 216 ); 217 } 218 219 return parent::delete_item( $request ); 220 } 221 222 /** 223 * Prepares a single font family output for response. 224 * 225 * @since 6.5.0 226 * 227 * @param WP_Post $item Post object. 228 * @param WP_REST_Request $request Request object. 229 * @return WP_REST_Response Response object. 230 */ 231 public function prepare_item_for_response( $item, $request ) { 232 $fields = $this->get_fields_for_response( $request ); 233 $data = array(); 234 235 if ( rest_is_field_included( 'id', $fields ) ) { 236 $data['id'] = $item->ID; 237 } 238 239 if ( rest_is_field_included( 'theme_json_version', $fields ) ) { 240 $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; 241 } 242 243 if ( rest_is_field_included( 'font_faces', $fields ) ) { 244 $data['font_faces'] = $this->get_font_face_ids( $item->ID ); 245 } 246 247 if ( rest_is_field_included( 'font_family_settings', $fields ) ) { 248 $data['font_family_settings'] = $this->get_settings_from_post( $item ); 249 } 250 251 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 252 $data = $this->add_additional_fields_to_object( $data, $request ); 253 $data = $this->filter_response_by_context( $data, $context ); 254 255 $response = rest_ensure_response( $data ); 256 257 if ( rest_is_field_included( '_links', $fields ) ) { 258 $links = $this->prepare_links( $item ); 259 $response->add_links( $links ); 260 } 261 262 /** 263 * Filters the font family data for a REST API response. 264 * 265 * @since 6.5.0 266 * 267 * @param WP_REST_Response $response The response object. 268 * @param WP_Post $post Font family post object. 269 * @param WP_REST_Request $request Request object. 270 */ 271 return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request ); 272 } 273 274 /** 275 * Retrieves the post's schema, conforming to JSON Schema. 276 * 277 * @since 6.5.0 278 * 279 * @return array Item schema data. 280 */ 281 public function get_item_schema() { 282 if ( $this->schema ) { 283 return $this->add_additional_fields_schema( $this->schema ); 284 } 285 286 $schema = array( 287 '$schema' => 'http://json-schema.org/draft-04/schema#', 288 'title' => $this->post_type, 289 'type' => 'object', 290 // Base properties for every Post. 291 'properties' => array( 292 'id' => array( 293 'description' => __( 'Unique identifier for the post.', 'default' ), 294 'type' => 'integer', 295 'context' => array( 'view', 'edit', 'embed' ), 296 'readonly' => true, 297 ), 298 'theme_json_version' => array( 299 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), 300 'type' => 'integer', 301 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 302 'minimum' => 2, 303 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 304 'context' => array( 'view', 'edit', 'embed' ), 305 ), 306 'font_faces' => array( 307 'description' => __( 'The IDs of the child font faces in the font family.' ), 308 'type' => 'array', 309 'context' => array( 'view', 'edit', 'embed' ), 310 'items' => array( 311 'type' => 'integer', 312 ), 313 ), 314 // Font family settings come directly from theme.json schema 315 // See https://schemas.wp.org/trunk/theme.json 316 'font_family_settings' => array( 317 'description' => __( 'font-face definition in theme.json format.' ), 318 'type' => 'object', 319 'context' => array( 'view', 'edit', 'embed' ), 320 'properties' => array( 321 'name' => array( 322 'description' => __( 'Name of the font family preset, translatable.' ), 323 'type' => 'string', 324 'arg_options' => array( 325 'sanitize_callback' => 'sanitize_text_field', 326 ), 327 ), 328 'slug' => array( 329 'description' => __( 'Kebab-case unique identifier for the font family preset.' ), 330 'type' => 'string', 331 'arg_options' => array( 332 'sanitize_callback' => 'sanitize_title', 333 ), 334 ), 335 'fontFamily' => array( 336 'description' => __( 'CSS font-family value.' ), 337 'type' => 'string', 338 'arg_options' => array( 339 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), 340 ), 341 ), 342 'preview' => array( 343 'description' => __( 'URL to a preview image of the font family.' ), 344 'type' => 'string', 345 'format' => 'uri', 346 'default' => '', 347 'arg_options' => array( 348 'sanitize_callback' => 'sanitize_url', 349 ), 350 ), 351 ), 352 'required' => array( 'name', 'slug', 'fontFamily' ), 353 'additionalProperties' => false, 354 ), 355 ), 356 ); 357 358 $this->schema = $schema; 359 360 return $this->add_additional_fields_schema( $this->schema ); 361 } 362 363 /** 364 * Retrieves the item's schema for display / public consumption purposes. 365 * 366 * @since 6.5.0 367 * 368 * @return array Public item schema data. 369 */ 370 public function get_public_item_schema() { 371 372 $schema = parent::get_public_item_schema(); 373 374 // Also remove `arg_options' from child font_family_settings properties, since the parent 375 // controller only handles the top level properties. 376 foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) { 377 unset( $property['arg_options'] ); 378 } 379 380 return $schema; 381 } 382 383 /** 384 * Retrieves the query params for the font family collection. 385 * 386 * @since 6.5.0 387 * 388 * @return array Collection parameters. 389 */ 390 public function get_collection_params() { 391 $query_params = parent::get_collection_params(); 392 393 // Remove unneeded params. 394 unset( 395 $query_params['after'], 396 $query_params['modified_after'], 397 $query_params['before'], 398 $query_params['modified_before'], 399 $query_params['search'], 400 $query_params['search_columns'], 401 $query_params['status'] 402 ); 403 404 $query_params['orderby']['default'] = 'id'; 405 $query_params['orderby']['enum'] = array( 'id', 'include' ); 406 407 /** 408 * Filters collection parameters for the font family controller. 409 * 410 * @since 6.5.0 411 * 412 * @param array $query_params JSON Schema-formatted collection parameters. 413 */ 414 return apply_filters( 'rest_wp_font_family_collection_params', $query_params ); 415 } 416 417 /** 418 * Get the arguments used when creating or updating a font family. 419 * 420 * @since 6.5.0 421 * 422 * @return array Font family create/edit arguments. 423 */ 424 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { 425 if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) { 426 $properties = $this->get_item_schema()['properties']; 427 return array( 428 'theme_json_version' => $properties['theme_json_version'], 429 // When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data. 430 // Font families don't currently support file uploads, but may accept preview files in the future. 431 'font_family_settings' => array( 432 'description' => __( 'font-family declaration in theme.json format, encoded as a string.' ), 433 'type' => 'string', 434 'required' => true, 435 'validate_callback' => array( $this, 'validate_font_family_settings' ), 436 'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ), 437 ), 438 ); 439 } 440 441 return parent::get_endpoint_args_for_item_schema( $method ); 442 } 443 444 /** 445 * Get the child font face post IDs. 446 * 447 * @since 6.5.0 448 * 449 * @param int $font_family_id Font family post ID. 450 * @return int[] Array of child font face post IDs. 451 */ 452 protected function get_font_face_ids( $font_family_id ) { 453 $query = new WP_Query( 454 array( 455 'fields' => 'ids', 456 'post_parent' => $font_family_id, 457 'post_type' => 'wp_font_face', 458 'posts_per_page' => 99, 459 'order' => 'ASC', 460 'orderby' => 'id', 461 'update_post_meta_cache' => false, 462 'update_post_term_cache' => false, 463 ) 464 ); 465 466 return $query->posts; 467 } 468 469 /** 470 * Prepares font family links for the request. 471 * 472 * @since 6.5.0 473 * 474 * @param WP_Post $post Post object. 475 * @return array Links for the given post. 476 */ 477 protected function prepare_links( $post ) { 478 // Entity meta. 479 $links = parent::prepare_links( $post ); 480 481 return array( 482 'self' => $links['self'], 483 'collection' => $links['collection'], 484 'font_faces' => $this->prepare_font_face_links( $post->ID ), 485 ); 486 } 487 488 /** 489 * Prepares child font face links for the request. 490 * 491 * @param int $font_family_id Font family post ID. 492 * @return array Links for the child font face posts. 493 */ 494 protected function prepare_font_face_links( $font_family_id ) { 495 $font_face_ids = $this->get_font_face_ids( $font_family_id ); 496 $links = array(); 497 foreach ( $font_face_ids as $font_face_id ) { 498 $links[] = array( 499 'embeddable' => true, 500 'href' => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ), 501 ); 502 } 503 return $links; 504 } 505 506 /** 507 * Prepares a single font family post for create or update. 508 * 509 * @since 6.5.0 510 * 511 * @param WP_REST_Request $request Request object. 512 * @return stdClass|WP_Error Post object or WP_Error. 513 */ 514 protected function prepare_item_for_database( $request ) { 515 $prepared_post = new stdClass(); 516 // Settings have already been decoded by ::sanitize_font_family_settings(). 517 $settings = $request->get_param( 'font_family_settings' ); 518 519 // This is an update and we merge with the existing font family. 520 if ( isset( $request['id'] ) ) { 521 $existing_post = $this->get_post( $request['id'] ); 522 if ( is_wp_error( $existing_post ) ) { 523 return $existing_post; 524 } 525 526 $prepared_post->ID = $existing_post->ID; 527 $existing_settings = $this->get_settings_from_post( $existing_post ); 528 $settings = array_merge( $existing_settings, $settings ); 529 } 530 531 $prepared_post->post_type = $this->post_type; 532 $prepared_post->post_status = 'publish'; 533 $prepared_post->post_title = $settings['name']; 534 $prepared_post->post_name = sanitize_title( $settings['slug'] ); 535 536 // Remove duplicate information from settings. 537 unset( $settings['name'] ); 538 unset( $settings['slug'] ); 539 540 $prepared_post->post_content = wp_json_encode( $settings ); 541 542 return $prepared_post; 543 } 544 545 /** 546 * Gets the font family's settings from the post. 547 * 548 * @since 6.5.0 549 * 550 * @param WP_Post $post Font family post object. 551 * @return array Font family settings array. 552 */ 553 protected function get_settings_from_post( $post ) { 554 $settings_json = json_decode( $post->post_content, true ); 555 556 // Default to empty strings if the settings are missing. 557 return array( 558 'name' => isset( $post->post_title ) && $post->post_title ? $post->post_title : '', 559 'slug' => isset( $post->post_name ) && $post->post_name ? $post->post_name : '', 560 'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '', 561 'preview' => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '', 562 ); 563 } 564 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |