diff --git a/.github/workflows/lint_phpcs.yml b/.github/workflows/lint_phpcs.yml new file mode 100644 index 0000000..96cc66e --- /dev/null +++ b/.github/workflows/lint_phpcs.yml @@ -0,0 +1,38 @@ +name: PHP CodeSniffer lint + +on: + pull_request: + branches: + - trunk + - develop + - branch-* + - enhancement/* + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest] + php-versions: ['8.2'] + + name: WP Media Plugin Family lint with PHPCS. PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}. + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none # XDebug can be enabled here 'coverage: xdebug' + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-scripts + + - name: Lint with phpcs + run: composer phpcs + diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..06339ea --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,35 @@ +name: PHP Stan lint + +on: + pull_request: + branches: + - trunk + - develop + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest] + php-versions: ['8.2'] + + name: Lint with PHP Stan. PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}. + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none # XDebug can be enabled here 'coverage: xdebug' + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-scripts + + - name: Lint with PHP Stan + run: composer phpstan -- --error-format=github \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..79ab7b9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,64 @@ +name: Unit tests + +on: + pull_request: + branches: + - trunk + - develop + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + + strategy: + fail-fast: true + matrix: + operating-system: [ubuntu-latest] + php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2'] + wp-versions: ['latest'] + + name: WP ${{ matrix.wp-versions }} with PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}. + + env: + WP_TESTS_DIR: "/tmp/tests/phpunit" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: xdebug + tools: composer:v2, phpunit + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Require phpcov for coverage reporting + run: composer require --dev --no-scripts phpunit/phpcov -W + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-scripts + + - name: Unit/Integration tests + run: composer test-unit + + - name: Code Coverage Report + if: ${{ matrix.php-versions == '8.2' }} + run: composer report-code-coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7a816f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +/composer.lock +tests/Unit/.phpunit.result.cache \ No newline at end of file diff --git a/README.md b/README.md index bdebe9d..3afd066 100644 --- a/README.md +++ b/README.md @@ -1 +1,96 @@ -# wp-media-plugin-family \ No newline at end of file +# Plugin Family + +This package bundles the WP Media plugin collection within a single plugin. + +## Functionality + +This package gathers essential data about each member plugin and provides their installation and activation links, similar to the [Imagify Partner](https://github.com/wp-media/wp-imagify-partner/) package. It returns an installation link for plugins that are not installed, and an activation link for those that are installed but not active. +For Imagify, the link generated by the Imagify Partner package is used. + +PS: The Imagify Partner package needs be installed along side. refer [here](https://github.com/wp-media/wp-imagify-partner/) for more information. + +## Usage Instructions + +- Install using composer: `composer require wp-media/plugin-family` +- Import the model that holds the filtered data into your view class. + +```php +use WPMedia\PluginFamily\Model\PluginFamily; + +class View { + protected $plugin_family; + + public function __construct( $plugin_family ) { + $this->plugin_family = $plugin_family; + } + + public function display_page() { + $plugin_family = $this->plugin_family->get_filtered_plugins( 'imagify-plugin/imagify' ); + + $data = [ + 'plugin_family' => $plugin_family['uncategorized'], + ]; + + $this->print_template( 'page-settings', $data ); + } +} +``` +The model returns an array with 2 keys ( categorized & uncategorized ). This for plugins like WP Rocket that needs to display the plugins by category. + +- The categorized version has the plugins grouped by their categories. +- The uncategorized version is the reverse of the former. + +Next, we need to invoke the controller responsible for managing the installation and activation. This controller has a corresponding interface that needs to be implemented. + +```php +use WPMedia\PluginFamily\Controller\{ PluginFamily, PluginFamilyInterface }; + +class Subscriber implements SubscriberInterface, PluginFamilyInterface { + + protected $plugin_family; + + public function __construct( $plugin_family ) { + $this->plugin_family = $plugin_family; + } + + public static function get_subscribed_events() { + $events = PluginFamily::get_subscribed_events(); + + return $events; + } + + public function install_activate() { + $this->plugin_family->install_activate(); + } + + public function display_error_notice() { + $this->plugin_family->display_error_notice(); + } +} +``` +The methods ( `install_activate` & `display_error_notice` ) are required. + +PS: This example is based on the assumption that your plugin utilizes EDA. If EDA is not available, you can iterate through the events returned by `PluginFamily::get_subscribed_events()` and use `add_action` accordingly. + +## Development & Testing + +To facilitate development and testing of the package, it is recommended to specify a development branch in the composer.json file of your project. + +## Taking WP Rocket as example + +```JSON +"require": { + "wp-media/plugin-family": "dev-whatever-dev-branch" +} +``` + +PS: Always have the dev prefix before the actual branch. + +```JSON +"repositories": [ + { + "type": "vcs", + "url": "https://github.com/wp-media/plugin-family" + } + ] +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ad4cf3c --- /dev/null +++ b/composer.json @@ -0,0 +1,45 @@ +{ + "name": "wp-media/plugin-family", + "description": "Organizes and displays WP Media plugin family across other members.", + "license": "GPL-3.0-or-later", + "authors": [ + { + "name": "WP Media", + "email": "contact@wp-media.me", + "homepage": "https://wp-media.me" + } + ], + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "require-dev": { + "phpcompatibility/phpcompatibility-wp": "^2.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "wp-coding-standards/wpcs": "^3", + "wp-media/phpunit": "^3", + "phpstan/extension-installer": "^1.4", + "szepeviktor/phpstan-wordpress": "^1.3" + }, + "autoload": { + "psr-4": { + "WPMedia\\PluginFamily\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "WPMedia\\PluginFamily\\Tests\\": "tests/" + } + }, + "scripts": { + "install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", + "phpcs": "phpcs --basepath=.", + "phpcs:fix": "phpcbf", + "test-unit": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration tests/Unit/phpunit.xml.dist --coverage-php tests/report/unit.cov", + "report-code-coverage": "\"vendor/bin/phpcov\" merge tests/report --clover tests/report/coverage.clover", + "phpstan": "vendor/bin/phpstan analyze --memory-limit=2G --no-progress" + } +} diff --git a/patchwork.json b/patchwork.json new file mode 100644 index 0000000..c7a192c --- /dev/null +++ b/patchwork.json @@ -0,0 +1,3 @@ +{ + "redefinable-internals": ["file_exists"] +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..c31f2cb --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,55 @@ + + + The custom ruleset for WP Media Plugin Family. + + + + + + src + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..5e36fec --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,6 @@ +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon +parameters: + level: 5 + paths: + - %currentWorkingDirectory%/src \ No newline at end of file diff --git a/src/Controller/PluginFamily.php b/src/Controller/PluginFamily.php new file mode 100644 index 0000000..26aad5b --- /dev/null +++ b/src/Controller/PluginFamily.php @@ -0,0 +1,254 @@ + 'install_activate', + ]; + } + + /** + * Process to install and activate plugin. + * + * @return void + */ + public function install_activate() { + if ( ! $this->is_allowed() ) { + wp_die( + 'Plugin Installation is not allowed.', + '', + [ 'back_link' => true ] + ); + } + + // Install plugin. + $this->install(); + + // Activate plugin. + $result = activate_plugin( $this->get_plugin(), '', is_multisite() ); + + if ( is_wp_error( $result ) ) { + $this->set_error( $result ); + } + + wp_safe_redirect( wp_get_referer() ); + exit; + } + + /** + * Install plugin. + * + * @return void + */ + private function install() { + if ( $this->is_installed() ) { + return; + } + + $upgrader_class = ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + if ( ! defined( 'ABSPATH' ) || ! file_exists( $upgrader_class ) ) { + wp_die( + 'Plugin Installation failed. class-wp-upgrader.php not found', + '', + [ 'back_link' => true ] + ); + } + + require_once $upgrader_class; // @phpstan-ignore-line + + $upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() ); + $result = $upgrader->install( $this->get_download_url() ); + + if ( is_wp_error( $result ) ) { + $this->set_error( $result ); + } + + clearstatcache(); + } + + /** + * Check if plugin is installed. + * + * @return boolean + */ + private function is_installed(): bool { + return file_exists( WP_PLUGIN_DIR . '/' . $this->get_plugin() ); + } + + /** + * Check if installation is allowed. + * + * @return boolean + */ + private function is_allowed(): bool { + if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'plugin_family_install_' . $this->get_slug() ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + return false; + } + + if ( ! current_user_can( is_multisite() ? 'manage_network_plugins' : 'install_plugins' ) ) { + return false; + } + + return true; + } + + /** + * Get plugin slug. + * + * @return string + */ + private function get_slug(): string { + return dirname( rawurldecode( sanitize_text_field( wp_unslash( $_GET['plugin_to_install'] ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + } + + /** + * Get plugin identifier. + * + * @return string + */ + private function get_plugin(): string { + return rawurldecode( sanitize_text_field( wp_unslash( $_GET['plugin_to_install'] ) ) ) . '.php'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + } + + /** + * Get plugin download url. + * + * @return string + */ + private function get_download_url(): string { + $plugin_install = ABSPATH . 'wp-admin/includes/plugin-install.php'; + + if ( ! defined( 'ABSPATH' ) || ! file_exists( $plugin_install ) ) { + wp_die( + 'Plugin Installation failed. plugin-install.php not found', + '', + [ 'back_link' => true ] + ); + } + + require_once $plugin_install; // @phpstan-ignore-line + + $data = [ + 'slug' => $this->get_slug(), + 'fields' => [ + 'download_link' => true, + 'short_description' => false, + 'sections' => false, + 'rating' => false, + 'ratings' => false, + 'downloaded' => false, + 'last_updated' => false, + 'added' => false, + 'tags' => false, + 'homepage' => false, + 'donate_link' => false, + ], + ]; + + // Get Plugin Infos. + $plugin_info = plugins_api( 'plugin_information', $data ); + + if ( is_wp_error( $plugin_info ) ) { + $this->set_error( $plugin_info ); + } + + // Ensure that $plugin_info is an object before accessing the property. + if ( ! is_object( $plugin_info ) || ! isset( $plugin_info->download_link ) ) { + return ''; + } + + return $plugin_info->download_link; + } + + /** + * Maybe display error notice. + * + * @return void + */ + public function display_error_notice() { + $errors = get_transient( $this->error_transient ); + + if ( ! $errors ) { + return; + } + + if ( ! is_wp_error( $errors ) ) { + delete_transient( $this->error_transient ); + return; + } + + $errors = $errors->get_error_messages(); + + if ( ! $errors ) { + $errors[] = 'Installation process failed'; + } + + $notice = '

' . implode( '
', $errors ) . '

'; + echo wp_kses_post( $notice ); + + // Remove transient after displaying notice. + delete_transient( $this->error_transient ); + } + + /** + * Store an error message in a transient then redirect. + * + * @param object $error A WP_Error object. + * @return void + */ + private function set_error( $error ) { + set_transient( $this->error_transient, $error, 30 ); + + wp_safe_redirect( wp_get_referer() ); + exit; + } +} diff --git a/src/Controller/PluginFamilyInterface.php b/src/Controller/PluginFamilyInterface.php new file mode 100644 index 0000000..e4fbe87 --- /dev/null +++ b/src/Controller/PluginFamilyInterface.php @@ -0,0 +1,19 @@ +filter_plugins_by_activation( $plugins, $main_plugin ); + } + + /** + * Filter plugins family data by activation status and returns both categorized and uncategorized format. + * + * @param array $plugins Array of family plugins. + * @param string $main_plugin Main plugin installed. + * + * @return array + */ + public function filter_plugins_by_activation( array $plugins, string $main_plugin ): array { + if ( empty( $plugins ) ) { + return []; + } + + list( $active_plugins, $inactive_plugins ) = [ [], [] ]; + + foreach ( $plugins as $cat => $cat_data ) { + foreach ( $cat_data['plugins'] as $plugin => $data ) { + + $plugin_path = $plugin . '.php'; + $plugin_slug = dirname( $plugin ); + $main_plugin_slug = dirname( $main_plugin ); + + /** + * Check for activated plugins and pop them out of the array + * to re-add them back using array_merge to be displayed after + * plugins that are not installed or not activated. + */ + if ( is_plugin_active( $plugin_path ) ) { + // set cta data of active plugins. + $plugins[ $cat ]['plugins'][ $plugin ]['cta'] = [ + 'text' => 'Activated', + 'url' => '#', + ]; + + // Send active plugin to new array. + $active_plugins[ $plugin ] = $plugins[ $cat ]['plugins'][ $plugin ]; + + // Remove active plugin from current category. + unset( $cat_data['plugins'][ $plugin ] ); + + // Send active plugin to the end of array in current category. + $cat_data['plugins'][ $plugin ] = $plugins[ $cat ]['plugins'][ $plugin ]; + + // Remove category with active plugin from current array. + unset( $plugins[ $cat ] ); + + // Send category with active plugins to the end of array. + $plugins[ $cat ] = $cat_data; + continue; + } + + $install_activate_url = admin_url( 'admin-post.php' ); + + $args = [ + 'action' => 'plugin_family_install_' . $plugin_slug, + '_wpnonce' => wp_create_nonce( 'plugin_family_install_' . $plugin_slug ), + 'plugin_to_install' => rawurlencode( $plugin ), + ]; + + if ( 'imagify-plugin' === $plugin_slug ) { + $args = [ + 'action' => 'install_imagify_from_partner_' . $main_plugin_slug, + '_wpnonce' => wp_create_nonce( 'install_imagify_from_partner' ), + '_wp_http_referer' => rawurlencode( $this->get_current_url() ), + ]; + } + + $install_activate_url = add_query_arg( $args, $install_activate_url ); + + // Set Installation link. + $plugins[ $cat ]['plugins'][ $plugin ]['cta'] = [ + 'text' => 'Install', + 'url' => $install_activate_url, + ]; + + // Create unique CTA data for WP Rocket. + if ( 'wp-rocket/wp-rocket' === $plugin ) { + $plugins[ $cat ]['plugins'][ $plugin ]['cta'] = [ + 'text' => 'Get it Now', + 'url' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify', + ]; + } + + // Set activation text. + if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin_path ) ) { + $plugins[ $cat ]['plugins'][ $plugin ]['cta']['text'] = 'Activate'; + + if ( 'wp-rocket/wp-rocket' === $plugin ) { + $plugins[ $cat ]['plugins'][ $plugin ]['cta']['url'] = $install_activate_url; + } + } + + // Send inactive plugins to new array. + $inactive_plugins[ $plugin ] = $plugins[ $cat ]['plugins'][ $plugin ]; + } + + // Remove main plugin from categorized array. + if ( isset( $plugins[ $cat ]['plugins'][ $main_plugin ] ) ) { + unset( $plugins[ $cat ]['plugins'][ $main_plugin ] ); + } + } + + $uncategorized = array_merge( $inactive_plugins, $active_plugins ); + // Remove main plugin from uncategorized array. + unset( $uncategorized[ $main_plugin ] ); + + return [ + 'categorized' => $plugins, + 'uncategorized' => $uncategorized, + ]; + } + + /** + * Get the current URL. + * Gotten from Imagify_Partner Package. + * + * @return string + */ + protected function get_current_url(): string { + if ( ! isset( $_SERVER['SERVER_PORT'], $_SERVER['HTTP_HOST'] ) ) { + return ''; + } + + $port = (int) wp_unslash( $_SERVER['SERVER_PORT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $port = 80 !== $port && 443 !== $port ? ( ':' . $port ) : ''; + $url = ! empty( $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] ) ? $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] : ( ! empty( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + + return 'http' . ( is_ssl() ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST'] . $port . $url; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } +} diff --git a/src/Model/wp_media_plugins.php b/src/Model/wp_media_plugins.php new file mode 100644 index 0000000..46bf8ef --- /dev/null +++ b/src/Model/wp_media_plugins.php @@ -0,0 +1,67 @@ + [ + 'title' => 'Optimize Performance', + 'plugins' => [ + 'wp-rocket/wp-rocket' => [ + 'logo' => [ + 'file' => 'logo-wp-rocket.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website, Instantly', + 'desc' => 'WP Rocket is the easiest way to make your WordPress website faster and boost your Google PageSpeed score. Get more traffic, better engagement, and higher conversions effortlessly.', + 'link' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify', + ], + 'imagify-plugin/imagify' => [ + 'logo' => [ + 'file' => 'logo-imagify.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website With Lighter Images', + 'desc' => 'Imagify is the easiest WordPress image optimizer. It automatically compresses images, converts them to WebP and AVIF formats, and lets you resize and optimize with just one click!', + 'link' => 'https://imagify.io/', + ], + ], + ], + 'boost_traffic' => [ + 'title' => 'Boost Traffic', + 'plugins' => [ + 'seo-by-rank-math/rank-math' => [ + 'logo' => [ + 'file' => 'logo-rank-math.svg', + 'width' => '60%', + ], + 'title' => 'The Swiss Army Knife of SEO Tools', + 'desc' => 'Rank Math SEO is the Best WordPress SEO plugin with the features of many SEO and AI SEO tools in a single package to help multiply your SEO traffic.', + 'link' => 'https://rankmath.com/wordpress/plugin/seo-suite/', + ], + ], + ], + 'protect_secure' => [ + 'title' => 'Protect & Secure', + 'plugins' => [ + 'backwpup/backwpup' => [ + 'logo' => [ + 'file' => 'logo-backwpup.svg', + 'width' => '60%', + ], + 'title' => 'The Easiest Way to Protect Your Website', + 'desc' => 'BackWPup is the most comprehensive and user-friendly backup & restore plugin for WordPress. Easily schedule automatic backups, securely store and restore with just a few clicks!', + 'link' => 'https://backwpup.com/', + ], + 'uk-cookie-consent/uk-cookie-consent' => [ + 'logo' => [ + 'file' => 'logo-termly.svg', + 'width' => '50%', + ], + 'title' => 'GDPR/CCPA Cookie Consent Banner', + 'desc' => 'One of the easiest, most comprehensive, and popular cookie consent plugins available. Google Gold Certified Partner to quickly comply with data privacy laws from around the world.', + 'link' => 'https://termly.io/resources/articles/wordpress-cookies-guide/', + ], + ], + ], +]; diff --git a/tests/Fixtures/src/Controller/PluginFamily/getPostInstallEvent.php b/tests/Fixtures/src/Controller/PluginFamily/getPostInstallEvent.php new file mode 100644 index 0000000..08b7eac --- /dev/null +++ b/tests/Fixtures/src/Controller/PluginFamily/getPostInstallEvent.php @@ -0,0 +1,32 @@ + [ + 'config' => [ + 'get_params' => [], + ], + 'expected' => [], + ], + 'testShouldReturnEmptyArrayIfActionIsNotValid' => [ + 'config' => [ + 'get_params' => [ + 'action' => 'plugin_family_install_some-wierd-plugin', + '_wpnonce' => '9a68f00b8d', + 'plugin_to_install' => 'some-wierd-plugin', + ], + ], + 'expected' => [], + ], + 'testShouldReturnExpected' => [ + 'config' => [ + 'get_params' => [ + 'action' => 'plugin_family_install_wp-rocket', + '_wpnonce' => '9a68f00b8d', + 'plugin_to_install' => 'wp-rocket', + ], + ], + 'expected' => [ + 'admin_post_plugin_family_install_wp-rocket' => 'install_activate', + ], + ], +]; \ No newline at end of file diff --git a/tests/Fixtures/src/Model/PluginFamily/filterPluginsByActivation.php b/tests/Fixtures/src/Model/PluginFamily/filterPluginsByActivation.php new file mode 100644 index 0000000..a2253d7 --- /dev/null +++ b/tests/Fixtures/src/Model/PluginFamily/filterPluginsByActivation.php @@ -0,0 +1,347 @@ + [ + 'title' => 'Optimize Performance', + 'plugins' => [ + 'wp-rocket/wp-rocket' => [ + 'logo' => [ + 'file' => 'logo-wp-rocket.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website, Instantly', + 'desc' => 'WP Rocket is the easiest way to make your WordPress website faster and boost your Google PageSpeed score. Get more traffic, better engagement, and higher conversions effortlessly.', + 'link' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify', + ], + 'imagify-plugin/imagify' => [ + 'logo' => [ + 'file' => 'logo-imagify.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website With Lighter Images', + 'desc' => 'Imagify is the easiest WordPress image optimizer. It automatically compresses images, converts them to WebP and AVIF formats, and lets you resize and optimize with just one click!', + 'link' => 'https://imagify.io/', + ], + ], + ], + 'boost_traffic' => [ + 'title' => 'Boost Traffic', + 'plugins' => [ + 'seo-by-rank-math/rank-math' => [ + 'logo' => [ + 'file' => 'logo-rank-math.svg', + 'width' => '60%', + ], + 'title' => 'The Swiss Army Knife of SEO Tools', + 'desc' => 'Rank Math SEO is the Best WordPress SEO plugin with the features of many SEO and AI SEO tools in a single package to help multiply your SEO traffic.', + 'link' => 'https://rankmath.com/wordpress/plugin/seo-suite/', + ], + ], + ], + 'protect_secure' => [ + 'title' => 'Protect & Secure', + 'plugins' => [ + 'backwpup/backwpup' => [ + 'logo' => [ + 'file' => 'logo-backwpup.svg', + 'width' => '60%', + ], + 'title' => 'The Easiest Way to Protect Your Website', + 'desc' => 'BackWPup is the most comprehensive and user-friendly backup & restore plugin for WordPress. Easily schedule automatic backups, securely store and restore with just a few clicks!', + 'link' => 'https://backwpup.com/', + ], + 'uk-cookie-consent/uk-cookie-consent' => [ + 'logo' => [ + 'file' => 'logo-termly.svg', + 'width' => '50%', + ], + 'title' => 'GDPR/CCPA Cookie Consent Banner', + 'desc' => 'One of the easiest, most comprehensive, and popular cookie consent plugins available. Google Gold Certified Partner to quickly comply with data privacy laws from around the world.', + 'link' => 'https://termly.io/resources/articles/wordpress-cookies-guide/', + ], + ], + ], +]; + +$expectedActivePluginAsTheLastElement = [ + 'boost_traffic' => [ + 'title' => 'Boost Traffic', + 'plugins' => [ + 'seo-by-rank-math/rank-math' => [ + 'logo' => [ + 'file' => 'logo-rank-math.svg', + 'width' => '60%', + ], + 'title' => 'The Swiss Army Knife of SEO Tools', + 'desc' => 'Rank Math SEO is the Best WordPress SEO plugin with the features of many SEO and AI SEO tools in a single package to help multiply your SEO traffic.', + 'link' => 'https://rankmath.com/wordpress/plugin/seo-suite/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_seo-by-rank-math&_wpnonce=9a68f00b8d&plugin_to_install=seo-by-rank-math%2Frank-math' + ], + ], + ], + ], + 'protect_secure' => [ + 'title' => 'Protect & Secure', + 'plugins' => [ + 'backwpup/backwpup' => [ + 'logo' => [ + 'file' => 'logo-backwpup.svg', + 'width' => '60%', + ], + 'title' => 'The Easiest Way to Protect Your Website', + 'desc' => 'BackWPup is the most comprehensive and user-friendly backup & restore plugin for WordPress. Easily schedule automatic backups, securely store and restore with just a few clicks!', + 'link' => 'https://backwpup.com/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_backwpup&_wpnonce=9a68f00b8d&plugin_to_install=backwpup%2Fbackwpup' + ], + ], + 'uk-cookie-consent/uk-cookie-consent' => [ + 'logo' => [ + 'file' => 'logo-termly.svg', + 'width' => '50%', + ], + 'title' => 'GDPR/CCPA Cookie Consent Banner', + 'desc' => 'One of the easiest, most comprehensive, and popular cookie consent plugins available. Google Gold Certified Partner to quickly comply with data privacy laws from around the world.', + 'link' => 'https://termly.io/resources/articles/wordpress-cookies-guide/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_uk-cookie-consent&_wpnonce=9a68f00b8d&plugin_to_install=uk-cookie-consent%2Fuk-cookie-consent' + ], + ], + ], + ], + 'optimize_performance' => [ + 'title' => 'Optimize Performance', + 'plugins' => [ + 'imagify-plugin/imagify' => [ + 'logo' => [ + 'file' => 'logo-imagify.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website With Lighter Images', + 'desc' => 'Imagify is the easiest WordPress image optimizer. It automatically compresses images, converts them to WebP and AVIF formats, and lets you resize and optimize with just one click!', + 'link' => 'https://imagify.io/', + 'cta' => [ + 'text' => 'Activated', + 'url' => '#' + ], + ], + ], + ], +]; + +$expectedCategoryWithActivePluginAsTheLastElement = [ + 'wp-rocket/wp-rocket' => [ + 'logo' => [ + 'file' => 'logo-wp-rocket.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website, Instantly', + 'desc' => 'WP Rocket is the easiest way to make your WordPress website faster and boost your Google PageSpeed score. Get more traffic, better engagement, and higher conversions effortlessly.', + 'link' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify', + 'cta' => [ + 'text' => 'Get it Now', + 'url' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify' + ], + ], + 'backwpup/backwpup' => [ + 'logo' => [ + 'file' => 'logo-backwpup.svg', + 'width' => '60%', + ], + 'title' => 'The Easiest Way to Protect Your Website', + 'desc' => 'BackWPup is the most comprehensive and user-friendly backup & restore plugin for WordPress. Easily schedule automatic backups, securely store and restore with just a few clicks!', + 'link' => 'https://backwpup.com/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_backwpup&_wpnonce=9a68f00b8d&plugin_to_install=backwpup%2Fbackwpup' + ], + ], + 'uk-cookie-consent/uk-cookie-consent' => [ + 'logo' => [ + 'file' => 'logo-termly.svg', + 'width' => '50%', + ], + 'title' => 'GDPR/CCPA Cookie Consent Banner', + 'desc' => 'One of the easiest, most comprehensive, and popular cookie consent plugins available. Google Gold Certified Partner to quickly comply with data privacy laws from around the world.', + 'link' => 'https://termly.io/resources/articles/wordpress-cookies-guide/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_uk-cookie-consent&_wpnonce=9a68f00b8d&plugin_to_install=uk-cookie-consent%2Fuk-cookie-consent' + ], + ], + 'seo-by-rank-math/rank-math' => [ + 'logo' => [ + 'file' => 'logo-rank-math.svg', + 'width' => '60%', + ], + 'title' => 'The Swiss Army Knife of SEO Tools', + 'desc' => 'Rank Math SEO is the Best WordPress SEO plugin with the features of many SEO and AI SEO tools in a single package to help multiply your SEO traffic.', + 'link' => 'https://rankmath.com/wordpress/plugin/seo-suite/', + 'cta' => [ + 'text' => 'Activated', + 'url' => '#' + ], + ], +]; + +$expectedActivateTextIfPluginIsAlreadyInstalled = [ + 'wp-rocket/wp-rocket' => [ + 'logo' => [ + 'file' => 'logo-wp-rocket.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website, Instantly', + 'desc' => 'WP Rocket is the easiest way to make your WordPress website faster and boost your Google PageSpeed score. Get more traffic, better engagement, and higher conversions effortlessly.', + 'link' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify', + 'cta' => [ + 'text' => 'Activate', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_wp-rocket&_wpnonce=9a68f00b8d&plugin_to_install=wp-rocket%2Fwp-rocket' + ], + ], + 'imagify-plugin/imagify' => [ + 'logo' => [ + 'file' => 'logo-imagify.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website With Lighter Images', + 'desc' => 'Imagify is the easiest WordPress image optimizer. It automatically compresses images, converts them to WebP and AVIF formats, and lets you resize and optimize with just one click!', + 'link' => 'https://imagify.io/', + 'cta' => [ + 'text' => 'Activate', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=install_imagify_from_partner_backwpup&_wpnonce=9a68f00b8d&_wp_http_referer=https%3A%2F%2Fexample.org%2Fwp-admin' + ], + ], + 'seo-by-rank-math/rank-math' => [ + 'logo' => [ + 'file' => 'logo-rank-math.svg', + 'width' => '60%', + ], + 'title' => 'The Swiss Army Knife of SEO Tools', + 'desc' => 'Rank Math SEO is the Best WordPress SEO plugin with the features of many SEO and AI SEO tools in a single package to help multiply your SEO traffic.', + 'link' => 'https://rankmath.com/wordpress/plugin/seo-suite/', + 'cta' => [ + 'text' => 'Activate', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_seo-by-rank-math&_wpnonce=9a68f00b8d&plugin_to_install=seo-by-rank-math%2Frank-math' + ], + ], + 'uk-cookie-consent/uk-cookie-consent' => [ + 'logo' => [ + 'file' => 'logo-termly.svg', + 'width' => '50%', + ], + 'title' => 'GDPR/CCPA Cookie Consent Banner', + 'desc' => 'One of the easiest, most comprehensive, and popular cookie consent plugins available. Google Gold Certified Partner to quickly comply with data privacy laws from around the world.', + 'link' => 'https://termly.io/resources/articles/wordpress-cookies-guide/', + 'cta' => [ + 'text' => 'Activate', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_uk-cookie-consent&_wpnonce=9a68f00b8d&plugin_to_install=uk-cookie-consent%2Fuk-cookie-consent' + ], + ], +]; + +$expectedUniqueInstallLinkForImagify = [ + 'wp-rocket/wp-rocket' => [ + 'logo' => [ + 'file' => 'logo-wp-rocket.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website, Instantly', + 'desc' => 'WP Rocket is the easiest way to make your WordPress website faster and boost your Google PageSpeed score. Get more traffic, better engagement, and higher conversions effortlessly.', + 'link' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify', + 'cta' => [ + 'text' => 'Get it Now', + 'url' => 'https://wp-rocket.me/?utm_source=imagify-coupon&utm_medium=plugin&utm_campaign=imagify' + ], + ], + 'imagify-plugin/imagify' => [ + 'logo' => [ + 'file' => 'logo-imagify.svg', + 'width' => '50%', + ], + 'title' => 'Speed Up Your Website With Lighter Images', + 'desc' => 'Imagify is the easiest WordPress image optimizer. It automatically compresses images, converts them to WebP and AVIF formats, and lets you resize and optimize with just one click!', + 'link' => 'https://imagify.io/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=install_imagify_from_partner_uk-cookie-consent&_wpnonce=9a68f00b8d&_wp_http_referer=https%3A%2F%2Fexample.org%2Fwp-admin' + ], + ], + 'seo-by-rank-math/rank-math' => [ + 'logo' => [ + 'file' => 'logo-rank-math.svg', + 'width' => '60%', + ], + 'title' => 'The Swiss Army Knife of SEO Tools', + 'desc' => 'Rank Math SEO is the Best WordPress SEO plugin with the features of many SEO and AI SEO tools in a single package to help multiply your SEO traffic.', + 'link' => 'https://rankmath.com/wordpress/plugin/seo-suite/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_seo-by-rank-math&_wpnonce=9a68f00b8d&plugin_to_install=seo-by-rank-math%2Frank-math' + ], + ], + 'backwpup/backwpup' => [ + 'logo' => [ + 'file' => 'logo-backwpup.svg', + 'width' => '60%', + ], + 'title' => 'The Easiest Way to Protect Your Website', + 'desc' => 'BackWPup is the most comprehensive and user-friendly backup & restore plugin for WordPress. Easily schedule automatic backups, securely store and restore with just a few clicks!', + 'link' => 'https://backwpup.com/', + 'cta' => [ + 'text' => 'Install', + 'url' => 'http://example.org/wp-admin/admin-post.php?action=plugin_family_install_backwpup&_wpnonce=9a68f00b8d&plugin_to_install=backwpup%2Fbackwpup' + ], + ], +]; + +return [ + 'testShouldReturnEmptyArrayIfEmptyArrayParsed' => [ + 'config' => [ + 'plugins' => [], + 'main_plugin' => 'wp-rocket/wp-rocket', + 'order' => 'both', + ], + 'expected' => [], + ], + 'testShouldReturnActivePluginAsTheLastElement' => [ + 'config' => [ + 'plugins' => $plugins, + 'main_plugin' => 'wp-rocket/wp-rocket', + 'order' => 'categorized', + 'active_plugin' => 'imagify-plugin/imagify.php', + ], + 'expected' => $expectedActivePluginAsTheLastElement, + ], + 'testShouldReturnCategoryWithActivePluginAsTheLastElement' => [ + 'config' => [ + 'plugins' => $plugins, + 'main_plugin' => 'imagify-plugin/imagify', + 'order' => 'uncategorized', + 'active_plugin' => 'seo-by-rank-math/rank-math.php', + ], + 'expected' => $expectedCategoryWithActivePluginAsTheLastElement, + ], + 'testShouldReturnActivateTextIfPluginIsAlreadyInstalled' => [ + 'config' => [ + 'plugins' => $plugins, + 'main_plugin' => 'backwpup/backwpup', + 'order' => 'uncategorized', + 'active_plugin' => '', + 'is_installed' => true, + ], + 'expected' => $expectedActivateTextIfPluginIsAlreadyInstalled, + ], + 'testShouldReturnUniqueInstallLinkForImagify' => [ + 'config' => [ + 'plugins' => $plugins, + 'main_plugin' => 'uk-cookie-consent/uk-cookie-consent', + 'order' => 'uncategorized', + 'active_plugin' => '', + ], + 'expected' => $expectedUniqueInstallLinkForImagify, + ], +]; \ No newline at end of file diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php new file mode 100644 index 0000000..3c9827f --- /dev/null +++ b/tests/Unit/TestCase.php @@ -0,0 +1,35 @@ +config ) ) { + $this->loadTestDataConfig(); + } + } + + public function configTestData() { + if ( empty( $this->config ) ) { + $this->loadTestDataConfig(); + } + + return isset( $this->config['test_data'] ) + ? $this->config['test_data'] + : $this->config; + } + + protected function loadTestDataConfig() { + $obj = new ReflectionObject( $this ); + $filename = $obj->getFileName(); + + $this->config = $this->getTestData( dirname( $filename ), basename( $filename, '.php' ) ); + } +} \ No newline at end of file diff --git a/tests/Unit/bootstrap.php b/tests/Unit/bootstrap.php new file mode 100644 index 0000000..8380fdc --- /dev/null +++ b/tests/Unit/bootstrap.php @@ -0,0 +1,10 @@ + + + + + + src + + + + + + ../../src + + + diff --git a/tests/Unit/src/Controller/PluginFamily/getPostInstallEvent.php b/tests/Unit/src/Controller/PluginFamily/getPostInstallEvent.php new file mode 100644 index 0000000..44ff89a --- /dev/null +++ b/tests/Unit/src/Controller/PluginFamily/getPostInstallEvent.php @@ -0,0 +1,37 @@ + $value ) { + $_GET[ $param ] = $value; + } + + Functions\when( 'sanitize_text_field' )->justReturn( $_GET['action'] ); + Functions\when( 'wp_unslash' )->justReturn( $_GET['action'] ); + } + + $this->assertSame( $expected, PluginFamily::get_post_install_event() ); + } +} \ No newline at end of file diff --git a/tests/Unit/src/Model/PluginFamily/filterPluginsByActivation.php b/tests/Unit/src/Model/PluginFamily/filterPluginsByActivation.php new file mode 100644 index 0000000..c8257e0 --- /dev/null +++ b/tests/Unit/src/Model/PluginFamily/filterPluginsByActivation.php @@ -0,0 +1,92 @@ +plugin_family = new PluginFamily(); + + $_SERVER = [ + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'example.org', + 'REQUEST_URI' => '/wp-admin', + ]; + } + + public function tear_down() { + $_SERVER = []; + + parent::tear_down(); + } + + /** + * @dataProvider configTestData + */ + public function testShouldReturnExpected( $config, $expected ) { + $this->config = $config; + + if ( 'both' === $config['order'] ) { + $this->assertSame( $expected, $this->get_filtered_plugins() ); + return; + } + + Functions\when( 'is_plugin_active' )->alias( function( $plugin ) use ($config) { + return $plugin === $config['active_plugin']; + } ); + + Functions\when( 'admin_url' )->alias( + function ( $path ) { + return "http://example.org/wp-admin/{$path}"; + } + ); + Functions\when( 'wp_create_nonce' )->justReturn( '9a68f00b8d' ); + Functions\when( 'add_query_arg' )->alias( function( $args, $link ) { + $url = ''; + + foreach( $args as $param => $value ) { + $url .= $param . '=' . $value . '&'; + } + + return $link . '?' . rtrim( $url, '&' ); + } ); + + Functions\when( 'wp_unslash' )->returnArg(); + Functions\when( 'is_ssl' )->justReturn( true ); + + if ( isset( $config['is_installed'] ) ) { + Functions\when( 'file_exists' )->justReturn( $config['is_installed'] ); + } + + $this->assertSame( $expected, $this->get_filtered_plugins() ); + } + + private function get_filtered_plugins(): array { + $filtered = $this->plugin_family + ->filter_plugins_by_activation( $this->config['plugins'], $this->config['main_plugin'] ); + + if ( 'both' !== $this->config['order'] ) { + return $filtered[ $this->config['order'] ]; + } + + return $filtered; + } +} \ No newline at end of file