diff --git a/.gitignore b/.gitignore index ccef0560f5e81eb78c47d43c0464ed4e04ee6434..5e55e37cdaf8d535a3ded8dabacb847b3daa74db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ -.idea \ No newline at end of file +.idea +composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json index 18c9f963211854a95ed55ebd922e47cc2f092f82..95f8e78db5678ea5d222a31e7efbeb708c73eb78 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "php": ">=5.2" }, "require-dev": { + "php": ">=5.5", "phpunit/phpunit": "<7", "wp-coding-standards/wpcs": "^0.14.1", "squizlabs/php_codesniffer": "^3.0.2", @@ -18,14 +19,14 @@ "wimg/php-compatibility": "^8" }, "autoload": { + }, "autoload-dev": { + "classmap": ["src", "tests"] }, "scripts": { "phpcs": "phpcs", "phpunit-unit": "phpunit --configuration phpunit-unit.xml --coverage-text --colors=never", - "phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage", - "phpunit-integration": "phpunit --configuration phpunit-integration.xml --coverage-text --colors=never", - "phpunit-integration-fast": "phpunit --configuration phpunit-integration.xml --no-coverage" + "phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage" } } diff --git a/phpunit-unit.xml b/phpunit-unit.xml new file mode 100644 index 0000000000000000000000000000000000000000..72701bbecbc7b15b8b408ee193a74a0f131d5615 --- /dev/null +++ b/phpunit-unit.xml @@ -0,0 +1,21 @@ +<phpunit bootstrap="tests/unit/bootstrap.php"> + <testsuites> + <testsuite> + <directory prefix="Test_" suffix=".php">./tests/unit/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory suffix=".php">src</directory> + </whitelist> + </filter> + + <logging> + <log type="junit" target="build-coverage/report.junit.xml"/> + <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/> + <log type="coverage-text" target="build-coverage/coverage.txt"/> + <log type="coverage-clover" target="build-coverage/clover.xml"/> + </logging> + +</phpunit> \ No newline at end of file diff --git a/src/Basic_Requirement_Checker.php b/src/Basic_Requirement_Checker.php index 6de82bf3692c243d10aa2ba5bd44ee414e86f1ea..0a439d121b6cf2cae2f95a1411de6cb4c25b5cc7 100644 --- a/src/Basic_Requirement_Checker.php +++ b/src/Basic_Requirement_Checker.php @@ -1,6 +1,6 @@ <?php -if (!class_exists('WPDesk_Translable')) { +if ( ! class_exists( 'WPDesk_Translable' ) ) { require_once 'Translable.php'; } @@ -9,38 +9,32 @@ if (!class_exists('WPDesk_Translable')) { * have to be compatible with PHP 5.2.x */ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { + const EXTENSION_NAME_OPENSSL = 'openssl'; + const HOOK_ADMIN_NOTICES_ACTION = 'admin_notices'; + /** @var string */ private $plugin_name = ''; - /** @var string */ private $plugin_file = ''; - /** @var string */ private $min_php_version; - /** @var string */ private $min_wp_version; - - /** @var string */ - private $min_wc_version; - + /** @var string|null */ + private $min_wc_version = null; + /** @var int|null */ + private $min_openssl_version = null; /** @var array */ private $plugin_require; - /** @var array */ private $module_require; - /** @var array */ private $setting_require; - /** @var array */ private $notices; - /** @var @string */ private $text_domain; - const EXTENSION_NAME_OPENSSL = 'openssl'; - /** * @param string $plugin_file * @param string $plugin_name @@ -62,10 +56,6 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { $this->notices = array(); } - public function get_text_domain() { - return $this->text_domain; - } - /** * @param string $version * @@ -99,6 +89,17 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { return $this; } + /** + * @param $version + * + * @return $this + */ + public function set_min_openssl_require( $version ) { + $this->min_openssl_version = $version; + + return $this; + } + /** * @param string $plugin_name * @param string $nice_plugin_name Nice plugin name for better looks in notice @@ -166,8 +167,15 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { $this->get_text_domain() ), esc_html( $this->plugin_name ), $this->min_wp_version ) ); } if ( ! is_null( $this->min_wc_version ) && $this->can_check_plugin_version() && ! $this->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 ) ); + $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 ( ! is_null( $this->min_openssl_version ) && ! $this->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 ); @@ -175,13 +183,46 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { return $notices; } + /** + * @param $min_version + * + * @return mixed + */ + public static function is_php_at_least( $min_version ) { + return version_compare( phpversion(), $min_version, '>=' ); + } + + /** + * Prepares message in html format + * + * @param string $message + * + * @return string + */ + private 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; + return did_action( 'plugins_loaded' ) > 0; } /** @@ -192,10 +233,59 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { * @return bool */ public static function is_wc_at_least( $min_version ) { - return defined('WC_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 + * + * @see https://www.openssl.org/docs/man1.1.0/crypto/OPENSSL_VERSION_NUMBER.html + * + * @return bool + */ + public static function is_open_ssl_at_least( $required_version ) { + return defined( 'OPENSSL_VERSION_NUMBER' ) && OPENSSL_VERSION_NUMBER > (int) $required_version; + } + + /** + * @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 => $nice_plugin_name ) { + if ( ! $this->is_wp_plugin_active( $plugin_name ) ) { + $notices[] = $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( $nice_plugin_name ) ), esc_html( basename( $nice_plugin_name ) ) ) ); + } + } + } + + return $notices; + } + + /** + * Checks if plugin is active. Needs to be enabled 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 ); + } + /** * @param array $notices * @@ -215,6 +305,15 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { return $notices; } + /** + * @param string $name + * + * @return bool + */ + public static function is_module_active( $name ) { + return extension_loaded( $name ); + } + /** * @param array $notices * @@ -234,25 +333,6 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { return $notices; } - /** - * @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 => $nice_plugin_name ) { - if ( ! $this->is_wp_plugin_active( $plugin_name ) ) { - $notices[] = $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( $nice_plugin_name ) ), esc_html( basename( $nice_plugin_name ) ) ) ); - } - } - } - - return $notices; - } - /** * @param string $name * @param mixed $value @@ -263,21 +343,12 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { return ini_get( $name ) === strval( $value ); } - /** - * @param string $name - * - * @return bool - */ - public static function is_module_active( $name ) { - return extension_loaded( $name ); - } - /** * @return void */ public function disable_plugin_render_notice() { - add_action( 'admin_notices', array( $this, 'deactivate_action' ) ); - add_action( 'admin_notices', array( $this, 'render_notices_action' ) ); + add_action( self::HOOK_ADMIN_NOTICES_ACTION, array( $this, 'deactivate_action' ) ); + add_action( self::HOOK_ADMIN_NOTICES_ACTION, array( $this, 'render_notices_action' ) ); } /** @@ -291,17 +362,6 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { } } - /** - * Prepares message in html format - * - * @param string $message - * - * @return string - */ - private function prepare_notice_message( $message ) { - return '<div class="error"><p>' . $message . '</p></div>'; - } - /** * @return void */ @@ -311,51 +371,4 @@ class WPDesk_Basic_Requirement_Checker implements WPDesk_Translable { } } - /** - * @param $min_version - * - * @return mixed - */ - public static function is_php_at_least( $min_version ) { - return version_compare( phpversion(), $min_version, '>=' ); - } - - /** - * @param string $min_version - * - * @return bool - */ - public static function is_wp_at_least( $min_version ) { - return version_compare( get_bloginfo( 'version' ), $min_version, '>=' ); - } - - /** - * Checks if plugin is active. Needs to be enabled 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 ssl version is valid - * - * @param int $required_version Version in hex. Version 9.6 is 0x000906000 - * @see https://www.openssl.org/docs/man1.1.0/crypto/OPENSSL_VERSION_NUMBER.html - * - * @return bool - */ - public static function is_open_ssl_at_least( $required_version ) { - return defined( 'OPENSSL_VERSION_NUMBER' ) && OPENSSL_VERSION_NUMBER > $required_version; - } - } diff --git a/src/Buildable.php b/src/Buildable.php index 5e38cbd8ee88d061428946632f0626a0795bc78b..78c73224961af9592e346b83fd58f360d06971fa 100644 --- a/src/Buildable.php +++ b/src/Buildable.php @@ -1,7 +1,8 @@ <?php /** - * Have info about what class should be built + * Have info about what class should be built by WPDesk_Builder + * * have to be compatible with PHP 5.2.x */ interface WPDesk_Buildable { diff --git a/src/Plugin/Has_Plugin_Info.php b/src/Plugin/Has_Plugin_Info.php new file mode 100644 index 0000000000000000000000000000000000000000..cd4168ecaf9fbf45911f76a75e23b12d567b4357 --- /dev/null +++ b/src/Plugin/Has_Plugin_Info.php @@ -0,0 +1,29 @@ +<?php + +if (!class_exists('WPDesk_Translable')) { + require_once '../Translable.php'; +} + + +/** + * Have MUST HAVE info for plugin instantion + * + * have to be compatible with PHP 5.2.x + */ +interface WPDesk_Has_Plugin_Info extends WPDesk_Translable { + /** + * @return string + */ + public function get_plugin_file_name(); + + /** + * @return string + */ + public function get_plugin_dir(); + + /** + * @return string + */ + public function get_version(); + +} \ No newline at end of file diff --git a/src/Plugin_Info.php b/src/Plugin/Plugin_Info.php similarity index 91% rename from src/Plugin_Info.php rename to src/Plugin/Plugin_Info.php index fca4beb327158c2e772d4d2b5ed1fea3ae146d0d..29a545998568ad7318700b139780f03c92ccbdc5 100644 --- a/src/Plugin_Info.php +++ b/src/Plugin/Plugin_Info.php @@ -1,17 +1,21 @@ <?php if (!class_exists('WPDesk_Translable')) { - require_once 'Translable.php'; + require_once '../Translable.php'; } if (!class_exists('WPDesk_Buildable')) { - require_once 'Buildable.php'; + require_once '../Buildable.php'; +} +if (!class_exists('WPDesk_Has_Plugin_Info')) { + require_once 'Has_Plugin_Info.php'; } /** * Structure with core info about plugin + * * have to be compatible with PHP 5.2.x */ -class WPDesk_Plugin_Info implements WPDesk_Translable, WPDesk_Buildable { +class WPDesk_Plugin_Info implements WPDesk_Translable, WPDesk_Buildable, WPDesk_Has_Plugin_Info { /** @var string */ private $plugin_file_name; diff --git a/src/Translable.php b/src/Translable.php index 3762dcf4b49c574144fff8761a74fd2c940eff17..32075f503d06cd35636bada1420cc2c751109c03 100644 --- a/src/Translable.php +++ b/src/Translable.php @@ -1,7 +1,8 @@ <?php /** - * Have info about textdomain + * Have info about textdomain - how to translate texts + * * have to be compatible with PHP 5.2.x */ interface WPDesk_Translable { diff --git a/tests/unit/Test_Basic_Requirement_Checker.php b/tests/unit/Test_Basic_Requirement_Checker.php new file mode 100644 index 0000000000000000000000000000000000000000..f7c2be96650ea921ece4cf39d9b9a398302bc544 --- /dev/null +++ b/tests/unit/Test_Basic_Requirement_Checker.php @@ -0,0 +1,176 @@ +<?php + +class Test_Basic_Requirement_Checker extends PHPUnit\Framework\TestCase { + const RANDOM_PLUGIN_FILE = 'file'; + + const RANDOM_PLUGIN_NAME = 'name'; + + const RANDOM_PLUGIN_TEXTDOMAIN = 'text'; + + const ALWAYS_VALID_PHP_VERSION = '5.2'; + + const ALWAYS_NOT_VALID_PHP_VERSION = '100.100'; + + const ALWAYS_VALID_WP_VERSION = '4.0'; + + const HOOK_TYPE_ACTION = 'action'; + + public function setUp() { + WP_Mock::setUp(); + + WP_Mock::wpFunction( 'get_bloginfo' ) + ->andReturn( self::ALWAYS_VALID_WP_VERSION ); + } + + public function tearDown() { + WP_Mock::tearDown(); + } + + public function test_php_version_check() { + $known_PHP_versions = [ '7.3', '7.2', '7.1', '7.0', '5.6', '5.5', '5.4', '5.3', '5.2' ]; + + $requirements = $this->create_requirements_for_php_wp( + self::ALWAYS_VALID_PHP_VERSION, + self::ALWAYS_VALID_WP_VERSION ); + + foreach ( $known_PHP_versions as $version ) { + $requirements->set_min_php_require( $version ); + if ( version_compare( PHP_VERSION, $version, '>=' ) ) { + $this->assertTrue( $requirements->are_requirements_met(), + 'Should be ok because WP is OK and PHP is OK' ); + } else { + $this->assertFalse( $requirements->are_requirements_met(), + 'Should fail because required PHP should be at least ' . $version ); + } + } + $requirements->set_min_php_require( self::ALWAYS_NOT_VALID_PHP_VERSION ); + $requirements->are_requirements_met(); + $this->expectOutputRegex( "/PHP/" ); + $requirements->render_notices_action(); + } + + /** + * @param string $php + * @param string $wp + * + * @return WPDesk_Basic_Requirement_Checker + */ + public function create_requirements_for_php_wp( $php, $wp ) { + return new WPDesk_Basic_Requirement_Checker( self::RANDOM_PLUGIN_FILE, self::RANDOM_PLUGIN_NAME, + self::RANDOM_PLUGIN_TEXTDOMAIN, $php, $wp ); + } + + public function test_wp_version_check() { + $wp_version_fail = '4.1'; + + $requirements = $this->create_requirements_for_php_wp( + self::ALWAYS_VALID_PHP_VERSION, + self::ALWAYS_VALID_WP_VERSION ); + + $this->assertTrue( $requirements->are_requirements_met(), 'Should be ok because WP is OK and PHP is OK' ); + $requirements->set_min_wp_require( $wp_version_fail ); + $this->assertFalse( $requirements->are_requirements_met(), + 'Should fail because required WP should be at least ' . $wp_version_fail ); + + $this->expectOutputRegex( "/WordPress/" ); + $requirements->render_notices_action(); + } + + /** + * @requires extension curl + */ + public function test_module_check() { + $requirements = $this->create_requirements_for_php_wp( + self::ALWAYS_VALID_PHP_VERSION, + self::ALWAYS_VALID_WP_VERSION ); + + $requirements->add_php_module_require( 'curl' ); + $this->assertTrue( $requirements->are_requirements_met(), 'Curl should exists' ); + + $this->expectOutputRegex( "/^$/" ); + $requirements->render_notices_action(); + } + + public function test_plugin_check_with_multisite() { + $multisite = true; + $exising_plugin_name = 'WooCommerce'; + $exising_multisite_plugin_name = 'Multisite'; + $not_existing_plugin_name = 'Not exist'; + + WP_Mock::wpFunction( 'get_option' ) + ->withArgs( [ 'active_plugins', [] ] ) + ->andReturn( [ $exising_plugin_name ] ); + + WP_Mock::wpFunction( 'is_multisite' ) + ->andReturn( $multisite ); + + WP_Mock::wpFunction( 'get_site_option' ) + ->withArgs( [ 'active_sitewide_plugins', [] ] ) + ->andReturn( [ $exising_multisite_plugin_name ] ); + + + $requirements = $this->create_requirements_for_php_wp( self::ALWAYS_VALID_PHP_VERSION, + self::ALWAYS_VALID_WP_VERSION ); + + $requirements->add_plugin_require( $exising_plugin_name ); + $this->assertTrue( $requirements->are_requirements_met(), 'Plugin should exists' ); + + $requirements->add_plugin_require( $exising_multisite_plugin_name ); + $this->assertTrue( $requirements->are_requirements_met(), 'Multisite plugin should exists' ); + + $requirements->add_plugin_require( $not_existing_plugin_name ); + $this->assertFalse( $requirements->are_requirements_met(), 'Plugin should not exists' ); + + $this->expectOutputRegex( "/$not_existing_plugin_name/" ); + $requirements->render_notices_action(); + } + + /** + * @requires extension openssl + */ + public function test_existing_openssl_requirement() { + $open_ssl_always_valid = 1; + $open_ssl_always_not_valid = 0x900905000; // 9.9.6 + + $requirements = $this->create_requirements_for_php_wp( self::ALWAYS_VALID_PHP_VERSION, + self::ALWAYS_VALID_WP_VERSION ); + + $this->assertTrue( $requirements->is_open_ssl_at_least( $open_ssl_always_valid ), + 'OpenSSL should have at least 0.1 version if exists' ); + + $this->assertFalse( $requirements->is_open_ssl_at_least( $open_ssl_always_not_valid ), + 'OpenSSL should fail for that high number' ); + + $requirements->set_min_openssl_require( $open_ssl_always_not_valid ); + + $this->assertFalse( $requirements->are_requirements_met(), + 'Requirement OpenSSL should fail for that high number' ); + + $this->expectOutputRegex( '/without OpenSSL module/' ); + $requirements->render_notices_action(); + } + + public function test_deactivate_plugin_notice() { + $requirements = $this->create_requirements_for_php_wp( self::ALWAYS_NOT_VALID_PHP_VERSION, + self::ALWAYS_VALID_WP_VERSION ); + + WP_Mock::expectActionAdded( WPDesk_Basic_Requirement_Checker::HOOK_ADMIN_NOTICES_ACTION, + [ $requirements, 'deactivate_action' ] ); + WP_Mock::expectActionAdded( WPDesk_Basic_Requirement_Checker::HOOK_ADMIN_NOTICES_ACTION, + [ $requirements, 'render_notices_action' ] ); + + $this->assertFalse( $requirements->are_requirements_met() ); + $requirements->disable_plugin_render_notice(); + + WP_Mock::wpFunction( 'deactivate_plugins' ) + ->once(); + + WP_Mock::wpFunction( 'plugin_basename' ) + ->once() + ->andReturn( 'whatever' ); + + $this->expectOutputRegex( '/cannot run on PHP/' ); + $requirements->deactivate_action(); + $requirements->render_notices_action(); + } +} diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..e55059f69f9265d81aa3a4afa8c679633a5a6ad9 --- /dev/null +++ b/tests/unit/bootstrap.php @@ -0,0 +1,23 @@ +<?php +/** + * PHPUnit bootstrap file + */ + +require_once __DIR__ . '/../../vendor/autoload.php'; + +error_reporting( E_ALL ); + +if ( getenv( 'PLUGIN_PATH' ) !== false ) { + define( 'PLUGIN_PATH', getenv( 'PLUGIN_PATH' ) ); +} else { + define( 'PLUGIN_PATH', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR ); +} + +if ( getenv( 'ABSPATH' ) !== false ) { + define( 'ABSPATH', getenv( 'ABSPATH' ) ); +} else { + define( 'ABSPATH', PLUGIN_PATH . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR ); +} + +WP_Mock::setUsePatchwork( true ); +WP_Mock::bootstrap();