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