diff --git a/README.md b/README.md index 116f65ecb..65b3f3633 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,66 @@ # Plugin Check -A repository for the new Plugin Check plugin from the [WordPress Performance Team](https://make.wordpress.org/performance/). +This repository is for the WordPress plugin checker, a tool for plugin developers to analyze their plugin code and flag any violations or concerns around plugin development best practices, from basic requirements like correct usage of internationalization functions to accessibility, performance, and security best practices. -If you would like to contribute please see the [CONTRIBUTING.md guide here](/CONTRIBUTING.md). +The WordPress plugin checker was [first proposed in summer 2022](https://make.wordpress.org/plugins/2022/07/05/proposal-for-a-wordpress-plugin-checker/) and is now at an early MVP stage. + +## Features + +### For end users + +* Allows analyzing any installed plugin using either a WP Admin screen or a WP-CLI command. +* Supports two kinds of checks: + * Static checks, which analyze the code, either using PHPCodeSniffer sniffs or custom logic e.g. using regular expressions. + * Runtime checks, which actually execute certain parts of the code, such as running specific WordPress hooks with the plugin active. +* Allows customizing which checks are run, either via a list of individual check identifiers, or specific check categories. +* Comes with an ever-growing list of checks for various plugin development requirements and best practices. Please see the [`Abstract_Check_Runner::register_checks()` method](/includes/Checker/Abstract_Check_Runner.php#L358) for a quick overview of currently available checks. + +### For developers + +* Facilitates efficient yet flexible authoring of new checks, either using a base class for common check patterns, or implementing an interface for more specific checks. + * Every check has to implement either the [`Static_Check`](/includes/Checker/Static_Check.php) or the [`Runtime_Check`](/includes/Checker/Runtime_Check.php) interface. + * Most checks will benefit from extending either the [`Abstract_File_Check`](/includes/Checker/Checks/Abstract_File_Check.php), the [`Abstract_PHPCodeSniffer_Check`](/includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php), or the [`Abstract_Runtime_Check`](/includes/Checker/Checks/Abstract_Runtime_Check.php) class. +* Comes with comprehensive unit test coverage. + +## How to use + +The WordPress plugin checker is a WordPress plugin itself, which can be installed on any WordPress site. While it is implemented in a way that should avoid any disruptions on the site that it is being used on, it is still **advised not to use the plugin checker in a production environment**. + +Currently, the only way to install the plugin checker is to download it from this GitHub repository. Please see the [contributing section below](#contributing) for further instructions. Once a first beta version is available, it will be distributed in a standalone ZIP file, e.g. via the wordpress.org plugin repository. + +After having the plugin activated, you can analyze any other plugin installed on the same site, either using the WP Admin user interface or WP-CLI: + +* To check a plugin using WP Admin, please navigate to the _Tools > Plugin Check_ menu. You need to be able to manage plugins on your site in order to access that screen. +* To check a plugin using WP-CLI, please use the `wp plugin check` command. For example, to check the "Hello Dolly" plugin: `wp plugin check hello.php` + * Note that by default when using WP-CLI, only static checks can be executed. In order to also include runtime checks, a workaround is currently necessary using the `--require` argument of WP-CLI, to manually load the `cli.php` file within the plugin checker directory before WordPress is loaded. For example: `wp plugin check hello.php --require=./wp-content/plugins/plugin-check/cli.php` + +## Contributing + +To set up the repository locally, you will need to clone this GitHub repository (or a fork of it) and then install the relevant dependencies: + +``` +git clone https://github.com/10up/plugin-check.git wp-content/plugins/plugin-check +cd wp-content/plugins/plugin-check +composer install +npm install +``` + +### Built-in development environment (optional) + +With the above commands, you can use the plugin in any development environment as you like. The recommended way is to use the built-in development environment, which is based on the [`@wordpress/env` package](https://www.npmjs.com/package/@wordpress/env), as that will allow you to use the preconfigured commands to e.g. run unit tests, linting etc. You will need to have Docker installed to use this environment. + +You can start the built-in environment as follows: +``` +npm run wp-env start +``` + +If you want to stop the environment again, you can use: +``` +npm run wp-env stop +``` + +For further information on contributing, please see the [contributing guide](/CONTRIBUTING.md). + +## License + +The WordPress plugin checker is free software, and is released under the terms of the GNU General Public License version 2 or (at your option) any later version. See [LICENSE](/LICENSE) for complete license. diff --git a/cli.php b/cli.php index fcd9cdb4b..7baf1491f 100644 --- a/cli.php +++ b/cli.php @@ -47,7 +47,7 @@ function() { if ( CLI_Runner::is_plugin_check() ) { if ( ! file_exists( ABSPATH . 'wp-content/object-cache.php' ) ) { - if ( ! copy( __DIR__ . '/object-cache.copy.php', ABSPATH . 'wp-content/object-cache.php' ) ) { + if ( ! copy( __DIR__ . '/drop-ins/object-cache.copy.php', ABSPATH . 'wp-content/object-cache.php' ) ) { WP_CLI::error( 'Unable to copy object-cache.php file.' ); } } diff --git a/object-cache.copy.php b/drop-ins/object-cache.copy.php similarity index 100% rename from object-cache.copy.php rename to drop-ins/object-cache.copy.php diff --git a/includes/Admin/Admin_Page.php b/includes/Admin/Admin_Page.php index 0ba4fb0a6..53d7a8196 100644 --- a/includes/Admin/Admin_Page.php +++ b/includes/Admin/Admin_Page.php @@ -48,7 +48,7 @@ public function __construct( Admin_AJAX $admin_ajax ) { */ public function add_hooks() { add_action( 'admin_menu', array( $this, 'add_and_initialize_page' ) ); - add_filter( 'plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 2 ); + add_filter( 'plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 4 ); $this->admin_ajax->add_hooks(); } @@ -166,9 +166,19 @@ public function render_page() { * * @param array $actions List of actions. * @param string $plugin_file Plugin main file. + * @param array $plugin_data An array of plugin data. + * @param string $context The plugin context. By default this can include 'all', + * 'active', 'inactive', 'recently_activated', 'upgrade', + * 'mustuse', 'dropins', and 'search'. * @return array The modified list of actions. */ - public function filter_plugin_action_links( $actions, $plugin_file ) { + public function filter_plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) { + + $plugin_check_base_name = plugin_basename( WP_PLUGIN_CHECK_MAIN_FILE ); + if ( in_array( $context, array( 'mustuse', 'dropins' ), true ) || $plugin_check_base_name === $plugin_file ) { + return $actions; + } + if ( current_user_can( 'activate_plugins' ) ) { $actions[] = sprintf( '%2$s', diff --git a/includes/CLI/Plugin_Check_Command.php b/includes/CLI/Plugin_Check_Command.php index ca44fa298..f0af82b67 100644 --- a/includes/CLI/Plugin_Check_Command.php +++ b/includes/CLI/Plugin_Check_Command.php @@ -81,6 +81,8 @@ public function __construct( Plugin_Context $plugin_context ) { * [--ignore-errors] * : Limit displayed results to exclude errors. * + * [--include-experimental] + * : Include experimental checks. * * ## EXAMPLES * @@ -124,6 +126,7 @@ public function check( $args, $assoc_args ) { $checks_to_run = array(); try { + $runner->set_experimental_flag( $options['include-experimental'] ); $runner->set_check_slugs( $checks ); $runner->set_plugin( $plugin ); @@ -204,10 +207,11 @@ public function check( $args, $assoc_args ) { */ private function get_options( $assoc_args ) { $defaults = array( - 'checks' => '', - 'format' => 'table', - 'ignore-warnings' => false, - 'ignore-errors' => false, + 'checks' => '', + 'format' => 'table', + 'ignore-warnings' => false, + 'ignore-errors' => false, + 'include-experimental' => false, ); $options = wp_parse_args( $assoc_args, $defaults ); diff --git a/includes/Checker/AJAX_Runner.php b/includes/Checker/AJAX_Runner.php index 05891c17f..48ffd9fb8 100644 --- a/includes/Checker/AJAX_Runner.php +++ b/includes/Checker/AJAX_Runner.php @@ -75,4 +75,15 @@ protected function get_check_slugs_param() { return $checks; } + + /** + * Returns the include experimental paramater based on the request. + * + * @since n.e.x.t + * + * @return bool Returns true to include experimental checks else false. + */ + protected function get_include_experimental_param() { + return false; + } } diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php index 114d161a7..70131236f 100644 --- a/includes/Checker/Abstract_Check_Runner.php +++ b/includes/Checker/Abstract_Check_Runner.php @@ -74,6 +74,14 @@ abstract class Abstract_Check_Runner implements Check_Runner { */ protected $runtime_environment; + /** + * Whether to include experimental checks. + * + * @since n.e.x.t + * @var bool + */ + protected $include_experimental; + /** * Determines if the current request is intended for the plugin checker. * @@ -101,6 +109,15 @@ abstract protected function get_plugin_param(); */ abstract protected function get_check_slugs_param(); + /** + * Returns the include experimental paramater based on the request. + * + * @since n.e.x.t + * + * @return bool Returns true to include experimental checks else false. + */ + abstract protected function get_include_experimental_param(); + /** * Sets whether the runner class was initialized early. * @@ -156,6 +173,27 @@ final public function set_plugin( $plugin ) { $this->plugin = $plugin; } + /** + * Sets whether to include experimental checks in the process. + * + * @since n.e.x.t + * + * @param bool $include_experimental True to include experimental checks. False to exclude. + * + * @throws Exception Thrown if the flag set does not match the original request parameter. + */ + final public function set_experimental_flag( $include_experimental ) { + if ( $this->initialized_early ) { + if ( $include_experimental !== $this->get_include_experimental_param() ) { + throw new Exception( + __( 'Invalid flag: The include-experimental flag does not match the original request parameter.', 'plugin-check' ) + ); + } + } + + $this->include_experimental = $include_experimental; + } + /** * Prepares the environment for running the requested checks. * @@ -287,6 +325,11 @@ final public function get_checks_to_run() { $check_flags = Check_Repository::TYPE_ALL; } + // Check whether to include experimental checks. + if ( $this->get_include_experimental() ) { + $check_flags = $check_flags | Check_Repository::INCLUDE_EXPERIMENTAL; + } + return $this->check_repository->get_checks( $check_flags, $check_slugs ); } @@ -340,6 +383,21 @@ final public function get_plugin_basename() { return $this->plugin_basename; } + /** + * Returns the value for the include experimental flag. + * + * @since n.e.x.t + * + * @return bool True if experimental checks are included. False if not. + */ + final public function get_include_experimental() { + if ( null !== $this->include_experimental ) { + return $this->include_experimental; + } + + return $this->get_include_experimental_param(); + } + /** Gets the Check_Context for the plugin. * * @since n.e.x.t diff --git a/includes/Checker/CLI_Runner.php b/includes/Checker/CLI_Runner.php index debfe557c..bffab0891 100644 --- a/includes/Checker/CLI_Runner.php +++ b/includes/Checker/CLI_Runner.php @@ -88,4 +88,19 @@ protected function get_check_slugs_param() { return $checks; } + + /** + * Returns the include experimental paramater based on the request. + * + * @since n.e.x.t + * + * @return bool Returns true to include experimental checks else false. + */ + protected function get_include_experimental_param() { + if ( in_array( '--include-experimental', $_SERVER['argv'], true ) ) { + return true; + } + + return false; + } } diff --git a/includes/Checker/Check.php b/includes/Checker/Check.php index d44bf4d08..9e74935a0 100644 --- a/includes/Checker/Check.php +++ b/includes/Checker/Check.php @@ -15,6 +15,30 @@ * @since n.e.x.t */ interface Check { + /** + * Stability value for stable checks. + * + * @since n.e.x.t + * @var string + */ + const STABILITY_STABLE = 'STABLE'; + + /** + * Stability value for experimental checks. + * + * @since n.e.x.t + * @var string + */ + const STABILITY_EXPERIMENTAL = 'EXPERIMENTAL'; + + /** + * Returns the check's stability. + * + * @since n.e.x.t + * + * @return string One of the check stability constant values. + */ + public function get_stability(); /** * Amends the given result by running the check on the associated plugin. @@ -27,4 +51,15 @@ interface Check { * the check). */ public function run( Check_Result $result ); + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories(); } diff --git a/includes/Checker/Check_Categories.php b/includes/Checker/Check_Categories.php new file mode 100644 index 000000000..d7aab9c59 --- /dev/null +++ b/includes/Checker/Check_Categories.php @@ -0,0 +1,73 @@ +getConstants(); + + /** + * List of categories. + * + * @var string[] $categories + */ + $categories = array_values( + array_filter( + $constants, + static function( $key ) { + return strpos( $key, 'CATEGORY_' ) === 0; + }, + ARRAY_FILTER_USE_KEY + ) + ); + } + + return $categories; + } + + /** + * Returns an array of checks. + * + * @since n.e.x.t + * + * @param array $checks An array of Check instances. + * @param array $categories An array of categories to filter by. + * @return array Filtered $checks list. + */ + public static function filter_checks_by_categories( array $checks, array $categories ) { + return array_filter( + $checks, + static function( $check ) use ( $categories ) { + // Return true if at least one of the check categories is among the filter categories. + return (bool) array_intersect( $check->get_categories(), $categories ); + } + ); + } +} diff --git a/includes/Checker/Check_Repository.php b/includes/Checker/Check_Repository.php index 8a8e98811..1c1141093 100644 --- a/includes/Checker/Check_Repository.php +++ b/includes/Checker/Check_Repository.php @@ -40,6 +40,14 @@ interface Check_Repository { */ const TYPE_ALL = 3; + /** + * Bitwise flag for experimental checks. + * + * @since n.e.x.t + * @var int + */ + const INCLUDE_EXPERIMENTAL = 4; + /** * Registers a check to the repository. * diff --git a/includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php b/includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php index 91d894beb..e00f1d7c0 100644 --- a/includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php +++ b/includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php @@ -86,6 +86,9 @@ final public function run( Check_Result $result ) { // Set the check arguments for PHPCS. $_SERVER['argv'] = $this->parse_argv( $this->get_args(), $defaults ); + // Reset PHP_CodeSniffer config. + $this->reset_php_codesniffer_config(); + // Run PHPCS. try { ob_start(); @@ -147,4 +150,26 @@ private function parse_argv( $argv, $defaults ) { return $defaults; } + + /** + * Resets \PHP_CodeSniffer\Config::$overriddenDefaults to prevent + * incorrect results when running multiple checks. + * + * @since n.e.x.t + */ + private function reset_php_codesniffer_config() { + if ( class_exists( '\PHP_CodeSniffer\Config' ) ) { + /* + * PHPStan ignore reason: PHPStan raised an issue because we can't + * use class in ReflectionClass. + * + * @phpstan-ignore-next-line + */ + $reflected_phpcs_config = new \ReflectionClass( '\PHP_CodeSniffer\Config' ); + $overridden_defaults = $reflected_phpcs_config->getProperty( 'overriddenDefaults' ); + $overridden_defaults->setAccessible( true ); + $overridden_defaults->setValue( array() ); + $overridden_defaults->setAccessible( false ); + } + } } diff --git a/includes/Checker/Checks/Code_Obfuscation_Check.php b/includes/Checker/Checks/Code_Obfuscation_Check.php index 6336d5a12..0b63f4ccc 100644 --- a/includes/Checker/Checks/Code_Obfuscation_Check.php +++ b/includes/Checker/Checks/Code_Obfuscation_Check.php @@ -8,7 +8,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; use Exception; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; +use WordPress\Plugin_Check\Traits\Stable_Check; /** * Check to detect PHP code obfuscation. @@ -17,6 +19,8 @@ */ class Code_Obfuscation_Check extends Abstract_File_Check { + use Stable_Check; + const TYPE_ZEND = 1; const TYPE_SOURCEGUARDIAN = 2; const TYPE_IONCUBE = 4; @@ -41,6 +45,19 @@ public function __construct( $flags = self::TYPE_ALL ) { $this->flags = $flags; } + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Amends the given result by running the check on the given list of files. * diff --git a/includes/Checker/Checks/Enqueued_Scripts_In_Footer_Check.php b/includes/Checker/Checks/Enqueued_Scripts_In_Footer_Check.php index a53ea85ff..852d3b7aa 100644 --- a/includes/Checker/Checks/Enqueued_Scripts_In_Footer_Check.php +++ b/includes/Checker/Checks/Enqueued_Scripts_In_Footer_Check.php @@ -7,6 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; +use WordPress\Plugin_Check\Traits\Stable_Check; + /** * Check for running WordPress enqueued resource parameters sniffs. * @@ -14,6 +17,21 @@ */ class Enqueued_Scripts_In_Footer_Check extends Abstract_PHP_CodeSniffer_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PERFORMANCE ); + } + /** * Returns an associative array of arguments to pass to PHPCS. * diff --git a/includes/Checker/Checks/Enqueued_Scripts_Size_Check.php b/includes/Checker/Checks/Enqueued_Scripts_Size_Check.php index 0d2b1a0ab..ad879860b 100644 --- a/includes/Checker/Checks/Enqueued_Scripts_Size_Check.php +++ b/includes/Checker/Checks/Enqueued_Scripts_Size_Check.php @@ -8,9 +8,11 @@ namespace WordPress\Plugin_Check\Checker\Checks; use Exception; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Preparations\Demo_Posts_Creation_Preparation; use WordPress\Plugin_Check\Checker\With_Shared_Preparations; +use WordPress\Plugin_Check\Traits\Stable_Check; use WordPress\Plugin_Check\Traits\URL_Aware; /** @@ -20,7 +22,7 @@ */ class Enqueued_Scripts_Size_Check extends Abstract_Runtime_Check implements With_Shared_Preparations { - use URL_Aware; + use URL_Aware, Stable_Check; /** * Threshold for script size to surface a warning for. @@ -49,6 +51,19 @@ public function __construct( $threshold_size = 300000 ) { $this->threshold_size = $threshold_size; } + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PERFORMANCE ); + } + /** * Runs this preparation step for the environment and returns a cleanup function. * diff --git a/includes/Checker/Checks/Enqueued_Styles_Scope_Check.php b/includes/Checker/Checks/Enqueued_Styles_Scope_Check.php index 3fa09aaba..5b3405705 100644 --- a/includes/Checker/Checks/Enqueued_Styles_Scope_Check.php +++ b/includes/Checker/Checks/Enqueued_Styles_Scope_Check.php @@ -8,9 +8,11 @@ namespace WordPress\Plugin_Check\Checker\Checks; use Exception; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Preparations\Demo_Posts_Creation_Preparation; use WordPress\Plugin_Check\Checker\With_Shared_Preparations; +use WordPress\Plugin_Check\Traits\Stable_Check; use WordPress\Plugin_Check\Traits\URL_Aware; /** @@ -20,7 +22,7 @@ */ class Enqueued_Styles_Scope_Check extends Abstract_Runtime_Check implements With_Shared_Preparations { - use URL_Aware; + use URL_Aware, Stable_Check; /** * List of viewable post types. @@ -46,6 +48,19 @@ class Enqueued_Styles_Scope_Check extends Abstract_Runtime_Check implements With */ private $plugin_style_count = array(); + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PERFORMANCE ); + } + /** * Runs this preparation step for the environment and returns a cleanup function. * diff --git a/includes/Checker/Checks/File_Type_Check.php b/includes/Checker/Checks/File_Type_Check.php index 77e10501e..4159ee642 100644 --- a/includes/Checker/Checks/File_Type_Check.php +++ b/includes/Checker/Checks/File_Type_Check.php @@ -8,7 +8,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; use Exception; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; +use WordPress\Plugin_Check\Traits\Stable_Check; /** * Check to detect disallowed file types. @@ -17,6 +19,8 @@ */ class File_Type_Check extends Abstract_File_Check { + use Stable_Check; + const TYPE_COMPRESSED = 1; const TYPE_PHAR = 2; const TYPE_VCS = 4; @@ -42,6 +46,19 @@ public function __construct( $flags = self::TYPE_ALL ) { $this->flags = $flags; } + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Amends the given result by running the check on the given list of files. * diff --git a/includes/Checker/Checks/I18n_Usage_Check.php b/includes/Checker/Checks/I18n_Usage_Check.php index eeb9a7741..e26acd508 100644 --- a/includes/Checker/Checks/I18n_Usage_Check.php +++ b/includes/Checker/Checks/I18n_Usage_Check.php @@ -7,6 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; +use WordPress\Plugin_Check\Traits\Stable_Check; + /** * Check for running WordPress internationalization sniffs. * @@ -14,6 +17,21 @@ */ class I18n_Usage_Check extends Abstract_PHP_CodeSniffer_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_GENERAL ); + } + /** * Returns an associative array of arguments to pass to PHPCS. * diff --git a/includes/Checker/Checks/Late_Escaping_Check.php b/includes/Checker/Checks/Late_Escaping_Check.php index b7a96e654..9a020e914 100644 --- a/includes/Checker/Checks/Late_Escaping_Check.php +++ b/includes/Checker/Checks/Late_Escaping_Check.php @@ -7,6 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; +use WordPress\Plugin_Check\Traits\Stable_Check; + /** * Check for running WordPress escape output sniffs. * @@ -14,6 +17,21 @@ */ class Late_Escaping_Check extends Abstract_PHP_CodeSniffer_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_SECURITY ); + } + /** * Returns an associative array of arguments to pass to PHPCS. * diff --git a/includes/Checker/Checks/Localhost_Check.php b/includes/Checker/Checks/Localhost_Check.php index 4a1a17272..b700859a8 100644 --- a/includes/Checker/Checks/Localhost_Check.php +++ b/includes/Checker/Checks/Localhost_Check.php @@ -7,7 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; +use WordPress\Plugin_Check\Traits\Stable_Check; /** * Check for detecting localhost in plugin files. @@ -16,6 +18,21 @@ */ class Localhost_Check extends Abstract_File_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Check the localhost in files. * diff --git a/includes/Checker/Checks/No_Unfiltered_Uploads_Check.php b/includes/Checker/Checks/No_Unfiltered_Uploads_Check.php index 161fdb031..7933bf72a 100644 --- a/includes/Checker/Checks/No_Unfiltered_Uploads_Check.php +++ b/includes/Checker/Checks/No_Unfiltered_Uploads_Check.php @@ -7,7 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; +use WordPress\Plugin_Check\Traits\Stable_Check; /** * Check for detecting "ALLOW_UNFILTERED_UPLOADS" constant in plugin files. @@ -16,6 +18,21 @@ */ class No_Unfiltered_Uploads_Check extends Abstract_File_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Check the "ALLOW_UNFILTERED_UPLOADS" constant in file. * diff --git a/includes/Checker/Checks/Performant_WP_Query_Params_Check.php b/includes/Checker/Checks/Performant_WP_Query_Params_Check.php index 535aa116c..6ada74eb7 100644 --- a/includes/Checker/Checks/Performant_WP_Query_Params_Check.php +++ b/includes/Checker/Checks/Performant_WP_Query_Params_Check.php @@ -7,6 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; +use WordPress\Plugin_Check\Traits\Stable_Check; + /** * Check for running WordPress performant WP_Query params sniffs. * @@ -14,6 +17,21 @@ */ class Performant_WP_Query_Params_Check extends Abstract_PHP_CodeSniffer_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PERFORMANCE ); + } + /** * Returns an associative array of arguments to pass to PHPCS. * diff --git a/includes/Checker/Checks/Plugin_Header_Text_Domain_Check.php b/includes/Checker/Checks/Plugin_Header_Text_Domain_Check.php index e498303fc..2939e8014 100644 --- a/includes/Checker/Checks/Plugin_Header_Text_Domain_Check.php +++ b/includes/Checker/Checks/Plugin_Header_Text_Domain_Check.php @@ -8,8 +8,10 @@ namespace WordPress\Plugin_Check\Checker\Checks; 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\Traits\Stable_Check; /** * Check for plugin header text domain. @@ -18,6 +20,21 @@ */ class Plugin_Header_Text_Domain_Check implements Static_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Amends the given result by running the check on the associated plugin. * diff --git a/includes/Checker/Checks/Plugin_Readme_Check.php b/includes/Checker/Checks/Plugin_Readme_Check.php index f9956d8e8..34895f5a5 100644 --- a/includes/Checker/Checks/Plugin_Readme_Check.php +++ b/includes/Checker/Checks/Plugin_Readme_Check.php @@ -7,7 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; +use WordPress\Plugin_Check\Traits\Stable_Check; /** * Check the plugins readme.txt file and contents. @@ -16,6 +18,21 @@ */ class Plugin_Readme_Check extends Abstract_File_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Check the readme.txt file. * diff --git a/includes/Checker/Checks/Plugin_Review_PHPCS_Check.php b/includes/Checker/Checks/Plugin_Review_PHPCS_Check.php index d23c6f2af..483124471 100644 --- a/includes/Checker/Checks/Plugin_Review_PHPCS_Check.php +++ b/includes/Checker/Checks/Plugin_Review_PHPCS_Check.php @@ -7,6 +7,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; +use WordPress\Plugin_Check\Checker\Check_Categories; +use WordPress\Plugin_Check\Traits\Stable_Check; + /** * Check for running WordPress plugin review PHPCS standard. * @@ -14,6 +17,21 @@ */ class Plugin_Review_PHPCS_Check extends Abstract_PHP_CodeSniffer_Check { + use Stable_Check; + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Returns an associative array of arguments to pass to PHPCS. * diff --git a/includes/Checker/Checks/Plugin_Updater_Check.php b/includes/Checker/Checks/Plugin_Updater_Check.php index 5dda6e329..befd82f2c 100644 --- a/includes/Checker/Checks/Plugin_Updater_Check.php +++ b/includes/Checker/Checks/Plugin_Updater_Check.php @@ -8,7 +8,9 @@ namespace WordPress\Plugin_Check\Checker\Checks; use Exception; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\Check_Result; +use WordPress\Plugin_Check\Traits\Stable_Check; /** * Check to detect plugin updater. @@ -17,6 +19,8 @@ */ class Plugin_Updater_Check extends Abstract_File_Check { + use Stable_Check; + const TYPE_PLUGIN_UPDATE_URI_HEADER = 1; const TYPE_PLUGIN_UPDATER_FILE = 2; const TYPE_PLUGIN_UPDATERS = 4; @@ -42,6 +46,19 @@ public function __construct( $flags = self::TYPE_ALL ) { $this->flags = $flags; } + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since n.e.x.t + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + /** * Amends the given result by running the check on the given list of files. * diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index 1f8fcac8c..761b0fee3 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -44,7 +44,13 @@ class Default_Check_Repository implements Check_Repository { */ public function register_check( $slug, Check $check ) { if ( ! $check instanceof Runtime_Check && ! $check instanceof Static_Check ) { - throw new Exception( __( 'Check must be an instance of Runtime_Check or Static_Check.', 'plugin-check' ) ); + throw new Exception( + sprintf( + /* translators: %s: The Check slug. */ + __( 'Check with slug "%s" must be an instance of Runtime_Check or Static_Check.', 'plugin-check' ), + $slug + ) + ); } if ( isset( $this->runtime_checks[ $slug ] ) || isset( $this->static_checks[ $slug ] ) ) { @@ -57,6 +63,16 @@ public function register_check( $slug, Check $check ) { ); } + if ( ! $check->get_categories() ) { + throw new Exception( + sprintf( + /* translators: %s: The Check slug. */ + __( 'Check with slug "%s" has no categories associated with it.', 'plugin-check' ), + $slug + ) + ); + } + $check_array = $check instanceof Runtime_Check ? 'runtime_checks' : 'static_checks'; $this->{$check_array}[ $slug ] = $check; } @@ -103,6 +119,17 @@ function ( $slug ) use ( $checks ) { ); } - return $checks; + // Return all checks, including experimental if requested. + if ( $flags & self::INCLUDE_EXPERIMENTAL ) { + return $checks; + } + + // Remove experimental checks before returning. + return array_filter( + $checks, + static function ( $check ) { + return $check->get_stability() !== Check::STABILITY_EXPERIMENTAL; + } + ); } } diff --git a/includes/Checker/Runtime_Environment_Setup.php b/includes/Checker/Runtime_Environment_Setup.php index b85f10edb..a3c742f46 100644 --- a/includes/Checker/Runtime_Environment_Setup.php +++ b/includes/Checker/Runtime_Environment_Setup.php @@ -55,7 +55,7 @@ public function set_up() { if ( $wp_filesystem || WP_Filesystem() ) { // Do not replace the object-cache.php file if it already exists. if ( ! $wp_filesystem->exists( WP_CONTENT_DIR . '/object-cache.php' ) ) { - $wp_filesystem->copy( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php', WP_CONTENT_DIR . '/object-cache.php' ); + $wp_filesystem->copy( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php', WP_CONTENT_DIR . '/object-cache.php' ); } } } @@ -93,7 +93,7 @@ public function clean_up() { // Check the drop-in file matches the copy. $original_content = $wp_filesystem->get_contents( WP_CONTENT_DIR . '/object-cache.php' ); - $copy_content = $wp_filesystem->get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php' ); + $copy_content = $wp_filesystem->get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php' ); if ( $original_content && $original_content === $copy_content ) { $wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' ); @@ -126,9 +126,9 @@ public function can_set_up() { } else { // Get the correct Plugin Check directory when run too early. if ( ! defined( 'WP_PLUGIN_CHECK_PLUGIN_DIR_PATH' ) ) { - $object_cache_copy = dirname( dirname( __DIR__ ) ) . '/plugin-check/object-cache.copy.php'; + $object_cache_copy = dirname( dirname( __DIR__ ) ) . '/plugin-check/drop-ins/object-cache.copy.php'; } else { - $object_cache_copy = WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php'; + $object_cache_copy = WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php'; } // If the file does not exist, check if we can place it. diff --git a/includes/Traits/Experimental_Check.php b/includes/Traits/Experimental_Check.php new file mode 100644 index 000000000..246c312a7 --- /dev/null +++ b/includes/Traits/Experimental_Check.php @@ -0,0 +1,26 @@ +admin_page->filter_plugin_action_links( array(), $base_file ); + $action_links = $this->admin_page->filter_plugin_action_links( array(), $base_file, array(), 'all' ); $this->assertEmpty( $action_links ); /** Administrator check */ @@ -144,7 +144,7 @@ public function test_filter_plugin_action_links() { grant_super_admin( $admin_user ); } wp_set_current_user( $admin_user ); - $action_links = $this->admin_page->filter_plugin_action_links( array(), $base_file ); + $action_links = $this->admin_page->filter_plugin_action_links( array(), $base_file, array(), 'all' ); $this->assertEquals( sprintf( @@ -155,4 +155,63 @@ public function test_filter_plugin_action_links() { $action_links[0] ); } + + public function test_filter_plugin_action_links_should_not_add_check_link_for_plugin_checker() { + + $base_file = plugin_basename( WP_PLUGIN_CHECK_MAIN_FILE ); + + $action_links = $this->admin_page->filter_plugin_action_links( array(), $base_file, array(), 'all' ); + $this->assertEmpty( $action_links ); + + /** Administrator check */ + $admin_user = self::factory()->user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( $admin_user ); + } + wp_set_current_user( $admin_user ); + $action_links = $this->admin_page->filter_plugin_action_links( array(), $base_file, array(), 'all' ); + + $this->assertEmpty( $action_links ); + } + + /** + * @dataProvider data_status_mustuse_and_dropins + */ + public function test_filter_plugin_action_links_should_not_add_check( $context, $is_admin ) { + + if ( $is_admin ) { + /** Administrator check */ + $admin_user = self::factory()->user->create( array( 'role' => 'administrator' ) ); + + if ( is_multisite() ) { + grant_super_admin( $admin_user ); + } + wp_set_current_user( $admin_user ); + } + + $actions = array( 'Test Action' ); + $actual = $this->admin_page->filter_plugin_action_links( + $actions, + plugin_basename( WP_PLUGIN_CHECK_MAIN_FILE ), + array(), + $context + ); + + $this->assertSame( $actions, $actual ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_status_mustuse_and_dropins() { + return array( + 'Must-Use' => array( 'mustuse', false ), + 'Must-Use with Admininistrator check' => array( 'mustuse', true ), + 'Drop-ins' => array( 'dropins', false ), + 'Drop-ins with Admininistrator check' => array( 'dropins', true ), + ); + } } diff --git a/tests/phpunit/Checker/Check_Categories_Tests.php b/tests/phpunit/Checker/Check_Categories_Tests.php new file mode 100644 index 000000000..a6d26035b --- /dev/null +++ b/tests/phpunit/Checker/Check_Categories_Tests.php @@ -0,0 +1,145 @@ +repository = new Default_Check_Repository(); + } + + public function test_get_categories() { + $check_categories = new Check_Categories(); + $categories = $check_categories->get_categories(); + + $reflection_class = new ReflectionClass( Check_Categories::class ); + $category_constants = $reflection_class->getConstants(); + + // Assert that all the CATEGORY_* constants are included in the returned categories array. + foreach ( $category_constants as $constant_value ) { + $this->assertContains( $constant_value, $categories ); + } + } + + /** + * @dataProvider data_checks_by_categories + */ + public function test_filter_checks_by_categories( array $categories, array $all_checks, array $expected_filtered_checks ) { + + foreach ( $all_checks as $check ) { + $this->repository->register_check( $check[0], $check[1] ); + } + + $checks = $this->repository->get_checks(); + + $check_categories = new Check_Categories(); + $filtered_checks = $check_categories->filter_checks_by_categories( $checks, $categories ); + + $this->assertEquals( $expected_filtered_checks, $filtered_checks ); + } + + public function data_checks_by_categories() { + + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_One.php'; + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_Two.php'; + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_Three.php'; + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_Four.php'; + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_Five.php'; + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_Six.php'; + require TESTS_PLUGIN_DIR . '/tests/phpunit/testdata/Checks/Category_Check_Seven.php'; + + $category_check_one = new Category_Check_One(); + $category_check_two = new Category_Check_Two(); + $category_check_three = new Category_Check_Three(); + $category_check_four = new Category_Check_Four(); + $category_check_five = new Category_Check_Five(); + $category_check_six = new Category_Check_Six(); + $category_check_seven = new Category_Check_Seven(); + + return array( + 'filter checks by general, plugin repo, and security categories' => array( + array( + Check_Categories::CATEGORY_GENERAL, + Check_Categories::CATEGORY_PLUGIN_REPO, + Check_Categories::CATEGORY_SECURITY, + ), + array( + array( 'Category_Check_One', $category_check_one ), + array( 'Category_Check_Two', $category_check_two ), + array( 'Category_Check_Three', $category_check_three ), + array( 'Category_Check_Four', $category_check_four ), + array( 'Category_Check_Five', $category_check_five ), + array( 'Category_Check_Six', $category_check_six ), + ), + array( + 'Category_Check_One' => $category_check_one, + 'Category_Check_Two' => $category_check_two, + 'Category_Check_Three' => $category_check_three, + 'Category_Check_Six' => $category_check_six, + ), + ), + 'filter checks by performance category' => array( + array( + Check_Categories::CATEGORY_PERFORMANCE, + ), + array( + array( 'Category_Check_One', $category_check_one ), + array( 'Category_Check_Two', $category_check_two ), + array( 'Category_Check_Three', $category_check_three ), + array( 'Category_Check_Four', $category_check_four ), + array( 'Category_Check_Five', $category_check_five ), + array( 'Category_Check_Six', $category_check_six ), + ), + array( + 'Category_Check_Four' => $category_check_four, + ), + ), + 'filter checks for multiple categories' => array( + array( + Check_Categories::CATEGORY_PLUGIN_REPO, + ), + array( + array( 'Category_Check_One', $category_check_one ), + array( 'Category_Check_Two', $category_check_two ), + array( 'Category_Check_Three', $category_check_three ), + array( 'Category_Check_Four', $category_check_four ), + array( 'Category_Check_Five', $category_check_five ), + array( 'Category_Check_Six', $category_check_six ), + array( 'Category_Check_Seven', $category_check_seven ), + ), + array( + 'Category_Check_Two' => $category_check_two, + 'Category_Check_Seven' => $category_check_seven, + ), + ), + 'filter checks by non-existing category' => array( + array( + 'plugin_demo', + ), + array( + array( 'Category_Check_One', $category_check_one ), + array( 'Category_Check_Two', $category_check_two ), + array( 'Category_Check_Three', $category_check_three ), + array( 'Category_Check_Four', $category_check_four ), + array( 'Category_Check_Five', $category_check_five ), + array( 'Category_Check_Six', $category_check_six ), + ), + array(), + ), + ); + } +} diff --git a/tests/phpunit/Checker/Checks/Enqueued_Scripts_In_Footer_Check_Tests.php b/tests/phpunit/Checker/Checks/Enqueued_Scripts_In_Footer_Check_Tests.php index 613277f35..b3120be30 100644 --- a/tests/phpunit/Checker/Checks/Enqueued_Scripts_In_Footer_Check_Tests.php +++ b/tests/phpunit/Checker/Checks/Enqueued_Scripts_In_Footer_Check_Tests.php @@ -8,9 +8,8 @@ use WordPress\Plugin_Check\Checker\Check_Context; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Checks\Enqueued_Scripts_In_Footer_Check; -use WordPress\Plugin_Check\Test_Utils\TestCase\Static_Check_UnitTestCase; -class Enqueued_Scripts_In_Footer_Check_Tests extends Static_Check_UnitTestCase { +class Enqueued_Scripts_In_Footer_Check_Tests extends WP_UnitTestCase { public function test_run_with_errors() { $enqueued_scripts_in_footer_check = new Enqueued_Scripts_In_Footer_Check(); diff --git a/tests/phpunit/Checker/Checks/I18n_Usage_Check_Tests.php b/tests/phpunit/Checker/Checks/I18n_Usage_Check_Tests.php index a7e9eba5c..7b6582ab0 100644 --- a/tests/phpunit/Checker/Checks/I18n_Usage_Check_Tests.php +++ b/tests/phpunit/Checker/Checks/I18n_Usage_Check_Tests.php @@ -8,9 +8,8 @@ use WordPress\Plugin_Check\Checker\Check_Context; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Checks\I18n_Usage_Check; -use WordPress\Plugin_Check\Test_Utils\TestCase\Static_Check_UnitTestCase; -class I18n_Usage_Check_Tests extends Static_Check_UnitTestCase { +class I18n_Usage_Check_Tests extends WP_UnitTestCase { public function test_run_with_errors() { $i18n_usage_check = new I18n_Usage_Check(); diff --git a/tests/phpunit/Checker/Checks/Late_Escaping_Check_Tests.php b/tests/phpunit/Checker/Checks/Late_Escaping_Check_Tests.php index fee7f1d5a..8f56ce46f 100644 --- a/tests/phpunit/Checker/Checks/Late_Escaping_Check_Tests.php +++ b/tests/phpunit/Checker/Checks/Late_Escaping_Check_Tests.php @@ -8,9 +8,8 @@ use WordPress\Plugin_Check\Checker\Check_Context; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Checks\Late_Escaping_Check; -use WordPress\Plugin_Check\Test_Utils\TestCase\Static_Check_UnitTestCase; -class Late_Escaping_Check_Tests extends Static_Check_UnitTestCase { +class Late_Escaping_Check_Tests extends WP_UnitTestCase { public function test_run_with_errors() { $late_escape_check = new Late_Escaping_Check(); diff --git a/tests/phpunit/Checker/Checks/Performant_WP_Query_Params_Check_Tests.php b/tests/phpunit/Checker/Checks/Performant_WP_Query_Params_Check_Tests.php index d166359cd..1aa95fed1 100644 --- a/tests/phpunit/Checker/Checks/Performant_WP_Query_Params_Check_Tests.php +++ b/tests/phpunit/Checker/Checks/Performant_WP_Query_Params_Check_Tests.php @@ -8,9 +8,8 @@ use WordPress\Plugin_Check\Checker\Check_Context; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Checks\Performant_WP_Query_Params_Check; -use WordPress\Plugin_Check\Test_Utils\TestCase\Static_Check_UnitTestCase; -class Performant_WP_Query_Params_Check_Tests extends Static_Check_UnitTestCase { +class Performant_WP_Query_Params_Check_Tests extends WP_UnitTestCase { public function test_run_with_errors() { $performant_query = new Performant_WP_Query_Params_Check(); diff --git a/tests/phpunit/Checker/Checks/Plugin_Review_PHPCS_Check_Tests.php b/tests/phpunit/Checker/Checks/Plugin_Review_PHPCS_Check_Tests.php index a8419f7ed..be99615b0 100644 --- a/tests/phpunit/Checker/Checks/Plugin_Review_PHPCS_Check_Tests.php +++ b/tests/phpunit/Checker/Checks/Plugin_Review_PHPCS_Check_Tests.php @@ -8,9 +8,8 @@ use WordPress\Plugin_Check\Checker\Check_Context; use WordPress\Plugin_Check\Checker\Check_Result; use WordPress\Plugin_Check\Checker\Checks\Plugin_Review_PHPCS_Check; -use WordPress\Plugin_Check\Test_Utils\TestCase\Static_Check_UnitTestCase; -class Plugin_Review_PHPCS_Check_Tests extends Static_Check_UnitTestCase { +class Plugin_Review_PHPCS_Check_Tests extends WP_UnitTestCase { public function test_run_with_errors() { $plugin_review_phpcs_check = new Plugin_Review_PHPCS_Check(); diff --git a/tests/phpunit/Checker/Default_Check_Repository_Tests.php b/tests/phpunit/Checker/Default_Check_Repository_Tests.php index bef748071..8822ce622 100644 --- a/tests/phpunit/Checker/Default_Check_Repository_Tests.php +++ b/tests/phpunit/Checker/Default_Check_Repository_Tests.php @@ -7,6 +7,9 @@ use WordPress\Plugin_Check\Checker\Check_Repository; use WordPress\Plugin_Check\Checker\Default_Check_Repository; +use WordPress\Plugin_Check\Test_Data\Check_Without_Category; +use WordPress\Plugin_Check\Test_Data\Experimental_Runtime_Check; +use WordPress\Plugin_Check\Test_Data\Experimental_Static_Check; use WordPress\Plugin_Check\Test_Data\Invalid_Check; use WordPress\Plugin_Check\Test_Data\Runtime_Check; use WordPress\Plugin_Check\Test_Data\Static_Check; @@ -35,7 +38,7 @@ public function test_register_runtime_check() { public function test_register_exception_thrown_for_invalid_check() { $this->expectException( 'Exception' ); - $this->expectExceptionMessage( 'Check must be an instance of Runtime_Check or Static_Check.' ); + $this->expectExceptionMessage( 'Check with slug "empty_check" must be an instance of Runtime_Check or Static_Check.' ); $this->repository->register_check( 'empty_check', new Invalid_Check() ); } @@ -64,6 +67,13 @@ public function test_register_exception_thrown_for_existing_check_slug_between_t $this->repository->register_check( 'check', new Runtime_Check() ); } + public function test_register_exception_thrown_for_missing_categories() { + $this->expectException( 'Exception' ); + $this->expectExceptionMessage( 'Check with slug "check" has no categories associated with it.' ); + + $this->repository->register_check( 'check', new Check_Without_Category() ); + } + public function test_get_checks_returns_all_checks() { $static_check = new Static_Check(); $runtime_check = new Runtime_Check(); @@ -115,4 +125,82 @@ public function test_get_checks_throws_exception_for_invalid_check_slug() { $this->repository->get_checks( Check_Repository::TYPE_ALL, array( 'invalid_check' ) ); } + + public function test_get_checks_returns_no_experimental_checks_by_default() { + $static_check = new Static_Check(); + $runtime_check = new Runtime_Check(); + $experimental_static_check = new Experimental_Static_Check(); + $experimental_runtime_check = new Experimental_Runtime_Check(); + + $this->repository->register_check( 'static_check', $static_check ); + $this->repository->register_check( 'runtime_check', $runtime_check ); + $this->repository->register_check( 'experimental_static_check', $experimental_static_check ); + $this->repository->register_check( 'experimental_runtime_check', $experimental_runtime_check ); + + $expected = array( + 'static_check' => $static_check, + 'runtime_check' => $runtime_check, + ); + + $this->assertSame( $expected, $this->repository->get_checks() ); + } + + public function test_get_checks_returns_experimental_checks_with_flag() { + $static_check = new Static_Check(); + $runtime_check = new Runtime_Check(); + $experimental_static_check = new Experimental_Static_Check(); + $experimental_runtime_check = new Experimental_Runtime_Check(); + + $this->repository->register_check( 'static_check', $static_check ); + $this->repository->register_check( 'runtime_check', $runtime_check ); + $this->repository->register_check( 'experimental_static_check', $experimental_static_check ); + $this->repository->register_check( 'experimental_runtime_check', $experimental_runtime_check ); + + $expected = array( + 'static_check' => $static_check, + 'experimental_static_check' => $experimental_static_check, + 'runtime_check' => $runtime_check, + 'experimental_runtime_check' => $experimental_runtime_check, + ); + + $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_ALL | Check_Repository::INCLUDE_EXPERIMENTAL ) ); + } + + public function test_get_checks_returns_experimental_static_checks_with_flag() { + $static_check = new Static_Check(); + $runtime_check = new Runtime_Check(); + $experimental_static_check = new Experimental_Static_Check(); + $experimental_runtime_check = new Experimental_Runtime_Check(); + + $this->repository->register_check( 'static_check', $static_check ); + $this->repository->register_check( 'runtime_check', $runtime_check ); + $this->repository->register_check( 'experimental_static_check', $experimental_static_check ); + $this->repository->register_check( 'experimental_runtime_check', $experimental_runtime_check ); + + $expected = array( + 'static_check' => $static_check, + 'experimental_static_check' => $experimental_static_check, + ); + + $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_STATIC | Check_Repository::INCLUDE_EXPERIMENTAL ) ); + } + + public function test_get_checks_returns_experimental_runtime_checks_with_flag() { + $static_check = new Static_Check(); + $runtime_check = new Runtime_Check(); + $experimental_static_check = new Experimental_Static_Check(); + $experimental_runtime_check = new Experimental_Runtime_Check(); + + $this->repository->register_check( 'static_check', $static_check ); + $this->repository->register_check( 'runtime_check', $runtime_check ); + $this->repository->register_check( 'experimental_static_check', $experimental_static_check ); + $this->repository->register_check( 'experimental_runtime_check', $experimental_runtime_check ); + + $expected = array( + 'runtime_check' => $runtime_check, + 'experimental_runtime_check' => $experimental_runtime_check, + ); + + $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_RUNTIME | Check_Repository::INCLUDE_EXPERIMENTAL ) ); + } } diff --git a/tests/phpunit/Checker/Runtime_Environment_Setup_Tests.php b/tests/phpunit/Checker/Runtime_Environment_Setup_Tests.php index d2ef289c6..6f78930fb 100644 --- a/tests/phpunit/Checker/Runtime_Environment_Setup_Tests.php +++ b/tests/phpunit/Checker/Runtime_Environment_Setup_Tests.php @@ -22,7 +22,7 @@ public function test_set_up() { $this->assertTrue( 0 <= strpos( $wpdb->last_query, $table_prefix . 'pc_' ) ); $this->assertTrue( $wp_filesystem->exists( WP_CONTENT_DIR . '/object-cache.php' ) ); - $this->assertSame( file_get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php' ), $wp_filesystem->get_contents( WP_CONTENT_DIR . '/object-cache.php' ) ); + $this->assertSame( file_get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php' ), $wp_filesystem->get_contents( WP_CONTENT_DIR . '/object-cache.php' ) ); } public function test_setup_with_existing_object_cache() { diff --git a/tests/phpunit/testdata/Checks/Category_Check_Five.php b/tests/phpunit/testdata/Checks/Category_Check_Five.php new file mode 100644 index 000000000..9b58d0a6a --- /dev/null +++ b/tests/phpunit/testdata/Checks/Category_Check_Five.php @@ -0,0 +1,21 @@ +add_message( true, @@ -18,4 +23,8 @@ public function run( Check_Result $check_result ) { ) ); } + + public function get_categories() { + return array( Check_Categories::CATEGORY_GENERAL ); + } } diff --git a/tests/phpunit/testdata/Checks/Experimental_Runtime_Check.php b/tests/phpunit/testdata/Checks/Experimental_Runtime_Check.php new file mode 100644 index 000000000..7b1101548 --- /dev/null +++ b/tests/phpunit/testdata/Checks/Experimental_Runtime_Check.php @@ -0,0 +1,21 @@ +getProperty( 'overriddenDefaults' ); - $overridden_defaults->setAccessible( true ); - $overridden_defaults->setValue( array() ); - $overridden_defaults->setAccessible( false ); - } - } -} diff --git a/tests/phpunit/utils/Traits/With_Mock_Filesystem.php b/tests/phpunit/utils/Traits/With_Mock_Filesystem.php index e0980a7b0..f4a474630 100644 --- a/tests/phpunit/utils/Traits/With_Mock_Filesystem.php +++ b/tests/phpunit/utils/Traits/With_Mock_Filesystem.php @@ -32,7 +32,7 @@ function() { WP_Filesystem(); // Simulate that the original object-cache.copy.php file exists. - $wp_filesystem->put_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php', file_get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php' ) ); + $wp_filesystem->put_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php', file_get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php' ) ); } /** @@ -59,6 +59,6 @@ function() { WP_Filesystem(); // Simulate that the original object-cache.copy.php file exists. - $wp_filesystem->put_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php', file_get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'object-cache.copy.php' ) ); + $wp_filesystem->put_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php', file_get_contents( WP_PLUGIN_CHECK_PLUGIN_DIR_PATH . 'drop-ins/object-cache.copy.php' ) ); } }