| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Font_Faces_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 6.5.0 8 */ 9 10 /** 11 * Class to access font faces through the REST API. 12 */ 13 class WP_REST_Font_Faces_Controller extends WP_REST_Posts_Controller { 14 15 /** 16 * The latest version of theme.json schema supported by the controller. 17 * 18 * @since 6.5.0 19 * @var int 20 */ 21 const LATEST_THEME_JSON_VERSION_SUPPORTED = 3; 22 23 /** 24 * Whether the controller supports batching. 25 * 26 * @since 6.5.0 27 * @var false 28 */ 29 protected $allow_batch = false; 30 31 /** 32 * Registers the routes for posts. 33 * 34 * @since 6.5.0 35 * 36 * @see register_rest_route() 37 */ 38 public function register_routes() { 39 register_rest_route( 40 $this->namespace, 41 '/' . $this->rest_base, 42 array( 43 'args' => array( 44 'font_family_id' => array( 45 'description' => __( 'The ID for the parent font family of the font face.' ), 46 'type' => 'integer', 47 'required' => true, 48 ), 49 ), 50 array( 51 'methods' => WP_REST_Server::READABLE, 52 'callback' => array( $this, 'get_items' ), 53 'permission_callback' => array( $this, 'get_items_permissions_check' ), 54 'args' => $this->get_collection_params(), 55 ), 56 array( 57 'methods' => WP_REST_Server::CREATABLE, 58 'callback' => array( $this, 'create_item' ), 59 'permission_callback' => array( $this, 'create_item_permissions_check' ), 60 'args' => $this->get_create_params(), 61 ), 62 'schema' => array( $this, 'get_public_item_schema' ), 63 ) 64 ); 65 66 register_rest_route( 67 $this->namespace, 68 '/' . $this->rest_base . '/(?P<id>[\d]+)', 69 array( 70 'args' => array( 71 'font_family_id' => array( 72 'description' => __( 'The ID for the parent font family of the font face.' ), 73 'type' => 'integer', 74 'required' => true, 75 ), 76 'id' => array( 77 'description' => __( 'Unique identifier for the font face.' ), 78 'type' => 'integer', 79 'required' => true, 80 ), 81 ), 82 array( 83 'methods' => WP_REST_Server::READABLE, 84 'callback' => array( $this, 'get_item' ), 85 'permission_callback' => array( $this, 'get_item_permissions_check' ), 86 'args' => array( 87 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 88 ), 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.', 'default' ), 99 ), 100 ), 101 ), 102 'schema' => array( $this, 'get_public_item_schema' ), 103 ) 104 ); 105 } 106 107 /** 108 * Checks if a given request has access to font faces. 109 * 110 * @since 6.5.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 public function get_items_permissions_check( $request ) { 116 $post_type = get_post_type_object( $this->post_type ); 117 118 if ( ! current_user_can( $post_type->cap->read ) ) { 119 return new WP_Error( 120 'rest_cannot_read', 121 __( 'Sorry, you are not allowed to access font faces.' ), 122 array( 'status' => rest_authorization_required_code() ) 123 ); 124 } 125 126 return true; 127 } 128 129 /** 130 * Checks if a given request has access to a font face. 131 * 132 * @since 6.5.0 133 * 134 * @param WP_REST_Request $request Full details about the request. 135 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 136 */ 137 public function get_item_permissions_check( $request ) { 138 $post = $this->get_post( $request['id'] ); 139 if ( is_wp_error( $post ) ) { 140 return $post; 141 } 142 143 if ( ! current_user_can( 'read_post', $post->ID ) ) { 144 return new WP_Error( 145 'rest_cannot_read', 146 __( 'Sorry, you are not allowed to access this font face.' ), 147 array( 'status' => rest_authorization_required_code() ) 148 ); 149 } 150 151 return true; 152 } 153 154 /** 155 * Validates settings when creating a font face. 156 * 157 * @since 6.5.0 158 * 159 * @param string $value Encoded JSON string of font face settings. 160 * @param WP_REST_Request $request Request object. 161 * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. 162 */ 163 public function validate_create_font_face_settings( $value, $request ) { 164 // Enforce JSON Schema validity for field before applying custom validation logic. 165 $args = $this->get_create_params(); 166 $validity = rest_validate_value_from_schema( $value, $args['font_face_settings'], 'font_face_settings' ); 167 168 if ( is_wp_error( $validity ) ) { 169 return $validity; 170 } 171 172 $settings = json_decode( $value, true ); 173 174 // Check settings string is valid JSON. 175 if ( null === $settings ) { 176 return new WP_Error( 177 'rest_invalid_param', 178 __( 'font_face_settings parameter must be a valid JSON string.' ), 179 array( 'status' => 400 ) 180 ); 181 } 182 183 // Check that the font face settings match the theme.json schema. 184 $schema = $this->get_item_schema()['properties']['font_face_settings']; 185 $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_face_settings' ); 186 187 if ( is_wp_error( $has_valid_settings ) ) { 188 $has_valid_settings->add_data( array( 'status' => 400 ) ); 189 return $has_valid_settings; 190 } 191 192 // Check that none of the required settings are empty values. 193 $required = $schema['required']; 194 foreach ( $required as $key ) { 195 if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { 196 return new WP_Error( 197 'rest_invalid_param', 198 /* translators: %s: Name of the missing font face settings parameter, e.g. "font_face_settings[src]". */ 199 sprintf( __( '%s cannot be empty.' ), "font_face_setting[ $key ]" ), 200 array( 'status' => 400 ) 201 ); 202 } 203 } 204 205 $srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] ); 206 $files = $request->get_file_params(); 207 208 foreach ( $srcs as $src ) { 209 // Check that each src is a non-empty string. 210 $src = ltrim( $src ); 211 if ( empty( $src ) ) { 212 return new WP_Error( 213 'rest_invalid_param', 214 /* translators: %s: Font face source parameter name: "font_face_settings[src]". */ 215 sprintf( __( '%s values must be non-empty strings.' ), 'font_face_settings[src]' ), 216 array( 'status' => 400 ) 217 ); 218 } 219 220 // Check that srcs are valid URLs or file references. 221 if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) { 222 return new WP_Error( 223 'rest_invalid_param', 224 /* translators: 1: Font face source parameter name: "font_face_settings[src]", 2: The invalid src value. */ 225 sprintf( __( '%1$s value "%2$s" must be a valid URL or file reference.' ), 'font_face_settings[src]', $src ), 226 array( 'status' => 400 ) 227 ); 228 } 229 } 230 231 // Check that each file in the request references a src in the settings. 232 foreach ( array_keys( $files ) as $file ) { 233 if ( ! in_array( $file, $srcs, true ) ) { 234 return new WP_Error( 235 'rest_invalid_param', 236 /* translators: 1: File key (e.g. "file-0") in the request data, 2: Font face source parameter name: "font_face_settings[src]". */ 237 sprintf( __( 'File %1$s must be used in %2$s.' ), $file, 'font_face_settings[src]' ), 238 array( 'status' => 400 ) 239 ); 240 } 241 } 242 243 return true; 244 } 245 246 /** 247 * Sanitizes the font face settings when creating a font face. 248 * 249 * @since 6.5.0 250 * 251 * @param string $value Encoded JSON string of font face settings. 252 * @return array Decoded and sanitized array of font face settings. 253 */ 254 public function sanitize_font_face_settings( $value ) { 255 // Settings arrive as stringified JSON, since this is a multipart/form-data request. 256 $settings = json_decode( $value, true ); 257 $schema = $this->get_item_schema()['properties']['font_face_settings']['properties']; 258 259 // Sanitize settings based on callbacks in the schema. 260 foreach ( $settings as $key => $value ) { 261 $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; 262 $settings[ $key ] = call_user_func( $sanitize_callback, $value ); 263 } 264 265 return $settings; 266 } 267 268 /** 269 * Retrieves a collection of font faces within the parent font family. 270 * 271 * @since 6.5.0 272 * 273 * @param WP_REST_Request $request Full details about the request. 274 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 275 */ 276 public function get_items( $request ) { 277 $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); 278 if ( is_wp_error( $font_family ) ) { 279 return $font_family; 280 } 281 282 return parent::get_items( $request ); 283 } 284 285 /** 286 * Retrieves a single font face within the parent font family. 287 * 288 * @since 6.5.0 289 * 290 * @param WP_REST_Request $request Full details about the request. 291 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 292 */ 293 public function get_item( $request ) { 294 $post = $this->get_post( $request['id'] ); 295 if ( is_wp_error( $post ) ) { 296 return $post; 297 } 298 299 // Check that the font face has a valid parent font family. 300 $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); 301 if ( is_wp_error( $font_family ) ) { 302 return $font_family; 303 } 304 305 if ( (int) $font_family->ID !== (int) $post->post_parent ) { 306 return new WP_Error( 307 'rest_font_face_parent_id_mismatch', 308 /* translators: %d: A post id. */ 309 sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), 310 array( 'status' => 404 ) 311 ); 312 } 313 314 return parent::get_item( $request ); 315 } 316 317 /** 318 * Creates a font face for the parent font family. 319 * 320 * @since 6.5.0 321 * 322 * @param WP_REST_Request $request Full details about the request. 323 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 324 */ 325 public function create_item( $request ) { 326 $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); 327 if ( is_wp_error( $font_family ) ) { 328 return $font_family; 329 } 330 331 // Settings have already been decoded by ::sanitize_font_face_settings(). 332 $settings = $request->get_param( 'font_face_settings' ); 333 $file_params = $request->get_file_params(); 334 335 // Check that the necessary font face properties are unique. 336 $query = new WP_Query( 337 array( 338 'post_type' => $this->post_type, 339 'posts_per_page' => 1, 340 'title' => WP_Font_Utils::get_font_face_slug( $settings ), 341 'update_post_meta_cache' => false, 342 'update_post_term_cache' => false, 343 ) 344 ); 345 if ( ! empty( $query->posts ) ) { 346 return new WP_Error( 347 'rest_duplicate_font_face', 348 __( 'A font face matching those settings already exists.' ), 349 array( 'status' => 400 ) 350 ); 351 } 352 353 // Move the uploaded font asset from the temp folder to the fonts directory. 354 if ( ! function_exists( 'wp_handle_upload' ) ) { 355 require_once ABSPATH . 'wp-admin/includes/file.php'; 356 } 357 358 $srcs = is_string( $settings['src'] ) ? array( $settings['src'] ) : $settings['src']; 359 $processed_srcs = array(); 360 $font_file_meta = array(); 361 362 foreach ( $srcs as $src ) { 363 // If src not a file reference, use it as is. 364 if ( ! isset( $file_params[ $src ] ) ) { 365 $processed_srcs[] = $src; 366 continue; 367 } 368 369 $file = $file_params[ $src ]; 370 $font_file = $this->handle_font_file_upload( $file ); 371 if ( is_wp_error( $font_file ) ) { 372 return $font_file; 373 } 374 375 $processed_srcs[] = $font_file['url']; 376 $font_file_meta[] = $this->relative_fonts_path( $font_file['file'] ); 377 } 378 379 // Store the updated settings for prepare_item_for_database to use. 380 $settings['src'] = count( $processed_srcs ) === 1 ? $processed_srcs[0] : $processed_srcs; 381 $request->set_param( 'font_face_settings', $settings ); 382 383 // Ensure that $settings data is slashed, so values with quotes are escaped. 384 // WP_REST_Posts_Controller::create_item uses wp_slash() on the post_content. 385 $font_face_post = parent::create_item( $request ); 386 387 if ( is_wp_error( $font_face_post ) ) { 388 return $font_face_post; 389 } 390 391 $font_face_id = $font_face_post->data['id']; 392 393 foreach ( $font_file_meta as $font_file_path ) { 394 add_post_meta( $font_face_id, '_wp_font_face_file', $font_file_path ); 395 } 396 397 return $font_face_post; 398 } 399 400 /** 401 * Deletes a single font face. 402 * 403 * @since 6.5.0 404 * 405 * @param WP_REST_Request $request Full details about the request. 406 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 407 */ 408 public function delete_item( $request ) { 409 $post = $this->get_post( $request['id'] ); 410 if ( is_wp_error( $post ) ) { 411 return $post; 412 } 413 414 $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); 415 if ( is_wp_error( $font_family ) ) { 416 return $font_family; 417 } 418 419 if ( (int) $font_family->ID !== (int) $post->post_parent ) { 420 return new WP_Error( 421 'rest_font_face_parent_id_mismatch', 422 /* translators: %d: A post id. */ 423 sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), 424 array( 'status' => 404 ) 425 ); 426 } 427 428 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 429 430 // We don't support trashing for font faces. 431 if ( ! $force ) { 432 return new WP_Error( 433 'rest_trash_not_supported', 434 /* translators: %s: force=true */ 435 sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), 436 array( 'status' => 501 ) 437 ); 438 } 439 440 return parent::delete_item( $request ); 441 } 442 443 /** 444 * Prepares a single font face output for response. 445 * 446 * @since 6.5.0 447 * 448 * @param WP_Post $item Post object. 449 * @param WP_REST_Request $request Request object. 450 * @return WP_REST_Response Response object. 451 */ 452 public function prepare_item_for_response( $item, $request ) { 453 $fields = $this->get_fields_for_response( $request ); 454 $data = array(); 455 456 if ( rest_is_field_included( 'id', $fields ) ) { 457 $data['id'] = $item->ID; 458 } 459 if ( rest_is_field_included( 'theme_json_version', $fields ) ) { 460 $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; 461 } 462 463 if ( rest_is_field_included( 'parent', $fields ) ) { 464 $data['parent'] = $item->post_parent; 465 } 466 467 if ( rest_is_field_included( 'font_face_settings', $fields ) ) { 468 $data['font_face_settings'] = $this->get_settings_from_post( $item ); 469 } 470 471 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 472 $data = $this->add_additional_fields_to_object( $data, $request ); 473 $data = $this->filter_response_by_context( $data, $context ); 474 475 $response = rest_ensure_response( $data ); 476 477 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 478 $links = $this->prepare_links( $item ); 479 $response->add_links( $links ); 480 } 481 482 /** 483 * Filters the font face data for a REST API response. 484 * 485 * @since 6.5.0 486 * 487 * @param WP_REST_Response $response The response object. 488 * @param WP_Post $post Font face post object. 489 * @param WP_REST_Request $request Request object. 490 */ 491 return apply_filters( 'rest_prepare_wp_font_face', $response, $item, $request ); 492 } 493 494 /** 495 * Retrieves the post's schema, conforming to JSON Schema. 496 * 497 * @since 6.5.0 498 * 499 * @return array Item schema data. 500 */ 501 public function get_item_schema() { 502 if ( $this->schema ) { 503 return $this->add_additional_fields_schema( $this->schema ); 504 } 505 506 $schema = array( 507 '$schema' => 'http://json-schema.org/draft-04/schema#', 508 'title' => $this->post_type, 509 'type' => 'object', 510 // Base properties for every Post. 511 'properties' => array( 512 'id' => array( 513 'description' => __( 'Unique identifier for the post.', 'default' ), 514 'type' => 'integer', 515 'context' => array( 'view', 'edit', 'embed' ), 516 'readonly' => true, 517 ), 518 'theme_json_version' => array( 519 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), 520 'type' => 'integer', 521 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 522 'minimum' => 2, 523 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 524 'context' => array( 'view', 'edit', 'embed' ), 525 ), 526 'parent' => array( 527 'description' => __( 'The ID for the parent font family of the font face.' ), 528 'type' => 'integer', 529 'context' => array( 'view', 'edit', 'embed' ), 530 ), 531 // Font face settings come directly from theme.json schema 532 // See https://schemas.wp.org/trunk/theme.json 533 'font_face_settings' => array( 534 'description' => __( 'font-face declaration in theme.json format.' ), 535 'type' => 'object', 536 'context' => array( 'view', 'edit', 'embed' ), 537 'properties' => array( 538 'fontFamily' => array( 539 'description' => __( 'CSS font-family value.' ), 540 'type' => 'string', 541 'default' => '', 542 'arg_options' => array( 543 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), 544 ), 545 ), 546 'fontStyle' => array( 547 'description' => __( 'CSS font-style value.' ), 548 'type' => 'string', 549 'default' => 'normal', 550 'arg_options' => array( 551 'sanitize_callback' => 'sanitize_text_field', 552 ), 553 ), 554 'fontWeight' => array( 555 'description' => __( 'List of available font weights, separated by a space.' ), 556 'default' => '400', 557 // Changed from `oneOf` to avoid errors from loose type checking. 558 // e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check. 559 'type' => array( 'string', 'integer' ), 560 'arg_options' => array( 561 'sanitize_callback' => 'sanitize_text_field', 562 ), 563 ), 564 'fontDisplay' => array( 565 'description' => __( 'CSS font-display value.' ), 566 'type' => 'string', 567 'default' => 'fallback', 568 'enum' => array( 569 'auto', 570 'block', 571 'fallback', 572 'swap', 573 'optional', 574 ), 575 'arg_options' => array( 576 'sanitize_callback' => 'sanitize_text_field', 577 ), 578 ), 579 'src' => array( 580 'description' => __( 'Paths or URLs to the font files.' ), 581 // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array, 582 // and causing a "matches more than one of the expected formats" error. 583 'anyOf' => array( 584 array( 585 'type' => 'string', 586 ), 587 array( 588 'type' => 'array', 589 'items' => array( 590 'type' => 'string', 591 ), 592 ), 593 ), 594 'default' => array(), 595 'arg_options' => array( 596 'sanitize_callback' => function ( $value ) { 597 return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value ); 598 }, 599 ), 600 ), 601 'fontStretch' => array( 602 'description' => __( 'CSS font-stretch value.' ), 603 'type' => 'string', 604 'arg_options' => array( 605 'sanitize_callback' => 'sanitize_text_field', 606 ), 607 ), 608 'ascentOverride' => array( 609 'description' => __( 'CSS ascent-override value.' ), 610 'type' => 'string', 611 'arg_options' => array( 612 'sanitize_callback' => 'sanitize_text_field', 613 ), 614 ), 615 'descentOverride' => array( 616 'description' => __( 'CSS descent-override value.' ), 617 'type' => 'string', 618 'arg_options' => array( 619 'sanitize_callback' => 'sanitize_text_field', 620 ), 621 ), 622 'fontVariant' => array( 623 'description' => __( 'CSS font-variant value.' ), 624 'type' => 'string', 625 'arg_options' => array( 626 'sanitize_callback' => 'sanitize_text_field', 627 ), 628 ), 629 'fontFeatureSettings' => array( 630 'description' => __( 'CSS font-feature-settings value.' ), 631 'type' => 'string', 632 'arg_options' => array( 633 'sanitize_callback' => 'sanitize_text_field', 634 ), 635 ), 636 'fontVariationSettings' => array( 637 'description' => __( 'CSS font-variation-settings value.' ), 638 'type' => 'string', 639 'arg_options' => array( 640 'sanitize_callback' => 'sanitize_text_field', 641 ), 642 ), 643 'lineGapOverride' => array( 644 'description' => __( 'CSS line-gap-override value.' ), 645 'type' => 'string', 646 'arg_options' => array( 647 'sanitize_callback' => 'sanitize_text_field', 648 ), 649 ), 650 'sizeAdjust' => array( 651 'description' => __( 'CSS size-adjust value.' ), 652 'type' => 'string', 653 'arg_options' => array( 654 'sanitize_callback' => 'sanitize_text_field', 655 ), 656 ), 657 'unicodeRange' => array( 658 'description' => __( 'CSS unicode-range value.' ), 659 'type' => 'string', 660 'arg_options' => array( 661 'sanitize_callback' => 'sanitize_text_field', 662 ), 663 ), 664 'preview' => array( 665 'description' => __( 'URL to a preview image of the font face.' ), 666 'type' => 'string', 667 'format' => 'uri', 668 'default' => '', 669 'arg_options' => array( 670 'sanitize_callback' => 'sanitize_url', 671 ), 672 ), 673 ), 674 'required' => array( 'fontFamily', 'src' ), 675 'additionalProperties' => false, 676 ), 677 ), 678 ); 679 680 $this->schema = $schema; 681 682 return $this->add_additional_fields_schema( $this->schema ); 683 } 684 685 /** 686 * Retrieves the item's schema for display / public consumption purposes. 687 * 688 * @since 6.5.0 689 * 690 * @return array Public item schema data. 691 */ 692 public function get_public_item_schema() { 693 694 $schema = parent::get_public_item_schema(); 695 696 // Also remove `arg_options' from child font_family_settings properties, since the parent 697 // controller only handles the top level properties. 698 foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) { 699 unset( $property['arg_options'] ); 700 } 701 702 return $schema; 703 } 704 705 /** 706 * Retrieves the query params for the font face collection. 707 * 708 * @since 6.5.0 709 * 710 * @return array Collection parameters. 711 */ 712 public function get_collection_params() { 713 $query_params = parent::get_collection_params(); 714 715 // Remove unneeded params. 716 unset( 717 $query_params['after'], 718 $query_params['modified_after'], 719 $query_params['before'], 720 $query_params['modified_before'], 721 $query_params['search'], 722 $query_params['search_columns'], 723 $query_params['slug'], 724 $query_params['status'] 725 ); 726 727 $query_params['orderby']['default'] = 'id'; 728 $query_params['orderby']['enum'] = array( 'id', 'include' ); 729 730 /** 731 * Filters collection parameters for the font face controller. 732 * 733 * @since 6.5.0 734 * 735 * @param array $query_params JSON Schema-formatted collection parameters. 736 */ 737 return apply_filters( 'rest_wp_font_face_collection_params', $query_params ); 738 } 739 740 /** 741 * Get the params used when creating a new font face. 742 * 743 * @since 6.5.0 744 * 745 * @return array Font face create arguments. 746 */ 747 public function get_create_params() { 748 $properties = $this->get_item_schema()['properties']; 749 return array( 750 'theme_json_version' => $properties['theme_json_version'], 751 // When creating, font_face_settings is stringified JSON, to work with multipart/form-data used 752 // when uploading font files. 753 'font_face_settings' => array( 754 'description' => __( 'font-face declaration in theme.json format, encoded as a string.' ), 755 'type' => 'string', 756 'required' => true, 757 'validate_callback' => array( $this, 'validate_create_font_face_settings' ), 758 'sanitize_callback' => array( $this, 'sanitize_font_face_settings' ), 759 ), 760 ); 761 } 762 763 /** 764 * Get the parent font family, if the ID is valid. 765 * 766 * @since 6.5.0 767 * 768 * @param int $font_family_id Supplied ID. 769 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. 770 */ 771 protected function get_parent_font_family_post( $font_family_id ) { 772 $error = new WP_Error( 773 'rest_post_invalid_parent', 774 __( 'Invalid post parent ID.', 'default' ), 775 array( 'status' => 404 ) 776 ); 777 778 if ( (int) $font_family_id <= 0 ) { 779 return $error; 780 } 781 782 $font_family_post = get_post( (int) $font_family_id ); 783 784 if ( empty( $font_family_post ) || empty( $font_family_post->ID ) 785 || 'wp_font_family' !== $font_family_post->post_type 786 ) { 787 return $error; 788 } 789 790 return $font_family_post; 791 } 792 793 /** 794 * Prepares links for the request. 795 * 796 * @since 6.5.0 797 * 798 * @param WP_Post $post Post object. 799 * @return array Links for the given post. 800 */ 801 protected function prepare_links( $post ) { 802 // Entity meta. 803 return array( 804 'self' => array( 805 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), 806 ), 807 'collection' => array( 808 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ), 809 ), 810 'parent' => array( 811 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ), 812 ), 813 ); 814 } 815 816 /** 817 * Prepares a single font face post for creation. 818 * 819 * @since 6.5.0 820 * 821 * @param WP_REST_Request $request Request object. 822 * @return stdClass Post object. 823 */ 824 protected function prepare_item_for_database( $request ) { 825 $prepared_post = new stdClass(); 826 827 // Settings have already been decoded by ::sanitize_font_face_settings(). 828 $settings = $request->get_param( 'font_face_settings' ); 829 830 // Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting, 831 // which may contain multibyte characters. 832 $title = WP_Font_Utils::get_font_face_slug( $settings ); 833 834 $prepared_post->post_type = $this->post_type; 835 $prepared_post->post_parent = $request['font_family_id']; 836 $prepared_post->post_status = 'publish'; 837 $prepared_post->post_title = $title; 838 $prepared_post->post_name = sanitize_title( $title ); 839 $prepared_post->post_content = wp_json_encode( $settings ); 840 841 return $prepared_post; 842 } 843 844 /** 845 * Sanitizes a single src value for a font face. 846 * 847 * @since 6.5.0 848 * 849 * @param string $value Font face src that is a URL or the key for a $_FILES array item. 850 * @return string Sanitized value. 851 */ 852 protected function sanitize_src( $value ) { 853 $value = ltrim( $value ); 854 return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value ); 855 } 856 857 /** 858 * Handles the upload of a font file using wp_handle_upload(). 859 * 860 * @since 6.5.0 861 * 862 * @param array $file Single file item from $_FILES. 863 * @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure. 864 */ 865 protected function handle_font_file_upload( $file ) { 866 add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); 867 // Filter the upload directory to return the fonts directory. 868 add_filter( 'upload_dir', '_wp_filter_font_directory' ); 869 870 $overrides = array( 871 'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ), 872 // Not testing a form submission. 873 'test_form' => false, 874 // Only allow uploading font files for this request. 875 'mimes' => WP_Font_Utils::get_allowed_font_mime_types(), 876 ); 877 878 // Bypasses is_uploaded_file() when running unit tests. 879 if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { 880 $overrides['action'] = 'wp_handle_mock_upload'; 881 } 882 883 $uploaded_file = wp_handle_upload( $file, $overrides ); 884 885 remove_filter( 'upload_dir', '_wp_filter_font_directory' ); 886 remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); 887 888 return $uploaded_file; 889 } 890 891 /** 892 * Handles file upload error. 893 * 894 * @since 6.5.0 895 * 896 * @param array $file File upload data. 897 * @param string $message Error message from wp_handle_upload(). 898 * @return WP_Error WP_Error object. 899 */ 900 public function handle_font_file_upload_error( $file, $message ) { 901 $status = 500; 902 $code = 'rest_font_upload_unknown_error'; 903 904 if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) { 905 $status = 400; 906 $code = 'rest_font_upload_invalid_file_type'; 907 } 908 909 return new WP_Error( $code, $message, array( 'status' => $status ) ); 910 } 911 912 /** 913 * Returns relative path to an uploaded font file. 914 * 915 * The path is relative to the current fonts directory. 916 * 917 * @since 6.5.0 918 * @access private 919 * 920 * @param string $path Full path to the file. 921 * @return string Relative path on success, unchanged path on failure. 922 */ 923 protected function relative_fonts_path( $path ) { 924 $new_path = $path; 925 926 $fonts_dir = wp_get_font_dir(); 927 if ( str_starts_with( $new_path, $fonts_dir['basedir'] ) ) { 928 $new_path = str_replace( $fonts_dir['basedir'], '', $new_path ); 929 $new_path = ltrim( $new_path, '/' ); 930 } 931 932 return $new_path; 933 } 934 935 /** 936 * Gets the font face's settings from the post. 937 * 938 * @since 6.5.0 939 * 940 * @param WP_Post $post Font face post object. 941 * @return array Font face settings array. 942 */ 943 protected function get_settings_from_post( $post ) { 944 $settings = json_decode( $post->post_content, true ); 945 $properties = $this->get_item_schema()['properties']['font_face_settings']['properties']; 946 947 // Provide required, empty settings if needed. 948 if ( null === $settings ) { 949 $settings = array( 950 'fontFamily' => '', 951 'src' => array(), 952 ); 953 } 954 955 // Only return the properties defined in the schema. 956 return array_intersect_key( $settings, $properties ); 957 } 958 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Jun 18 08:20:10 2026 | Cross-referenced by PHPXref |