diff --git a/composer.json b/composer.json index 7c143881d..566b99dd2 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,17 @@ "comment unspam", "comment untrash", "comment update", + "font", + "font collection", + "font collection get", + "font collection is-registered", + "font collection list", + "font collection list-categories", + "font collection list-families", + "font face", + "font face install", + "font family", + "font family install", "menu", "menu create", "menu delete", diff --git a/entity-command.php b/entity-command.php index 0c1e03079..8b1be319d 100644 --- a/entity-command.php +++ b/entity-command.php @@ -113,3 +113,19 @@ }, ) ); + +if ( class_exists( 'WP_CLI\Dispatcher\CommandNamespace' ) ) { + WP_CLI::add_command( 'font', 'Font_Namespace' ); +} + +$wpcli_entity_font_version_check = array( + 'before_invoke' => function () { + if ( Utils\wp_version_compare( '6.5', '<' ) ) { + WP_CLI::error( 'Requires WordPress 6.5 or greater.' ); + } + }, +); + +WP_CLI::add_command( 'font collection', 'Font_Collection_Command', $wpcli_entity_font_version_check ); +WP_CLI::add_command( 'font family', 'Font_Family_Command', $wpcli_entity_font_version_check ); +WP_CLI::add_command( 'font face', 'Font_Face_Command', $wpcli_entity_font_version_check ); diff --git a/features/font-collection.feature b/features/font-collection.feature new file mode 100644 index 000000000..38acd953f --- /dev/null +++ b/features/font-collection.feature @@ -0,0 +1,82 @@ +Feature: Manage WordPress font collections + + Background: + Given a WP install + + @require-wp-6.5 + Scenario: Listing font collections + When I try `wp font collection list` + Then STDOUT should be a table containing rows: + | slug | name | description | categories | + | google-fonts | Google Fonts | Install from Google Fonts. Fonts are copied to and served from your site. | Sans Serif (sans-serif), Display (display), Serif (serif), Handwriting (handwriting), Monospace (monospace) | + + @require-wp-6.5 + Scenario: Getting a non-existent font collection + When I try `wp font collection get nonexistent-collection` + Then the return code should be 1 + And STDERR should contain: + """ + doesn't exist + """ + + @require-wp-6.5 + Scenario: Checking whether a font collection is registered + When I try `wp font collection is-registered nonexistent-collection` + Then the return code should be 1 + + When I run `wp font collection is-registered google-fonts` + Then the return code should be 0 + + @require-wp-6.5 + Scenario: Listing font families in a collection + When I run `wp font collection list-families google-fonts --format=count` + Then STDOUT should be a number + + @require-wp-6.5 + Scenario: Listing font families in a collection with fields + When I run `wp font collection list-families google-fonts --fields=slug,name --format=csv` + Then STDOUT should contain: + """ + slug,name + """ + + @require-wp-6.5 + Scenario: Filtering font families by category + When I run `wp font collection list-families google-fonts --category=sans-serif --format=count` + Then STDOUT should be a number + + @require-wp-6.5 + Scenario: Listing categories in a collection + When I run `wp font collection list-categories google-fonts --format=csv` + Then STDOUT should contain: + """ + slug,name + """ + + @require-wp-6.5 + Scenario: Getting a non-existent collection for list-families + When I try `wp font collection list-families nonexistent-collection` + Then the return code should be 1 + And STDERR should contain: + """ + doesn't exist + """ + + @require-wp-6.5 + Scenario: Getting a non-existent collection for list-categories + When I try `wp font collection list-categories nonexistent-collection` + Then the return code should be 1 + And STDERR should contain: + """ + doesn't exist + """ + + @less-than-wp-6.5 + Scenario: Font collection commands fail on WordPress < 6.5 + Given a WP install + When I try `wp font collection list` + Then the return code should be 1 + And STDERR should contain: + """ + Requires WordPress 6.5 or greater + """ diff --git a/features/font-face.feature b/features/font-face.feature new file mode 100644 index 000000000..51ab88b3d --- /dev/null +++ b/features/font-face.feature @@ -0,0 +1,68 @@ +Feature: Manage WordPress font faces + + Background: + Given a WP install + + @require-wp-6.5 + Scenario: Installing a font face + Given I run `wp post create --post_type=wp_font_family --post_title="Test Family" --post_status=publish --porcelain` + And save STDOUT as {FONT_FAMILY_ID} + + When I run `wp font face install {FONT_FAMILY_ID} --src="https://example.com/font.woff2" --porcelain` + Then STDOUT should be a number + And save STDOUT as {FONT_FACE_ID} + + When I run `wp post get {FONT_FACE_ID} --field=post_parent` + Then STDOUT should be: + """ + {FONT_FAMILY_ID} + """ + + @require-wp-6.5 + Scenario: Installing a font face with custom properties + Given I run `wp post create --post_type=wp_font_family --post_title="Test Family" --post_status=publish --porcelain` + And save STDOUT as {FONT_FAMILY_ID} + + When I run `wp font face install {FONT_FAMILY_ID} --src="font.woff2" --font-weight=700 --font-style=italic --porcelain` + Then STDOUT should be a number + And save STDOUT as {FONT_FACE_ID} + + When I run `wp post get {FONT_FACE_ID} --field=post_title` + Then STDOUT should contain: + """ + 700 + """ + And STDOUT should contain: + """ + italic + """ + + @require-wp-6.5 + Scenario: Installing a font face with invalid parent + When I try `wp font face install 999999 --src="font.woff2"` + Then the return code should be 1 + And STDERR should contain: + """ + doesn't exist + """ + + @require-wp-6.5 + Scenario: Installing a font face without required src parameter + Given I run `wp post create --post_type=wp_font_family --post_title="Test Family" --post_status=publish --porcelain` + And save STDOUT as {FONT_FAMILY_ID} + + When I try `wp font face install {FONT_FAMILY_ID}` + Then the return code should be 1 + And STDERR should contain: + """ + missing --src parameter + """ + + @less-than-wp-6.5 + Scenario: Font face install commands fail on WordPress < 6.5 + When I try `wp font face install 1 --src=test.woff2` + Then the return code should be 1 + And STDERR should contain: + """ + Requires WordPress 6.5 or greater + """ diff --git a/features/font-family.feature b/features/font-family.feature new file mode 100644 index 000000000..4a4799cba --- /dev/null +++ b/features/font-family.feature @@ -0,0 +1,46 @@ +Feature: Manage WordPress font families + + Background: + Given a WP install + + @require-wp-6.5 + Scenario: Installing a font family from a collection + When I run `wp font family install google-fonts "roboto" --porcelain` + Then STDOUT should be a number + And save STDOUT as {FONT_FAMILY_ID} + + When I run `wp post get {FONT_FAMILY_ID} --field=post_title` + Then STDOUT should contain: + """ + Roboto + """ + + When I run `wp post list --post_type=wp_font_face --post_parent={FONT_FAMILY_ID} --format=count` + Then STDOUT should be a number + + @require-wp-6.5 + Scenario: Installing a font family from a non-existent collection + When I try `wp font family install nonexistent-collection roboto` + Then the return code should be 1 + And STDERR should contain: + """ + doesn't exist + """ + + @require-wp-6.5 + Scenario: Installing a non-existent font family from a collection + When I try `wp font family install google-fonts nonexistent-family` + Then the return code should be 1 + And STDERR should contain: + """ + not found + """ + + @less-than-wp-6.5 + Scenario: Font family install commands fail on WordPress < 6.5 + When I try `wp font family install google-fonts roboto` + Then the return code should be 1 + And STDERR should contain: + """ + Requires WordPress 6.5 or greater + """ diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6ab1a6827..5edb5334a 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -61,6 +61,8 @@ */src/Taxonomy_Command\.php$ */src/Comment(_Meta)?_Command\.php$ + */src/Font(_Collection|_Face|_Family)?_Command\.php$ + */src/Font_Namespace\.php$ */src/Menu(_Item|_Location)?_Command\.php$ */src/Network_Meta_Command\.php$ */src/Network_Namespace\.php$ diff --git a/src/Font_Collection_Command.php b/src/Font_Collection_Command.php new file mode 100644 index 000000000..6a81a26e6 --- /dev/null +++ b/src/Font_Collection_Command.php @@ -0,0 +1,415 @@ +] + * : Prints the value of a single field for each collection. + * + * [--fields=] + * : Limit the output to specific collection fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - count + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for each collection: + * + * * slug + * * name + * * description + * * categories + * + * ## EXAMPLES + * + * # List all font collections + * $ wp font collection list + * +------------------+-------------------+ + * | slug | name | + * +------------------+-------------------+ + * | google-fonts | Google Fonts | + * +------------------+-------------------+ + * + * # List collections in JSON format + * $ wp font collection list --format=json + * [{"slug":"google-fonts","name":"Google Fonts"}] + * + * @subcommand list + */ + public function list_( $args, $assoc_args ) { + $font_library = WP_Font_Library::get_instance(); + $collections = $font_library->get_font_collections(); + + $items = []; + + /** + * @var \WP_Font_Collection $collection + */ + foreach ( $collections as $collection ) { + $data = $collection->get_data(); + + if ( is_wp_error( $data ) ) { + WP_CLI::warning( $data ); + continue; + } + + $items[] = [ + 'slug' => $collection->slug, + 'name' => $data['name'] ?? '', + 'description' => $data['description'] ?? '', + 'categories' => $this->format_categories( $data['categories'] ?? [] ), + ]; + } + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_items( $items ); + } + + /** + * Gets details about a registered font collection. + * + * ## OPTIONS + * + * + * : Font collection slug. + * + * [--field=] + * : Instead of returning the whole collection, returns the value of a single field. + * + * [--fields=] + * : Limit the output to specific fields. Defaults to all fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * These fields will be displayed by default for the specified collection: + * + * * slug + * * name + * * description + * * categories + * + * ## EXAMPLES + * + * # Get details of a specific collection + * $ wp font collection get google-fonts + * +-------+------------------+ + * | Field | Value | + * +-------+------------------+ + * | slug | google-fonts | + * | name | Google Fonts | + * +-------+------------------+ + * + * # Get the name field only + * $ wp font collection get google-fonts --field=name + * Google Fonts + */ + public function get( $args, $assoc_args ) { + $slug = $args[0]; + $font_library = WP_Font_Library::get_instance(); + $collection = $font_library->get_font_collection( $slug ); + + if ( ! $collection ) { + WP_CLI::error( "Font collection {$slug} doesn't exist." ); + } + + $collection_data = $collection->get_data(); + + if ( is_wp_error( $collection_data ) ) { + WP_CLI::error( $collection_data ); + } + + $data = [ + 'slug' => $collection->slug, + 'name' => $collection_data['name'] ?? '', + 'description' => $collection_data['description'] ?? '', + 'categories' => $this->format_categories( $collection_data['categories'] ?? [] ), + ]; + + $formatter = $this->get_formatter( $assoc_args ); + $formatter->display_item( $data ); + } + + /** + * Checks if a font collection is registered. + * + * ## OPTIONS + * + * + * : Font collection slug. + * + * ## EXAMPLES + * + * # Bash script for checking if a font collection is registered, with fallback. + * + * if wp font collection is-registered google-fonts 2>/dev/null; then + * # Font collection is registered. Do something. + * else + * # Fallback if collection is not registered. + * fi + * + * @subcommand is-registered + * + * @param string[] $args Positional arguments. + * @param array $assoc_args Associative arguments. + */ + public function is_registered( $args, $assoc_args ) { + $slug = $args[0]; + $font_library = WP_Font_Library::get_instance(); + $collection = $font_library->get_font_collection( $slug ); + + if ( ! $collection ) { + WP_CLI::halt( 1 ); + } + + WP_CLI::halt( 0 ); + } + + /** + * Lists font families in a collection. + * + * ## OPTIONS + * + * + * : Font collection slug. + * + * [--category=] + * : Filter by category slug. + * + * [--field=] + * : Prints the value of a single field for each family. + * + * [--fields=] + * : Limit the output to specific family fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - count + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * * slug + * * name + * * fontFamily + * * categories + * * preview + * + * ## EXAMPLES + * + * # List all font families in a collection + * $ wp font collection list-families google-fonts + * + * # List font families in a specific category + * $ wp font collection list-families google-fonts --category=sans-serif + * + * @subcommand list-families + */ + public function list_families( $args, $assoc_args ) { + $slug = $args[0]; + $font_library = WP_Font_Library::get_instance(); + $collection = $font_library->get_font_collection( $slug ); + + if ( ! $collection ) { + WP_CLI::error( "Font collection {$slug} doesn't exist." ); + } + + $collection_data = $collection->get_data(); + + if ( is_wp_error( $collection_data ) ) { + WP_CLI::error( $collection_data ); + } + + /** + * @var FontCollectionData $collection_data + */ + + $font_families = $collection_data['font_families'] ?? []; + + if ( empty( $font_families ) || ! is_array( $font_families ) ) { + WP_CLI::error( 'No font families found in this collection.' ); + } + + $category = \WP_CLI\Utils\get_flag_value( $assoc_args, 'category' ); + + $items = []; + foreach ( $font_families as $family ) { + $family_categories = $family['categories'] ?? []; + if ( $category && ! in_array( $category, $family_categories, true ) ) { + continue; + } + + $settings = $family['font_family_settings'] ?? []; + + $items[] = [ + 'slug' => $settings['slug'] ?? '', + 'name' => $settings['name'] ?? '', + 'fontFamily' => $settings['fontFamily'] ?? '', + 'categories' => implode( ', ', $family_categories ), + 'preview' => $settings['preview'] ?? '', + ]; + } + + $fields = [ 'slug', 'name', 'fontFamily', 'categories', 'preview' ]; + $formatter = new Formatter( $assoc_args, $fields, 'font-family' ); + $formatter->display_items( $items ); + } + + /** + * Lists categories in a collection. + * + * ## OPTIONS + * + * + * : Font collection slug. + * + * [--field=] + * : Prints the value of a single field for each category. + * + * [--fields=] + * : Limit the output to specific category fields. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - count + * - yaml + * --- + * + * ## AVAILABLE FIELDS + * + * * slug + * * name + * + * ## EXAMPLES + * + * # List all categories in a collection + * $ wp font collection list-categories google-fonts + * +-------------+--------------+ + * | slug | name | + * +-------------+--------------+ + * | sans-serif | Sans Serif | + * | display | Display | + * +-------------+--------------+ + * + * @subcommand list-categories + */ + public function list_categories( $args, $assoc_args ) { + $slug = $args[0]; + $font_library = WP_Font_Library::get_instance(); + $collection = $font_library->get_font_collection( $slug ); + + if ( ! $collection ) { + WP_CLI::error( "Font collection {$slug} doesn't exist." ); + } + + $collection_data = $collection->get_data(); + + if ( is_wp_error( $collection_data ) ) { + WP_CLI::error( $collection_data ); + } + + $categories = $collection_data['categories'] ?? null; + + if ( empty( $categories ) || ! is_array( $categories ) ) { + WP_CLI::error( 'No categories found in this collection.' ); + } + + $fields = [ 'slug', 'name' ]; + $formatter = new Formatter( $assoc_args, $fields, 'category' ); + $formatter->display_items( $categories ); + } + + private function format_categories( array $categories ): string { + return implode( + ', ', + array_map( + static function ( $category ) { + return "{$category['name']} ({$category['slug']})"; + }, + $categories + ) + ); + } + + private function get_formatter( &$assoc_args ) { + return new Formatter( $assoc_args, $this->fields, 'font-collection' ); + } +} diff --git a/src/Font_Face_Command.php b/src/Font_Face_Command.php new file mode 100644 index 000000000..1ca5b2941 --- /dev/null +++ b/src/Font_Face_Command.php @@ -0,0 +1,124 @@ + + * : Font family ID. + * + * --src= + * : Font face source URL or file path. + * + * [--font-family=] + * : CSS font-family value. + * + * [--font-style=