diff --git a/includes/Checker/Checks/Plugin_Repo/Plugin_Header_Fields_Check.php b/includes/Checker/Checks/Plugin_Repo/Plugin_Header_Fields_Check.php index 5f5be7c29..ec099aa76 100644 --- a/includes/Checker/Checks/Plugin_Repo/Plugin_Header_Fields_Check.php +++ b/includes/Checker/Checks/Plugin_Repo/Plugin_Header_Fields_Check.php @@ -10,19 +10,24 @@ use Exception; use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; -use WordPress\Plugin_Check\Checker\Static_Check; +use WordPress\Plugin_Check\Checker\Checks\Abstract_File_Check; use WordPress\Plugin_Check\Traits\Amend_Check_Result; +use WordPress\Plugin_Check\Traits\Find_Readme; +use WordPress\Plugin_Check\Traits\License_Utils; use WordPress\Plugin_Check\Traits\Stable_Check; +use WordPressdotorg\Plugin_Directory\Readme\Parser; /** * Check for plugin header fields. * * @since 1.2.0 */ -class Plugin_Header_Fields_Check implements Static_Check { +class Plugin_Header_Fields_Check extends Abstract_File_Check { use Amend_Check_Result; use Stable_Check; + use License_Utils; + use Find_Readme; /** * Gets the categories for the check. @@ -43,6 +48,7 @@ public function get_categories() { * @since 1.2.0 * * @param Check_Result $result The check result to amend, including the plugin context to check. + * @param array $files Array of plugin files. * * @throws Exception Thrown when the check fails with a critical error (unrelated to any errors detected as part of the check). * @@ -50,7 +56,7 @@ public function get_categories() { * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function run( Check_Result $result ) { + public function check_files( Check_Result $result, array $files ) { $plugin_main_file = $result->plugin()->main_file(); $labels = array( @@ -67,6 +73,8 @@ public function run( Check_Result $result ) { 'RequiresPHP' => 'Requires PHP', 'UpdateURI' => 'Update URI', 'RequiresPlugins' => 'Requires Plugins', + 'License' => 'License', + 'LicenseURI' => 'License URI', ); $restricted_labels = array( @@ -249,6 +257,59 @@ public function run( Check_Result $result ) { } } + if ( empty( $plugin_header['License'] ) ) { + $this->add_result_error_for_file( + $result, + __( 'Your plugin has no license declared in Plugin Header.
Please update your plugin header with a GPLv2 (or later) compatible license. It is necessary to declare the license of this plugin. You can do this by using the fields available both in the plugin readme and in the plugin headers.', 'plugin-check' ), + 'plugin_header_no_license', + $plugin_main_file, + 0, + 0, + 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#no-gpl-compatible-license-declared', + 9 + ); + } else { + $plugin_license = $this->get_normalized_license( $plugin_header['License'] ); + + if ( ! $this->is_gpl_compatible_license( $plugin_license ) ) { + $this->add_result_error_for_file( + $result, + __( 'Your plugin has an invalid license declared in Plugin Header.
Please update your readme with a valid GPL license identifier. It is necessary to declare the license of this plugin. You can do this by using the fields available both in the plugin readme and in the plugin headers.', 'plugin-check' ), + 'plugin_header_invalid_license', + $plugin_main_file, + 0, + 0, + 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#no-gpl-compatible-license-declared', + 9 + ); + } + + if ( ! $result->plugin()->is_single_file_plugin() ) { + $readme = $this->filter_files_for_readme( $files, $result->plugin()->path() ); + + if ( ! empty( $readme ) ) { + $readme_file = reset( $readme ); + + $parser = new Parser( $readme_file ); + + $readme_license = $parser->license; + + if ( ! empty( $readme_license ) && $plugin_license !== $readme_license ) { + $this->add_result_warning_for_file( + $result, + __( 'Your plugin has a different license declared in the readme file and plugin header.
Please update your readme with a valid GPL license identifier.', 'plugin-check' ), + 'license_mismatch', + $plugin_main_file, + 0, + 0, + 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#declared-license-mismatched', + 9 + ); + } + } + } + } + $found_headers = array(); foreach ( $restricted_labels as $restricted_key => $restricted_label ) { diff --git a/includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php b/includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php index 71ee0e530..912a334da 100644 --- a/includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php +++ b/includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php @@ -12,6 +12,7 @@ use WordPress\Plugin_Check\Checker\Checks\Abstract_File_Check; use WordPress\Plugin_Check\Traits\Amend_Check_Result; use WordPress\Plugin_Check\Traits\Find_Readme; +use WordPress\Plugin_Check\Traits\License_Utils; use WordPress\Plugin_Check\Traits\Stable_Check; use WordPressdotorg\Plugin_Directory\Readme\Parser; @@ -27,6 +28,7 @@ class Plugin_Readme_Check extends Abstract_File_Check { use Amend_Check_Result; use Find_Readme; use Stable_Check; + use License_Utils; /** * Gets the categories for the check. @@ -300,9 +302,7 @@ private function check_default_text( Check_Result $result, string $readme_file, * @param Parser $parser The Parser object. */ private function check_license( Check_Result $result, string $readme_file, Parser $parser ) { - $license = $parser->license; - $matches_license = array(); - $plugin_main_file = $result->plugin()->main_file(); + $license = $parser->license; // Filter the readme files. if ( empty( $license ) ) { @@ -318,114 +318,41 @@ private function check_license( Check_Result $result, string $readme_file, Parse ); return; - } else { - $license = $this->normalize_licenses( $license ); } + $license = $this->get_normalized_license( $license ); + // Test for a valid SPDX license identifier. - if ( ! preg_match( '/^([a-z0-9\-\+\.]+)(\sor\s([a-z0-9\-\+\.]+))*$/i', $license ) ) { + if ( ! $this->is_valid_license_identifier( $license ) ) { $this->add_result_warning_for_file( $result, __( 'Your plugin has an invalid license declared.
Please update your readme with a valid SPDX license identifier.', 'plugin-check' ), - 'invalid_license', + 'invalid_license_identifier', $readme_file, 0, 0, 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#no-gpl-compatible-license-declared', 9 ); - } - - $pattern = preg_quote( 'License', '/' ); - $has_license = self::file_preg_match( "/(*ANYCRLF)^.*$pattern\s*:\s*(.*)$/im", array( $plugin_main_file ), $matches_license ); - if ( ! $has_license ) { - $this->add_result_error_for_file( - $result, - __( 'Your plugin has no license declared in Plugin Header.
Please update your plugin header with a GPLv2 (or later) compatible license. It is necessary to declare the license of this plugin. You can do this by using the fields available both in the plugin readme and in the plugin headers.', 'plugin-check' ), - 'no_license', - $plugin_main_file, - 0, - 0, - 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#no-gpl-compatible-license-declared', - 9 - ); - } else { - $plugin_license = $this->normalize_licenses( $matches_license[1] ); - } - // Checks for a valid license in Plugin Header. - if ( ! empty( $plugin_license ) && ! preg_match( '/GPL|GNU|MIT|FreeBSD|New BSD|BSD-3-Clause|BSD 3 Clause|OpenLDAP|Expat|Apache|MPL20/im', $plugin_license ) ) { - $this->add_result_error_for_file( - $result, - __( 'Your plugin has an invalid license declared in Plugin Header.
Please update your readme with a valid GPL license identifier. It is necessary to declare the license of this plugin. You can do this by using the fields available both in the plugin readme and in the plugin headers.', 'plugin-check' ), - 'invalid_license', - $plugin_main_file, - 0, - 0, - 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#no-gpl-compatible-license-declared', - 9 - ); + return; } - // Check different license types. - if ( ! empty( $plugin_license ) && ! empty( $license ) && $license !== $plugin_license ) { + // Test for a valid GPL compatible license. + if ( ! $this->is_gpl_compatible_license( $license ) ) { $this->add_result_warning_for_file( $result, - __( 'Your plugin has a different license declared in the readme file and plugin header.
Please update your readme with a valid GPL license identifier.', 'plugin-check' ), - 'license_mismatch', + __( 'Your plugin has an invalid license declared.
Please update your readme with a valid GPL compatible license identifier.', 'plugin-check' ), + 'invalid_license', $readme_file, 0, 0, - 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#declared-license-mismatched', + 'https://developer.wordpress.org/plugins/wordpress-org/common-issues/#no-gpl-compatible-license-declared', 9 ); } } - /** - * Normalize licenses to compare them. - * - * @since 1.0.2 - * - * @param string $license The license to normalize. - * @return string - */ - private function normalize_licenses( $license ) { - $license = trim( $license ); - $license = str_replace( ' ', ' ', $license ); - - // Remove some strings at the end. - $strings_to_remove = array( - '.', - 'http://www.gnu.org/licenses/old-licenses/gpl-2.0.html', - 'https://www.gnu.org/licenses/old-licenses/gpl-2.0.html', - 'https://www.gnu.org/licenses/gpl-3.0.html', - ' or later', - '-or-later', - '+', - ); - foreach ( $strings_to_remove as $string_to_remove ) { - $position = strrpos( $license, $string_to_remove ); - - if ( false !== $position ) { - // To remove from the end, the string to remove must be at the end. - if ( $position + strlen( $string_to_remove ) === strlen( $license ) ) { - $license = trim( substr( $license, 0, $position ) ); - } - } - } - - // Versions. - $license = str_replace( '-', '', $license ); - $license = str_replace( 'GNU General Public License (GPL)', 'GPL', $license ); - $license = str_replace( 'GNU General Public License', 'GPL', $license ); - $license = str_replace( ' version ', 'v', $license ); - $license = preg_replace( '/GPL\s*[-|\.]*\s*[v]?([0-9])(\.[0])?/i', 'GPL$1', $license, 1 ); - $license = str_replace( '.', '', $license ); - - return $license; - } - /** * Checks the readme file stable tag. * diff --git a/includes/Traits/License_Utils.php b/includes/Traits/License_Utils.php new file mode 100644 index 000000000..701a0571a --- /dev/null +++ b/includes/Traits/License_Utils.php @@ -0,0 +1,88 @@ +assertCount( 0, wp_list_filter( $warnings['load.php'][0][0], array( 'code' => 'plugin_header_invalid_requires_plugins' ) ) ); } } + + public function test_run_with_errors_no_license() { + $check = new Plugin_Header_Fields_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-plugin-readme-errors-no-license/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $errors = $check_result->get_errors(); + + $this->assertNotEmpty( $errors ); + $this->assertArrayHasKey( 'load.php', $errors ); + + $this->assertCount( 1, wp_list_filter( $errors['load.php'][0][0], array( 'code' => 'plugin_header_no_license' ) ) ); + } + + public function test_run_with_errors_mismatched_license() { + $check = new Plugin_Header_Fields_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-plugin-readme-errors-license/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $warnings = $check_result->get_warnings(); + + $this->assertNotEmpty( $warnings ); + $this->assertArrayHasKey( 'load.php', $warnings ); + + $this->assertCount( 1, wp_list_filter( $warnings['load.php'][0][0], array( 'code' => 'license_mismatch' ) ) ); + } + + public function test_run_with_errors_invalid_license() { + $check = new Plugin_Header_Fields_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-plugin-readme-errors-invalid-license/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $errors = $check_result->get_errors(); + + $this->assertNotEmpty( $errors ); + $this->assertArrayHasKey( 'load.php', $errors ); + + $this->assertCount( 1, wp_list_filter( $errors['load.php'][0][0], array( 'code' => 'plugin_header_invalid_license' ) ) ); + } } diff --git a/tests/phpunit/tests/Checker/Checks/Plugin_Readme_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/Plugin_Readme_Check_Tests.php index 6bbb60c63..4e9528620 100644 --- a/tests/phpunit/tests/Checker/Checks/Plugin_Readme_Check_Tests.php +++ b/tests/phpunit/tests/Checker/Checks/Plugin_Readme_Check_Tests.php @@ -156,10 +156,7 @@ public function test_run_with_errors_license() { // Check for invalid license warning. $this->assertArrayHasKey( 0, $warnings['readme.txt'] ); $this->assertArrayHasKey( 0, $warnings['readme.txt'][0] ); - $this->assertCount( 1, wp_list_filter( $warnings['readme.txt'][0][0], array( 'code' => 'invalid_license' ) ) ); - - // Check for not same license warning. - $this->assertCount( 1, wp_list_filter( $warnings['readme.txt'][0][0], array( 'code' => 'license_mismatch' ) ) ); + $this->assertCount( 1, wp_list_filter( $warnings['readme.txt'][0][0], array( 'code' => 'invalid_license_identifier' ) ) ); } public function test_run_with_errors_no_license() { @@ -180,6 +177,21 @@ public function test_run_with_errors_no_license() { $this->assertCount( 1, wp_list_filter( $errors['readme.txt'][0][0], array( 'code' => 'no_license' ) ) ); } + public function test_run_with_errors_invalid_license() { + $readme_check = new Plugin_Readme_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-plugin-readme-errors-invalid-license/load.php' ); + $check_result = new Check_Result( $check_context ); + + $readme_check->run( $check_result ); + + $warnings = $check_result->get_warnings(); + + $this->assertNotEmpty( $warnings ); + $this->assertArrayHasKey( 'readme.txt', $warnings ); + + $this->assertCount( 1, wp_list_filter( $warnings['readme.txt'][0][0], array( 'code' => 'invalid_license_identifier' ) ) ); + } + public function test_run_without_error_mpl2_license() { $readme_check = new Plugin_Readme_Check(); $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-plugin-readme-mpl2-license-without-errors/load.php' ); @@ -187,9 +199,10 @@ public function test_run_without_error_mpl2_license() { $readme_check->run( $check_result ); - $errors = $check_result->get_errors(); + $warnings = $check_result->get_warnings(); - $this->assertEmpty( $errors ); + $this->assertNotEmpty( $warnings ); + $this->assertCount( 0, wp_list_filter( $warnings['readme.txt'][0][0], array( 'code' => 'invalid_license' ) ) ); } public function test_run_with_errors_mpl1_license() { @@ -199,15 +212,15 @@ public function test_run_with_errors_mpl1_license() { $readme_check->run( $check_result ); - $errors = $check_result->get_errors(); + $warnings = $check_result->get_warnings(); - $this->assertNotEmpty( $errors ); - $this->assertArrayHasKey( 'load.php', $errors ); + $this->assertNotEmpty( $warnings ); + $this->assertArrayHasKey( 'readme.txt', $warnings ); // Check for invalid license. - $this->assertArrayHasKey( 0, $errors['load.php'] ); - $this->assertArrayHasKey( 0, $errors['load.php'][0] ); - $this->assertCount( 1, wp_list_filter( $errors['load.php'][0][0], array( 'code' => 'invalid_license' ) ) ); + $this->assertArrayHasKey( 0, $warnings['readme.txt'] ); + $this->assertArrayHasKey( 0, $warnings['readme.txt'][0] ); + $this->assertCount( 1, wp_list_filter( $warnings['readme.txt'][0][0], array( 'code' => 'invalid_license' ) ) ); } public function test_run_with_errors_tested_upto() { @@ -248,8 +261,7 @@ public function test_run_md_with_errors() { $this->assertNotEmpty( $warnings ); $this->assertArrayHasKey( 'readme.md', $warnings ); - $this->assertCount( 1, wp_list_filter( $warnings['readme.md'][0][0], array( 'code' => 'invalid_license' ) ) ); - $this->assertCount( 1, wp_list_filter( $warnings['readme.md'][0][0], array( 'code' => 'license_mismatch' ) ) ); + $this->assertCount( 1, wp_list_filter( $warnings['readme.md'][0][0], array( 'code' => 'invalid_license_identifier' ) ) ); $this->assertCount( 1, wp_list_filter( $warnings['readme.md'][0][0], array( 'code' => 'mismatched_plugin_name' ) ) ); $this->assertCount( 1, wp_list_filter( $warnings['readme.md'][0][0], array( 'code' => 'readme_invalid_contributors' ) ) ); }