diff --git a/src/Basic_Requirement_Checker.php b/src/Basic_Requirement_Checker.php index 52d585057eac53765538ba5e7aea8f98b5288d28..7dc01af4026e796c74cb4c567a42c8b25055ba40 100644 --- a/src/Basic_Requirement_Checker.php +++ b/src/Basic_Requirement_Checker.php @@ -5,672 +5,673 @@ if ( ! interface_exists( 'WPDesk_Requirement_Checker' ) ) { } if ( ! class_exists( 'WPDesk_Basic_Requirement_Checker' ) ) { - /** - * Checks requirements for plugin - * have to be compatible with PHP 5.3.x - */ - class WPDesk_Basic_Requirement_Checker implements WPDesk_Requirement_Checker { + /** + * Checks requirements for plugin + * have to be compatible with PHP 5.3.x + */ + class WPDesk_Basic_Requirement_Checker implements WPDesk_Requirement_Checker { - const EXTENSION_NAME_OPENSSL = 'openssl'; + const EXTENSION_NAME_OPENSSL = 'openssl'; - const HOOK_ADMIN_NOTICES_ACTION = 'admin_notices'; + const HOOK_ADMIN_NOTICES_ACTION = 'admin_notices'; - const HOOK_PLUGIN_DEACTIVATED_ACTION = 'deactivated_plugin'; + const HOOK_PLUGIN_DEACTIVATED_ACTION = 'deactivated_plugin'; - const HOOK_PLUGIN_ACTIVATED_ACTION = 'activated_plugin'; + const HOOK_PLUGIN_ACTIVATED_ACTION = 'activated_plugin'; - const PLUGIN_INFO_KEY_NICE_NAME = 'nice_name'; + const PLUGIN_INFO_KEY_NICE_NAME = 'nice_name'; - const PLUGIN_INFO_KEY_NAME = 'name'; + const PLUGIN_INFO_KEY_NAME = 'name'; - const PLUGIN_INFO_VERSION = 'version'; + const PLUGIN_INFO_VERSION = 'version'; - const PLUGIN_INFO_FAKE_REQUIRED_MINIMUM_VERSION = '0.0'; + const PLUGIN_INFO_FAKE_REQUIRED_MINIMUM_VERSION = '0.0'; - const PLUGIN_INFO_APPEND_PLUGIN_DATA = 'required_version'; + const PLUGIN_INFO_APPEND_PLUGIN_DATA = 'required_version'; - const PLUGIN_INFO_TRANSIENT_NAME = 'require_plugins_data'; + const PLUGIN_INFO_TRANSIENT_NAME = 'require_plugins_data'; - const EXPIRATION_TRANSIENT_NAME = 'require_plugins_data_exp'; + const EXPIRATION_TRANSIENT_NAME = 'require_plugins_data_exp'; - const CACHE_TIME = 300; + const CACHE_TIME = 300; - /** @var string */ - protected $plugin_name; + /** @var string */ + protected $plugin_name; - /** @var string */ - private $plugin_file; + /** @var string */ + private $plugin_file; - /** @var string */ - private $min_php_version; + /** @var string */ + private $min_php_version; - /** @var string */ - private $min_wp_version; + /** @var string */ + private $min_wp_version; - /** @var string|null */ - private $min_wc_version = NULL; + /** @var string|null */ + private $min_wc_version = NULL; - /** @var int|null */ - private $min_openssl_version = NULL; - - /** @var array */ - protected $plugin_require; + /** @var int|null */ + private $min_openssl_version = NULL; + + /** @var array */ + protected $plugin_require; - /** @var bool */ - protected $should_check_plugin_versions = FALSE; + /** @var bool */ + protected $should_check_plugin_versions = FALSE; - /** @var array */ - private $module_require; - - /** @var array */ - private $setting_require; - - /** @var array */ - protected $notices; - - /** @var @string */ - private $text_domain; - - /** - * @param string $plugin_file - * @param string $plugin_name - * @param string $text_domain - * @param string $php_version - * @param string $wp_version - */ - public function __construct($plugin_file, $plugin_name, $text_domain, $php_version, $wp_version) { - $this->plugin_file = $plugin_file; - $this->plugin_name = $plugin_name; - $this->text_domain = $text_domain; - - $this->set_min_php_require($php_version); - $this->set_min_wp_require($wp_version); - - $this->plugin_require = array(); - $this->module_require = array(); - $this->setting_require = array(); - $this->notices = array(); - } - - /** - * @param string $version - * - * @return $this - */ - public function set_min_php_require($version) { - $this->min_php_version = $version; - - return $this; - } - - /** - * @param string $version - * - * @return $this - */ - public function set_min_wp_require($version) { - $this->min_wp_version = $version; - - return $this; - } - - /** - * @param string $version - * - * @return $this - */ - public function set_min_wc_require($version) { - $this->min_wc_version = $version; - - return $this; - } - - /** - * @param $version - * - * @return $this - */ - public function set_min_openssl_require($version) { - $this->min_openssl_version = $version; - - return $this; - } - - /** - * @param string $plugin_name Name in wp format dir/file.php - * @param string $nice_plugin_name Nice plugin name for better looks in notice - * @param string $plugin_require_version required plugin minimum version - * - * @return $this - */ - public function add_plugin_require($plugin_name, $nice_plugin_name = NULL, $plugin_require_version = NULL) { - if ($plugin_require_version) { - $this->should_check_plugin_versions = TRUE; - } - $this->plugin_require[$plugin_name] = array( - self::PLUGIN_INFO_KEY_NAME => $plugin_name, - self::PLUGIN_INFO_KEY_NICE_NAME => $nice_plugin_name === NULL ? $plugin_name : $nice_plugin_name, - self::PLUGIN_INFO_VERSION => $plugin_require_version === NULL ? - self::PLUGIN_INFO_FAKE_REQUIRED_MINIMUM_VERSION : $plugin_require_version, - ); - - return $this; - } - - /** - * Add plugin to require list. Plugin is from repository so we can ask for installation. - * - * @param string $plugin_name Name in wp format dir/file.php - * @param string $version Required version of the plugin. - * @param string $nice_plugin_name Nice plugin name for better looks in notice - * - * @return $this - */ - public function add_plugin_repository_require($plugin_name, $version, $nice_plugin_name = NULL) { - $this->plugin_require[$plugin_name] = array( - self::PLUGIN_INFO_KEY_NAME => $plugin_name, - self::PLUGIN_INFO_VERSION => $version, - 'repository_url' => 'http://downloads.wordpress.org/plugin/' . dirname($plugin_name) . '.latest-stable.zip', - self::PLUGIN_INFO_KEY_NICE_NAME => $nice_plugin_name === NULL ? $plugin_name : $nice_plugin_name - ); - - return $this; - } - - /** - * @param string $module_name - * @param string $nice_name Nice module name for better looks in notice - * - * @return $this - */ - public function add_php_module_require($module_name, $nice_name = NULL) { - if ($nice_name === NULL) { - $this->module_require[$module_name] = $module_name; - } - else { - $this->module_require[$module_name] = $nice_name; - } - - return $this; - } - - /** - * @param string $setting - * @param mixed $value - * - * @return $this - */ - public function add_php_setting_require($setting, $value) { - $this->setting_require[$setting] = $value; - - return $this; - } - - /** - * Returns true if are requirements are met. - * - * @return bool - */ - public function are_requirements_met() { - $this->notices = $this->prepare_requirement_notices(); - - return count($this->notices) === 0; - } - - /** - * @return array - */ - private function prepare_requirement_notices() { - $notices = array(); - if (!self::is_php_at_least($this->min_php_version)) { - $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run on PHP versions older than %s. Please contact your host and ask them to upgrade.', - $this->get_text_domain()), esc_html($this->plugin_name), $this->min_php_version)); - } - if (!self::is_wp_at_least($this->min_wp_version)) { - $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run on WordPress versions older than %s. Please update WordPress.', - $this->get_text_domain()), esc_html($this->plugin_name), $this->min_wp_version)); - } - if ($this->min_wc_version !== NULL && $this->can_check_plugin_version() && !self::is_wc_at_least($this->min_wc_version)) { - $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run on WooCommerce versions older than %s. Please update WooCommerce.', - $this->get_text_domain()), esc_html($this->plugin_name), $this->min_wc_version)); - } - if ($this->min_openssl_version !== NULL && !self::is_open_ssl_at_least($this->min_openssl_version)) { - $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without OpenSSL module version at least %s. Please update OpenSSL module.', - $this->get_text_domain()), esc_html($this->plugin_name), - '0x' . dechex($this->min_openssl_version))); - } - - $notices = $this->append_plugin_require_notices($notices); - $notices = $this->append_module_require_notices($notices); - $notices = $this->append_settings_require_notices($notices); - if ($this->should_check_plugin_versions) { - $notices = $this->check_minimum_require_plugins_version_and_append_notices($notices); - } - - return $notices; - } - - /** - * @param $min_version - * - * @return mixed - */ - public static function is_php_at_least($min_version) { - return version_compare(PHP_VERSION, $min_version, '>='); - } - - /** - * Prepares message in html format - * - * @param string $message - * - * @return string - */ - protected function prepare_notice_message($message) { - return '<div class="error"><p>' . $message . '</p></div>'; - } - - public function get_text_domain() { - return $this->text_domain; - } - - /** - * @param string $min_version - * - * @return bool - */ - public static function is_wp_at_least($min_version) { - return version_compare(get_bloginfo('version'), $min_version, '>='); - } - - /** - * Are plugins loaded so we can check the version - * - * @return bool - */ - private function can_check_plugin_version() { - return did_action('plugins_loaded') > 0; - } - - /** - * Checks if plugin is active and have designated version. Needs to be enabled in deferred way. - * - * @param string $min_version - * - * @return bool - */ - public static function is_wc_at_least($min_version) { - return defined('WC_VERSION') && - version_compare(WC_VERSION, $min_version, '>='); - } - - /** - * Checks if ssl version is valid - * - * @param int $required_version Version in hex. Version 9.6 is 0x000906000 - * - * @return bool - * @see https://www.openssl.org/docs/man1.1.0/crypto/OPENSSL_VERSION_NUMBER.html - * - */ - public static function is_open_ssl_at_least($required_version) { - return defined('OPENSSL_VERSION_NUMBER') && OPENSSL_VERSION_NUMBER > (int) $required_version; - } - - /** - * @param $notices array - * - * @return array - */ - private function check_minimum_require_plugins_version_and_append_notices($notices) { - $required_plugins = $this->retrieve_required_plugins_data(); - if (count($required_plugins) > 0) { - foreach ($required_plugins as $plugin) { - if (version_compare($plugin['Version'], $plugin[self::PLUGIN_INFO_APPEND_PLUGIN_DATA], '<')) { - $notices[] = $this->prepare_notice_message( - sprintf( - __('The “%1$s” plugin requires at least %2$s version of %3$s to work correctly. Please update it to its latest release.', $this->get_text_domain()), - esc_html($this->plugin_name), - $plugin[self::PLUGIN_INFO_APPEND_PLUGIN_DATA], - $plugin['Name'] - ) - ); - } - } - } - - return $notices; - } - - /** - * Check the plugins directory and retrieve all plugin files with plugin data. - * - * @return array In format [ 'plugindir/pluginfile.php' => ['Name' => 'Plugin Name', 'Version' => '1.0.1', ...], ] - */ - private static function retrieve_plugins_data_in_transient() { - $current_time = time(); - $plugins = get_transient(self::PLUGIN_INFO_TRANSIENT_NAME); - $expiration_time = get_transient(self::EXPIRATION_TRANSIENT_NAME); - $is_expired = !$expiration_time || $current_time > $expiration_time; - - if ($plugins === FALSE || $is_expired) { - static $never_executed = TRUE; - if ($never_executed) { - $never_executed = FALSE; - /** Required when WC starts later and these data should be in cache */ - add_filter('extra_plugin_headers', function($headers = array()) { - $headers[] = 'WC tested up to'; - $headers[] = 'WC requires at least'; - $headers[] = 'Woo'; - - return array_unique($headers); - }); - } - - if (!function_exists('get_plugins')) { - require_once ABSPATH . '/wp-admin/includes/plugin.php'; - } - - $plugins = function_exists('get_plugins') ? get_plugins() : array(); - set_transient(self::PLUGIN_INFO_TRANSIENT_NAME, $plugins, 0); - set_transient(self::EXPIRATION_TRANSIENT_NAME, $current_time + self::CACHE_TIME, 0); - } - - return $plugins; - } - - /** - * Check the plugins directory and retrieve all required plugin files with plugin data. - * - * @return array In format [ 'plugindir/pluginfile.php' => ['Name' => 'Plugin Name', 'Version' => '1.0.1', 'required_version' => '1.0.2']... ] - */ - private function retrieve_required_plugins_data() { - $require_plugins = array(); - $plugins = self::retrieve_plugins_data_in_transient(); - if (is_array($plugins)) { - if (count($plugins) > 0) { - if (!empty($this->plugin_require)) { - foreach ($this->plugin_require as $plugin) { - $plugin_file_name = $plugin[self::PLUGIN_INFO_KEY_NAME]; - $plugin_version = $plugin[self::PLUGIN_INFO_VERSION]; - - if (self::is_wp_plugin_active($plugin_file_name)) { - $require_plugins[$plugin_file_name] = $plugins[$plugin_file_name]; - $require_plugins[$plugin_file_name][self::PLUGIN_INFO_APPEND_PLUGIN_DATA] = $plugin_version; - } - } - } - } - } - - return $require_plugins; - } - - /** - * @param array $notices - * - * @return array - */ - private function append_plugin_require_notices($notices) { - if (count($this->plugin_require) > 0) { - foreach ($this->plugin_require as $plugin_name => $plugin_info) { - $notice = NULL; - if (isset($plugin_info['repository_url'])) { - $notice = $this->prepare_plugin_repository_require_notice($plugin_info); - } - elseif (!self::is_wp_plugin_active($plugin_name)) { - $notice = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without %s active. Please install and activate %s plugin.', - $this->get_text_domain()), esc_html($this->plugin_name), - esc_html(basename($plugin_info[self::PLUGIN_INFO_KEY_NICE_NAME])), - esc_html(basename($plugin_info[self::PLUGIN_INFO_KEY_NICE_NAME])))); - } - - if ($notice !== NULL) { - $notices[] = $notice; - } - } - } - - return $notices; - } - - /** - * Prepares WP install url and injects info about plugin to the WP update engine. - * - * @param array $plugin_info - * - * @return string - */ - private function prepare_plugin_repository_install_url($plugin_info) { - $slug = basename($plugin_info[self::PLUGIN_INFO_KEY_NAME]); - $install_url = self_admin_url('update.php?action=install-plugin&plugin=' . $slug); - if (function_exists('wp_nonce_url') && function_exists('wp_create_nonce')) { - $install_url = wp_nonce_url($install_url, 'install-plugin_' . $slug); - } - add_filter('plugins_api', function($api, $action, $args) use ($plugin_info, $slug) { - if ('plugin_information' !== $action || - FALSE !== $api || - !isset($args->slug) || - $slug !== $args->slug - ) { - return $api; - } - - $api = new stdClass(); - $api->name = $plugin_info['nice_name']; // self in closures requires 5.4 - $api->version = ''; - $api->download_link = esc_url($plugin_info['repository_url']); // self in closures requires 5.4 - - return $api; - }, 10, 3); - - return $install_url; - } - - /** - * @param array $plugin_info Internal required plugin info data. - * - * @return string|null Return null if no notice is needed. - */ - private function prepare_plugin_repository_require_notice($plugin_info) { - $name = $plugin_info[self::PLUGIN_INFO_KEY_NAME]; - $nice_name = $plugin_info[self::PLUGIN_INFO_KEY_NICE_NAME]; - - if (!self::is_wp_plugin_active($name)) { - if (!self::is_wp_plugin_installed($name)) { - $install_url = $this->prepare_plugin_repository_install_url($plugin_info); - - return $this->prepare_notice_message(sprintf(wp_kses(__('The “%s” plugin requires free %s plugin. <a href="%s">Install %s</a>', - $this->get_text_domain()), array('a' => array('href' => array()))), - $this->plugin_name, $nice_name, esc_url($install_url), $nice_name)); - } - $activate_url = 'plugins.php?action=activate&plugin=' . urlencode($plugin_info[self::PLUGIN_INFO_KEY_NAME]) . '&plugin_status=all&paged=1&s'; - if (function_exists('wp_create_nonce')) { - $activate_url .= '&_wpnonce=' . urlencode(wp_create_nonce('activate-plugin_' . $name)); - } - - return $this->prepare_notice_message(sprintf(wp_kses(__('The “%s” plugin requires activating %s plugin. <a href="%s">Activate %s</a>', - $this->get_text_domain()), array('a' => array('href' => array()))), - $this->plugin_name, $nice_name, esc_url(admin_url($activate_url)), $nice_name)); - } - - return NULL; - } - - /** - * Checks if plugin is active. Needs to be used in deferred way. - * - * @param string $plugin_file - * - * @return bool - */ - public static function is_wp_plugin_active($plugin_file) { - $active_plugins = (array) get_option('active_plugins', array()); - - if (is_multisite()) { - $active_plugins = array_merge($active_plugins, get_site_option('active_sitewide_plugins', array())); - } - - return in_array($plugin_file, $active_plugins) || array_key_exists($plugin_file, $active_plugins); - } - - /** - * Checks if plugin is installed. Needs to be enabled in deferred way. - * - * @param string $plugin_file - * - * @return bool - */ - public static function is_wp_plugin_installed($plugin_file) { - $plugins_data = self::retrieve_plugins_data_in_transient(); - - return array_key_exists($plugin_file, (array) $plugins_data); - } - - /** - * @param array $notices - * - * @return array - */ - private function append_module_require_notices($notices) { - if (count($this->module_require) > 0) { - foreach ($this->module_require as $module_name => $nice_module_name) { - if (!self::is_module_active($module_name)) { - $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without %s PHP module installed. Please contact your host and ask them to install %s.', - $this->get_text_domain()), esc_html($this->plugin_name), - esc_html(basename($nice_module_name)), esc_html(basename($nice_module_name)))); - } - } - } - - return $notices; - } - - /** - * @param string $name - * - * @return bool - */ - public static function is_module_active($name) { - return extension_loaded($name); - } - - /** - * @param array $notices - * - * @return array - */ - private function append_settings_require_notices($notices) { - if (count($this->setting_require) > 0) { - foreach ($this->setting_require as $setting => $value) { - if (!self::is_setting_set($setting, $value)) { - $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without %s PHP setting set to %s. Please contact your host and ask them to set %s.', - $this->get_text_domain()), esc_html($this->plugin_name), - esc_html(basename($setting)), - esc_html(basename($value)), esc_html(basename($setting)))); - } - } - } - - return $notices; - } - - /** - * @param string $name - * @param mixed $value - * - * @return bool - */ - public static function is_setting_set($name, $value) { - return ini_get($name) === (string) $value; - } - - /** - * @return void - * - * @deprecated use render_notices or disable_plugin - */ - public function disable_plugin_render_notice() { - add_action(self::HOOK_ADMIN_NOTICES_ACTION, array( - $this, - 'handle_render_notices_action' - )); - } - - /** - * Renders requirement notices in admin panel - * - * @return void - */ - public function render_notices() { - add_action(self::HOOK_ADMIN_NOTICES_ACTION, array( - $this, - 'handle_render_notices_action' - )); - } - - /** - * Renders requirement notices in admin panel - * - * @return void - */ - public function disable_plugin() { - add_action(self::HOOK_ADMIN_NOTICES_ACTION, array( - $this, - 'handle_deactivate_action' - )); - } - - /** - * @return void - * @internal Do not use as public. Public only for wp action. - * - */ - public function handle_deactivate_action() { - if (isset($this->plugin_file)) { - deactivate_plugins(plugin_basename($this->plugin_file)); - - delete_transient(self::PLUGIN_INFO_TRANSIENT_NAME); - } - } - - /** - * Triggers the transient delete after plugin deactivated - * - * @return void - */ - public function transient_delete_on_plugin_version_changed() { - add_action(self::HOOK_PLUGIN_DEACTIVATED_ACTION, array( - $this, - 'handle_transient_delete_action' - )); - add_action(self::HOOK_PLUGIN_ACTIVATED_ACTION, array( - $this, - 'handle_transient_delete_action' - )); - } - - /** - * Handles the transient delete - * - * @return void - */ - public function handle_transient_delete_action() { - delete_transient(self::PLUGIN_INFO_TRANSIENT_NAME); - delete_transient(self::EXPIRATION_TRANSIENT_NAME); - } - - /** - * Should be called as WordPress action - * - * @return void - * @internal Do not use as public. Public only for wp action. - * - */ - public function handle_render_notices_action() { - foreach ($this->notices as $notice) { - echo $notice; - } - } - } + /** @var array */ + private $module_require; + + /** @var array */ + private $setting_require; + + /** @var array */ + protected $notices; + + /** @var @string */ + private $text_domain; + + /** + * @param string $plugin_file + * @param string $plugin_name + * @param string $text_domain + * @param string $php_version + * @param string $wp_version + */ + public function __construct($plugin_file, $plugin_name, $text_domain, $php_version, $wp_version) { + $this->plugin_file = $plugin_file; + $this->plugin_name = $plugin_name; + $this->text_domain = $text_domain; + + $this->set_min_php_require($php_version); + $this->set_min_wp_require($wp_version); + + $this->plugin_require = array(); + $this->module_require = array(); + $this->setting_require = array(); + $this->notices = array(); + } + + /** + * @param string $version + * + * @return $this + */ + public function set_min_php_require($version) { + $this->min_php_version = $version; + + return $this; + } + + /** + * @param string $version + * + * @return $this + */ + public function set_min_wp_require($version) { + $this->min_wp_version = $version; + + return $this; + } + + /** + * @param string $version + * + * @return $this + */ + public function set_min_wc_require($version) { + $this->min_wc_version = $version; + + return $this; + } + + /** + * @param $version + * + * @return $this + */ + public function set_min_openssl_require($version) { + $this->min_openssl_version = $version; + + return $this; + } + + /** + * @param string $plugin_name Name in wp format dir/file.php + * @param string $nice_plugin_name Nice plugin name for better looks in notice + * @param string $plugin_require_version required plugin minimum version + * + * @return $this + */ + public function add_plugin_require($plugin_name, $nice_plugin_name = NULL, $plugin_require_version = NULL) { + if ($plugin_require_version) { + $this->should_check_plugin_versions = TRUE; + } + $this->plugin_require[$plugin_name] = array( + self::PLUGIN_INFO_KEY_NAME => $plugin_name, + self::PLUGIN_INFO_KEY_NICE_NAME => $nice_plugin_name === NULL ? $plugin_name : $nice_plugin_name, + self::PLUGIN_INFO_VERSION => $plugin_require_version === NULL ? + self::PLUGIN_INFO_FAKE_REQUIRED_MINIMUM_VERSION : $plugin_require_version, + ); + + return $this; + } + + /** + * Add plugin to require list. Plugin is from repository so we can ask for installation. + * + * @param string $plugin_name Name in wp format dir/file.php + * @param string $version Required version of the plugin. + * @param string $nice_plugin_name Nice plugin name for better looks in notice + * + * @return $this + */ + public function add_plugin_repository_require($plugin_name, $version, $nice_plugin_name = NULL) { + $this->plugin_require[$plugin_name] = array( + self::PLUGIN_INFO_KEY_NAME => $plugin_name, + self::PLUGIN_INFO_VERSION => $version, + 'repository_url' => 'http://downloads.wordpress.org/plugin/' . dirname($plugin_name) . '.latest-stable.zip', + self::PLUGIN_INFO_KEY_NICE_NAME => $nice_plugin_name === NULL ? $plugin_name : $nice_plugin_name + ); + + return $this; + } + + /** + * @param string $module_name + * @param string $nice_name Nice module name for better looks in notice + * + * @return $this + */ + public function add_php_module_require($module_name, $nice_name = NULL) { + if ($nice_name === NULL) { + $this->module_require[$module_name] = $module_name; + } + else { + $this->module_require[$module_name] = $nice_name; + } + + return $this; + } + + /** + * @param string $setting + * @param mixed $value + * + * @return $this + */ + public function add_php_setting_require($setting, $value) { + $this->setting_require[$setting] = $value; + + return $this; + } + + /** + * Returns true if are requirements are met. + * + * @return bool + */ + public function are_requirements_met() { + $this->notices = $this->prepare_requirement_notices(); + + return count($this->notices) === 0; + } + + /** + * @return array + */ + private function prepare_requirement_notices() { + $notices = array(); + if (!self::is_php_at_least($this->min_php_version)) { + $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run on PHP versions older than %s. Please contact your host and ask them to upgrade.', + $this->get_text_domain()), esc_html($this->plugin_name), $this->min_php_version)); + } + if (!self::is_wp_at_least($this->min_wp_version)) { + $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run on WordPress versions older than %s. Please update WordPress.', + $this->get_text_domain()), esc_html($this->plugin_name), $this->min_wp_version)); + } + if ($this->min_wc_version !== NULL && $this->can_check_plugin_version() && !self::is_wc_at_least($this->min_wc_version)) { + $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run on WooCommerce versions older than %s. Please update WooCommerce.', + $this->get_text_domain()), esc_html($this->plugin_name), $this->min_wc_version)); + } + if ($this->min_openssl_version !== NULL && !self::is_open_ssl_at_least($this->min_openssl_version)) { + $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without OpenSSL module version at least %s. Please update OpenSSL module.', + $this->get_text_domain()), esc_html($this->plugin_name), + '0x' . dechex($this->min_openssl_version))); + } + + $notices = $this->append_plugin_require_notices($notices); + $notices = $this->append_module_require_notices($notices); + $notices = $this->append_settings_require_notices($notices); + if ($this->should_check_plugin_versions) { + $notices = $this->check_minimum_require_plugins_version_and_append_notices($notices); + } + + return $notices; + } + + /** + * @param $min_version + * + * @return mixed + */ + public static function is_php_at_least($min_version) { + return version_compare(PHP_VERSION, $min_version, '>='); + } + + /** + * Prepares message in html format + * + * @param string $message + * + * @return string + */ + protected function prepare_notice_message($message) { + return '<div class="error"><p>' . $message . '</p></div>'; + } + + public function get_text_domain() { + return $this->text_domain; + } + + /** + * @param string $min_version + * + * @return bool + */ + public static function is_wp_at_least($min_version) { + return version_compare(get_bloginfo('version'), $min_version, '>='); + } + + /** + * Are plugins loaded so we can check the version + * + * @return bool + */ + private function can_check_plugin_version() { + return did_action('plugins_loaded') > 0; + } + + /** + * Checks if plugin is active and have designated version. Needs to be enabled in deferred way. + * + * @param string $min_version + * + * @return bool + */ + public static function is_wc_at_least($min_version) { + return defined('WC_VERSION') && + version_compare(WC_VERSION, $min_version, '>='); + } + + /** + * Checks if ssl version is valid + * + * @param int $required_version Version in hex. Version 9.6 is 0x000906000 + * + * @return bool + * @see https://www.openssl.org/docs/man1.1.0/crypto/OPENSSL_VERSION_NUMBER.html + * + */ + public static function is_open_ssl_at_least($required_version) { + return defined('OPENSSL_VERSION_NUMBER') && OPENSSL_VERSION_NUMBER > (int) $required_version; + } + + /** + * @param $notices array + * + * @return array + */ + private function check_minimum_require_plugins_version_and_append_notices($notices) { + $required_plugins = $this->retrieve_required_plugins_data(); + if (count($required_plugins) > 0) { + foreach ($required_plugins as $plugin) { + if (version_compare($plugin['Version'], $plugin[self::PLUGIN_INFO_APPEND_PLUGIN_DATA], '<')) { + $notices[] = $this->prepare_notice_message( + sprintf( + __('The “%1$s” plugin requires at least %2$s version of %3$s to work correctly. Please update it to its latest release.', $this->get_text_domain()), + esc_html($this->plugin_name), + $plugin[self::PLUGIN_INFO_APPEND_PLUGIN_DATA], + $plugin['Name'] + ) + ); + } + } + } + + return $notices; + } + + /** + * Check the plugins directory and retrieve all plugin files with plugin data. + * + * @return array In format [ 'plugindir/pluginfile.php' => ['Name' => 'Plugin Name', 'Version' => '1.0.1', ...], ] + */ + private static function retrieve_plugins_data_in_transient() { + $current_time = time(); + $plugins = get_transient(self::PLUGIN_INFO_TRANSIENT_NAME); + $expiration_time = get_transient(self::EXPIRATION_TRANSIENT_NAME); + $is_expired = !$expiration_time || $current_time > $expiration_time; + + if ($plugins === FALSE || $is_expired) { + static $never_executed = TRUE; + if ($never_executed) { + $never_executed = FALSE; + /** Required when WC starts later and these data should be in cache */ + add_filter('extra_plugin_headers', function($headers = array()) { + $headers[] = 'WC tested up to'; + $headers[] = 'WC requires at least'; + $headers[] = 'Woo'; + + return array_unique($headers); + }); + } + + if (!function_exists('get_plugins')) { + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + } + + $plugins = function_exists('get_plugins') ? get_plugins() : array(); + set_transient(self::PLUGIN_INFO_TRANSIENT_NAME, $plugins, 0); + set_transient(self::EXPIRATION_TRANSIENT_NAME, $current_time + self::CACHE_TIME, 0); + } + + return $plugins; + } + + /** + * Check the plugins directory and retrieve all required plugin files with plugin data. + * + * @return array In format [ 'plugindir/pluginfile.php' => ['Name' => 'Plugin Name', 'Version' => '1.0.1', 'required_version' => '1.0.2']... ] + */ + private function retrieve_required_plugins_data() { + $require_plugins = array(); + $plugins = self::retrieve_plugins_data_in_transient(); + if (is_array($plugins)) { + if (count($plugins) > 0) { + if (!empty($this->plugin_require)) { + foreach ($this->plugin_require as $plugin) { + $plugin_file_name = $plugin[self::PLUGIN_INFO_KEY_NAME]; + $plugin_version = $plugin[self::PLUGIN_INFO_VERSION]; + + if (self::is_wp_plugin_active($plugin_file_name)) { + $require_plugins[$plugin_file_name] = $plugins[$plugin_file_name]; + $require_plugins[$plugin_file_name][self::PLUGIN_INFO_APPEND_PLUGIN_DATA] = $plugin_version; + } + } + } + } + } + + return $require_plugins; + } + + /** + * @param array $notices + * + * @return array + */ + private function append_plugin_require_notices($notices) { + if (count($this->plugin_require) > 0) { + foreach ($this->plugin_require as $plugin_name => $plugin_info) { + $notice = NULL; + if (isset($plugin_info['repository_url'])) { + $notice = $this->prepare_plugin_repository_require_notice($plugin_info); + } + elseif (!self::is_wp_plugin_active($plugin_name)) { + $notice = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without %s active. Please install and activate %s plugin.', + $this->get_text_domain()), esc_html($this->plugin_name), + esc_html(basename($plugin_info[self::PLUGIN_INFO_KEY_NICE_NAME])), + esc_html(basename($plugin_info[self::PLUGIN_INFO_KEY_NICE_NAME])))); + } + + if ($notice !== NULL) { + $notices[] = $notice; + } + } + } + + return $notices; + } + + /** + * Prepares WP install url and injects info about plugin to the WP update engine. + * + * @param array $plugin_info + * + * @return string + */ + private function prepare_plugin_repository_install_url($plugin_info) { + $slug = basename($plugin_info[self::PLUGIN_INFO_KEY_NAME]); + $install_url = self_admin_url('update.php?action=install-plugin&plugin=' . $slug); + if (function_exists('wp_nonce_url') && function_exists('wp_create_nonce')) { + $install_url = wp_nonce_url($install_url, 'install-plugin_' . $slug); + } + add_filter('plugins_api', function($api, $action, $args) use ($plugin_info, $slug) { + if ('plugin_information' !== $action || + FALSE !== $api || + !isset($args->slug) || + $slug !== $args->slug + ) { + return $api; + } + + $api = new stdClass(); + $api->name = $plugin_info['nice_name']; // self in closures requires 5.4 + $api->version = ''; + $api->download_link = esc_url($plugin_info['repository_url']); // self in closures requires 5.4 + + return $api; + }, 10, 3); + + return $install_url; + } + + /** + * @param array $plugin_info Internal required plugin info data. + * + * @return string|null Return null if no notice is needed. + */ + private function prepare_plugin_repository_require_notice($plugin_info) { + $name = $plugin_info[self::PLUGIN_INFO_KEY_NAME]; + $nice_name = $plugin_info[self::PLUGIN_INFO_KEY_NICE_NAME]; + + if (!self::is_wp_plugin_active($name)) { + if (!self::is_wp_plugin_installed($name)) { + $install_url = $this->prepare_plugin_repository_install_url($plugin_info); + + return $this->prepare_notice_message(sprintf(wp_kses(__('The “%s” plugin requires free %s plugin. <a href="%s">Install %s</a>', + $this->get_text_domain()), array('a' => array('href' => array()))), + $this->plugin_name, $nice_name, esc_url($install_url), $nice_name)); + } + $activate_url = 'plugins.php?action=activate&plugin=' . urlencode($plugin_info[self::PLUGIN_INFO_KEY_NAME]) . '&plugin_status=all&paged=1&s'; + if (function_exists('wp_create_nonce')) { + $activate_url .= '&_wpnonce=' . urlencode(wp_create_nonce('activate-plugin_' . $name)); + } + + return $this->prepare_notice_message(sprintf(wp_kses(__('The “%s” plugin requires activating %s plugin. <a href="%s">Activate %s</a>', + $this->get_text_domain()), array('a' => array('href' => array()))), + $this->plugin_name, $nice_name, esc_url(admin_url($activate_url)), $nice_name)); + } + + return NULL; + } + + /** + * Checks if plugin is active. Needs to be used in deferred way. + * + * @param string $plugin_file + * + * @return bool + */ + public static function is_wp_plugin_active($plugin_file) { + $active_plugins = (array) get_option('active_plugins', array()); + + if (is_multisite()) { + $active_plugins = array_merge($active_plugins, get_site_option('active_sitewide_plugins', array())); + } + + return in_array($plugin_file, $active_plugins) || array_key_exists($plugin_file, $active_plugins); + } + + /** + * Checks if plugin is installed. Needs to be enabled in deferred way. + * + * @param string $plugin_file + * + * @return bool + */ + public static function is_wp_plugin_installed($plugin_file) { + $plugins_data = self::retrieve_plugins_data_in_transient(); + + return array_key_exists($plugin_file, (array) $plugins_data); + } + + /** + * @param array $notices + * + * @return array + */ + private function append_module_require_notices($notices) { + if (count($this->module_require) > 0) { + foreach ($this->module_require as $module_name => $nice_module_name) { + if (!self::is_module_active($module_name)) { + $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without %s PHP module installed. Please contact your host and ask them to install %s.', + $this->get_text_domain()), esc_html($this->plugin_name), + esc_html(basename($nice_module_name)), esc_html(basename($nice_module_name)))); + } + } + } + + return $notices; + } + + /** + * @param string $name + * + * @return bool + */ + public static function is_module_active($name) { + return extension_loaded($name); + } + + /** + * @param array $notices + * + * @return array + */ + private function append_settings_require_notices($notices) { + if (count($this->setting_require) > 0) { + foreach ($this->setting_require as $setting => $value) { + if (!self::is_setting_set($setting, $value)) { + $notices[] = $this->prepare_notice_message(sprintf(__('The “%s” plugin cannot run without %s PHP setting set to %s. Please contact your host and ask them to set %s.', + $this->get_text_domain()), esc_html($this->plugin_name), + esc_html(basename($setting)), + esc_html(basename($value)), esc_html(basename($setting)))); + } + } + } + + return $notices; + } + + /** + * @param string $name + * @param mixed $value + * + * @return bool + */ + public static function is_setting_set($name, $value) { + return ini_get($name) === (string) $value; + } + + /** + * @return void + * + * @deprecated use render_notices or disable_plugin + */ + public function disable_plugin_render_notice() { + add_action(self::HOOK_ADMIN_NOTICES_ACTION, array( + $this, + 'handle_render_notices_action' + )); + } + + /** + * Renders requirement notices in admin panel + * + * @return void + */ + public function render_notices() { + add_action(self::HOOK_ADMIN_NOTICES_ACTION, array( + $this, + 'handle_render_notices_action' + )); + } + + /** + * Renders requirement notices in admin panel + * + * @return void + */ + public function disable_plugin() { + add_action(self::HOOK_ADMIN_NOTICES_ACTION, array( + $this, + 'handle_deactivate_action' + )); + } + + /** + * @return void + * @internal Do not use as public. Public only for wp action. + * + */ + public function handle_deactivate_action() { + if (isset($this->plugin_file)) { + deactivate_plugins(plugin_basename($this->plugin_file)); + + delete_transient(self::PLUGIN_INFO_TRANSIENT_NAME); + } + } + + /** + * Triggers the transient delete after plugin deactivated + * + * @return void + */ + public function transient_delete_on_plugin_version_changed() { + add_action(self::HOOK_PLUGIN_DEACTIVATED_ACTION, array( + $this, + 'handle_transient_delete_action' + )); + add_action(self::HOOK_PLUGIN_ACTIVATED_ACTION, array( + $this, + 'handle_transient_delete_action' + )); + } + + /** + * Handles the transient delete + * + * @return void + */ + public function handle_transient_delete_action() { + delete_transient(self::PLUGIN_INFO_TRANSIENT_NAME); + delete_transient(self::EXPIRATION_TRANSIENT_NAME); + } + + /** + * Should be called as WordPress action + * + * @return void + * @internal Do not use as public. Public only for wp action. + * + */ + public function handle_render_notices_action() { + foreach ($this->notices as $notice) { + echo $notice; + } + } + + } }