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