Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

License check improvements #746

Draft
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -43,14 +48,15 @@
* @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).
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @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(
Expand All @@ -67,6 +73,8 @@
'RequiresPHP' => 'Requires PHP',
'UpdateURI' => 'Update URI',
'RequiresPlugins' => 'Requires Plugins',
'License' => 'License',
'LicenseURI' => 'License URI',
);

$restricted_labels = array(
Expand Down Expand Up @@ -249,6 +257,59 @@
}
}

if ( empty( $plugin_header['License'] ) ) {
$this->add_result_error_for_file(
$result,
__( '<strong>Your plugin has no license declared in Plugin Header.</strong><br>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,
__( '<strong>Your plugin has an invalid license declared in Plugin Header.</strong><br>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
);

Check warning on line 284 in includes/Checker/Checks/Plugin_Repo/Plugin_Header_Fields_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Plugin_Repo/Plugin_Header_Fields_Check.php#L275-L284

Added lines #L275 - L284 were not covered by tests
}

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,
__( '<strong>Your plugin has a different license declared in the readme file and plugin header.</strong><br>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 ) {
Expand Down
99 changes: 13 additions & 86 deletions includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,6 +28,7 @@
use Amend_Check_Result;
use Find_Readme;
use Stable_Check;
use License_Utils;

/**
* Gets the categories for the check.
Expand Down Expand Up @@ -300,9 +302,7 @@
* @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 ) ) {
Expand All @@ -318,114 +318,41 @@
);

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,
__( '<strong>Your plugin has an invalid license declared.</strong><br>Please update your readme with a valid SPDX license identifier.', 'plugin-check' ),
'invalid_license',
'invalid_license_identifier',

Check warning on line 330 in includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php#L330

Added line #L330 was not covered by tests
$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,
__( '<strong>Your plugin has no license declared in Plugin Header.</strong><br>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,
__( '<strong>Your plugin has an invalid license declared in Plugin Header.</strong><br>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 warning on line 338 in includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php#L338

Added line #L338 was not covered by tests
}

// 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,
__( '<strong>Your plugin has a different license declared in the readme file and plugin header.</strong><br>Please update your readme with a valid GPL license identifier.', 'plugin-check' ),
'license_mismatch',
__( '<strong>Your plugin has an invalid license declared.</strong><br>Please update your readme with a valid GPL compatible license identifier.', 'plugin-check' ),
'invalid_license',

Check warning on line 346 in includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php#L345-L346

Added lines #L345 - L346 were not covered by tests
$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',

Check warning on line 350 in includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Plugin_Repo/Plugin_Readme_Check.php#L350

Added line #L350 was not covered by tests
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.
*
Expand Down
88 changes: 88 additions & 0 deletions includes/Traits/License_Utils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* Trait WordPress\Plugin_Check\Traits\License_Utils
*
* @package plugin-check
*/

namespace WordPress\Plugin_Check\Traits;

/**
* Trait for license utilities.
*
* @since 1.0.0
*/
trait License_Utils {

/**
* Returns normalized license.
*
* @since 1.3.0
*
* @param string $license The license to normalize.
* @return string Normalized license.
*/
protected function get_normalized_license( $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 if the license is valid identifier.
*
* @since 1.3.0
*
* @param string $license License text.
* @return bool true if the license is valid identifier, otherwise false.
*/
protected function is_valid_license_identifier( $license ) {
$match = preg_match( '/^([a-z0-9\-\+\.]+)(\sor\s([a-z0-9\-\+\.]+))*$/i', $license );

return ( false === $match || 0 === $match ) ? false : true;
}

/**
* Checks if the license is GPL compatible.
*
* @since 1.3.0
*
* @param string $license License text.
* @return bool true if the license is GPL compatible, otherwise false.
*/
protected function is_gpl_compatible_license( $license ) {
$match = preg_match( '/GPL|GNU|MIT|FreeBSD|New BSD|BSD-3-Clause|BSD 3 Clause|OpenLDAP|Expat|Apache|MPL20/im', $license );

return ( false === $match || 0 === $match ) ? false : true;
}
}
22 changes: 22 additions & 0 deletions tests/behat/features/plugin-check.feature
Original file line number Diff line number Diff line change
Expand Up @@ -611,3 +611,25 @@ Feature: Test that the WP-CLI command works.
"""
Success: Checks complete. No errors found.
"""

Scenario: Check for missing license in single file plugin
Given a WP install with the Plugin Check plugin
And a wp-content/plugins/foo-single.php file:
"""
<?php
/**
* Plugin Name: Foo Single
* Plugin URI: https://foo-single.com
* Description: Custom plugin.
* Version: 0.1.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
*/

"""

When I run the WP-CLI command `plugin check foo-single.php`
Then STDOUT should contain:
"""
plugin_header_no_license
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Plugin Name: Test Plugin Readme Errors (invalid license)
* Plugin URI: https://github.com/WordPress/plugin-check
* Description: Test plugin for the Readme check.
* Requires at least: 6.0
* Requires PHP: 5.6
* Version: 1.0.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
* License: Oculus VR Inc. Software Development Kit License
* Text Domain: test-plugin-check-errors-invalid-license
*
* @package test-plugin-check-errors-invalid-license
*/
Loading