From a2f369b7274373b6b3d4b44e5232ec4931c61847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 21 Jun 2021 11:35:33 +0200 Subject: [PATCH 01/22] Add skeleton to implement string extraction from theme.json --- src/MakePotCommand.php | 23 +++++++++++++++++++++++ src/ThemeJsonExtractor.php | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/ThemeJsonExtractor.php diff --git a/src/MakePotCommand.php b/src/MakePotCommand.php index f29a2c79..202e2022 100644 --- a/src/MakePotCommand.php +++ b/src/MakePotCommand.php @@ -69,6 +69,11 @@ class MakePotCommand extends WP_CLI_Command { */ protected $skip_block_json = false; + /** + * @var bool + */ + protected $skip_theme_json = false; + /** * @var bool */ @@ -206,6 +211,9 @@ class MakePotCommand extends WP_CLI_Command { * [--skip-block-json] * : Skips string extraction from block.json files. * + * [--skip-theme-json] + * : Skips string extraction from theme.json files. + * * [--skip-audit] * : Skips string audit where it tries to find possible mistakes in translatable strings. Useful when running in an * automated environment. @@ -285,6 +293,7 @@ public function handle_arguments( $args, $assoc_args ) { $this->skip_js = Utils\get_flag_value( $assoc_args, 'skip-js', $this->skip_js ); $this->skip_php = Utils\get_flag_value( $assoc_args, 'skip-php', $this->skip_php ); $this->skip_block_json = Utils\get_flag_value( $assoc_args, 'skip-block-json', $this->skip_block_json ); + $this->skip_theme_json = Utils\get_flag_value( $assoc_args, 'skip-theme-json', $this->skip_theme_json ); $this->skip_audit = Utils\get_flag_value( $assoc_args, 'skip-audit', $this->skip_audit ); $this->headers = Utils\get_flag_value( $assoc_args, 'headers', $this->headers ); $this->file_comment = Utils\get_flag_value( $assoc_args, 'file-comment' ); @@ -624,6 +633,20 @@ protected function extract_strings() { ] ); } + + if ( ! $this->skip_theme_json ) { + ThemeJsonExtractor::fromDirectory( + $this->source, + $translations, + [ + // Only look for theme.json files, nothing else. + 'restrictFileNames' => [ 'theme.json' ], + 'include' => $this->include, + 'exclude' => $this->exclude, + 'extensions' => [ 'json' ], + ] + ); + } } catch ( \Exception $e ) { WP_CLI::error( $e->getMessage() ); } diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php new file mode 100644 index 00000000..cf553e2b --- /dev/null +++ b/src/ThemeJsonExtractor.php @@ -0,0 +1,19 @@ + Date: Mon, 21 Jun 2021 12:19:08 +0200 Subject: [PATCH 02/22] Extract strings using intermediate theme-i18n file --- src/ThemeJsonExtractor.php | 158 ++++++++++++++++++++++++++++++++++++- src/theme-i18n.json | 51 ++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/theme-i18n.json diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index cf553e2b..1c0fca3f 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -14,6 +14,162 @@ final class ThemeJsonExtractor extends Extractor implements ExtractorInterface { * @inheritdoc */ public static function fromString( $string, Translations $translations, array $options = [] ) { - // TODO: IMPLEMENT. + $file = $options['file']; + WP_CLI::debug( "Parsing file {$file}", 'make-pot' ); + + $theme_json = json_decode( $string, true ); + + if ( null === $theme_json ) { + WP_CLI::debug( + sprintf( + 'Could not parse file %1$s: error code %2$s', + $file, + json_last_error() + ), + 'make-pot' + ); + + return; + } + + $fields = self::get_fields_to_translate(); + foreach ( $fields as $field ) { + $path = $field['path']; + $key = $field['key']; + $context = $field['context']; + + /* + * We need to process the paths that include '*' separately. + * One example of such a path would be: + * [ 'settings', 'blocks', '*', 'color', 'palette' ] + */ + $nodes_to_iterate = array_keys( $path, '*', true ); + if ( ! empty( $nodes_to_iterate ) ) { + /* + * At the moment, we only need to support one '*' in the path, so take it directly. + * - base will be [ 'settings', 'blocks' ] + * - data will be [ 'color', 'palette' ] + */ + $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); + $data_path = array_slice( $path, $nodes_to_iterate[0] + 1 ); + $base_tree = self::_wp_array_get( $theme_json, $base_path, array() ); + foreach ( $base_tree as $node_name => $node_data ) { + $array_to_translate = self::_wp_array_get( $node_data, $data_path, null ); + if ( is_null( $array_to_translate ) ) { + continue; + } + + // Whole path will be [ 'settings', 'blocks', 'core/paragraph', 'color', 'palette' ]. + $whole_path = array_merge( $base_path, array( $node_name ), $data_path ); + $translation = $translations->insert( $context, $original ); + $translation->addReference( $file ); + } + } else { + $array_to_translate = self::_wp_array_get( $theme_json, $path, null ); + if ( is_null( $array_to_translate ) ) { + continue; + } + + $translation = $translations->insert( $context, $original ); + $translation->addReference( $file ); + } + } + } + + private static function read_json_file( $file_path ) { + $config = array(); + if ( $file_path ) { + $decoded_file = json_decode( + file_get_contents( $file_path ), + true + ); + + $json_decoding_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_decoding_error ) { + WP_CLI::debug( "Error when decoding {$file_path}", 'make-pot' ); + + return $config; + } + + if ( is_array( $decoded_file ) ) { + $config = $decoded_file; + } + } + return $config; } + + private static function get_fields_to_translate() { + if ( null === self::$theme_json_i18n ) { + $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + self::$theme_json_i18n = self::extract_paths_to_translate( $file_structure ); + } + return self::$theme_json_i18n; + } + + private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) { + $result = array(); + foreach ( $i18n_partial as $property => $partial_child ) { + if ( is_numeric( $property ) ) { + foreach ( $partial_child as $key => $context ) { + return array( + array( + 'path' => $current_path, + 'key' => $key, + 'context' => $context, + ), + ); + } + } + $result = array_merge( + $result, + self::extract_paths_to_translate( $partial_child, array_merge( $current_path, array( $property ) ) ) + ); + } + return $result; + } + + /** + * Accesses an array in depth based on a path of keys. + * + * It is the PHP equivalent of JavaScript's `lodash.get()` and mirroring it may help other components + * retain some symmetry between client and server implementations. + * + * Example usage: + * + * $array = array( + * 'a' => array( + * 'b' => array( + * 'c' => 1, + * ), + * ), + * ); + * _wp_array_get( $array, array( 'a', 'b', 'c' ) ); + * + * @param array $array An array from which we want to retrieve some information. + * @param array $path An array of keys describing the path with which to retrieve information. + * @param mixed $default The return value if the path does not exist within the array, + * or if `$array` or `$path` are not arrays. + * + * @return mixed The value from the path specified. + */ + private function _wp_array_get( $array, $path, $default = null ) { + // Confirm $path is valid. + if ( ! is_array( $path ) || 0 === count( $path ) ) { + return $default; + } + + foreach ( $path as $path_element ) { + if ( + ! is_array( $array ) || + ( ! is_string( $path_element ) && ! is_integer( $path_element ) && ! is_null( $path_element ) ) || + ! array_key_exists( $path_element, $array ) + ) { + return $default; + } + $array = $array[ $path_element ]; + } + + return $array; + } + } diff --git a/src/theme-i18n.json b/src/theme-i18n.json new file mode 100644 index 00000000..a01a5aa5 --- /dev/null +++ b/src/theme-i18n.json @@ -0,0 +1,51 @@ +{ + "settings": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ], + "duotone": [ + { + "name": "Duotone name" + } + ] + }, + "blocks": { + "*": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ] + } + } + } + } +} From 4ba7014048962b6669c35c2cd6e7b869f56cd81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 21 Jun 2021 12:38:25 +0200 Subject: [PATCH 03/22] Iterate over each preset individually --- src/ThemeJsonExtractor.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 1c0fca3f..56a6f462 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -59,10 +59,14 @@ public static function fromString( $string, Translations $translations, array $o continue; } - // Whole path will be [ 'settings', 'blocks', 'core/paragraph', 'color', 'palette' ]. - $whole_path = array_merge( $base_path, array( $node_name ), $data_path ); - $translation = $translations->insert( $context, $original ); - $translation->addReference( $file ); + foreach ( $array_to_translate as $item_key => $item_to_translate ) { + if ( empty( $item_to_translate[ $key ] ) ) { + continue; + } + + $translation = $translations->insert( $context, $array_to_translate[ $item_key ][ $key ] ); + $translation->addReference( $file ); + } } } else { $array_to_translate = self::_wp_array_get( $theme_json, $path, null ); @@ -70,8 +74,14 @@ public static function fromString( $string, Translations $translations, array $o continue; } - $translation = $translations->insert( $context, $original ); - $translation->addReference( $file ); + foreach ( $array_to_translate as $item_key => $item_to_translate ) { + if ( empty( $item_to_translate[ $key ] ) ) { + continue; + } + + $translation = $translations->insert( $context, $array_to_translate[ $item_key ][ $key ] ); + $translation->addReference( $file ); + } } } } From 23249796fc7f77bd26a1cf97208f405283188ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 21 Jun 2021 12:55:25 +0200 Subject: [PATCH 04/22] Fix lint issue: rename _wp_array_get to array_get --- src/ThemeJsonExtractor.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 56a6f462..25c4edb9 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -52,9 +52,9 @@ public static function fromString( $string, Translations $translations, array $o */ $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); $data_path = array_slice( $path, $nodes_to_iterate[0] + 1 ); - $base_tree = self::_wp_array_get( $theme_json, $base_path, array() ); + $base_tree = self::array_get( $theme_json, $base_path, array() ); foreach ( $base_tree as $node_name => $node_data ) { - $array_to_translate = self::_wp_array_get( $node_data, $data_path, null ); + $array_to_translate = self::array_get( $node_data, $data_path, null ); if ( is_null( $array_to_translate ) ) { continue; } @@ -69,7 +69,7 @@ public static function fromString( $string, Translations $translations, array $o } } } else { - $array_to_translate = self::_wp_array_get( $theme_json, $path, null ); + $array_to_translate = self::array_get( $theme_json, $path, null ); if ( is_null( $array_to_translate ) ) { continue; } @@ -153,7 +153,7 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path * ), * ), * ); - * _wp_array_get( $array, array( 'a', 'b', 'c' ) ); + * array_get( $array, array( 'a', 'b', 'c' ) ); * * @param array $array An array from which we want to retrieve some information. * @param array $path An array of keys describing the path with which to retrieve information. @@ -162,7 +162,7 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path * * @return mixed The value from the path specified. */ - private function _wp_array_get( $array, $path, $default = null ) { + private function array_get( $array, $path, $default = null ) { // Confirm $path is valid. if ( ! is_array( $path ) || 0 === count( $path ) ) { return $default; From e5fa7d1eee1b3be4dc51b6170a477e7911db9ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 21 Jun 2021 17:38:34 +0200 Subject: [PATCH 05/22] Fix variable name --- src/ThemeJsonExtractor.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 25c4edb9..cbdbab78 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -109,11 +109,9 @@ private static function read_json_file( $file_path ) { } private static function get_fields_to_translate() { - if ( null === self::$theme_json_i18n ) { - $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); - self::$theme_json_i18n = self::extract_paths_to_translate( $file_structure ); - } - return self::$theme_json_i18n; + $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); + return $theme_json_i18n; } private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) { From ac47a45249962f5a1654026b0073f00f8b9f8f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 21 Jun 2021 18:31:44 +0200 Subject: [PATCH 06/22] Add behat tests --- features/makepot.feature | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/features/makepot.feature b/features/makepot.feature index af55cc2a..ec6fd87f 100644 --- a/features/makepot.feature +++ b/features/makepot.feature @@ -3155,3 +3155,59 @@ Feature: Generate a POT file of a WordPress project """ msgid "Notice" """ + +Scenario: Skips theme.json file if skip-theme-json flag provided + Given an empty foo-theme directory + And a foo-theme/theme.json file: + """ + { + "version": "1", + "settings": { + "color": { + "palette": [ + { "slug": "black", "color": "#000000", "name": "Black" } + ] + } + } + } + """ + + When I try `wp i18n make-pot foo-theme --skip-theme-json` + Then STDOUT should be: + """ + Success: POT file successfully generated! + """ + And the foo-theme/foo-theme.pot file should exist + But the foo-theme/foo-theme.pot file should not contain: + """ + msgctxt "Color name" + msgid "Black" + """ + + Scenario: Extract strings from theme.json files + Given an empty foo-theme directory + And a foo-theme/theme.json file: + """ + { + "version": "1", + "settings": { + "color": { + "palette": [ + { "slug": "black", "color": "#000000", "name": "Black" } + ] + } + } + } + """ + + When I try `wp i18n make-pot foo-theme` + Then STDOUT should be: + """ + Success: POT file successfully generated! + """ + And the foo-theme/foo-theme.pot file should exist + And the foo-theme/foo-theme.pot file should contain: + """ + msgctxt "Color name" + msgid "Black" + """ \ No newline at end of file From 44206c6ebc830fb9fdc9abaa40dbbca0fec85022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 21 Jun 2021 18:49:55 +0200 Subject: [PATCH 07/22] More behat tests --- features/makepot.feature | 70 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/features/makepot.feature b/features/makepot.feature index ec6fd87f..f1df0ae8 100644 --- a/features/makepot.feature +++ b/features/makepot.feature @@ -3164,9 +3164,73 @@ Scenario: Skips theme.json file if skip-theme-json flag provided "version": "1", "settings": { "color": { + "duotone": [ + { "slug": "dark-grayscale", "name": "Dark grayscale", "colors": [] } + ], + "gradients": [ + { "slug": "purple-to-yellow", "name": "Purple to yellow" } + ], "palette": [ - { "slug": "black", "color": "#000000", "name": "Black" } + { "slug": "black", "color": "#000000", "name": "Black" }, + { "slug": "white", "color": "#000000", "name": "White" } ] + }, + "typography": { + "fontSizes": [ + { "name": "Small", "slug": "small", "size": "13px" } + ] + } + } + } + """ + + When I try `wp i18n make-pot foo-theme` + Then STDOUT should be: + """ + Success: POT file successfully generated! + """ + And the foo-theme/foo-theme.pot file should exist + And the foo-theme/foo-theme.pot file should contain: + """ + msgctxt "Duotone name" + msgid "Dark grayscale" + """ + And the foo-theme/foo-theme.pot file should contain: + """ + msgctxt "Gradient name" + msgid "Purple to yellow" + """ + And the foo-theme/foo-theme.pot file should contain: + """ + msgctxt "Color name" + msgid "White" + """ + And the foo-theme/foo-theme.pot file should contain: + """ + msgctxt "Color name" + msgid "White" + """ + And the foo-theme/foo-theme.pot file should contain: + """ + msgctxt "Font size name" + msgid "Small" + """ + + Scenario: Extract strings from the blocks section of theme.json files + Given an empty foo-theme directory + And a foo-theme/theme.json file: + """ + { + "version": "1", + "settings": { + "blocks": { + "core/paragraph": { + "color": { + "palette": [ + { "slug": "black", "color": "#000000", "name": "Black" } + ] + } + } } } } @@ -3184,7 +3248,7 @@ Scenario: Skips theme.json file if skip-theme-json flag provided msgid "Black" """ - Scenario: Extract strings from theme.json files + Scenario: Extract strings from the top-level section of theme.json files Given an empty foo-theme directory And a foo-theme/theme.json file: """ @@ -3210,4 +3274,4 @@ Scenario: Skips theme.json file if skip-theme-json flag provided """ msgctxt "Color name" msgid "Black" - """ \ No newline at end of file + """ From 183faa0c771a375ebe48eb41444f6024abaab053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Tue, 22 Jun 2021 17:37:08 +0200 Subject: [PATCH 08/22] Add PHPDoc --- src/ThemeJsonExtractor.php | 70 +++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index cbdbab78..d2bf76be 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -86,6 +86,17 @@ public static function fromString( $string, Translations $translations, array $o } } + /** + * Given a file path, reads it as a JSON file + * and returns an array with its contents. + * + * Returns an empty array in case of error. + * + * Ported from the core class `WP_Theme_JSON_Resolver`. + * + * @param string $file_path Path to file. + * @return array Contents of the file. + */ private static function read_json_file( $file_path ) { $config = array(); if ( $file_path ) { @@ -108,12 +119,70 @@ private static function read_json_file( $file_path ) { return $config; } + /** + * Returns a data structure to help setting up translations for theme.json data. + * + * array( + * array( + * 'path' => array( 'settings', 'color', 'palette' ), + * 'key' => 'key-that-stores-the-string-to-translate', + * 'context' => 'translation-context', + * ), + * array( + * 'path' => 'etc', + * 'key' => 'etc', + * 'context' => 'etc', + * ), + * ) + * + * Ported from the core class `WP_Theme_JSON_Resolver`. + * + * @return array An array of theme.json fields that are translatable and the keys that are translatable. + */ private static function get_fields_to_translate() { $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); return $theme_json_i18n; } + /** + * Converts a tree as in theme-i18.json file a linear array + * containing metadata to translate a theme.json file. + * + * For example, given this input: + * + * { + * "settings": { + * "*": { + * "typography": { + * "fontSizes": [ { "name": "Font size name" } ], + * "fontStyles": [ { "name": "Font size name" } ] + * } + * } + * } + * } + * + * will return this output: + * + * array( + * 0 => array( + * 'path' => array( 'settings', '*', 'typography', 'fontSizes' ), + * 'key' => 'name', + * 'context' => 'Font size name' + * ), + * 1 => array( + * 'path' => array( 'settings', '*', 'typography', 'fontStyles' ), + * 'key' => 'name', + * 'context' => 'Font style name' + * ) + * ) + * + * Ported from the core class `WP_Theme_JSON_Resolver`. + * + * @param array $i18n_partial A tree that follows the format of theme-i18n.json. + * @param array $current_path Keeps track of the path as we walk down the given tree. + * @return array A linear array containing the paths to translate. + */ private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) { $result = array(); foreach ( $i18n_partial as $property => $partial_child ) { @@ -157,7 +226,6 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path * @param array $path An array of keys describing the path with which to retrieve information. * @param mixed $default The return value if the path does not exist within the array, * or if `$array` or `$path` are not arrays. - * * @return mixed The value from the path specified. */ private function array_get( $array, $path, $default = null ) { From 3689a1e63d2aa8a4aaefe072b43ac129e9a1b2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Tue, 22 Jun 2021 18:19:07 +0200 Subject: [PATCH 09/22] Make array_get static --- src/ThemeJsonExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index d2bf76be..9c368eae 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -228,7 +228,7 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path * or if `$array` or `$path` are not arrays. * @return mixed The value from the path specified. */ - private function array_get( $array, $path, $default = null ) { + private static function array_get( $array, $path, $default = null ) { // Confirm $path is valid. if ( ! is_array( $path ) || 0 === count( $path ) ) { return $default; From 145db7459ba9f28332aa82b7c61dc1296754bcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Wed, 30 Jun 2021 14:01:13 +0200 Subject: [PATCH 10/22] Pull the theme-i18n.json file from elsewhere --- src/ThemeJsonExtractor.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 9c368eae..facdc372 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -95,13 +95,15 @@ public static function fromString( $string, Translations $translations, array $o * Ported from the core class `WP_Theme_JSON_Resolver`. * * @param string $file_path Path to file. + * @param array $context A valid context resource created with stream_context_create(). Optional. + * * @return array Contents of the file. */ - private static function read_json_file( $file_path ) { + private static function read_json_file( $file_path, $context = null ) { $config = array(); if ( $file_path ) { $decoded_file = json_decode( - file_get_contents( $file_path ), + file_get_contents( $file_path, false, $context ), true ); @@ -140,7 +142,20 @@ private static function read_json_file( $file_path ) { * @return array An array of theme.json fields that are translatable and the keys that are translatable. */ private static function get_fields_to_translate() { - $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + $context = stream_context_create( array( + 'http' => array( + 'method' => 'GET', + 'header' => 'Content-type: application/json', + 'timeout' => '3', // To make sure it resolves in a reasonable timeframe. + ) + )); + // Using the WordPress.org SVN repo resolved to a 403. + // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); + $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/gutenberg/wp/trunk/lib/experimental-i18n-theme.json', $context ); + if ( empty( $theme_json_i18n ) ) { + WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); + $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + } $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); return $theme_json_i18n; } From f57712de128e5469260e248d283064a4e27bcde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Wed, 30 Jun 2021 17:32:04 +0200 Subject: [PATCH 11/22] Use URI from wordpress-develop --- src/ThemeJsonExtractor.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index facdc372..161bb7bd 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -151,11 +151,11 @@ private static function get_fields_to_translate() { )); // Using the WordPress.org SVN repo resolved to a 403. // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); - $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/gutenberg/wp/trunk/lib/experimental-i18n-theme.json', $context ); - if ( empty( $theme_json_i18n ) ) { - WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); - $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); - } + $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/5.8/src/wp-includes/theme-i18n.json', $context ); + // if ( empty( $theme_json_i18n ) ) { + // WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); + // $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + // } $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); return $theme_json_i18n; } From 37bf31ae0b04b147636a71f464e1d77346d54d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 1 Jul 2021 16:54:39 +0200 Subject: [PATCH 12/22] Use master from wordpress-develop --- src/ThemeJsonExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 161bb7bd..7ea05b85 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -151,7 +151,7 @@ private static function get_fields_to_translate() { )); // Using the WordPress.org SVN repo resolved to a 403. // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); - $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/5.8/src/wp-includes/theme-i18n.json', $context ); + $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/master/src/wp-includes/theme-i18n.json', $context ); // if ( empty( $theme_json_i18n ) ) { // WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); // $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); From f21497a44c2f330f8b6132289e5a363e36fa6122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 1 Jul 2021 17:32:46 +0200 Subject: [PATCH 13/22] Should fall back to local if remote is empty --- src/ThemeJsonExtractor.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 7ea05b85..908ab17c 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -152,10 +152,10 @@ private static function get_fields_to_translate() { // Using the WordPress.org SVN repo resolved to a 403. // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/master/src/wp-includes/theme-i18n.json', $context ); - // if ( empty( $theme_json_i18n ) ) { - // WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); - // $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); - // } + if ( empty( $file_structure ) ) { + WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); + $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + } $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); return $theme_json_i18n; } From 68949cbf29fc54abc5a95d13e6435d00727dc9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 1 Jul 2021 17:33:57 +0200 Subject: [PATCH 14/22] Fix lint issues --- src/ThemeJsonExtractor.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 908ab17c..ac320e39 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -142,18 +142,20 @@ private static function read_json_file( $file_path, $context = null ) { * @return array An array of theme.json fields that are translatable and the keys that are translatable. */ private static function get_fields_to_translate() { - $context = stream_context_create( array( - 'http' => array( - 'method' => 'GET', - 'header' => 'Content-type: application/json', - 'timeout' => '3', // To make sure it resolves in a reasonable timeframe. + $context = stream_context_create( + array( + 'http' => array( + 'method' => 'GET', + 'header' => 'Content-type: application/json', + 'timeout' => '3', // To make sure it resolves in a reasonable timeframe. + ), ) - )); + ); // Using the WordPress.org SVN repo resolved to a 403. // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/master/src/wp-includes/theme-i18n.json', $context ); if ( empty( $file_structure ) ) { - WP_CLI::debug( "Remote file could not be accessed, will use local file as fallback" ); + WP_CLI::debug( 'Remote file could not be accessed, will use local file as fallback' ); $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); } $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); From 1c4bf87e51d32bef2b45951cc78d0524f62dc651 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 16:06:28 +0200 Subject: [PATCH 15/22] Clean up loop --- src/ThemeJsonExtractor.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index ac320e39..f79882ab 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -53,33 +53,33 @@ public static function fromString( $string, Translations $translations, array $o $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); $data_path = array_slice( $path, $nodes_to_iterate[0] + 1 ); $base_tree = self::array_get( $theme_json, $base_path, array() ); - foreach ( $base_tree as $node_name => $node_data ) { - $array_to_translate = self::array_get( $node_data, $data_path, null ); + foreach ( $base_tree as $node_data ) { + $array_to_translate = self::array_get( $node_data, $data_path ); if ( is_null( $array_to_translate ) ) { continue; } - foreach ( $array_to_translate as $item_key => $item_to_translate ) { + foreach ( $array_to_translate as $item_to_translate ) { if ( empty( $item_to_translate[ $key ] ) ) { continue; } - $translation = $translations->insert( $context, $array_to_translate[ $item_key ][ $key ] ); + $translation = $translations->insert( $context, $item_to_translate[ $key ] ); $translation->addReference( $file ); } } } else { - $array_to_translate = self::array_get( $theme_json, $path, null ); + $array_to_translate = self::array_get( $theme_json, $path ); if ( is_null( $array_to_translate ) ) { continue; } - foreach ( $array_to_translate as $item_key => $item_to_translate ) { + foreach ( $array_to_translate as $item_to_translate ) { if ( empty( $item_to_translate[ $key ] ) ) { continue; } - $translation = $translations->insert( $context, $array_to_translate[ $item_key ][ $key ] ); + $translation = $translations->insert( $context, $item_to_translate[ $key ] ); $translation->addReference( $file ); } } From 50451e57adfbc9e39244aa4d961298b6528cc6fb Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 16:37:03 +0200 Subject: [PATCH 16/22] Use short array notation --- src/ThemeJsonExtractor.php | 70 +++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index f79882ab..90f593f5 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -52,7 +52,7 @@ public static function fromString( $string, Translations $translations, array $o */ $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); $data_path = array_slice( $path, $nodes_to_iterate[0] + 1 ); - $base_tree = self::array_get( $theme_json, $base_path, array() ); + $base_tree = self::array_get( $theme_json, $base_path, [] ); foreach ( $base_tree as $node_data ) { $array_to_translate = self::array_get( $node_data, $data_path ); if ( is_null( $array_to_translate ) ) { @@ -100,7 +100,7 @@ public static function fromString( $string, Translations $translations, array $o * @return array Contents of the file. */ private static function read_json_file( $file_path, $context = null ) { - $config = array(); + $config = []; if ( $file_path ) { $decoded_file = json_decode( file_get_contents( $file_path, false, $context ), @@ -124,18 +124,18 @@ private static function read_json_file( $file_path, $context = null ) { /** * Returns a data structure to help setting up translations for theme.json data. * - * array( - * array( - * 'path' => array( 'settings', 'color', 'palette' ), + * [ + * [ + * 'path' => [ 'settings', 'color', 'palette' ], * 'key' => 'key-that-stores-the-string-to-translate', * 'context' => 'translation-context', - * ), - * array( + * ], + * [ * 'path' => 'etc', * 'key' => 'etc', * 'context' => 'etc', - * ), - * ) + * ], + * ] * * Ported from the core class `WP_Theme_JSON_Resolver`. * @@ -143,13 +143,13 @@ private static function read_json_file( $file_path, $context = null ) { */ private static function get_fields_to_translate() { $context = stream_context_create( - array( - 'http' => array( + [ + 'http' => [ 'method' => 'GET', 'header' => 'Content-type: application/json', 'timeout' => '3', // To make sure it resolves in a reasonable timeframe. - ), - ) + ], + ] ); // Using the WordPress.org SVN repo resolved to a 403. // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); @@ -181,18 +181,18 @@ private static function get_fields_to_translate() { * * will return this output: * - * array( - * 0 => array( - * 'path' => array( 'settings', '*', 'typography', 'fontSizes' ), + * [ + * 0 => [ + * 'path' => [ 'settings', '*', 'typography', 'fontSizes' ], * 'key' => 'name', * 'context' => 'Font size name' - * ), - * 1 => array( - * 'path' => array( 'settings', '*', 'typography', 'fontStyles' ), + * ], + * 1 => [ + * 'path' => [ 'settings', '*', 'typography', 'fontStyles' ], * 'key' => 'name', * 'context' => 'Font style name' - * ) - * ) + * ] + * ] * * Ported from the core class `WP_Theme_JSON_Resolver`. * @@ -200,23 +200,23 @@ private static function get_fields_to_translate() { * @param array $current_path Keeps track of the path as we walk down the given tree. * @return array A linear array containing the paths to translate. */ - private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) { - $result = array(); + private static function extract_paths_to_translate( $i18n_partial, $current_path = [] ) { + $result = []; foreach ( $i18n_partial as $property => $partial_child ) { if ( is_numeric( $property ) ) { foreach ( $partial_child as $key => $context ) { - return array( - array( + return [ + [ 'path' => $current_path, 'key' => $key, 'context' => $context, - ), - ); + ], + ]; } } $result = array_merge( $result, - self::extract_paths_to_translate( $partial_child, array_merge( $current_path, array( $property ) ) ) + self::extract_paths_to_translate( $partial_child, array_merge( $current_path, [ $property ] ) ) ); } return $result; @@ -230,14 +230,14 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path * * Example usage: * - * $array = array( - * 'a' => array( - * 'b' => array( + * $array = [ + * 'a' => [ + * 'b' => [ * 'c' => 1, - * ), - * ), - * ); - * array_get( $array, array( 'a', 'b', 'c' ) ); + * ], + * ], + * ]; + * array_get( $array, [ 'a', 'b', 'c' ] ); * * @param array $array An array from which we want to retrieve some information. * @param array $path An array of keys describing the path with which to retrieve information. From 207df59aa638e2183d69d95118244979932a9202 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 16:39:11 +0200 Subject: [PATCH 17/22] Replace is_null() with null === --- src/ThemeJsonExtractor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 90f593f5..e9139304 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -55,7 +55,7 @@ public static function fromString( $string, Translations $translations, array $o $base_tree = self::array_get( $theme_json, $base_path, [] ); foreach ( $base_tree as $node_data ) { $array_to_translate = self::array_get( $node_data, $data_path ); - if ( is_null( $array_to_translate ) ) { + if ( null === $array_to_translate ) { continue; } @@ -70,7 +70,7 @@ public static function fromString( $string, Translations $translations, array $o } } else { $array_to_translate = self::array_get( $theme_json, $path ); - if ( is_null( $array_to_translate ) ) { + if ( null === $array_to_translate ) { continue; } @@ -254,7 +254,7 @@ private static function array_get( $array, $path, $default = null ) { foreach ( $path as $path_element ) { if ( ! is_array( $array ) || - ( ! is_string( $path_element ) && ! is_integer( $path_element ) && ! is_null( $path_element ) ) || + ( ! is_string( $path_element ) && ! is_integer( $path_element ) && null !== $path_element ) || ! array_key_exists( $path_element, $array ) ) { return $default; From 9f2a19babb5ec721cebe2af965d44c892d081dc0 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 16:45:41 +0200 Subject: [PATCH 18/22] Remove unneeded variable --- src/ThemeJsonExtractor.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index e9139304..9bff7e24 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -158,12 +158,11 @@ private static function get_fields_to_translate() { WP_CLI::debug( 'Remote file could not be accessed, will use local file as fallback' ); $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); } - $theme_json_i18n = self::extract_paths_to_translate( $file_structure ); - return $theme_json_i18n; + return self::extract_paths_to_translate( $file_structure ); } /** - * Converts a tree as in theme-i18.json file a linear array + * Converts a tree as in theme-i18.json file into a linear array * containing metadata to translate a theme.json file. * * For example, given this input: From df97093ed3fafe2dd651d9226cf2917b0373f37c Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 16:54:38 +0200 Subject: [PATCH 19/22] Fix rebase conflict in feature file --- features/makepot.feature | 59 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/features/makepot.feature b/features/makepot.feature index f1df0ae8..936196b3 100644 --- a/features/makepot.feature +++ b/features/makepot.feature @@ -3156,7 +3156,36 @@ Feature: Generate a POT file of a WordPress project msgid "Notice" """ -Scenario: Skips theme.json file if skip-theme-json flag provided + + Scenario: Skips theme.json file if skip-theme-json flag provided + Given an empty foo-theme directory + And a foo-theme/theme.json file: + """ + { + "version": "1", + "settings": { + "color": { + "palette": [ + { "slug": "black", "color": "#000000", "name": "Black" } + ] + } + } + } + """ + + When I try `wp i18n make-pot foo-theme --skip-theme-json` + Then STDOUT should be: + """ + Success: POT file successfully generated! + """ + And the foo-theme/foo-theme.pot file should exist + But the foo-theme/foo-theme.pot file should not contain: + """ + msgctxt "Color name" + msgid "Black" + """ + + Scenario: Extract strings from the top-level section of theme.json files Given an empty foo-theme directory And a foo-theme/theme.json file: """ @@ -3236,34 +3265,6 @@ Scenario: Skips theme.json file if skip-theme-json flag provided } """ - When I try `wp i18n make-pot foo-theme --skip-theme-json` - Then STDOUT should be: - """ - Success: POT file successfully generated! - """ - And the foo-theme/foo-theme.pot file should exist - But the foo-theme/foo-theme.pot file should not contain: - """ - msgctxt "Color name" - msgid "Black" - """ - - Scenario: Extract strings from the top-level section of theme.json files - Given an empty foo-theme directory - And a foo-theme/theme.json file: - """ - { - "version": "1", - "settings": { - "color": { - "palette": [ - { "slug": "black", "color": "#000000", "name": "Black" } - ] - } - } - } - """ - When I try `wp i18n make-pot foo-theme` Then STDOUT should be: """ From ccea6c371964920bf2fb74cfe7e814b83ba1d5e8 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 17:39:32 +0200 Subject: [PATCH 20/22] Fix broken loop in path extraction --- src/ThemeJsonExtractor.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 9bff7e24..56406753 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -204,14 +204,13 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path foreach ( $i18n_partial as $property => $partial_child ) { if ( is_numeric( $property ) ) { foreach ( $partial_child as $key => $context ) { - return [ - [ - 'path' => $current_path, - 'key' => $key, - 'context' => $context, - ], + $result[] = [ + 'path' => $current_path, + 'key' => $key, + 'context' => $context, ]; } + return $result; } $result = array_merge( $result, From f286473474ef80c4ddf6a8b8495b224d29ee5781 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 17:42:46 +0200 Subject: [PATCH 21/22] Move fallback file --- {src => assets}/theme-i18n.json | 0 src/ThemeJsonExtractor.php | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename {src => assets}/theme-i18n.json (100%) diff --git a/src/theme-i18n.json b/assets/theme-i18n.json similarity index 100% rename from src/theme-i18n.json rename to assets/theme-i18n.json diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 56406753..8091a52c 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -155,8 +155,8 @@ private static function get_fields_to_translate() { // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/master/src/wp-includes/theme-i18n.json', $context ); if ( empty( $file_structure ) ) { - WP_CLI::debug( 'Remote file could not be accessed, will use local file as fallback' ); - $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + WP_CLI::debug( 'Remote file could not be accessed, will use local file as fallback', 'make-pot' ); + $file_structure = self::read_json_file( __DIR__ . '/../assets/theme-i18n.json' ); } return self::extract_paths_to_translate( $file_structure ); } From b3bede04d41c8a36331e6f3dc9b7b5453452e73c Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 19 Jul 2021 18:07:23 +0200 Subject: [PATCH 22/22] Use Utils\http_request for remote request --- src/ThemeJsonExtractor.php | 83 ++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/ThemeJsonExtractor.php b/src/ThemeJsonExtractor.php index 8091a52c..b36ec325 100644 --- a/src/ThemeJsonExtractor.php +++ b/src/ThemeJsonExtractor.php @@ -6,10 +6,18 @@ use Gettext\Extractors\ExtractorInterface; use Gettext\Translations; use WP_CLI; +use WP_CLI\Utils; final class ThemeJsonExtractor extends Extractor implements ExtractorInterface { use IterableCodeExtractor; + /** + * Source URL from which to download the latest theme-i18n.json file. + * + * @var string + */ + const THEME_JSON_SOURCE = 'https://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json'; + /** * @inheritdoc */ @@ -87,38 +95,33 @@ public static function fromString( $string, Translations $translations, array $o } /** - * Given a file path, reads it as a JSON file - * and returns an array with its contents. - * - * Returns an empty array in case of error. + * Given a remote URL, fetches it remotely and returns its content. * - * Ported from the core class `WP_Theme_JSON_Resolver`. + * Returns an empty string in case of error. * - * @param string $file_path Path to file. - * @param array $context A valid context resource created with stream_context_create(). Optional. + * @param string $url URL of the file to fetch. * - * @return array Contents of the file. + * @return string Contents of the file. */ - private static function read_json_file( $file_path, $context = null ) { - $config = []; - if ( $file_path ) { - $decoded_file = json_decode( - file_get_contents( $file_path, false, $context ), - true - ); - - $json_decoding_error = json_last_error(); - if ( JSON_ERROR_NONE !== $json_decoding_error ) { - WP_CLI::debug( "Error when decoding {$file_path}", 'make-pot' ); + private static function remote_get( $url ) { + if ( ! $url ) { + return ''; + } - return $config; - } + $headers = [ 'Content-type: application/json' ]; + $options = [ 'halt_on_error' => false ]; + $response = Utils\http_request( 'GET', $url, null, $headers, $options ); - if ( is_array( $decoded_file ) ) { - $config = $decoded_file; - } + if ( + ! $response->success + || 200 > (int) $response->status_code + || 300 <= $response->status_code + ) { + WP_CLI::debug( "Failed to download from URL {$url}", 'make-pot' ); + return ''; } - return $config; + + return trim( $response->body ); } /** @@ -142,22 +145,24 @@ private static function read_json_file( $file_path, $context = null ) { * @return array An array of theme.json fields that are translatable and the keys that are translatable. */ private static function get_fields_to_translate() { - $context = stream_context_create( - [ - 'http' => [ - 'method' => 'GET', - 'header' => 'Content-type: application/json', - 'timeout' => '3', // To make sure it resolves in a reasonable timeframe. - ], - ] - ); - // Using the WordPress.org SVN repo resolved to a 403. - // $file_structure = self::read_json_file( 'http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json', $context ); - $file_structure = self::read_json_file( 'https://raw.githubusercontent.com/WordPress/wordpress-develop/master/src/wp-includes/theme-i18n.json', $context ); - if ( empty( $file_structure ) ) { + $json = self::remote_get( self::THEME_JSON_SOURCE ); + + if ( empty( $json ) ) { WP_CLI::debug( 'Remote file could not be accessed, will use local file as fallback', 'make-pot' ); - $file_structure = self::read_json_file( __DIR__ . '/../assets/theme-i18n.json' ); + $json = file_get_contents( __DIR__ . '/../assets/theme-i18n.json' ); } + + $file_structure = json_decode( $json, true ); + + if ( JSON_ERROR_NONE !== json_last_error() ) { + WP_CLI::debug( 'Error when decoding theme-i18n.json file', 'make-pot' ); + return []; + } + + if ( ! is_array( $file_structure ) ) { + return []; + } + return self::extract_paths_to_translate( $file_structure ); }