diff --git a/admin/class-create-theme.php b/admin/class-create-theme.php index 16cd3b2..fa3cc41 100644 --- a/admin/class-create-theme.php +++ b/admin/class-create-theme.php @@ -149,7 +149,7 @@ class Create_Block_Theme_Admin { // Add readme.txt. $zip->addFromStringToTheme( 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Augment style.css @@ -198,7 +198,6 @@ class Create_Block_Theme_Admin { $theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] ); $theme['slug'] = $theme_slug; $theme['template'] = ''; - $theme['original_theme'] = wp_get_theme()->get( 'Name' ); $theme['text_domain'] = $theme_slug; // Use previous theme's tags if custom tags are empty. @@ -218,7 +217,7 @@ class Create_Block_Theme_Admin { // Add readme.txt. $zip->addFromStringToTheme( 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Augment style.css @@ -267,7 +266,7 @@ class Create_Block_Theme_Admin { $theme['tags_custom'] = sanitize_text_field( $theme['tags_custom'] ); $theme['image_credits'] = sanitize_textarea_field( $theme['image_credits'] ); $theme['recommended_plugins'] = sanitize_textarea_field( $theme['recommended_plugins'] ); - $theme['is_parent_theme'] = true; + $theme['is_child_theme'] = true; $theme['text_domain'] = $child_theme_slug; $theme['template'] = $parent_theme_slug; $theme['slug'] = $child_theme_slug; @@ -282,7 +281,7 @@ class Create_Block_Theme_Admin { // Add readme.txt. $zip->addFromStringToTheme( 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Add style.css. @@ -357,7 +356,7 @@ class Create_Block_Theme_Admin { // Add readme.txt. file_put_contents( $blank_theme_path . DIRECTORY_SEPARATOR . 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Add new metadata. diff --git a/admin/create-theme/theme-create.php b/admin/create-theme/theme-create.php index 25401d3..d6715f6 100644 --- a/admin/create-theme/theme-create.php +++ b/admin/create-theme/theme-create.php @@ -7,9 +7,10 @@ class Theme_Create { ); public static function clone_current_theme( $theme ) { - - $theme['version'] = '1.0'; - $theme['tags_custom'] = implode( ', ', wp_get_theme()->get( 'Tags' ) ); + // Default values for cloned themes + $theme['is_cloned_theme'] = true; + $theme['version'] = '1.0'; + $theme['tags_custom'] = implode( ', ', wp_get_theme()->get( 'Tags' ) ); // Create theme directory. $new_theme_path = get_theme_root() . DIRECTORY_SEPARATOR . $theme['slug']; @@ -33,7 +34,11 @@ class Theme_Create { Theme_Utils::clone_theme_to_folder( $new_theme_path, $theme['slug'], $theme['name'] ); Theme_Templates::add_templates_to_local( 'all', $new_theme_path, $theme['slug'], $template_options ); file_put_contents( path_join( $new_theme_path, 'theme.json' ), MY_Theme_JSON_Resolver::export_theme_data( 'all' ) ); - file_put_contents( path_join( $new_theme_path, 'readme.txt' ), Theme_Readme::build_readme_txt( $theme ) ); + + // Create the text of readme.txt file and write it to the file. + $readme_content = Theme_Readme::create( $theme ); + file_put_contents( path_join( $new_theme_path, 'readme.txt' ), $readme_content ); + file_put_contents( path_join( $new_theme_path, 'style.css' ), Theme_Styles::update_style_css( file_get_contents( path_join( $new_theme_path, 'style.css' ) ), $theme ) ); if ( $theme['subfolder'] ) { @@ -61,7 +66,7 @@ class Theme_Create { // Add readme.txt. file_put_contents( $new_theme_path . DIRECTORY_SEPARATOR . 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Add style.css. @@ -109,7 +114,7 @@ class Theme_Create { // Add readme.txt. file_put_contents( $blank_theme_path . DIRECTORY_SEPARATOR . 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Add new metadata. diff --git a/admin/create-theme/theme-readme.php b/admin/create-theme/theme-readme.php index b6a6d9e..53b6e57 100644 --- a/admin/create-theme/theme-readme.php +++ b/admin/create-theme/theme-readme.php @@ -1,132 +1,145 @@ get( 'Name' ) ?? ''; $original_uri = wp_get_theme()->get( 'ThemeURI' ) ?? ''; $original_author = wp_get_theme()->get( 'Author' ) ?? ''; - $original_readme = get_stylesheet_directory() . '/readme.txt' ?? ''; - $original_license = ''; - $original_license_uri = ''; - $readme_content = file_exists( $original_readme ) ? file_get_contents( $original_readme ) : ''; + $original_license = self::get_prop( 'License' ); + $original_license_uri = self::get_prop( 'License URI' ); - if ( ! $readme_content ) { - return; - } - - // Get license from original theme readme.txt - if ( str_contains( $readme_content, 'License:' ) ) { - $starts = strpos( $readme_content, 'License:' ) + strlen( 'License:' ); - $ends = strpos( $readme_content, 'License URI:', $starts ); - $original_license = trim( substr( $readme_content, $starts, $ends - $starts ) ); - } - - // Get license URI from original theme readme.txt - if ( str_contains( $readme_content, 'License URI:' ) ) { - $starts = strpos( $readme_content, 'License URI:' ) + strlen( 'License URI:' ); - $ends = strpos( $readme_content, '== Description ==', $starts ); - $original_license_uri = trim( substr( $readme_content, $starts, $ends - $starts ) ); - } - - if ( empty( $original_license ) || empty( $original_license_uri ) ) { - return; - } - - $theme_credit_content = sprintf( - /* translators: 1: New Theme name, 2: Original Theme Name. 3. Original Theme URI. 4. Original Theme Author. 5. Original Theme License. 6. Original Theme License URI. */ - __( '%1$s is based on %2$s (%3$s), (C) %4$s, [%5$s](%6$s)', 'create-block-theme' ), - $new_name, - $original_name, - $original_uri, - $original_author, - $original_license, - $original_license_uri + return array( + 'name' => $original_name, + 'uri' => $original_uri, + 'author' => $original_author, + 'license' => $original_license, + 'license_uri' => $original_license_uri, ); - - if ( $is_parent_theme ) { - $theme_credit_content = sprintf( - /* translators: 1: New Theme name, 2: Parent Theme Name. 3. Parent Theme URI. 4. Parent Theme Author. 5. Parent Theme License. 6. Parent Theme License URI. */ - __( '%1$s is a child theme of %2$s (%3$s), (C) %4$s, [%5$s](%6$s)', 'create-block-theme' ), - $new_name, - $original_name, - $original_uri, - $original_author, - $original_license, - $original_license_uri - ); - } - - return $theme_credit_content; } /** - * Build copyright section. - * Used in readme.txt of cloned themes or child themes. + * Build default copyright text for a theme. * - * @return string + * @param string $name The theme name. + * @param string $copy_year The current year. + * @param string $author The theme author. + * @return string The default copyright text. */ - static function copyright_section( $new_copyright_section, $original_theme_credits, $name, $copy_year, $author, $image_credits ) { - // Default copyright section. - $copyright_section = "== Copyright == + private static function get_copyright_text( $theme ) { + $name = $theme['name']; + $year = $theme['copy_year'] ?? gmdate( 'Y' ); + $author = $theme['author'] ?? ''; -{$name} WordPress Theme, (C) {$copy_year} {$author} + $text = " +{$name} WordPress Theme, (C) {$year} {$author} {$name} is distributed under the terms of the GNU GPL. This program is free software: you can redistribute it and/or modify @@ -137,123 +150,184 @@ the Free Software Foundation, either version 2 of the License, or This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details."; +GNU General Public License for more details. +"; - // If a new copyright section is required, then build ones based on the current theme. - if ( $new_copyright_section ) { - $copyright_section_intro = '== Copyright =='; + $is_child_theme = $theme['is_child_theme'] ?? false; + $is_cloned_theme = $theme['is_cloned_theme'] ?? false; - // Get current theme readme.txt - $current_readme = get_stylesheet_directory() . '/readme.txt' ?? ''; - $current_readme_content = file_exists( $current_readme ) ? file_get_contents( $current_readme ) : ''; + /* + * If the theme is a child theme or a cloned theme, add a reference to the parent theme. + * + * Example: "My Child Theme is a child theme of My Parent Theme (https://example.org/themes/my-parent-theme), (C) the WordPress team, [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html)" + */ + if ( $is_child_theme || $is_cloned_theme ) { + $original_theme = self::get_active_theme_data(); - if ( ! $current_readme_content ) { - return; - } + $reference_string = $is_child_theme + ? '%1$s is a child theme of %2$s (%3$s), (C) %4$s, [%5$s](%6$s)' + : '%1$s is based on %2$s (%3$s), (C) %4$s, [%5$s](%6$s)'; - // Copy copyright section from current theme readme.txt - if ( str_contains( $current_readme_content, $copyright_section_intro ) ) { - $copyright_section_start = strpos( $current_readme_content, $copyright_section_intro ); - $copyright_section = substr( $current_readme_content, $copyright_section_start ); + $reference = sprintf( + $reference_string, + $name, + $original_theme['name'], + $original_theme['uri'], + $original_theme['author'], + $original_theme['license'], + $original_theme['license_uri'] + ); - if ( $original_theme_credits ) { - $new_copyright_section = str_replace( $copyright_section_intro . "\n", '', $copyright_section ); - $copyright_section = $copyright_section_intro . "\n\n" . $original_theme_credits . "\n" . $new_copyright_section; - } - } + $text .= "\n\n" . $reference; } - if ( $image_credits ) { - $copyright_section = $copyright_section . "\n" . $image_credits; - } - - return $copyright_section; - } - - /** - * Build Recommended Plugins section. - * - * @return string - */ - static function recommended_plugins_section( $recommended_plugins, $updated_readme = '' ) { - $recommended_plugins_section = ''; - - if ( ! $recommended_plugins ) { - return ''; - } - - $section_start = "\n== Recommended Plugins ==\n"; - - // Remove existing Recommended Plugins section. - if ( $updated_readme && str_contains( $updated_readme, $section_start ) ) { - $pattern = '/\s+== Recommended Plugins ==\s+(.*?)(?=(\n\=\=)|$)/s'; - preg_match_all( $pattern, $updated_readme, $matches ); - $current_section = $matches[0][0]; - $updated_readme = str_replace( $current_section, '', $updated_readme ); - } - - $recommended_plugins_section = $section_start . "\n" . $recommended_plugins . "\n"; - - if ( $updated_readme ) { - return $updated_readme . $recommended_plugins_section; - } - - return $recommended_plugins_section; + return $text; } /** * Update current readme.txt file, rather than building a new one. * + * @param array $theme The theme data. + * { + * @type string $description The theme description. + * @type string $author The theme author. + * @type string $image_credits The image credits. + * @type string $recommended_plugins The recommended plugins. + * } + * @param string $readme_content readme.txt content. * @return string */ - public static function update_readme_txt( $theme ) { - $description = $theme['description']; - $author = $theme['author']; - $wp_version = get_bloginfo( 'version' ); + public static function update( $theme, $readme_content = '' ) { + // Theme data. + $description = $theme['description'] ?? ''; + $author = $theme['author'] ?? ''; + $wp_version = $theme['wp_version'] ?? get_bloginfo( 'version' ); $image_credits = $theme['image_credits'] ?? ''; $recommended_plugins = $theme['recommended_plugins'] ?? ''; - $updated_readme = ''; - $current_readme = get_stylesheet_directory() . '/readme.txt' ?? ''; - $readme_content = file_exists( $current_readme ) ? file_get_contents( $current_readme ) : ''; - - if ( ! $readme_content ) { - return; - } - - $updated_readme = $readme_content; // Update description. - if ( $description ) { - $pattern = '/(== Description ==)(.*?)(\n\n=|$)/s'; - preg_match_all( $pattern, $updated_readme, $matches ); - $current_description = $matches[0][0]; - $updated_readme = str_replace( $current_description, "== Description ==\n\n{$description}\n\n=", $updated_readme ); - } + $readme_content = self::add_or_update_section( 'Description', $description, $readme_content ); // Update Author/Contributors. - if ( $author ) { - $pattern = '/(Contributors:)(.*?)(\n|$)/s'; - preg_match_all( $pattern, $updated_readme, $matches ); - $current_uri = $matches[0][0]; - $updated_readme = str_replace( $current_uri, "Contributors: {$author}\n", $updated_readme ); - } + $readme_content = self::add_or_update_prop( 'Contributors', $author, $readme_content ); // Update "Tested up to" version. - if ( $wp_version ) { - $pattern = '/(Tested up to:)(.*?)(\n|$)/s'; - preg_match_all( $pattern, $updated_readme, $matches ); - $current_uri = $matches[0][0]; - $updated_readme = str_replace( $current_uri, "Tested up to: {$wp_version}\n", $updated_readme ); + $readme_content = self::add_or_update_prop( 'Tested up to', $wp_version, $readme_content ); + + // Update recommended plugins section. + $readme_content = self::add_or_update_section( 'Recommended Plugins', $recommended_plugins, $readme_content ); + + // Update image credits section. + $readme_content = self::add_or_update_section( 'Images', $image_credits, $readme_content ); + + return $readme_content; + } + + /** + * Write a section to the readme.txt file. + * + * @param string $section_title Section to write. + * @param string $section_content New content to write. + * @param string $current_content Current content to manipulate. + * + * @return void + */ + public static function add_or_update_section( $section_title, $section_content, $readme_content = '' ) { + // If the section content is empty, return the current content. This avoids adding empty sections. + if ( empty( $section_content ) ) { + return $readme_content; } - if ( $recommended_plugins ) { - $updated_readme = self::recommended_plugins_section( $recommended_plugins, $updated_readme ); + $section_content = trim( $section_content, "\r" ); + $section_content = trim( $section_content, "\n" ); + + // Regular expression to find the section, handling both '==' and '===' + $pattern = '/(={2,3}\s*' . preg_quote( $section_title, '/' ) . '\s*={2,3})(.*?)(?=(={2,3}|$))/s'; + $replacement = "== $section_title ==\n\n$section_content\n\n"; + + // Check if the section exists + if ( preg_match( $pattern, $readme_content ) ) { + // Replace the existing section content + $updated_content = preg_replace( $pattern, $replacement, $readme_content ); + } else { + // Remove any trailing whitespace, newlines or carriage returns from current content + $readme_content = rtrim( $readme_content ); + + // Ensure two newlines before appending new section + if ( ! empty( $readme_content ) ) { + $readme_content .= "\n\n\n"; + } + + // Append new section if not found + $updated_content = $readme_content . $replacement; } - if ( $image_credits ) { - $updated_readme = $updated_readme . "\n\n" . $image_credits; - } + return $updated_content; + } + /** + * Adds or updates a property in the readme content. + * + * @param string $prop_name The name of the property. + * @param string $prop_value The value of the property. + * @param string $readme_content The content of the readme file. + * @return string The updated readme content. + */ + private static function add_or_update_prop( $prop_name, $prop_value, $readme_content ) { + if ( empty( $prop_value ) ) { + return $readme_content; + } + $pattern = '/(' . preg_quote( $prop_name, '/' ) . ')(.*?)(\n|$)/s'; + preg_match_all( $pattern, $readme_content, $matches ); + $current_uri = $matches[0][0]; + $updated_readme = str_replace( $current_uri, "{$prop_name}: {$prop_value}\n", $readme_content ); return $updated_readme; } + + /** + * Get property value from the readme content. + * + * @return string The property value + */ + private static function get_prop( $property, $readme_content = '' ) { + if ( empty( $readme_content ) ) { + $readme_content = self::get_content(); + } + + // Build the regular expression pattern to match the line + $pattern = '/^' . preg_quote( $property, '/' ) . ': (.*)$/m'; + + // Use preg_match to find a matching line + if ( preg_match( $pattern, $readme_content, $matches ) ) { + // Return the capturing group which contains the value after the colon + return trim( $matches[1] ); + } else { + // Return null if no match is found + return null; + } + } + + public static function get_sections() { + + $readme_content = self::get_content(); + $sections = array(); + + // Regular expression to find the section, handling both '==' and '===' + $pattern = '/(={2,3}\s*(.*?)\s*={2,3})(.*?)(?=(={2,3}|$))/s'; + + // Find all sections + preg_match_all( $pattern, $readme_content, $matches, PREG_SET_ORDER ); + + // Loop through the matches + foreach ( $matches as $match ) { + $section_title = str_replace( '-', '_', sanitize_title( $match[2] ) ); + $section_content = trim( $match[3] ); + + // Add the section to the sections array + $sections[ $section_title ] = $section_content; + } + + return $sections; + } + } diff --git a/admin/create-theme/theme-utils.php b/admin/create-theme/theme-utils.php index aa6dfcb..3a5339b 100644 --- a/admin/create-theme/theme-utils.php +++ b/admin/create-theme/theme-utils.php @@ -91,26 +91,6 @@ class Theme_Utils { } } - public static function get_readme_data() { - $readme_location = get_template_directory() . '/readme.txt'; - - if ( ! file_exists( $readme_location ) ) { - throw new Exception( 'No readme file found' ); - } - - $readme_file_contents = file_get_contents( $readme_location ); - - $readme_file_details = array(); - - // Handle Recommended Plugins. - $pattern = '/== Recommended Plugins ==\s+(.*?)(\s+==|$)/s'; - preg_match_all( $pattern, $readme_file_contents, $matches ); - $readme_file_details['recommendedPlugins'] = $matches[1][0] ?? ''; - - return $readme_file_details; - } - - /** * Relocate the theme to a new folder and activate the newly relocated theme. */ diff --git a/includes/class-create-block-theme-api.php b/includes/class-create-block-theme-api.php index aae4bc0..f1a6569 100644 --- a/includes/class-create-block-theme-api.php +++ b/includes/class-create-block-theme-api.php @@ -165,7 +165,8 @@ class Create_Block_Theme_API { function rest_get_readme_data( $request ) { try { - $readme_data = Theme_Utils::get_readme_data(); + $readme_data = Theme_Readme::get_sections(); + return new WP_REST_Response( array( 'status' => 'SUCCESS', @@ -201,7 +202,8 @@ class Create_Block_Theme_API { function rest_create_child_theme( $request ) { - $theme = $this->sanitize_theme_data( $request->get_params() ); + $theme = $this->sanitize_theme_data( $request->get_params() ); + $theme['is_child_theme'] = true; //TODO: Handle screenshots $screenshot = null; @@ -281,7 +283,7 @@ class Create_Block_Theme_API { // Add readme.txt. $zip->addFromStringToTheme( 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Build style.css with new theme metadata @@ -329,7 +331,7 @@ class Create_Block_Theme_API { // Add readme.txt. $zip->addFromStringToTheme( 'readme.txt', - Theme_Readme::build_readme_txt( $theme ) + Theme_Readme::create( $theme ) ); // Build style.css with new theme metadata @@ -407,16 +409,16 @@ class Create_Block_Theme_API { * Update the theme metadata and relocate the theme. */ function rest_update_theme( $request ) { - $theme = $request->get_params(); + $theme = $this->sanitize_theme_data( $request->get_params() ); // Update the metadata of the theme in the style.css file $style_css = file_get_contents( get_stylesheet_directory() . '/style.css' ); $style_css = Theme_Styles::update_style_css( $style_css, $theme ); file_put_contents( get_stylesheet_directory() . '/style.css', $style_css ); - file_put_contents( - get_stylesheet_directory() . '/readme.txt', - Theme_Readme::update_readme_txt( $theme ) - ); + + $readme_content = Theme_Readme::get_content(); + $readme_content = Theme_Readme::update( $theme, $readme_content ); + file_put_contents( Theme_Readme::file_path(), $readme_content ); // Replace Screenshot if ( wp_get_theme()->get_screenshot() !== $theme['screenshot'] ) { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c34c1aa..3b4b76e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,7 +10,7 @@ > - ./tests/ + ./tests/ ./tests/test-sample.php diff --git a/src/editor-sidebar/metadata-editor-modal.js b/src/editor-sidebar/metadata-editor-modal.js index 7ccdc87..9d0b7e1 100644 --- a/src/editor-sidebar/metadata-editor-modal.js +++ b/src/editor-sidebar/metadata-editor-modal.js @@ -26,7 +26,7 @@ import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { postUpdateThemeMetadata } from '../resolvers'; +import { postUpdateThemeMetadata, fetchReadmeData } from '../resolvers'; const ALLOWED_SCREENSHOT_MEDIA_TYPES = [ 'image/png', @@ -53,6 +53,7 @@ export const ThemeMetadataEditorModal = ( { onRequestClose } ) => { useSelect( async ( select ) => { const themeData = select( 'core' ).getCurrentTheme(); + const readmeData = await fetchReadmeData(); setTheme( { name: themeData.name.raw, description: themeData.description.raw, @@ -62,6 +63,7 @@ export const ThemeMetadataEditorModal = ( { onRequestClose } ) => { author_uri: themeData.author_uri.raw, tags_custom: themeData.tags.rendered, screenshot: themeData.screenshot, + recommended_plugins: readmeData.recommended_plugins, subfolder: themeData.stylesheet.lastIndexOf( '/' ) > 1 ? themeData.stylesheet.substring( diff --git a/src/resolvers.js b/src/resolvers.js index 3a0281a..3176f8a 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -29,6 +29,30 @@ export async function fetchThemeJson() { } } +export async function fetchReadmeData() { + const fetchOptions = { + path: '/create-block-theme/v1/get-readme-data', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }; + + try { + const response = await apiFetch( fetchOptions ); + if ( ! response?.data || 'SUCCESS' !== response?.status ) { + throw new Error( + `Failed to fetch readme data: ${ + response?.message || response?.status + }` + ); + } + return response?.data; + } catch ( e ) { + // @todo: handle error + } +} + export async function postCreateThemeVariation( name ) { return apiFetch( { path: '/create-block-theme/v1/create-variation', diff --git a/tests/CbtThemeReadme/addOrUpdateSection.php b/tests/CbtThemeReadme/addOrUpdateSection.php new file mode 100644 index 0000000..5ee5f6f --- /dev/null +++ b/tests/CbtThemeReadme/addOrUpdateSection.php @@ -0,0 +1,50 @@ +assertStringContainsString( $section_title, $readme, 'The section title is missing.' ); + $this->assertStringContainsString( $section_content, $readme, 'The section content is missing' ); + + // Update the section. + $section_content_updated = 'Updated content xyz890'; + + $readme = Theme_Readme::add_or_update_section( $section_title, $section_content_updated ); + + // Check if the old content was updated. + $this->assertStringNotContainsString( $section_content, $readme, 'The old content is still present.' ); + + // Check if the new content was added. + $this->assertStringContainsString( $section_title, $readme, 'The section title is missing.' ); + $this->assertStringContainsString( $section_content_updated, $readme, 'The updated content is missing.' ); + + // Check if that the section title was added only once. + $section_count = substr_count( $readme, $section_title ); + $this->assertEquals( 1, $section_count, 'The section title was added more than once.' ); + } + + public function test_add_or_update_section_with_no_content() { + $section_title = 'Test Section'; + $section_content = ''; + + // Empty section should not be added. + $readme = Theme_Readme::add_or_update_section( $section_title, $section_content ); + $this->assertStringNotContainsString( $section_title, $readme, 'The title of an empty section should not be added.' ); + } +} diff --git a/tests/CbtThemeReadme/base.php b/tests/CbtThemeReadme/base.php new file mode 100644 index 0000000..96b5f02 --- /dev/null +++ b/tests/CbtThemeReadme/base.php @@ -0,0 +1,64 @@ +orig_active_theme_slug = get_option( 'stylesheet' ); + + // Create a test theme directory. + $this->test_theme_dir = DIR_TESTDATA . '/themes/'; + + // Register test theme directory. + register_theme_directory( $this->test_theme_dir ); + + // Switch to the test theme. + switch_theme( 'test-theme-readme' ); + + // Store the original readme.txt content. + $this->orig_readme_content = Theme_Readme::get_content(); + } + + /** + * Tears down tests. + */ + public function tear_down() { + parent::tear_down(); + + // Restore the original readme.txt content. + file_put_contents( Theme_Readme::file_path(), $this->orig_readme_content ); + + // Restore the original active theme. + switch_theme( $this->orig_active_theme_slug ); + } +} diff --git a/tests/CbtThemeReadme/create.php b/tests/CbtThemeReadme/create.php new file mode 100644 index 0000000..d5647f5 --- /dev/null +++ b/tests/CbtThemeReadme/create.php @@ -0,0 +1,115 @@ +assertStringContainsString( $expected_name, $readme_without_newlines, 'The expected name is missing.' ); + $this->assertStringContainsString( $expected_author, $readme_without_newlines, 'The expected author is missing.' ); + $this->assertStringContainsString( $expected_wp_version, $readme_without_newlines, 'The expected WP version is missing.' ); + $this->assertStringContainsString( $expected_image_credits, $readme_without_newlines, 'The expected image credits are missing.' ); + $this->assertStringContainsString( $expected_recommended_plugins, $readme_without_newlines, 'The expected recommended plugins are missing.' ); + + // Assetion specific to child themes. + if ( isset( $data['is_child_theme'] ) && $data['is_child_theme'] ) { + $this->assertStringContainsString( + $data['name'] . ' is a child theme of Test Readme Theme (https://example.org/themes/test-readme-theme), (C) the WordPress team, [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html)', + $readme_without_newlines, + 'The expected reference to the parent theme is missing.' + ); + } + + // Assetion specific to child themes. + if ( isset( $data['is_cloned_theme'] ) && $data['is_cloned_theme'] ) { + $this->assertStringContainsString( + $data['name'] . ' is based on Test Readme Theme (https://example.org/themes/test-readme-theme), (C) the WordPress team, [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html)', + $readme_without_newlines, + 'The expected reference to the parent theme is missing.' + ); + } + } + + public function data_test_create() { + return array( + 'complete data for a nomal theme' => array( + 'data' => array( + 'name' => 'My Theme', + 'description' => 'New theme description', + 'uri' => 'https://example.com', + 'author' => 'New theme author', + 'author_uri' => 'https://example.com/author', + 'copyright_year' => '2077', + 'wp_version' => '12.12', + 'required_php_version' => '10.0', + 'license' => 'GPLv2 or later', + 'license_uri' => 'https://www.gnu.org/licenses/gpl-2.0.html', + 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', + 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', + ), + ), + 'complete data for a child theme' => array( + 'data' => array( + 'name' => 'My Child Theme', + 'description' => 'New child theme description', + 'uri' => 'https://example.com', + 'author' => 'New theme author', + 'author_uri' => 'https://example.com/author', + 'copyright_year' => '2078', + 'wp_version' => '13.13', + 'required_php_version' => '11.0', + 'license' => 'GPLv2 or later', + 'license_uri' => 'https://www.gnu.org/licenses/gpl-2.0.html', + 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', + 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', + 'is_child_theme' => true, + ), + ), + 'complete data for a cloned theme' => array( + 'data' => array( + 'name' => 'My Cloned Theme', + 'description' => 'New cloned theme description', + 'uri' => 'https://example.com', + 'author' => 'New theme author', + 'author_uri' => 'https://example.com/author', + 'copyright_year' => '2079', + 'wp_version' => '14.14', + 'required_php_version' => '12.0', + 'license' => 'GPLv2 or later', + 'license_uri' => 'https://www.gnu.org/licenses/gpl-2.0.html', + 'image_credits' => 'The images were taken from https://example.org and have a CC0 license.', + 'recommended_plugins' => 'The theme is best used with the following plugins: Plugin 1, Plugin 2, Plugin 3.', + 'is_cloned_theme' => true, + ), + ), + // TODO: Add more test cases. + ); + } +} diff --git a/tests/CbtThemeReadme/filePath.php b/tests/CbtThemeReadme/filePath.php new file mode 100644 index 0000000..2bd390a --- /dev/null +++ b/tests/CbtThemeReadme/filePath.php @@ -0,0 +1,20 @@ +assertEquals( $expected, $result ); + + $this->assertEquals( 'test-theme-readme', get_option( 'stylesheet' ) ); + } +} diff --git a/tests/CbtThemeReadme/getContent.php b/tests/CbtThemeReadme/getContent.php new file mode 100644 index 0000000..4405a98 --- /dev/null +++ b/tests/CbtThemeReadme/getContent.php @@ -0,0 +1,18 @@ +assertEquals( $expected, $result ); + } +} diff --git a/tests/CbtThemeReadme/update.php b/tests/CbtThemeReadme/update.php new file mode 100644 index 0000000..da38e24 --- /dev/null +++ b/tests/CbtThemeReadme/update.php @@ -0,0 +1,49 @@ +assertStringContainsString( $expected_author, $readme_without_newlines, 'The expected author is missing.' ); + $this->assertStringContainsString( $expected_wp_version, $readme_without_newlines, 'The expected WP version is missing.' ); + $this->assertStringContainsString( $expected_image_credits, $readme_without_newlines, 'The expected image credits are missing.' ); + $this->assertStringContainsString( $expected_recommended_plugins, $readme_without_newlines, 'The expected recommended plugins are missing.' ); + } + + public function data_test_update() { + return array( + 'complete data' => array( + 'data' => array( + 'description' => 'New theme description', + 'author' => 'New theme author', + 'wp_version' => '12.12', + 'image_credits' => 'New image credits', + 'recommended_plugins' => 'New recommended plugins', + ), + ), + // TODO: Add more test cases. + ); + } +} diff --git a/tests/data/themes/test-theme-readme/readme.txt b/tests/data/themes/test-theme-readme/readme.txt new file mode 100644 index 0000000..3065dda --- /dev/null +++ b/tests/data/themes/test-theme-readme/readme.txt @@ -0,0 +1,54 @@ +=== Test Readme Theme === +Contributors: wordpressdotorg +Requires at least: 6.5 +Tested up to: 6.5 +Requires PHP: 7.0 +Stable tag: 2.1 +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + + +== Description == + +Test Readme Theme is a test theme created with the sole purpose of testing the Create_Block_Theme_Readme class. + + +== Changelog == + += 1.0 = +* Initial release + += 1.1 = +* Added new feature + += 2.0 = +* Added new major feature + += 2.1 = +* Added new feature + + +== Copyright == + +Test Readme Theme WordPress Theme, (C) 2023 WordPress.org +Test Readme Theme is distributed under the terms of the GNU GPL. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + + +=== Images === + +License: CC0 https://creativecommons.org/publicdomain/zero/1.0/ + +museum.webp - https://www.rawpixel.com/image/3297419/free-photo-image-interior-hallway-architecture +tourist-and-building.webp - https://www.rawpixel.com/image/5928004/photo-image-public-domain-hand-person + + diff --git a/tests/data/themes/test-theme-readme/style.css b/tests/data/themes/test-theme-readme/style.css new file mode 100644 index 0000000..67c1a88 --- /dev/null +++ b/tests/data/themes/test-theme-readme/style.css @@ -0,0 +1,15 @@ +/* +Theme Name: Test Readme Theme +Theme URI: https://example.org/themes/test-readme-theme +Author: the WordPress team +Author URI: https://wordpress.org +Description: Test Readme Theme is a theme for testing the readme.txt file reading/writing capabilities of the Create Block Theme plugin. +Requires at least: 6.4 +Tested up to: 6.4 +Requires PHP: 7.0 +Version: 1.0 +License: GNU General Public License v2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html +Text Domain: testreadmetheme +Tags: one-column, custom-colors, custom-menu, custom-logo, editor-style, featured-images, full-site-editing, block-patterns, rtl-language-support, sticky-post, threaded-comments, translation-ready, wide-blocks, block-styles, style-variations, accessibility-ready, blog, portfolio, news +*/ diff --git a/tests/data/themes/test-theme-readme/theme.json b/tests/data/themes/test-theme-readme/theme.json new file mode 100644 index 0000000..c4c73c8 --- /dev/null +++ b/tests/data/themes/test-theme-readme/theme.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2, + "styles": {}, + "settings": {} +}