File: /var/www/html/wp-content/plugins/plugin-check/includes/Checker/Abstract_Check_Runner.php
<?php
/**
* Class WordPress\Plugin_Check\Checker\Abstract_Check_runner
*
* @package plugin-check
*/
namespace WordPress\Plugin_Check\Checker;
use Exception;
use WordPress\Plugin_Check\Checker\Exception\Invalid_Check_Slug_Exception;
use WordPress\Plugin_Check\Checker\Preparations\Universal_Runtime_Preparation;
use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility;
/**
* Abstract Check Runner class.
*
* @since 1.0.0
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
*/
abstract class Abstract_Check_Runner implements Check_Runner {
/**
* True if the class was initialized early in the WordPress load process.
*
* @since 1.0.0
* @var bool
*/
protected $initialized_early;
/**
* The check slugs to run.
*
* @since 1.0.0
* @var array
*/
protected $check_slugs;
/**
* The plugin slug.
*
* @since 1.2.0
* @var string
*/
protected $slug;
/**
* The check slugs to exclude.
*
* @since 1.0.0
* @var array
*/
protected $check_exclude_slugs;
/**
* The plugin parameter.
*
* @since 1.0.0
* @var string
*/
protected $plugin;
/**
* An instance of the Checks class.
*
* @since 1.0.0
* @var Checks
*/
protected $checks;
/**
* The plugin basename to check.
*
* @since 1.0.0
* @var string
*/
protected $plugin_basename;
/**
* Whether to delete the plugin folder during cleanup.
*
* Used when downloading a plugin from a URL.
*
* @since 1.1.0
* @var bool
*/
private $delete_plugin_folder = false;
/**
* An instance of the Check_Repository.
*
* @since 1.0.0
* @var Check_Repository
*/
private $check_repository;
/**
* Runtime environment.
*
* @since 1.0.0
* @var Runtime_Environment_Setup
*/
protected $runtime_environment;
/**
* Whether to include experimental checks.
*
* @since 1.0.0
* @var bool
*/
protected $include_experimental;
/**
* Checks category for the filter.
*
* @since 1.0.0
* @var array
*/
protected $check_categories;
/**
* The mode to run checks in.
*
* @since 1.7.0
* @var string
*/
protected $mode;
/**
* Returns the plugin parameter based on the request.
*
* @since 1.0.0
*
* @return string The plugin parameter from the request.
*/
abstract protected function get_plugin_param();
/**
* Returns an array of Check slugs to run based on the request.
*
* @since 1.0.0
*
* @return array An array of Check slugs.
*/
abstract protected function get_check_slugs_param();
/**
* Returns an array of Check slugs to exclude based on the request.
*
* @since 1.0.0
*
* @return array An array of Check slugs.
*/
abstract protected function get_check_exclude_slugs_param();
/**
* Returns the include experimental parameter based on the request.
*
* @since 1.0.0
*
* @return bool Returns true to include experimental checks else false.
*/
abstract protected function get_include_experimental_param();
/**
* Returns an array of categories for filtering the checks.
*
* @since 1.0.0
*
* @return array An array of categories.
*/
abstract protected function get_categories_param();
/**
* Returns plugin slug parameter.
*
* @since 1.2.0
*
* @return string Plugin slug.
*/
abstract protected function get_slug_param();
/**
* Returns the mode parameter.
*
* @since 1.7.0
*
* @return string The mode parameter.
*/
abstract protected function get_mode_param();
/**
* Sets whether the runner class was initialized early.
*
* @since 1.0.0
*/
final public function __construct() {
$this->initialized_early = ! did_action( 'muplugins_loaded' );
$this->check_repository = new Default_Check_Repository();
$this->runtime_environment = new Runtime_Environment_Setup();
}
/**
* Sets the check slugs to be run.
*
* @since 1.0.0
*
* @param array $check_slugs An array of check slugs to be run.
*
* @throws Exception Thrown if the checks do not match those in the original request.
*/
final public function set_check_slugs( array $check_slugs ) {
if ( $this->initialized_early ) {
// Compare the check slugs to see if there was an error.
if ( $check_slugs !== $this->get_check_slugs_param() ) {
throw new Exception(
__( 'Invalid checks: The checks to run do not match the original request.', 'plugin-check' )
);
}
}
$this->check_slugs = $check_slugs;
}
/**
* Sets the check slugs to be excluded.
*
* @since 1.0.0
*
* @param array $check_slugs An array of check slugs to be excluded.
*
* @throws Exception Thrown if the checks do not match those in the original request.
*/
final public function set_check_exclude_slugs( array $check_slugs ) {
if ( $this->initialized_early ) {
// Compare the check slugs to see if there was an error.
if ( $check_slugs !== $this->get_check_exclude_slugs_param() ) {
throw new Exception(
__( 'Invalid checks: The checks to exclude do not match the original request.', 'plugin-check' )
);
}
}
$this->check_exclude_slugs = $check_slugs;
}
/**
* Sets the plugin slug or basename to be checked.
*
* @since 1.0.0
*
* @param string $plugin The plugin slug or basename to be checked.
*
* @throws Exception Thrown if the plugin set does not match the original request parameter.
*/
final public function set_plugin( $plugin ) {
if ( $this->initialized_early ) {
// Compare the plugin parameter to see if there was an error.
if ( $plugin !== $this->get_plugin_param() ) {
throw new Exception(
__( 'Invalid plugin: The plugin set does not match the original request parameter.', 'plugin-check' )
);
}
}
$this->plugin = $plugin;
}
/**
* Sets whether to include experimental checks in the process.
*
* @since 1.0.0
*
* @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(
sprintf(
/* translators: %s: include-experimental */
__( 'Invalid flag: The %s value does not match the original request parameter.', 'plugin-check' ),
'include-experimental'
)
);
}
}
$this->include_experimental = $include_experimental;
}
/**
* Sets categories for filtering the checks.
*
* @since 1.0.0
*
* @param array $categories An array of categories for filtering.
*
* @throws Exception Thrown if the categories does not match the original request parameter.
*/
final public function set_categories( $categories ) {
if ( $this->initialized_early ) {
if ( $categories !== $this->get_categories_param() ) {
throw new Exception(
sprintf(
/* translators: %s: categories */
__( 'Invalid categories: The %s value does not match the original request parameter.', 'plugin-check' ),
'categories'
)
);
}
}
$this->check_categories = $categories;
}
/**
* Prepares the environment for running the requested checks.
*
* @since 1.0.0
*
* @return callable Cleanup function to revert any changes made here.
*
* @throws Exception Thrown exception when preparation fails.
*/
final public function prepare() {
if ( $this->initialized_early ) {
/*
* When initialized early, plugins are not loaded yet when this method is called.
* Therefore it could be that check slugs provided refer to addon checks that are not loaded yet.
* In that case, the only reliable option is to assume that it refers to an addon check and that the addon
* check is a runtime check. We don't know, but better to have the runtime preparations initialize
* unnecessarily rather than not having them when needed.
*
* The actual checks to run are retrieved later (once plugins are loaded), so if one of the provided slugs
* is actually invalid, the exception will still be thrown at that point.
*/
try {
$checks = $this->get_checks_to_run();
$initialize_runtime = $this->has_runtime_check( $checks );
} catch ( Invalid_Check_Slug_Exception $e ) {
$initialize_runtime = true;
}
} else {
// When not initialized early, all checks are loaded, so we can simply see if there are runtime checks.
$initialize_runtime = $this->has_runtime_check( $this->get_checks_to_run() );
}
$cleanup_functions = array();
if ( $initialize_runtime ) {
$cleanup_functions = $this->initialize_runtime();
}
if ( $this->delete_plugin_folder ) {
$cleanup_functions = function () {
// It must be a directory at this point, but double check just in case.
if ( is_dir( $this->plugin_basename ) ) {
rmdir( $this->plugin_basename );
}
};
}
return function () use ( $cleanup_functions ) {
foreach ( $cleanup_functions as $cleanup_function ) {
$cleanup_function();
}
};
}
/**
* Runs the checks against the plugin.
*
* @since 1.0.0
*
* @return Check_Result An object containing all check results.
*/
final public function run() {
$checks = $this->get_checks_to_run();
$preparations = $this->get_shared_preparations( $checks );
$cleanups = array();
// Prepare all shared preparations.
foreach ( $preparations as $preparation ) {
$instance = new $preparation['class']( ...$preparation['args'] );
$cleanups[] = $instance->prepare();
}
$results = $this->get_checks_instance()->run_checks( $this->get_check_context(), $checks, $this );
if ( ! empty( $cleanups ) ) {
foreach ( $cleanups as $cleanup ) {
$cleanup();
}
}
return $results;
}
/**
* Determines if any of the checks are a runtime check.
*
* @since 1.0.0
*
* @param array $checks An array of check instances to run.
* @return bool Returns true if one or more checks is a runtime check.
*/
private function has_runtime_check( array $checks ) {
foreach ( $checks as $check ) {
if ( $check instanceof Runtime_Check ) {
return true;
}
}
return false;
}
/**
* Returns all shared preparations used by the checks to run.
*
* @since 1.0.0
*
* @param array $checks An array of Check instances to run.
* @return array An array of Preparations to run where each item is an array with keys `class` and `args`.
*/
private function get_shared_preparations( array $checks ) {
$shared_preparations = array();
foreach ( $checks as $check ) {
if ( ! $check instanceof With_Shared_Preparations ) {
continue;
}
$preparations = $check->get_shared_preparations();
foreach ( $preparations as $class => $args ) {
$key = $class . '::' . md5( json_encode( $args ) );
if ( ! isset( $shared_preparations[ $key ] ) ) {
$shared_preparations[ $key ] = array(
'class' => $class,
'args' => $args,
);
}
}
}
return array_values( $shared_preparations );
}
/**
* Returns the Check instances to run.
*
* @since 1.0.0
*
* @return array An array map of check slugs to Check instances.
*
* @throws Exception Thrown when invalid flag is passed, or Check slug does not exist.
*/
final public function get_checks_to_run() {
$check_slugs = $this->get_check_slugs();
$check_flags = Check_Repository::TYPE_STATIC;
// Check if conditions are met in order to perform Runtime Checks.
if ( $this->allow_runtime_checks() ) {
$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;
}
$excluded_checks = $this->get_check_exclude_slugs();
$collection = $this->check_repository->get_checks( $check_flags )
->require( $check_slugs ) // Ensures all of the given slugs are valid.
->include( $check_slugs ) // Ensures only the checks with the given slugs are included.
->exclude( $excluded_checks ); // Exclude provided checks from list.
// Filters the checks by specific categories.
$categories = $this->get_categories();
if ( $categories ) {
$collection = Check_Categories::filter_checks_by_categories( $collection, $categories );
}
return $collection->to_map();
}
/**
* Initializes the runtime environment so that runtime checks can be run against a separate set of database tables.
*
* @since 1.3.0
*
* @return callable[] Array of cleanup functions to run after the process has completed.
*/
protected function initialize_runtime(): array {
$preparation = new Universal_Runtime_Preparation( $this->get_check_context() );
return array( $preparation->prepare() );
}
/**
* Checks whether the current environment allows for runtime checks to be used.
*
* @since 1.2.0
*
* @return bool True if runtime checks are allowed, false otherwise.
*/
protected function allow_runtime_checks(): bool {
// Ensure that is_plugin_active() is available.
require_once ABSPATH . 'wp-admin/includes/plugin.php';
return ( $this->initialized_early || $this->runtime_environment->can_set_up() )
&& is_plugin_active( $this->get_plugin_basename() );
}
/**
* Creates and returns the Check instance.
*
* @since 1.0.0
*
* @return Checks An instance of the Checks class.
*
* @throws Exception Thrown if the plugin slug is invalid.
*/
protected function get_checks_instance() {
if ( null !== $this->checks ) {
return $this->checks;
}
$this->checks = new Checks();
return $this->checks;
}
/**
* Returns the check slugs to run.
*
* @since 1.0.0
*
* @return array An array of check slugs to run.
*/
private function get_check_slugs() {
if ( null !== $this->check_slugs ) {
return $this->check_slugs;
}
return $this->get_check_slugs_param();
}
/**
* Returns the check slugs to exclude.
*
* @since 1.0.0
*
* @return array An array of check slugs to exclude.
*/
private function get_check_exclude_slugs() {
if ( null !== $this->check_exclude_slugs ) {
return $this->check_exclude_slugs;
}
return $this->get_check_exclude_slugs_param();
}
/**
* Returns the plugin basename.
*
* @since 1.0.0
*
* @return string The plugin basename to check.
*/
final public function get_plugin_basename() {
if ( null === $this->plugin_basename ) {
$plugin = null !== $this->plugin ? $this->plugin : $this->get_plugin_param();
if ( filter_var( $plugin, FILTER_VALIDATE_URL ) ) {
$this->plugin_basename = Plugin_Request_Utility::download_plugin( $plugin );
$this->delete_plugin_folder = true;
} elseif ( Plugin_Request_Utility::is_directory_valid_plugin( $plugin ) ) {
$this->plugin_basename = $plugin;
} else {
$this->plugin_basename = Plugin_Request_Utility::get_plugin_basename_from_input( $plugin );
}
}
return $this->plugin_basename;
}
/**
* Returns the value for the include experimental flag.
*
* @since 1.0.0
*
* @return bool True if experimental checks are included. False if not.
*/
final protected function get_include_experimental() {
if ( null !== $this->include_experimental ) {
return $this->include_experimental;
}
return $this->get_include_experimental_param();
}
/**
* Returns an array of categories for filtering the checks.
*
* @since 1.0.0
*
* @return array An array of categories.
*/
final protected function get_categories() {
if ( null !== $this->check_categories ) {
return $this->check_categories;
}
return $this->get_categories_param();
}
/**
* Returns plugin slug.
*
* @since 1.2.0
*
* @return string Plugin slug.
*/
final protected function get_slug() {
if ( null !== $this->slug ) {
return $this->slug;
}
return $this->get_slug_param();
}
/**
* Returns the mode to run checks in.
*
* @since 1.7.0
*
* @return string The check mode.
*/
final protected function get_mode() {
if ( null !== $this->mode ) {
return $this->mode;
}
return $this->get_mode_param();
}
/** Gets the Check_Context for the plugin.
*
* @since 1.0.0
*
* @return Check_Context The check context for the plugin file.
*/
private function get_check_context() {
$plugin_basename = $this->get_plugin_basename();
$plugin_path = is_dir( $plugin_basename ) ? $plugin_basename : WP_PLUGIN_DIR . '/' . $plugin_basename;
return new Check_Context( $plugin_path, $this->get_slug(), $this->get_mode() );
}
/**
* Sets the plugin slug.
*
* @since 1.2.0
*
* @param string $slug Plugin slug.
*/
final public function set_slug( $slug ) {
if ( ! empty( $slug ) ) {
$this->slug = $slug;
} else {
$basename = $this->get_plugin_basename();
$this->slug = ( '.' === pathinfo( $basename, PATHINFO_DIRNAME ) ) ? $basename : dirname( $basename );
}
}
/**
* Sets the runtime environment setup.
*
* @since 1.0.0
*
* @param Runtime_Environment_Setup $runtime_environment_setup Runtime environment instance.
*/
final public function set_runtime_environment_setup( $runtime_environment_setup ) {
$this->runtime_environment = $runtime_environment_setup;
}
/**
* Sets the mode to run checks in.
*
* @since 1.7.0
*
* @param string $mode The mode to run checks in.
*/
final public function set_mode( $mode ) {
if ( ! empty( $mode ) && in_array( $mode, array( 'new', 'update' ), true ) ) {
$this->mode = $mode;
} else {
$this->mode = 'new';
}
}
}