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