Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font Library: create font faces through the REST API #57702

Merged
merged 9 commits into from
Jan 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
'args' => array(),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
Expand Down Expand Up @@ -217,9 +217,9 @@
return $parent;
}

$font_face = parent::get_item( $request );
$response = parent::get_item( $request );

if ( (int) $parent->ID !== (int) $font_face->data['parent'] ) {
if ( (int) $parent->ID !== (int) $response->data['parent'] ) {
return new WP_Error(
'rest_font_face_parent_id_mismatch',
/* translators: %d: A post id. */
Expand All @@ -228,7 +228,99 @@
);
}

return $font_face;
return $response;
}

/**
* Creates a font face for the parent font family.
*
* @since 6.5.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
// Create the font face post so we have an ID to attach the font files to.
$font_face_id = wp_insert_post(
array(
'post_type' => $this->post_type,
'post_parent' => $request['parent'],
'post_status' => 'publish',
'post_title' => 'Font Face',
creativecoder marked this conversation as resolved.
Show resolved Hide resolved
'post_name' => 'wp-font-face',
)
);

$font_face_settings = json_decode( $request->get_param( 'font_face_settings' ), true );
$file_params = $request->get_file_params();

// Move the uploaded font asset from the temp folder to the fonts directory.
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}

if ( is_string( $font_face_settings['src'] ) ) {
$file = $file_params[ $font_face_settings['src'] ];
$font_file = $this->handle_font_file_upload( $file );
if ( isset( $font_file['error'] ) ) {
return new WP_Error(
'rest_font_upload_unknown_error',
$font_file['error'],
array( 'status' => 500 )
);
}
$font_face_settings['src'] = $font_file['url'];
$font_relative_path = _wp_relative_fonts_path( $font_file['file'] );
add_post_meta( $font_face_id, '_wp_font_face_file', $font_relative_path );
} else {
foreach ( $font_face_settings['src'] as $index => $src ) {
$file = $file_params[ $src ];
$font_file = $this->handle_font_file_upload( $file );
if ( isset( $font_file['error'] ) ) {
return new WP_Error(
'rest_font_upload_unknown_error',
$font_file['error'],
array( 'status' => 500 )
);
}
$font_face_settings['src'][ $index ] = $font_file['url'];

$font_relative_path = _wp_relative_fonts_path( $font_file['file'] );
add_post_meta( $font_face_id, '_wp_font_face_file', $font_relative_path );
}
}

wp_update_post(
array(
'ID' => $font_face_id,
'post_content' => wp_json_encode( $font_face_settings ),
)
);

$font_face_post = get_post( $font_face_id );

return $this->prepare_item_for_response( $font_face_post, $request );
}

protected function handle_font_file_upload( $file ) {
add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) );
add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) );

$overrides = array(
// Not testing a form submission.
'test_form' => false,
// Seems mime type for files that are not images cannot be tested.
// See wp_check_filetype_and_ext().
'test_type' => false,
creativecoder marked this conversation as resolved.
Show resolved Hide resolved
'mimes' => WP_Font_Library::get_expected_font_mime_types_per_php_version(),
);

$uploaded_file = wp_handle_upload( $file, $overrides );

remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) );
remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) );

return $uploaded_file;
}

/**
Expand All @@ -255,6 +347,30 @@
return parent::delete_item( $request );
}

/**
* Prepares a single post output for response.
*
* @since 6.5.0
*
* @param WP_Post $item Post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {

Check warning on line 359 in lib/experimental/fonts/font-library/class-wp-rest-font-faces-controller.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Unused function parameter $request.
$data = array();

$data['id'] = $item->ID;
$data['theme_json_version'] = 2;
creativecoder marked this conversation as resolved.
Show resolved Hide resolved
$data['parent'] = $item->post_parent;
$data['font_face_settings'] = json_decode( $item->post_content, true );

$response = rest_ensure_response( $data );
$links = $this->prepare_links( $item );
$response->add_links( $links );

return $response;
}

/**
* Prepares links for the request.
*
Expand Down
23 changes: 23 additions & 0 deletions lib/experimental/fonts/font-library/font-library.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,26 @@
);

wp_register_font_collection( $default_font_collection );

/**
* Returns relative path to an uploaded font file.
*
* The path is relative to the current fonts dir.
*
* @since 6.5.0
* @access private
*
* @param string $path Full path to the file.
* @return string Relative path on success, unchanged path on failure.
*/
function _wp_relative_fonts_path( $path ) {

Check failure on line 133 in lib/experimental/fonts/font-library/font-library.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

The "_wp_relative_fonts_path()" function should be guarded against redeclaration.
creativecoder marked this conversation as resolved.
Show resolved Hide resolved
$new_path = $path;

$fonts_dir = WP_Font_Library::get_fonts_dir();
if ( str_starts_with( $new_path, $fonts_dir ) ) {
$new_path = str_replace( $fonts_dir, '', $new_path );
$new_path = ltrim( $new_path, '/' );
}

return $new_path;
}
148 changes: 124 additions & 24 deletions phpunit/tests/fonts/font-library/wpRestFontFacesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,93 @@ class WP_REST_Font_Faces_Controller_Test extends WP_Test_REST_Controller_Testcas
protected static $font_face_id1;
protected static $font_face_id2;

