From 09cb911ec68434ab09032b95676d8d4656ad47c9 Mon Sep 17 00:00:00 2001 From: Nicholas Pagazani Date: Wed, 26 Jul 2023 22:42:20 -0400 Subject: [PATCH 1/4] Add FSE Starter Themes Command --- load-application.php | 1 + src/commands/fse-starter-themes.php | 90 +++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/commands/fse-starter-themes.php diff --git a/load-application.php b/load-application.php index e2edc680..88dbb369 100644 --- a/load-application.php +++ b/load-application.php @@ -50,6 +50,7 @@ new Team51\Command\WPCOM_Get_Stickers(), new Team51\Command\WPCOM_Add_Sticker(), new Team51\Command\WPCOM_Remove_Sticker(), + new Team51\Command\FSE_Starter_Themes(), ) ); diff --git a/src/commands/fse-starter-themes.php b/src/commands/fse-starter-themes.php new file mode 100644 index 00000000..54f7d992 --- /dev/null +++ b/src/commands/fse-starter-themes.php @@ -0,0 +1,90 @@ +setDescription( 'Produce a list of Team 51 recommended FSE starter themes.' ) + ->setHelp( 'This command produces a list of acceptable FSE themes to be used as a starter themes for no-code theme builds.' ); + } + + /** + * {@inheritDoc} + */ + protected function execute( InputInterface $input, OutputInterface $output ): int { + $owner = 'Automattic'; + $repository = 'themes'; + + $themes = GitHub_API_Helper::call_api( sprintf( 'repos/%s/%s/contents', $owner, $repository ) ); + + if ( is_null( $themes ) ) { + $output->writeln( + '❌ Failed to retrieve themes', + OutputInterface::VERBOSITY_QUIET + ); + return null; + } + + foreach ( $themes as $theme ) { + if ( 'dir' === $theme->type ) { + $theme_files = GitHub_API_Helper::call_api( sprintf( 'repos/%s/%s/contents/%s', $owner, $repository, $theme->name ) ); + + if ( is_null( $theme_files ) ) { + $output->writeln( + '❌ Failed to retrieve theme contents', + OutputInterface::VERBOSITY_QUIET + ); + continue; + } + + $theme_json_exists = false; + $inc_patterns_exists = false; + $empty_template_value = true; + foreach ( $theme_files as $file ) { + if ( 'theme.json' === $file->name ) { + $theme_json_exists = true; + } + if ( $file->path === $theme->name . '/inc/patterns' ) { + $inc_patterns_exists = true; + } + if ( 'style.css' === $file->name ) { + $css_content = GitHub_API_Helper::call_api( $file->url, 'GET' ); + var_dump( $css_content ); + + if ( preg_match( '/Template:\s*(.*)/i', $css_content, $matches ) ) { + $empty_template_value = trim( $matches[1] ) === ''; + } + } + } + + if ( $theme_json_exists && ! $inc_patterns_exists && $empty_template_value ) { + $output->writeln( $theme->name, OutputInterface::VERBOSITY_NORMAL ); + } + } + } + return 0; + } + +} From 97f2f126fafc27d31740ec57f9f56a4477fc59bb Mon Sep 17 00:00:00 2001 From: Antonius Hegyes Date: Thu, 27 Jul 2023 09:24:12 +0200 Subject: [PATCH 2/4] Fix method return type (must be int) --- src/commands/fse-starter-themes.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/commands/fse-starter-themes.php b/src/commands/fse-starter-themes.php index 54f7d992..75b6545f 100644 --- a/src/commands/fse-starter-themes.php +++ b/src/commands/fse-starter-themes.php @@ -38,13 +38,9 @@ protected function execute( InputInterface $input, OutputInterface $output ): in $repository = 'themes'; $themes = GitHub_API_Helper::call_api( sprintf( 'repos/%s/%s/contents', $owner, $repository ) ); - if ( is_null( $themes ) ) { - $output->writeln( - '❌ Failed to retrieve themes', - OutputInterface::VERBOSITY_QUIET - ); - return null; + $output->writeln( '❌ Failed to retrieve themes', OutputInterface::VERBOSITY_QUIET ); + return 1; } foreach ( $themes as $theme ) { @@ -70,6 +66,7 @@ protected function execute( InputInterface $input, OutputInterface $output ): in $inc_patterns_exists = true; } if ( 'style.css' === $file->name ) { + var_dump( $file->url ); $css_content = GitHub_API_Helper::call_api( $file->url, 'GET' ); var_dump( $css_content ); From 01672eb9c2fd8058ee8d04c13cf272632dbfef4d Mon Sep 17 00:00:00 2001 From: Nicholas Pagazani Date: Thu, 27 Jul 2023 22:03:42 -0400 Subject: [PATCH 3/4] Fix file content API call --- src/commands/fse-starter-themes.php | 59 +++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/commands/fse-starter-themes.php b/src/commands/fse-starter-themes.php index 75b6545f..6d1f2e54 100644 --- a/src/commands/fse-starter-themes.php +++ b/src/commands/fse-starter-themes.php @@ -66,9 +66,23 @@ protected function execute( InputInterface $input, OutputInterface $output ): in $inc_patterns_exists = true; } if ( 'style.css' === $file->name ) { - var_dump( $file->url ); - $css_content = GitHub_API_Helper::call_api( $file->url, 'GET' ); - var_dump( $css_content ); + $css_content_response = $this->call_github_api( $file->url ); + + if ( isset( $css_content_response->content ) ) { + $css_content = base64_decode( $css_content_response->content ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + } + + if ( isset( $css_content ) ) { + // Match "Theme Name:" + if ( preg_match( '/Theme Name:\s*(.*)/i', $css_content, $matches ) ) { + $theme_name = trim( $matches[1] ); + } + + // Match "Theme URI:" + if ( preg_match( '/Theme URI:\s*(.*)/i', $css_content, $matches ) ) { + $theme_uri = trim( $matches[1] ); + } + } if ( preg_match( '/Template:\s*(.*)/i', $css_content, $matches ) ) { $empty_template_value = trim( $matches[1] ) === ''; @@ -78,10 +92,49 @@ protected function execute( InputInterface $input, OutputInterface $output ): in if ( $theme_json_exists && ! $inc_patterns_exists && $empty_template_value ) { $output->writeln( $theme->name, OutputInterface::VERBOSITY_NORMAL ); + $output->writeln( $theme_uri, OutputInterface::VERBOSITY_NORMAL ); + $output->writeln( $theme_name, OutputInterface::VERBOSITY_NORMAL ); } } } return 0; } + protected function call_github_api( string $url ): ?object { + $ch = curl_init(); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init + + curl_setopt_array( // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt_array + $ch, + array( + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => array( + 'Accept: application/vnd.github+json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . GITHUB_API_TOKEN, + 'X-GitHub-Api-Version: 2022-11-28', + 'User-Agent: PHP', + ), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => false, + CURLOPT_FAILONERROR => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + ) + ); + + $response = curl_exec( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec + + if ( curl_errno( $ch ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_errno + echo 'Error:' . curl_error( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl, WordPress.Security.EscapeOutput.OutputNotEscaped_curl_error + } + + curl_close( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close + + return json_decode( $response ) ? json_decode( $response ) : null; + } + } From 9cb4f904003b7a1d404e13790ac341cca9e806f5 Mon Sep 17 00:00:00 2001 From: Nicholas Pagazani Date: Wed, 9 Aug 2023 14:34:48 -0400 Subject: [PATCH 4/4] Improve uri matching, add results to a table --- src/commands/fse-starter-themes.php | 53 ++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/commands/fse-starter-themes.php b/src/commands/fse-starter-themes.php index 6d1f2e54..b0db9001 100644 --- a/src/commands/fse-starter-themes.php +++ b/src/commands/fse-starter-themes.php @@ -5,6 +5,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Helper\Table; use function Team51\Helper\get_github_repository; use Team51\Helper\GitHub_API_Helper; use function Team51\Helper\maybe_define_console_verbosity; @@ -36,11 +37,14 @@ protected function configure(): void { protected function execute( InputInterface $input, OutputInterface $output ): int { $owner = 'Automattic'; $repository = 'themes'; + $theme_list = []; $themes = GitHub_API_Helper::call_api( sprintf( 'repos/%s/%s/contents', $owner, $repository ) ); if ( is_null( $themes ) ) { $output->writeln( '❌ Failed to retrieve themes', OutputInterface::VERBOSITY_QUIET ); return 1; + } else { + $output->writeln( '✅ Retrieving themes...', OutputInterface::VERBOSITY_QUIET ); } foreach ( $themes as $theme ) { @@ -79,8 +83,12 @@ protected function execute( InputInterface $input, OutputInterface $output ): in } // Match "Theme URI:" - if ( preg_match( '/Theme URI:\s*(.*)/i', $css_content, $matches ) ) { - $theme_uri = trim( $matches[1] ); + $theme_uri = "https://github.com/Automattic/themes/tree/trunk/" . $theme->name; + if (preg_match('/Theme URI:([^\n]*\S)/i', $css_content, $matches)) { + $found_uri = trim($matches[1]); + if (!empty($found_uri)) { + $theme_uri = $found_uri; + } } } @@ -91,15 +99,50 @@ protected function execute( InputInterface $input, OutputInterface $output ): in } if ( $theme_json_exists && ! $inc_patterns_exists && $empty_template_value ) { - $output->writeln( $theme->name, OutputInterface::VERBOSITY_NORMAL ); - $output->writeln( $theme_uri, OutputInterface::VERBOSITY_NORMAL ); - $output->writeln( $theme_name, OutputInterface::VERBOSITY_NORMAL ); + $theme_list[] = array( + 'name' => $theme->name, + 'uri' => $theme_uri, + 'title' => $theme_name, + ); } } } + + $output->writeln( 'Found ' . count( $theme_list ) . ' compatible starter themes.', OutputInterface::VERBOSITY_VERBOSE ); + $this->output_theme_list( $theme_list, $output ); + return 0; } + /** + * Outputs in tabular form the list of WordPress 6.0+ themes. + * + * @param array $theme_list The list of sites with the plugin installed. + * @param OutputInterface $output The output object. + * + * @return void + */ + protected function output_theme_list( array $theme_list, OutputInterface $output ): void { + $table = new Table( $output ); + + $table->setHeaderTitle( 'FSE Starter Themes' ); + $table->setHeaders( array( 'Theme Name', 'Slug', 'URL' ) ); + + foreach ( $theme_list as $theme ) { + $table->addRow( + array( + $theme['title'], + $theme['name'], + $theme['uri'], + ) + ); + } + + $table->setColumnMaxWidth( 0, 128 ); + $table->setStyle( 'box-double' ); + $table->render(); + } + protected function call_github_api( string $url ): ?object { $ch = curl_init(); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init