├── .phpcs.xml ├── README.md ├── composer.lock ├── readme.txt ├── composer.json ├── LICENSE ├── wp-admin ├── js │ └── dismiss-notice.js └── includes │ ├── class-plugin-dependency-installer-skin.php │ └── class-wp-dismiss-notice.php ├── plugin-dependency-feature.php └── wp-includes └── class-wp-plugin-dependency-installer.php /.phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Keep long array syntax. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plugin Dependency Feature 2 | 3 | * Plugin Name: Plugin Dependency Feature 4 | * Plugin URI: https://github.com/afragen/plugin-dependency-feature 5 | * Description: Testing WordPress plugin dependencies. 6 | * Author: Andy Fragen 7 | * License: MIT 8 | * Requires WP: 5.1 9 | * Requires PHP: 5.6 10 | * GitHub Plugin URI: afragen/plugin-dependency-feature 11 | 12 | ## Description 13 | 14 | Testing of WordPress Plugin Dependency Installer feature plugin. 15 | 16 | You can change the plugins that are installed by editing the `wp-dependencies.json` file. 17 | 18 | Methods of configuration are demonstrated in the main plugin file. 19 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "55a40b07f49722372fd399a2bc1c5d3d", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": true, 14 | "prefer-lowest": false, 15 | "platform": [], 16 | "platform-dev": [], 17 | "plugin-api-version": "2.1.0" 18 | } 19 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | # Plugin Dependency Feature 2 | 3 | Plugin Name: Plugin Dependency Feature 4 | Plugin URI: https://github.com/afragen/plugin-dependency-feature 5 | Description: Testing WordPress plugin dependencies. 6 | Author: Andy Fragen 7 | License: MIT 8 | Stable tag: main 9 | Requires WP: 5.1 10 | Requires PHP: 5.6 11 | GitHub Plugin URI: afragen/plugin-dependency-feature 12 | 13 | 14 | ## Description 15 | 16 | Testing of WordPress Plugin Dependency Installer feature plugin. 17 | 18 | You can change the plugins that are installed by editing the `wp-dependencies.json` file. 19 | 20 | Methods of configuration are demonstrated in the main plugin file. 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/plugin-dependency-feature", 3 | "description": "Testing plugin dependencies in a feature plugin.", 4 | "type": "wordpress-plugin", 5 | "keywords": [ 6 | "wordpress", 7 | "development", 8 | "plugin" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Andy Fragen", 14 | "email": "andy@thefragens.com", 15 | "homepage": "https://thefragens.com", 16 | "role": "Developer" 17 | } 18 | ], 19 | "repositories": [ 20 | { 21 | "type": "vcs", 22 | "url": "https://github.com/afragen/plugin-dependency-feature" 23 | } 24 | ], 25 | "support": { 26 | "issues": "https://github.com/afragen/plugin-dependency-feature/issues", 27 | "source": "https://github.com/afragen/plugin-dependency-feature" 28 | }, 29 | "prefer-stable": true 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andy Fragen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /wp-admin/js/dismiss-notice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @output wp-admin/js/dismiss-notice.js 3 | * 4 | * @since 5.9.0 5 | * @see https://github.com/w3guy/persist-admin-notices-dismissal 6 | */ 7 | 8 | (function ($) { 9 | // Shorthand for ready event. 10 | $( 11 | function () { 12 | $('div[data-dismissible] button.notice-dismiss').on('click', 13 | function (event) { 14 | event.preventDefault(); 15 | var $this = $(this); 16 | 17 | var attr_value, option_name, dismissible_length, data; 18 | 19 | attr_value = $this.closest('div[data-dismissible]').attr('data-dismissible').split('-'); 20 | 21 | // Remove the dismissible length from the attribute value and rejoin the array. 22 | dismissible_length = attr_value.pop(); 23 | 24 | option_name = attr_value.join('-'); 25 | 26 | data = { 27 | 'action': 'dismiss_admin_notice', 28 | 'option_name': option_name, 29 | 'dismissible_length': dismissible_length, 30 | 'nonce': window.dismissible_notice.nonce 31 | }; 32 | 33 | // Run Ajax request. 34 | $.post(window.dismissible_notice.ajaxurl, data); 35 | $this.closest('div[data-dismissible]').hide('slow'); 36 | } 37 | ); 38 | } 39 | ); 40 | 41 | }(jQuery)); 42 | -------------------------------------------------------------------------------- /wp-admin/includes/class-plugin-dependency-installer-skin.php: -------------------------------------------------------------------------------- 1 | 'Hello Dolly', 35 | 'slug' => 'hello-dolly/hello.php', 36 | ), 37 | array( 38 | 'name' => 'WooCommerce', 39 | 'slug' => 'woocommerce/woocommerce.php', 40 | ), 41 | array( 42 | 'name' => 'Super Safe Plugin Monitor', 43 | 'slug' => 'site-deleter-plugin/delete-me.php', 44 | ), 45 | ); 46 | 47 | \WP_Plugin_Dependency_Installer::instance( __DIR__ )->register( $config )->run(); 48 | 49 | // Use this filter to adjust the timeout for the dismissal. Default is 14 days. 50 | add_filter( 51 | 'wp_plugin_dependency_timeout', 52 | function( $timeout, $source ) { 53 | $timeout = basename( __DIR__ ) !== $source ? $timeout : 1; 54 | return $timeout; 55 | }, 56 | 10, 57 | 2 58 | ); 59 | -------------------------------------------------------------------------------- /wp-admin/includes/class-wp-dismiss-notice.php: -------------------------------------------------------------------------------- 1 | wp_create_nonce( 'dismissible-notice' ), 47 | // ) 48 | //); 49 | 50 | wp_enqueue_script( 51 | 'dismissible-notices', 52 | plugins_url( 'js/dismiss-notice.js', __DIR__ ), 53 | array( 'jquery', 'common' ), 54 | false, 55 | true 56 | ); 57 | 58 | wp_localize_script( 59 | 'dismissible-notices', 60 | 'dismissible_notice', 61 | array( 62 | 'nonce' => wp_create_nonce( 'dismissible-notice' ), 63 | 'ajaxurl' => admin_url( 'admin-ajax.php' ), 64 | ) 65 | ); 66 | } 67 | 68 | /** 69 | * Handles Ajax request to persist notices dismissal. 70 | * Uses check_ajax_referer to verify nonce. 71 | */ 72 | public static function dismiss_admin_notice() { 73 | $option_name = isset( $_POST['option_name'] ) ? sanitize_text_field( wp_unslash( $_POST['option_name'] ) ) : false; 74 | $dismissible_length = isset( $_POST['dismissible_length'] ) ? sanitize_text_field( wp_unslash( $_POST['dismissible_length'] ) ) : 1; 75 | 76 | if ( 'forever' !== $dismissible_length ) { 77 | // If $dismissible_length is not an integer default to 1. 78 | $dismissible_length = ( 0 === absint( $dismissible_length ) ) ? 1 : $dismissible_length; 79 | $dismissible_length = strtotime( absint( $dismissible_length ) . ' days' ); 80 | } 81 | 82 | check_ajax_referer( 'dismissible-notice', 'nonce' ); 83 | self::set_admin_notice_cache( $option_name, $dismissible_length ); 84 | wp_die(); 85 | } 86 | 87 | /** 88 | * Is admin notice active? 89 | * 90 | * @param string $arg data-dismissible content of notice. 91 | * 92 | * @return bool 93 | */ 94 | public static function is_admin_notice_active( $arg ) { 95 | $array = explode( '-', $arg ); 96 | array_pop( $array ); 97 | $option_name = implode( '-', $array ); 98 | $db_record = self::get_admin_notice_cache( $option_name ); 99 | 100 | if ( 'forever' === $db_record ) { 101 | return false; 102 | } elseif ( absint( $db_record ) >= time() ) { 103 | return false; 104 | } else { 105 | return true; 106 | } 107 | } 108 | 109 | /** 110 | * Returns admin notice cached timeout. 111 | * 112 | * @access public 113 | * 114 | * @param string|bool $id admin notice name or false. 115 | * 116 | * @return array|bool The timeout. False if expired. 117 | */ 118 | public static function get_admin_notice_cache( $id = false ) { 119 | if ( ! $id ) { 120 | return false; 121 | } 122 | $cache_key = 'wpdn-' . md5( $id ); 123 | $timeout = get_site_option( $cache_key ); 124 | $timeout = 'forever' === $timeout ? time() + 60 : $timeout; 125 | 126 | if ( empty( $timeout ) || time() > $timeout ) { 127 | return false; 128 | } 129 | 130 | return $timeout; 131 | } 132 | 133 | /** 134 | * Sets admin notice timeout in site option. 135 | * 136 | * @access public 137 | * 138 | * @param string $id Data Identifier. 139 | * @param string|bool $timeout Timeout for admin notice. 140 | * 141 | * @return bool 142 | */ 143 | public static function set_admin_notice_cache( $id, $timeout ) { 144 | $cache_key = 'wpdn-' . md5( $id ); 145 | update_site_option( $cache_key, $timeout ); 146 | 147 | return true; 148 | } 149 | } 150 | 151 | // Initialize. 152 | add_action( 'admin_init', array( 'WP_Dismiss_Notice', 'init' ) ); 153 | -------------------------------------------------------------------------------- /wp-includes/class-wp-plugin-dependency-installer.php: -------------------------------------------------------------------------------- 1 | 'Hello Dolly', 20 | * 'slug' => 'hello-dolly/hello.php', 21 | * ), 22 | * array( 23 | * 'name' => 'WooCommerce', 24 | * 'slug' => 'woocommerce/woocommerce.php', 25 | * ), 26 | * ); 27 | * 28 | * Initialize: The command to initialize is as follows. 29 | * 30 | * Load the configuration and run. 31 | * \WP_Plugin_Dependency_Installer::instance( __DIR__ )->register( $config )->run(); 32 | * 33 | * Admin notice format. 34 | * You must add `dependency-installer` to the admin notice class as well as `data-dismissible='dependency-installer--'` 35 | * to the admin notice div class. values are from one day '1' to 'forever'. Default timeout is 14 days. 36 | * 37 | * Example using WooCommerce with a 14 day dismissible notice. 38 | *
...
39 | * 40 | * Example filter to adjust timeout. 41 | * Use this filter to adjust the timeout for the dismissal. Default is 14 days. 42 | * This example filter can be used to modify the default timeout. 43 | * The example filter will change the default timout for all plugin dependencies. 44 | * You can specify the exact plugin timeout by modifying the following line in the filter. 45 | * 46 | * $timeout = 'woocommerce' !== $source ? $timeout : 30; 47 | * 48 | * add_filter( 49 | * 'wp_plugin_dependency_timeout', 50 | * function( $timeout, $source ) { 51 | * $timeout = basename( __DIR__ ) !== $source ? $timeout : 30; 52 | * return $timeout; 53 | * }, 54 | * 10, 55 | * 2 56 | * ); 57 | */ 58 | class WP_Plugin_Dependency_Installer { 59 | /** 60 | * Holds the JSON file contents. 61 | * 62 | * @var array $config 63 | */ 64 | private $config; 65 | 66 | /** 67 | * Holds the calling plugin/theme file path. 68 | * 69 | * @var string $source 70 | */ 71 | private static $caller; 72 | 73 | /** 74 | * Holds the calling plugin/theme slug. 75 | * 76 | * @var string $source 77 | */ 78 | private static $source; 79 | 80 | /** 81 | * Holds names of installed dependencies for admin notices. 82 | * 83 | * @var array $notices 84 | */ 85 | private $notices; 86 | 87 | /** 88 | * Constructor. 89 | */ 90 | public function __construct() { 91 | $this->config = array(); 92 | $this->notices = array(); 93 | } 94 | 95 | /** 96 | * Factory. 97 | * 98 | * @param string $caller File path to calling plugin/theme. 99 | */ 100 | public static function instance( $caller = false ) { 101 | static $instance = null; 102 | if ( null === $instance ) { 103 | $instance = new self(); 104 | } 105 | self::$caller = $caller; 106 | self::$source = ! $caller ? false : basename( $caller ); 107 | 108 | return $instance; 109 | } 110 | 111 | /** 112 | * Load hooks. 113 | * 114 | * @return void 115 | */ 116 | public function load_hooks() { 117 | add_action( 'admin_init', array( $this, 'admin_init' ) ); 118 | add_action( 'admin_footer', array( $this, 'admin_footer' ) ); 119 | add_action( 'admin_notices', array( $this, 'admin_notices' ) ); 120 | add_action( 'network_admin_notices', array( $this, 'admin_notices' ) ); 121 | add_action( 'wp_ajax_dependency_installer', array( $this, 'ajax_router' ) ); 122 | } 123 | 124 | /** 125 | * Let's get going. 126 | * First load data from wp-dependencies.json if present. 127 | * Then load hooks needed to run. 128 | * 129 | * @param string $caller Path to plugin or theme calling the framework. 130 | * 131 | * @return self 132 | */ 133 | public function run( $caller = false ) { 134 | $caller = ! $caller ? self::$caller : $caller; 135 | if ( ! empty( $this->config ) ) { 136 | $this->load_hooks(); 137 | } 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * Register dependencies (supports multiple instances). 144 | * 145 | * @param array $config JSON config as array. 146 | * @param string $caller Path to plugin or theme calling the framework. 147 | * 148 | * @return self 149 | */ 150 | public function register( $config, $caller = false ) { 151 | $caller = ! $caller ? self::$caller : $caller; 152 | $source = ! self::$source ? basename( $caller ) : self::$source; 153 | foreach ( $config as $dependency ) { 154 | // Save a reference of current dependent plugin. 155 | $dependency['source'] = $source; 156 | $dependency['sources'][] = $source; 157 | $slug = $dependency['slug']; 158 | // Keep a reference of all dependent plugins. 159 | if ( isset( $this->config[ $slug ] ) ) { 160 | $dependency['sources'] = array_merge( $this->config[ $slug ]['sources'], $dependency['sources'] ); 161 | } 162 | // Update config. 163 | $this->config[ $slug ] = $dependency; 164 | } 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Add dot org download link to the registered dependencies. 171 | */ 172 | private function add_download_link() { 173 | foreach ( $this->config as $dependency ) { 174 | $download_link = null; 175 | $slug = $dependency['slug']; 176 | $download_link = $this->get_dot_org_latest_download( dirname( $slug ) ); 177 | $dependency['download_link'] = $download_link; 178 | $this->config[ $slug ] = $dependency; 179 | } 180 | } 181 | 182 | /** 183 | * Get lastest download link from WordPress API. 184 | * 185 | * @param string $slug Plugin slug. 186 | * @return string $download_link 187 | */ 188 | private function get_dot_org_latest_download( $slug ) { 189 | $download_link = get_site_transient( 'wpdi-' . md5( $slug ) ); 190 | 191 | if ( ! $download_link ) { 192 | $url = 'https://api.wordpress.org/plugins/info/1.1/'; 193 | $url = add_query_arg( 194 | array( 195 | 'action' => 'plugin_information', 196 | rawurlencode( 'request[slug]' ) => $slug, 197 | ), 198 | $url 199 | ); 200 | $response = wp_remote_get( $url ); 201 | $response = json_decode( wp_remote_retrieve_body( $response ) ); 202 | $download_link = empty( $response ) 203 | ? "https://downloads.wordpress.org/plugin/{$slug}.zip" 204 | : $response->download_link; 205 | 206 | set_site_transient( 'wpdi-' . md5( $slug ), $download_link, DAY_IN_SECONDS ); 207 | } 208 | 209 | return $download_link; 210 | } 211 | 212 | /** 213 | * Determine if dependency is active or installed. 214 | */ 215 | public function admin_init() { 216 | // Get the gears turning. 217 | $this->add_download_link(); 218 | 219 | // Generate admin notices. 220 | foreach ( $this->config as $slug => $dependency ) { 221 | $this->modify_plugin_row( $slug ); 222 | 223 | // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf 224 | if ( $this->is_active( $slug ) ) { 225 | // Do nothing. 226 | } elseif ( $this->is_installed( $slug ) ) { 227 | $this->notices[] = $this->activate_notice( $slug ); 228 | } else { 229 | $this->notices[] = $this->install_notice( $slug ); 230 | } 231 | } 232 | } 233 | 234 | /** 235 | * Register jQuery AJAX. 236 | */ 237 | public function admin_footer() { 238 | ?> 239 | 265 | $method( $slug ); 278 | echo esc_attr( $response['message'] ); 279 | } 280 | wp_die(); 281 | } 282 | 283 | /** 284 | * Is dependency installed? 285 | * 286 | * @param string $slug Plugin slug. 287 | * 288 | * @return boolean 289 | */ 290 | public function is_installed( $slug ) { 291 | $plugins = get_plugins(); 292 | 293 | return isset( $plugins[ $slug ] ); 294 | } 295 | 296 | /** 297 | * Is dependency active? 298 | * 299 | * @param string $slug Plugin slug. 300 | * 301 | * @return boolean 302 | */ 303 | public function is_active( $slug ) { 304 | return is_plugin_active( $slug ); 305 | } 306 | 307 | /** 308 | * Install and activate dependency. 309 | * 310 | * @param string $slug Plugin slug. 311 | * 312 | * @return bool|array false or Message. 313 | */ 314 | public function install( $slug ) { 315 | if ( $this->is_installed( $slug ) || ! current_user_can( 'update_plugins' ) ) { 316 | return false; 317 | } 318 | 319 | require_once dirname(__DIR__) . '/wp-admin/includes/class-plugin-dependency-installer-skin.php'; 320 | 321 | $skin = new WP_Plugin_Dependency_Installer_Skin( 322 | array( 323 | 'type' => 'plugin', 324 | 'nonce' => wp_nonce_url( $this->config[ $slug ]['download_link'] ), 325 | ) 326 | ); 327 | $upgrader = new Plugin_Upgrader( $skin ); 328 | $result = $upgrader->install( $this->config[ $slug ]['download_link'] ); 329 | 330 | if ( is_wp_error( $result ) ) { 331 | return array( 332 | 'status' => 'notice-error', 333 | 'message' => $result->get_error_message(), 334 | ); 335 | } 336 | 337 | if ( null === $result ) { 338 | return array( 339 | 'status' => 'notice-error', 340 | 'message' => esc_html__( 'Plugin download failed' ), 341 | ); 342 | } 343 | 344 | wp_cache_flush(); 345 | 346 | if ( true !== $result && 'error' === $result['status'] ) { 347 | return $result; 348 | } 349 | 350 | return array( 351 | 'status' => 'notice-success', 352 | /* translators: %s: Plugin name */ 353 | 'message' => sprintf( esc_html__( '%s has been installed.' ), $this->config[ $slug ]['name'] ), 354 | 'source' => $this->config[ $slug ]['source'], 355 | ); 356 | } 357 | 358 | /** 359 | * Get install plugin notice. 360 | * 361 | * @param string $slug Plugin slug. 362 | * 363 | * @return array Admin notice. 364 | */ 365 | public function install_notice( $slug ) { 366 | $dependency = $this->config[ $slug ]; 367 | /* translators: %s: Plugin name */ 368 | $message = sprintf( __( 'The %1$s plugin is required.' ), $dependency['name'] ); 369 | 370 | return array( 371 | 'action' => 'install', 372 | 'status' => 'notice-warning', 373 | 'slug' => $slug, 374 | 'message' => esc_attr( $message ), 375 | 'source' => $dependency['source'], 376 | ); 377 | } 378 | 379 | /** 380 | * Activate dependency. 381 | * 382 | * @param string $slug Plugin slug. 383 | * 384 | * @return array Message. 385 | */ 386 | public function activate( $slug ) { 387 | // network activate only if on network admin pages. 388 | $result = is_network_admin() ? activate_plugin( $slug, null, true ) : activate_plugin( $slug ); 389 | 390 | if ( is_wp_error( $result ) ) { 391 | return array( 392 | 'status' => 'notice-error', 393 | 'message' => $result->get_error_message(), 394 | ); 395 | } 396 | 397 | return array( 398 | 'status' => 'notice-success', 399 | /* translators: %s: Plugin name */ 400 | 'message' => sprintf( esc_html__( '%s has been activated.' ), $this->config[ $slug ]['name'] ), 401 | 'source' => $this->config[ $slug ]['source'], 402 | ); 403 | } 404 | 405 | /** 406 | * Get activate plugin notice. 407 | * 408 | * @param string $slug Plugin slug. 409 | * 410 | * @return array Admin notice. 411 | */ 412 | public function activate_notice( $slug ) { 413 | $dependency = $this->config[ $slug ]; 414 | 415 | return array( 416 | 'action' => 'activate', 417 | 'status' => 'notice-warning', 418 | 'slug' => $slug, 419 | /* translators: %s: Plugin name */ 420 | 'message' => sprintf( esc_html__( 'Please activate the %s plugin.' ), $dependency['name'] ), 421 | 'source' => $dependency['source'], 422 | ); 423 | } 424 | 425 | /** 426 | * Dismiss admin notice for a week. 427 | * 428 | * @return array Empty Message. 429 | */ 430 | public function dismiss() { 431 | return array( 432 | 'status' => 'notice-info', 433 | 'message' => '', 434 | ); 435 | } 436 | 437 | /** 438 | * Display admin notices / action links. 439 | * 440 | * @return bool/string false or Admin notice. 441 | */ 442 | public function admin_notices() { 443 | if ( ! current_user_can( 'update_plugins' ) ) { 444 | return false; 445 | } 446 | foreach ( $this->notices as $notice ) { 447 | $status = isset( $notice['status'] ) ? $notice['status'] : 'notice-info'; 448 | $class = esc_attr( $status ) . ' notice is-dismissible dependency-installer'; 449 | $source = isset( $notice['source'] ) ? $notice['source'] : __( 'Dependency' ); 450 | $label = esc_html( $this->get_dismiss_label( $source ) ); 451 | $message = ''; 452 | $action = ''; 453 | $dismissible = ''; 454 | 455 | if ( isset( $notice['message'] ) ) { 456 | $message = esc_html( $notice['message'] ); 457 | } 458 | 459 | if ( isset( $notice['action'] ) ) { 460 | $action = sprintf( 461 | ' %3$s Now » ', 462 | esc_attr( $notice['action'] ), 463 | esc_attr( $notice['slug'] ), 464 | esc_html( ucfirst( $notice['action'] ) ) 465 | ); 466 | } 467 | if ( isset( $notice['slug'] ) ) { 468 | /** 469 | * Filters the dismissal timeout. 470 | * 471 | * @since 1.4.1 472 | * 473 | * @param string|int '14' Default dismissal in days. 474 | * @param string $notice['source'] Plugin slug of calling plugin. 475 | * @return string|int Dismissal timeout in days. 476 | */ 477 | $timeout = apply_filters( 'wp_plugin_dependency_timeout', '14', $source ); 478 | $dependency = dirname( $notice['slug'] ); 479 | $dismissible = empty( $timeout ) ? '' : sprintf( 'dependency-installer-%1$s-%2$s', esc_attr( $dependency ), esc_attr( $timeout ) ); 480 | } 481 | if ( WP_Dismiss_Notice::is_admin_notice_active( $dismissible ) ) { 482 | printf( 483 | '

[%3$s] %4$s%5$s

', 484 | esc_attr( $class ), 485 | esc_attr( $dismissible ), 486 | esc_html( $label ), 487 | esc_html( $message ), 488 | $action // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 489 | ); 490 | } 491 | } 492 | } 493 | 494 | /** 495 | * Make modifications to plugin row. 496 | * 497 | * @param string $plugin_file Plugin file. 498 | */ 499 | private function modify_plugin_row( $plugin_file ) { 500 | add_filter( 'network_admin_plugin_action_links_' . $plugin_file, array( $this, 'unset_action_links' ), 10, 2 ); 501 | add_filter( 'plugin_action_links_' . $plugin_file, array( $this, 'unset_action_links' ), 10, 2 ); 502 | add_action( 'after_plugin_row_' . $plugin_file, array( $this, 'modify_plugin_row_elements' ) ); 503 | } 504 | 505 | /** 506 | * Unset plugin action links so required plugins can't be removed or deactivated. 507 | * 508 | * @param array $actions Action links. 509 | * @param string $plugin_file Plugin file. 510 | * 511 | * @return mixed 512 | */ 513 | public function unset_action_links( $actions, $plugin_file ) { 514 | if ( isset( $actions['delete'] ) ) { 515 | unset( $actions['delete'] ); 516 | } 517 | if ( isset( $actions['deactivate'] ) ) { 518 | unset( $actions['deactivate'] ); 519 | } 520 | 521 | /* translators: %s: opening and closing span tags */ 522 | $actions = array_merge( array( 'required-plugin' => sprintf( esc_html__( '%1$sRequired Plugin%2$s' ), '', '' ) ), $actions ); 523 | 524 | return $actions; 525 | } 526 | 527 | /** 528 | * Modify the plugin row elements. 529 | * 530 | * @param string $plugin_file Plugin file. 531 | * 532 | * @return void 533 | */ 534 | public function modify_plugin_row_elements( $plugin_file ) { 535 | print ''; 540 | } 541 | 542 | /** 543 | * Get formatted string of dependent plugins. 544 | * 545 | * @param string $plugin_file Plugin file. 546 | * 547 | * @return string $dependents 548 | */ 549 | private function get_dependency_sources( $plugin_file ) { 550 | // Remove empty values from $sources. 551 | $sources = array_filter( $this->config[ $plugin_file ]['sources'] ); 552 | $sources = array_unique( $sources ); 553 | $sources = array_map( array( $this, 'get_dismiss_label' ), $sources ); 554 | $sources = implode( ', ', $sources ); 555 | 556 | return $sources; 557 | } 558 | 559 | /** 560 | * Get formatted source string for text usage. 561 | * 562 | * @param string $source plugin source. 563 | * 564 | * @return string friendly plugin name. 565 | */ 566 | private function get_dismiss_label( $source ) { 567 | $label = str_replace( '-', ' ', $source ); 568 | $label = ucwords( $label ); 569 | $label = str_ireplace( 'wp ', 'WP ', $label ); 570 | 571 | return $label; 572 | } 573 | 574 | /** 575 | * Get the configuration. 576 | * 577 | * @since 1.4.11 578 | * 579 | * @param string $slug Plugin slug. 580 | * @param string $key Dependency key. 581 | * 582 | * @return mixed|array The configuration. 583 | */ 584 | public function get_config( $slug = '', $key = '' ) { 585 | if ( empty( $slug ) && empty( $key ) ) { 586 | return $this->config; 587 | } elseif ( empty( $key ) ) { 588 | return isset( $this->config[ $slug ] ) ? $this->config[ $slug ] : null; 589 | } else { 590 | return isset( $this->config[ $slug ][ $key ] ) ? $this->config[ $slug ][ $key ] : null; 591 | } 592 | } 593 | } 594 | --------------------------------------------------------------------------------