private static $font_file_ttf;
private static $font_file_woff2;

public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$font_family_id = $factory->post->create( array( 'post_type' => 'wp_font_family' ) );
self::$other_font_family_id = $factory->post->create( array( 'post_type' => 'wp_font_family' ) );
self::$font_face_id1 = $factory->post->create(
array(
'post_type' => 'wp_font_face',
'post_parent' => self::$font_family_id,
'post_type' => 'wp_font_face',
'post_parent' => self::$font_family_id,
'post_status' => 'publish',
'post_title' => 'Font Face',
'post_name' => 'wp-font-face',
'post_content' => wp_json_encode(
array(
'font_face_settings' => array(
'fontFamily' => 'Open Sans',
'fontWeight' => '400',
'fontStyle' => 'normal',
'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ),
),
)
),
)
);
self::$font_face_id2 = $factory->post->create(
array(
'post_type' => 'wp_font_face',
'post_parent' => self::$font_family_id,
'post_type' => 'wp_font_face',
'post_parent' => self::$font_family_id,
'post_status' => 'publish',
'post_title' => 'Font Face',
'post_name' => 'wp-font-face',
'post_content' => wp_json_encode(
array(
'font_face_settings' => array(
'fontFamily' => 'Open Sans',
'fontWeight' => '900',
'fontStyle' => 'normal',
'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ),
),
)
),
)
);

self::$admin_id = $factory->user->create(
self::$admin_id = $factory->user->create(
array(
'role' => 'administrator',
)
);

self::$editor_id = $factory->user->create(
array(
'role' => 'editor',
)
);
}

public static function wpTearDownAfterClass() {
if ( file_exists( self::$font_file_ttf ) ) {
unlink( self::$font_file_ttf );
}
if ( file_exists( self::$font_file_woff2 ) ) {
unlink( self::$font_file_woff2 );
}

self::delete_user( self::$admin_id );
self::delete_user( self::$editor_id );
}

public function set_up() {
parent::set_up();

// @core-merge Use `DIR_TESTDATA` instead of `GUTENBERG_DIR_TESTDATA`.
$font_file_ttf = GUTENBERG_DIR_TESTDATA . '/fonts/OpenSans-Regular.ttf';
self::$font_file_ttf = get_temp_dir() . 'OpenSans-Regular.ttf';
if ( ! file_exists( self::$font_file_ttf ) ) {
copy( $font_file_ttf, self::$font_file_ttf );
}

// @core-merge Use `DIR_TESTDATA` instead of `GUTENBERG_DIR_TESTDATA`.
$font_file_woff2 = GUTENBERG_DIR_TESTDATA . '/fonts/OpenSans-Regular.woff2';
self::$font_file_woff2 = get_temp_dir() . 'codeispoetry.png';
if ( ! file_exists( self::$font_file_woff2 ) ) {
copy( $font_file_woff2, self::$font_file_woff2 );
}
}

/**
* @covers WP_REST_Font_Faces_Controller::register_routes
*/
Expand Down Expand Up @@ -94,8 +152,10 @@ public function test_get_items() {

$this->assertSame( 200, $response->get_status() );
$this->assertCount( 2, $data );
$this->assertSame( self::$font_face_id1, $data[0]['id'] );
$this->assertSame( self::$font_face_id2, $data[1]['id'] );
$this->assertArrayHasKey( '_links', $data[0] );
$this->check_font_face_data( $data[0], self::$font_face_id1, $data[0]['_links'] );
$this->assertArrayHasKey( '_links', $data[1] );
$this->check_font_face_data( $data[1], self::$font_face_id2, $data[1]['_links'] );
}

/**
Expand Down Expand Up @@ -131,12 +191,8 @@ public function test_get_item() {
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );

$fields = array(
'id',
'parent',
);
$data = $response->get_data();
$this->assertSameSets( $fields, array_keys( $data ) );
$data = $response->get_data();
$this->check_font_face_data( $data, self::$font_face_id1, $response->get_links() );
}

/**
Expand Down Expand Up @@ -205,17 +261,34 @@ public function test_get_item_invalid_parent_id() {
*/
public function test_create_item() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
$request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
$request->set_param( 'theme_json_version', 2 );
$request->set_param(
'font_face_settings',
wp_json_encode(
array(
'fontFamily' => 'Open Sans',
'fontWeight' => '400',
'fontStyle' => 'normal',
'src' => 'file-0',
)
)
);
$request->set_file_params(
array(
'file-0' => array(
'file' => file_get_contents( self::$font_file_ttf ),
'name' => 'OpenSans-Regular.ttf',
'size' => filesize( self::$font_file_ttf ),
'tmp_name' => self::$font_file_ttf,
),
)
);

$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();

$fields = array(
'id',
'parent',
);
$data = $response->get_data();
$this->assertSameSets( $fields, array_keys( $data ) );

$this->check_font_face_data( $data, $data['id'], $data->get_links() );
$this->assertSame( self::$font_family_id, $data['parent'], "The returned revision's id should match the parent id." );
}

Expand Down Expand Up @@ -288,10 +361,16 @@ public function test_delete_item_invalid_delete_permissions() {
}

/**
* @doesNotPerformAssertions
* @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
*/
public function test_prepare_item() {
// Not yet using the prepare_item method for font faces.
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id2 );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 200, $response->get_status() );

$data = $response->get_data();
$this->check_font_face_data( $data, self::$font_face_id2, $response->get_links() );
}

/**
Expand All @@ -309,4 +388,25 @@ public function test_get_item_schema() {
$this->assertArrayHasKey( 'parent', $properties );
$this->assertArrayHasKey( 'font_face_settings', $properties );
}

protected function check_font_face_data( $data, $post_id, $links ) {
$post = get_post( $post_id );

$this->assertArrayHasKey( 'id', $data );
$this->assertSame( $post->ID, $data['id'] );

$this->assertArrayHasKey( 'parent', $data );
$this->assertSame( $post->post_parent, $data['parent'] );

$this->assertArrayHasKey( 'theme_json_version', $data );
$this->assertSame( 2, $data['theme_json_version'] );
creativecoder marked this conversation as resolved.
Show resolved Hide resolved

$this->assertArrayHasKey( 'font_face_settings', $data );
$this->assertSame( $post->post_content, wp_json_encode( $data['font_face_settings'], JSON_UNESCAPED_SLASHES ) );

$this->assertNotEmpty( $links );
$this->assertSame( rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), $links['self'][0]['href'] );
$this->assertSame( rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces' ), $links['collection'][0]['href'] );
$this->assertSame( rest_url( 'wp/v2/font-families/' . $post->post_parent ), $links['parent'][0]['href'] );
}
}
Loading