plugin.php (88385B)
1 <?php 2 /** 3 * WordPress Plugin Administration API 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** 10 * Parses the plugin contents to retrieve plugin's metadata. 11 * 12 * All plugin headers must be on their own line. Plugin description must not have 13 * any newlines, otherwise only parts of the description will be displayed. 14 * The below is formatted for printing. 15 * 16 * /* 17 * Plugin Name: Name of the plugin. 18 * Plugin URI: The home page of the plugin. 19 * Description: Plugin description. 20 * Author: Plugin author's name. 21 * Author URI: Link to the author's website. 22 * Version: Plugin version. 23 * Text Domain: Optional. Unique identifier, should be same as the one used in 24 * load_plugin_textdomain(). 25 * Domain Path: Optional. Only useful if the translations are located in a 26 * folder above the plugin's base path. For example, if .mo files are 27 * located in the locale folder then Domain Path will be "/locale/" and 28 * must have the first slash. Defaults to the base folder the plugin is 29 * located in. 30 * Network: Optional. Specify "Network: true" to require that a plugin is activated 31 * across all sites in an installation. This will prevent a plugin from being 32 * activated on a single site when Multisite is enabled. 33 * Requires at least: Optional. Specify the minimum required WordPress version. 34 * Requires PHP: Optional. Specify the minimum required PHP version. 35 * * / # Remove the space to close comment. 36 * 37 * The first 8 KB of the file will be pulled in and if the plugin data is not 38 * within that first 8 KB, then the plugin author should correct their plugin 39 * and move the plugin data headers to the top. 40 * 41 * The plugin file is assumed to have permissions to allow for scripts to read 42 * the file. This is not checked however and the file is only opened for 43 * reading. 44 * 45 * @since 1.5.0 46 * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers. 47 * @since 5.8.0 Added support for `Update URI` header. 48 * 49 * @param string $plugin_file Absolute path to the main plugin file. 50 * @param bool $markup Optional. If the returned data should have HTML markup applied. 51 * Default true. 52 * @param bool $translate Optional. If the returned data should be translated. Default true. 53 * @return array { 54 * Plugin data. Values will be empty if not supplied by the plugin. 55 * 56 * @type string $Name Name of the plugin. Should be unique. 57 * @type string $Title Title of the plugin and link to the plugin's site (if set). 58 * @type string $Description Plugin description. 59 * @type string $Author Author's name. 60 * @type string $AuthorURI Author's website address (if set). 61 * @type string $Version Plugin version. 62 * @type string $TextDomain Plugin textdomain. 63 * @type string $DomainPath Plugins relative directory path to .mo files. 64 * @type bool $Network Whether the plugin can only be activated network-wide. 65 * @type string $RequiresWP Minimum required version of WordPress. 66 * @type string $RequiresPHP Minimum required version of PHP. 67 * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. 68 * } 69 */ 70 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { 71 72 $default_headers = array( 73 'Name' => 'Plugin Name', 74 'PluginURI' => 'Plugin URI', 75 'Version' => 'Version', 76 'Description' => 'Description', 77 'Author' => 'Author', 78 'AuthorURI' => 'Author URI', 79 'TextDomain' => 'Text Domain', 80 'DomainPath' => 'Domain Path', 81 'Network' => 'Network', 82 'RequiresWP' => 'Requires at least', 83 'RequiresPHP' => 'Requires PHP', 84 'UpdateURI' => 'Update URI', 85 // Site Wide Only is deprecated in favor of Network. 86 '_sitewide' => 'Site Wide Only', 87 ); 88 89 $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); 90 91 // Site Wide Only is the old header for Network. 92 if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) { 93 /* translators: 1: Site Wide Only: true, 2: Network: true */ 94 _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), '<code>Site Wide Only: true</code>', '<code>Network: true</code>' ) ); 95 $plugin_data['Network'] = $plugin_data['_sitewide']; 96 } 97 $plugin_data['Network'] = ( 'true' === strtolower( $plugin_data['Network'] ) ); 98 unset( $plugin_data['_sitewide'] ); 99 100 // If no text domain is defined fall back to the plugin slug. 101 if ( ! $plugin_data['TextDomain'] ) { 102 $plugin_slug = dirname( plugin_basename( $plugin_file ) ); 103 if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) { 104 $plugin_data['TextDomain'] = $plugin_slug; 105 } 106 } 107 108 if ( $markup || $translate ) { 109 $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate ); 110 } else { 111 $plugin_data['Title'] = $plugin_data['Name']; 112 $plugin_data['AuthorName'] = $plugin_data['Author']; 113 } 114 115 return $plugin_data; 116 } 117 118 /** 119 * Sanitizes plugin data, optionally adds markup, optionally translates. 120 * 121 * @since 2.7.0 122 * 123 * @see get_plugin_data() 124 * 125 * @access private 126 * 127 * @param string $plugin_file Path to the main plugin file. 128 * @param array $plugin_data An array of plugin data. See `get_plugin_data()`. 129 * @param bool $markup Optional. If the returned data should have HTML markup applied. 130 * Default true. 131 * @param bool $translate Optional. If the returned data should be translated. Default true. 132 * @return array { 133 * Plugin data. Values will be empty if not supplied by the plugin. 134 * 135 * @type string $Name Name of the plugin. Should be unique. 136 * @type string $Title Title of the plugin and link to the plugin's site (if set). 137 * @type string $Description Plugin description. 138 * @type string $Author Author's name. 139 * @type string $AuthorURI Author's website address (if set). 140 * @type string $Version Plugin version. 141 * @type string $TextDomain Plugin textdomain. 142 * @type string $DomainPath Plugins relative directory path to .mo files. 143 * @type bool $Network Whether the plugin can only be activated network-wide. 144 * } 145 */ 146 function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup = true, $translate = true ) { 147 148 // Sanitize the plugin filename to a WP_PLUGIN_DIR relative path. 149 $plugin_file = plugin_basename( $plugin_file ); 150 151 // Translate fields. 152 if ( $translate ) { 153 $textdomain = $plugin_data['TextDomain']; 154 if ( $textdomain ) { 155 if ( ! is_textdomain_loaded( $textdomain ) ) { 156 if ( $plugin_data['DomainPath'] ) { 157 load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) . $plugin_data['DomainPath'] ); 158 } else { 159 load_plugin_textdomain( $textdomain, false, dirname( $plugin_file ) ); 160 } 161 } 162 } elseif ( 'hello.php' === basename( $plugin_file ) ) { 163 $textdomain = 'default'; 164 } 165 if ( $textdomain ) { 166 foreach ( array( 'Name', 'PluginURI', 'Description', 'Author', 'AuthorURI', 'Version' ) as $field ) { 167 // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain 168 $plugin_data[ $field ] = translate( $plugin_data[ $field ], $textdomain ); 169 } 170 } 171 } 172 173 // Sanitize fields. 174 $allowed_tags_in_links = array( 175 'abbr' => array( 'title' => true ), 176 'acronym' => array( 'title' => true ), 177 'code' => true, 178 'em' => true, 179 'strong' => true, 180 ); 181 182 $allowed_tags = $allowed_tags_in_links; 183 $allowed_tags['a'] = array( 184 'href' => true, 185 'title' => true, 186 ); 187 188 // Name is marked up inside <a> tags. Don't allow these. 189 // Author is too, but some plugins have used <a> here (omitting Author URI). 190 $plugin_data['Name'] = wp_kses( $plugin_data['Name'], $allowed_tags_in_links ); 191 $plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags ); 192 193 $plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags ); 194 $plugin_data['Version'] = wp_kses( $plugin_data['Version'], $allowed_tags ); 195 196 $plugin_data['PluginURI'] = esc_url( $plugin_data['PluginURI'] ); 197 $plugin_data['AuthorURI'] = esc_url( $plugin_data['AuthorURI'] ); 198 199 $plugin_data['Title'] = $plugin_data['Name']; 200 $plugin_data['AuthorName'] = $plugin_data['Author']; 201 202 // Apply markup. 203 if ( $markup ) { 204 if ( $plugin_data['PluginURI'] && $plugin_data['Name'] ) { 205 $plugin_data['Title'] = '<a href="' . $plugin_data['PluginURI'] . '">' . $plugin_data['Name'] . '</a>'; 206 } 207 208 if ( $plugin_data['AuthorURI'] && $plugin_data['Author'] ) { 209 $plugin_data['Author'] = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>'; 210 } 211 212 $plugin_data['Description'] = wptexturize( $plugin_data['Description'] ); 213 214 if ( $plugin_data['Author'] ) { 215 $plugin_data['Description'] .= sprintf( 216 /* translators: %s: Plugin author. */ 217 ' <cite>' . __( 'By %s.' ) . '</cite>', 218 $plugin_data['Author'] 219 ); 220 } 221 } 222 223 return $plugin_data; 224 } 225 226 /** 227 * Get a list of a plugin's files. 228 * 229 * @since 2.8.0 230 * 231 * @param string $plugin Path to the plugin file relative to the plugins directory. 232 * @return string[] Array of file names relative to the plugin root. 233 */ 234 function get_plugin_files( $plugin ) { 235 $plugin_file = WP_PLUGIN_DIR . '/' . $plugin; 236 $dir = dirname( $plugin_file ); 237 238 $plugin_files = array( plugin_basename( $plugin_file ) ); 239 240 if ( is_dir( $dir ) && WP_PLUGIN_DIR !== $dir ) { 241 242 /** 243 * Filters the array of excluded directories and files while scanning the folder. 244 * 245 * @since 4.9.0 246 * 247 * @param string[] $exclusions Array of excluded directories and files. 248 */ 249 $exclusions = (array) apply_filters( 'plugin_files_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) ); 250 251 $list_files = list_files( $dir, 100, $exclusions ); 252 $list_files = array_map( 'plugin_basename', $list_files ); 253 254 $plugin_files = array_merge( $plugin_files, $list_files ); 255 $plugin_files = array_values( array_unique( $plugin_files ) ); 256 } 257 258 return $plugin_files; 259 } 260 261 /** 262 * Check the plugins directory and retrieve all plugin files with plugin data. 263 * 264 * WordPress only supports plugin files in the base plugins directory 265 * (wp-content/plugins) and in one directory above the plugins directory 266 * (wp-content/plugins/my-plugin). The file it looks for has the plugin data 267 * and must be found in those two locations. It is recommended to keep your 268 * plugin files in their own directories. 269 * 270 * The file with the plugin data is the file that will be included and therefore 271 * needs to have the main execution for the plugin. This does not mean 272 * everything must be contained in the file and it is recommended that the file 273 * be split for maintainability. Keep everything in one file for extreme 274 * optimization purposes. 275 * 276 * @since 1.5.0 277 * 278 * @param string $plugin_folder Optional. Relative path to single plugin folder. 279 * @return array[] Array of arrays of plugin data, keyed by plugin file name. See `get_plugin_data()`. 280 */ 281 function get_plugins( $plugin_folder = '' ) { 282 283 $cache_plugins = wp_cache_get( 'plugins', 'plugins' ); 284 if ( ! $cache_plugins ) { 285 $cache_plugins = array(); 286 } 287 288 if ( isset( $cache_plugins[ $plugin_folder ] ) ) { 289 return $cache_plugins[ $plugin_folder ]; 290 } 291 292 $wp_plugins = array(); 293 $plugin_root = WP_PLUGIN_DIR; 294 if ( ! empty( $plugin_folder ) ) { 295 $plugin_root .= $plugin_folder; 296 } 297 298 // Files in wp-content/plugins directory. 299 $plugins_dir = @opendir( $plugin_root ); 300 $plugin_files = array(); 301 302 if ( $plugins_dir ) { 303 while ( ( $file = readdir( $plugins_dir ) ) !== false ) { 304 if ( '.' === substr( $file, 0, 1 ) ) { 305 continue; 306 } 307 308 if ( is_dir( $plugin_root . '/' . $file ) ) { 309 $plugins_subdir = @opendir( $plugin_root . '/' . $file ); 310 311 if ( $plugins_subdir ) { 312 while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) { 313 if ( '.' === substr( $subfile, 0, 1 ) ) { 314 continue; 315 } 316 317 if ( '.php' === substr( $subfile, -4 ) ) { 318 $plugin_files[] = "$file/$subfile"; 319 } 320 } 321 322 closedir( $plugins_subdir ); 323 } 324 } else { 325 if ( '.php' === substr( $file, -4 ) ) { 326 $plugin_files[] = $file; 327 } 328 } 329 } 330 331 closedir( $plugins_dir ); 332 } 333 334 if ( empty( $plugin_files ) ) { 335 return $wp_plugins; 336 } 337 338 foreach ( $plugin_files as $plugin_file ) { 339 if ( ! is_readable( "$plugin_root/$plugin_file" ) ) { 340 continue; 341 } 342 343 // Do not apply markup/translate as it will be cached. 344 $plugin_data = get_plugin_data( "$plugin_root/$plugin_file", false, false ); 345 346 if ( empty( $plugin_data['Name'] ) ) { 347 continue; 348 } 349 350 $wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data; 351 } 352 353 uasort( $wp_plugins, '_sort_uname_callback' ); 354 355 $cache_plugins[ $plugin_folder ] = $wp_plugins; 356 wp_cache_set( 'plugins', $cache_plugins, 'plugins' ); 357 358 return $wp_plugins; 359 } 360 361 /** 362 * Check the mu-plugins directory and retrieve all mu-plugin files with any plugin data. 363 * 364 * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins). 365 * 366 * @since 3.0.0 367 * @return array[] Array of arrays of mu-plugin data, keyed by plugin file name. See `get_plugin_data()`. 368 */ 369 function get_mu_plugins() { 370 $wp_plugins = array(); 371 $plugin_files = array(); 372 373 if ( ! is_dir( WPMU_PLUGIN_DIR ) ) { 374 return $wp_plugins; 375 } 376 377 // Files in wp-content/mu-plugins directory. 378 $plugins_dir = @opendir( WPMU_PLUGIN_DIR ); 379 if ( $plugins_dir ) { 380 while ( ( $file = readdir( $plugins_dir ) ) !== false ) { 381 if ( '.php' === substr( $file, -4 ) ) { 382 $plugin_files[] = $file; 383 } 384 } 385 } else { 386 return $wp_plugins; 387 } 388 389 closedir( $plugins_dir ); 390 391 if ( empty( $plugin_files ) ) { 392 return $wp_plugins; 393 } 394 395 foreach ( $plugin_files as $plugin_file ) { 396 if ( ! is_readable( WPMU_PLUGIN_DIR . "/$plugin_file" ) ) { 397 continue; 398 } 399 400 // Do not apply markup/translate as it will be cached. 401 $plugin_data = get_plugin_data( WPMU_PLUGIN_DIR . "/$plugin_file", false, false ); 402 403 if ( empty( $plugin_data['Name'] ) ) { 404 $plugin_data['Name'] = $plugin_file; 405 } 406 407 $wp_plugins[ $plugin_file ] = $plugin_data; 408 } 409 410 if ( isset( $wp_plugins['index.php'] ) && filesize( WPMU_PLUGIN_DIR . '/index.php' ) <= 30 ) { 411 // Silence is golden. 412 unset( $wp_plugins['index.php'] ); 413 } 414 415 uasort( $wp_plugins, '_sort_uname_callback' ); 416 417 return $wp_plugins; 418 } 419 420 /** 421 * Callback to sort array by a 'Name' key. 422 * 423 * @since 3.1.0 424 * 425 * @access private 426 * 427 * @param array $a array with 'Name' key. 428 * @param array $b array with 'Name' key. 429 * @return int Return 0 or 1 based on two string comparison. 430 */ 431 function _sort_uname_callback( $a, $b ) { 432 return strnatcasecmp( $a['Name'], $b['Name'] ); 433 } 434 435 /** 436 * Check the wp-content directory and retrieve all drop-ins with any plugin data. 437 * 438 * @since 3.0.0 439 * @return array[] Array of arrays of dropin plugin data, keyed by plugin file name. See `get_plugin_data()`. 440 */ 441 function get_dropins() { 442 $dropins = array(); 443 $plugin_files = array(); 444 445 $_dropins = _get_dropins(); 446 447 // Files in wp-content directory. 448 $plugins_dir = @opendir( WP_CONTENT_DIR ); 449 if ( $plugins_dir ) { 450 while ( ( $file = readdir( $plugins_dir ) ) !== false ) { 451 if ( isset( $_dropins[ $file ] ) ) { 452 $plugin_files[] = $file; 453 } 454 } 455 } else { 456 return $dropins; 457 } 458 459 closedir( $plugins_dir ); 460 461 if ( empty( $plugin_files ) ) { 462 return $dropins; 463 } 464 465 foreach ( $plugin_files as $plugin_file ) { 466 if ( ! is_readable( WP_CONTENT_DIR . "/$plugin_file" ) ) { 467 continue; 468 } 469 470 // Do not apply markup/translate as it will be cached. 471 $plugin_data = get_plugin_data( WP_CONTENT_DIR . "/$plugin_file", false, false ); 472 473 if ( empty( $plugin_data['Name'] ) ) { 474 $plugin_data['Name'] = $plugin_file; 475 } 476 477 $dropins[ $plugin_file ] = $plugin_data; 478 } 479 480 uksort( $dropins, 'strnatcasecmp' ); 481 482 return $dropins; 483 } 484 485 /** 486 * Returns drop-ins that WordPress uses. 487 * 488 * Includes Multisite drop-ins only when is_multisite() 489 * 490 * @since 3.0.0 491 * @return array[] Key is file name. The value is an array, with the first value the 492 * purpose of the drop-in and the second value the name of the constant that must be 493 * true for the drop-in to be used, or true if no constant is required. 494 */ 495 function _get_dropins() { 496 $dropins = array( 497 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE 498 'db.php' => array( __( 'Custom database class.' ), true ), // Auto on load. 499 'db-error.php' => array( __( 'Custom database error message.' ), true ), // Auto on error. 500 'install.php' => array( __( 'Custom installation script.' ), true ), // Auto on installation. 501 'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // Auto on maintenance. 502 'object-cache.php' => array( __( 'External object cache.' ), true ), // Auto on load. 503 'php-error.php' => array( __( 'Custom PHP error message.' ), true ), // Auto on error. 504 'fatal-error-handler.php' => array( __( 'Custom PHP fatal error handler.' ), true ), // Auto on error. 505 ); 506 507 if ( is_multisite() ) { 508 $dropins['sunrise.php'] = array( __( 'Executed before Multisite is loaded.' ), 'SUNRISE' ); // SUNRISE 509 $dropins['blog-deleted.php'] = array( __( 'Custom site deleted message.' ), true ); // Auto on deleted blog. 510 $dropins['blog-inactive.php'] = array( __( 'Custom site inactive message.' ), true ); // Auto on inactive blog. 511 $dropins['blog-suspended.php'] = array( __( 'Custom site suspended message.' ), true ); // Auto on archived or spammed blog. 512 } 513 514 return $dropins; 515 } 516 517 /** 518 * Determines whether a plugin is active. 519 * 520 * Only plugins installed in the plugins/ folder can be active. 521 * 522 * Plugins in the mu-plugins/ folder can't be "activated," so this function will 523 * return false for those plugins. 524 * 525 * For more information on this and similar theme functions, check out 526 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ 527 * Conditional Tags} article in the Theme Developer Handbook. 528 * 529 * @since 2.5.0 530 * 531 * @param string $plugin Path to the plugin file relative to the plugins directory. 532 * @return bool True, if in the active plugins list. False, not in the list. 533 */ 534 function is_plugin_active( $plugin ) { 535 return in_array( $plugin, (array) get_option( 'active_plugins', array() ), true ) || is_plugin_active_for_network( $plugin ); 536 } 537 538 /** 539 * Determines whether the plugin is inactive. 540 * 541 * Reverse of is_plugin_active(). Used as a callback. 542 * 543 * For more information on this and similar theme functions, check out 544 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ 545 * Conditional Tags} article in the Theme Developer Handbook. 546 * 547 * @since 3.1.0 548 * 549 * @see is_plugin_active() 550 * 551 * @param string $plugin Path to the plugin file relative to the plugins directory. 552 * @return bool True if inactive. False if active. 553 */ 554 function is_plugin_inactive( $plugin ) { 555 return ! is_plugin_active( $plugin ); 556 } 557 558 /** 559 * Determines whether the plugin is active for the entire network. 560 * 561 * Only plugins installed in the plugins/ folder can be active. 562 * 563 * Plugins in the mu-plugins/ folder can't be "activated," so this function will 564 * return false for those plugins. 565 * 566 * For more information on this and similar theme functions, check out 567 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ 568 * Conditional Tags} article in the Theme Developer Handbook. 569 * 570 * @since 3.0.0 571 * 572 * @param string $plugin Path to the plugin file relative to the plugins directory. 573 * @return bool True if active for the network, otherwise false. 574 */ 575 function is_plugin_active_for_network( $plugin ) { 576 if ( ! is_multisite() ) { 577 return false; 578 } 579 580 $plugins = get_site_option( 'active_sitewide_plugins' ); 581 if ( isset( $plugins[ $plugin ] ) ) { 582 return true; 583 } 584 585 return false; 586 } 587 588 /** 589 * Checks for "Network: true" in the plugin header to see if this should 590 * be activated only as a network wide plugin. The plugin would also work 591 * when Multisite is not enabled. 592 * 593 * Checks for "Site Wide Only: true" for backward compatibility. 594 * 595 * @since 3.0.0 596 * 597 * @param string $plugin Path to the plugin file relative to the plugins directory. 598 * @return bool True if plugin is network only, false otherwise. 599 */ 600 function is_network_only_plugin( $plugin ) { 601 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); 602 if ( $plugin_data ) { 603 return $plugin_data['Network']; 604 } 605 return false; 606 } 607 608 /** 609 * Attempts activation of plugin in a "sandbox" and redirects on success. 610 * 611 * A plugin that is already activated will not attempt to be activated again. 612 * 613 * The way it works is by setting the redirection to the error before trying to 614 * include the plugin file. If the plugin fails, then the redirection will not 615 * be overwritten with the success message. Also, the options will not be 616 * updated and the activation hook will not be called on plugin error. 617 * 618 * It should be noted that in no way the below code will actually prevent errors 619 * within the file. The code should not be used elsewhere to replicate the 620 * "sandbox", which uses redirection to work. 621 * {@source 13 1} 622 * 623 * If any errors are found or text is outputted, then it will be captured to 624 * ensure that the success redirection will update the error redirection. 625 * 626 * @since 2.5.0 627 * @since 5.2.0 Test for WordPress version and PHP version compatibility. 628 * 629 * @param string $plugin Path to the plugin file relative to the plugins directory. 630 * @param string $redirect Optional. URL to redirect to. 631 * @param bool $network_wide Optional. Whether to enable the plugin for all sites in the network 632 * or just the current site. Multisite only. Default false. 633 * @param bool $silent Optional. Whether to prevent calling activation hooks. Default false. 634 * @return null|WP_Error Null on success, WP_Error on invalid file. 635 */ 636 function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) { 637 $plugin = plugin_basename( trim( $plugin ) ); 638 639 if ( is_multisite() && ( $network_wide || is_network_only_plugin( $plugin ) ) ) { 640 $network_wide = true; 641 $current = get_site_option( 'active_sitewide_plugins', array() ); 642 $_GET['networkwide'] = 1; // Back compat for plugins looking for this value. 643 } else { 644 $current = get_option( 'active_plugins', array() ); 645 } 646 647 $valid = validate_plugin( $plugin ); 648 if ( is_wp_error( $valid ) ) { 649 return $valid; 650 } 651 652 $requirements = validate_plugin_requirements( $plugin ); 653 if ( is_wp_error( $requirements ) ) { 654 return $requirements; 655 } 656 657 if ( $network_wide && ! isset( $current[ $plugin ] ) 658 || ! $network_wide && ! in_array( $plugin, $current, true ) 659 ) { 660 if ( ! empty( $redirect ) ) { 661 // We'll override this later if the plugin can be included without fatal error. 662 wp_redirect( add_query_arg( '_error_nonce', wp_create_nonce( 'plugin-activation-error_' . $plugin ), $redirect ) ); 663 } 664 665 ob_start(); 666 667 // Load the plugin to test whether it throws any errors. 668 plugin_sandbox_scrape( $plugin ); 669 670 if ( ! $silent ) { 671 /** 672 * Fires before a plugin is activated. 673 * 674 * If a plugin is silently activated (such as during an update), 675 * this hook does not fire. 676 * 677 * @since 2.9.0 678 * 679 * @param string $plugin Path to the plugin file relative to the plugins directory. 680 * @param bool $network_wide Whether to enable the plugin for all sites in the network 681 * or just the current site. Multisite only. Default false. 682 */ 683 do_action( 'activate_plugin', $plugin, $network_wide ); 684 685 /** 686 * Fires as a specific plugin is being activated. 687 * 688 * This hook is the "activation" hook used internally by register_activation_hook(). 689 * The dynamic portion of the hook name, `$plugin`, refers to the plugin basename. 690 * 691 * If a plugin is silently activated (such as during an update), this hook does not fire. 692 * 693 * @since 2.0.0 694 * 695 * @param bool $network_wide Whether to enable the plugin for all sites in the network 696 * or just the current site. Multisite only. Default false. 697 */ 698 do_action( "activate_{$plugin}", $network_wide ); 699 } 700 701 if ( $network_wide ) { 702 $current = get_site_option( 'active_sitewide_plugins', array() ); 703 $current[ $plugin ] = time(); 704 update_site_option( 'active_sitewide_plugins', $current ); 705 } else { 706 $current = get_option( 'active_plugins', array() ); 707 $current[] = $plugin; 708 sort( $current ); 709 update_option( 'active_plugins', $current ); 710 } 711 712 if ( ! $silent ) { 713 /** 714 * Fires after a plugin has been activated. 715 * 716 * If a plugin is silently activated (such as during an update), 717 * this hook does not fire. 718 * 719 * @since 2.9.0 720 * 721 * @param string $plugin Path to the plugin file relative to the plugins directory. 722 * @param bool $network_wide Whether to enable the plugin for all sites in the network 723 * or just the current site. Multisite only. Default false. 724 */ 725 do_action( 'activated_plugin', $plugin, $network_wide ); 726 } 727 728 if ( ob_get_length() > 0 ) { 729 $output = ob_get_clean(); 730 return new WP_Error( 'unexpected_output', __( 'The plugin generated unexpected output.' ), $output ); 731 } 732 733 ob_end_clean(); 734 } 735 736 return null; 737 } 738 739 /** 740 * Deactivate a single plugin or multiple plugins. 741 * 742 * The deactivation hook is disabled by the plugin upgrader by using the $silent 743 * parameter. 744 * 745 * @since 2.5.0 746 * 747 * @param string|string[] $plugins Single plugin or list of plugins to deactivate. 748 * @param bool $silent Prevent calling deactivation hooks. Default false. 749 * @param bool|null $network_wide Whether to deactivate the plugin for all sites in the network. 750 * A value of null will deactivate plugins for both the network 751 * and the current site. Multisite only. Default null. 752 */ 753 function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) { 754 if ( is_multisite() ) { 755 $network_current = get_site_option( 'active_sitewide_plugins', array() ); 756 } 757 $current = get_option( 'active_plugins', array() ); 758 $do_blog = false; 759 $do_network = false; 760 761 foreach ( (array) $plugins as $plugin ) { 762 $plugin = plugin_basename( trim( $plugin ) ); 763 if ( ! is_plugin_active( $plugin ) ) { 764 continue; 765 } 766 767 $network_deactivating = ( false !== $network_wide ) && is_plugin_active_for_network( $plugin ); 768 769 if ( ! $silent ) { 770 /** 771 * Fires before a plugin is deactivated. 772 * 773 * If a plugin is silently deactivated (such as during an update), 774 * this hook does not fire. 775 * 776 * @since 2.9.0 777 * 778 * @param string $plugin Path to the plugin file relative to the plugins directory. 779 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network 780 * or just the current site. Multisite only. Default false. 781 */ 782 do_action( 'deactivate_plugin', $plugin, $network_deactivating ); 783 } 784 785 if ( false !== $network_wide ) { 786 if ( is_plugin_active_for_network( $plugin ) ) { 787 $do_network = true; 788 unset( $network_current[ $plugin ] ); 789 } elseif ( $network_wide ) { 790 continue; 791 } 792 } 793 794 if ( true !== $network_wide ) { 795 $key = array_search( $plugin, $current, true ); 796 if ( false !== $key ) { 797 $do_blog = true; 798 unset( $current[ $key ] ); 799 } 800 } 801 802 if ( $do_blog && wp_is_recovery_mode() ) { 803 list( $extension ) = explode( '/', $plugin ); 804 wp_paused_plugins()->delete( $extension ); 805 } 806 807 if ( ! $silent ) { 808 /** 809 * Fires as a specific plugin is being deactivated. 810 * 811 * This hook is the "deactivation" hook used internally by register_deactivation_hook(). 812 * The dynamic portion of the hook name, `$plugin`, refers to the plugin basename. 813 * 814 * If a plugin is silently deactivated (such as during an update), this hook does not fire. 815 * 816 * @since 2.0.0 817 * 818 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network 819 * or just the current site. Multisite only. Default false. 820 */ 821 do_action( "deactivate_{$plugin}", $network_deactivating ); 822 823 /** 824 * Fires after a plugin is deactivated. 825 * 826 * If a plugin is silently deactivated (such as during an update), 827 * this hook does not fire. 828 * 829 * @since 2.9.0 830 * 831 * @param string $plugin Path to the plugin file relative to the plugins directory. 832 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network 833 * or just the current site. Multisite only. Default false. 834 */ 835 do_action( 'deactivated_plugin', $plugin, $network_deactivating ); 836 } 837 } 838 839 if ( $do_blog ) { 840 update_option( 'active_plugins', $current ); 841 } 842 if ( $do_network ) { 843 update_site_option( 'active_sitewide_plugins', $network_current ); 844 } 845 } 846 847 /** 848 * Activate multiple plugins. 849 * 850 * When WP_Error is returned, it does not mean that one of the plugins had 851 * errors. It means that one or more of the plugin file paths were invalid. 852 * 853 * The execution will be halted as soon as one of the plugins has an error. 854 * 855 * @since 2.6.0 856 * 857 * @param string|string[] $plugins Single plugin or list of plugins to activate. 858 * @param string $redirect Redirect to page after successful activation. 859 * @param bool $network_wide Whether to enable the plugin for all sites in the network. 860 * Default false. 861 * @param bool $silent Prevent calling activation hooks. Default false. 862 * @return bool|WP_Error True when finished or WP_Error if there were errors during a plugin activation. 863 */ 864 function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) { 865 if ( ! is_array( $plugins ) ) { 866 $plugins = array( $plugins ); 867 } 868 869 $errors = array(); 870 foreach ( $plugins as $plugin ) { 871 if ( ! empty( $redirect ) ) { 872 $redirect = add_query_arg( 'plugin', $plugin, $redirect ); 873 } 874 $result = activate_plugin( $plugin, $redirect, $network_wide, $silent ); 875 if ( is_wp_error( $result ) ) { 876 $errors[ $plugin ] = $result; 877 } 878 } 879 880 if ( ! empty( $errors ) ) { 881 return new WP_Error( 'plugins_invalid', __( 'One of the plugins is invalid.' ), $errors ); 882 } 883 884 return true; 885 } 886 887 /** 888 * Remove directory and files of a plugin for a list of plugins. 889 * 890 * @since 2.6.0 891 * 892 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. 893 * 894 * @param string[] $plugins List of plugin paths to delete, relative to the plugins directory. 895 * @param string $deprecated Not used. 896 * @return bool|null|WP_Error True on success, false if `$plugins` is empty, `WP_Error` on failure. 897 * `null` if filesystem credentials are required to proceed. 898 */ 899 function delete_plugins( $plugins, $deprecated = '' ) { 900 global $wp_filesystem; 901 902 if ( empty( $plugins ) ) { 903 return false; 904 } 905 906 $checked = array(); 907 foreach ( $plugins as $plugin ) { 908 $checked[] = 'checked[]=' . $plugin; 909 } 910 911 $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&' . implode( '&', $checked ), 'bulk-plugins' ); 912 913 ob_start(); 914 $credentials = request_filesystem_credentials( $url ); 915 $data = ob_get_clean(); 916 917 if ( false === $credentials ) { 918 if ( ! empty( $data ) ) { 919 require_once ABSPATH . 'wp-admin/admin-header.php'; 920 echo $data; 921 require_once ABSPATH . 'wp-admin/admin-footer.php'; 922 exit; 923 } 924 return; 925 } 926 927 if ( ! WP_Filesystem( $credentials ) ) { 928 ob_start(); 929 // Failed to connect. Error and request again. 930 request_filesystem_credentials( $url, '', true ); 931 $data = ob_get_clean(); 932 933 if ( ! empty( $data ) ) { 934 require_once ABSPATH . 'wp-admin/admin-header.php'; 935 echo $data; 936 require_once ABSPATH . 'wp-admin/admin-footer.php'; 937 exit; 938 } 939 return; 940 } 941 942 if ( ! is_object( $wp_filesystem ) ) { 943 return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) ); 944 } 945 946 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) { 947 return new WP_Error( 'fs_error', __( 'Filesystem error.' ), $wp_filesystem->errors ); 948 } 949 950 // Get the base plugin folder. 951 $plugins_dir = $wp_filesystem->wp_plugins_dir(); 952 if ( empty( $plugins_dir ) ) { 953 return new WP_Error( 'fs_no_plugins_dir', __( 'Unable to locate WordPress plugin directory.' ) ); 954 } 955 956 $plugins_dir = trailingslashit( $plugins_dir ); 957 958 $plugin_translations = wp_get_installed_translations( 'plugins' ); 959 960 $errors = array(); 961 962 foreach ( $plugins as $plugin_file ) { 963 // Run Uninstall hook. 964 if ( is_uninstallable_plugin( $plugin_file ) ) { 965 uninstall_plugin( $plugin_file ); 966 } 967 968 /** 969 * Fires immediately before a plugin deletion attempt. 970 * 971 * @since 4.4.0 972 * 973 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 974 */ 975 do_action( 'delete_plugin', $plugin_file ); 976 977 $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) ); 978 979 // If plugin is in its own directory, recursively delete the directory. 980 // Base check on if plugin includes directory separator AND that it's not the root plugin folder. 981 if ( strpos( $plugin_file, '/' ) && $this_plugin_dir !== $plugins_dir ) { 982 $deleted = $wp_filesystem->delete( $this_plugin_dir, true ); 983 } else { 984 $deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file ); 985 } 986 987 /** 988 * Fires immediately after a plugin deletion attempt. 989 * 990 * @since 4.4.0 991 * 992 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 993 * @param bool $deleted Whether the plugin deletion was successful. 994 */ 995 do_action( 'deleted_plugin', $plugin_file, $deleted ); 996 997 if ( ! $deleted ) { 998 $errors[] = $plugin_file; 999 continue; 1000 } 1001 1002 $plugin_slug = dirname( $plugin_file ); 1003 1004 if ( 'hello.php' === $plugin_file ) { 1005 $plugin_slug = 'hello-dolly'; 1006 } 1007 1008 // Remove language files, silently. 1009 if ( '.' !== $plugin_slug && ! empty( $plugin_translations[ $plugin_slug ] ) ) { 1010 $translations = $plugin_translations[ $plugin_slug ]; 1011 1012 foreach ( $translations as $translation => $data ) { 1013 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' ); 1014 $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' ); 1015 1016 $json_translation_files = glob( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '-*.json' ); 1017 if ( $json_translation_files ) { 1018 array_map( array( $wp_filesystem, 'delete' ), $json_translation_files ); 1019 } 1020 } 1021 } 1022 } 1023 1024 // Remove deleted plugins from the plugin updates list. 1025 $current = get_site_transient( 'update_plugins' ); 1026 if ( $current ) { 1027 // Don't remove the plugins that weren't deleted. 1028 $deleted = array_diff( $plugins, $errors ); 1029 1030 foreach ( $deleted as $plugin_file ) { 1031 unset( $current->response[ $plugin_file ] ); 1032 } 1033 1034 set_site_transient( 'update_plugins', $current ); 1035 } 1036 1037 if ( ! empty( $errors ) ) { 1038 if ( 1 === count( $errors ) ) { 1039 /* translators: %s: Plugin filename. */ 1040 $message = __( 'Could not fully remove the plugin %s.' ); 1041 } else { 1042 /* translators: %s: Comma-separated list of plugin filenames. */ 1043 $message = __( 'Could not fully remove the plugins %s.' ); 1044 } 1045 1046 return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) ); 1047 } 1048 1049 return true; 1050 } 1051 1052 /** 1053 * Validate active plugins 1054 * 1055 * Validate all active plugins, deactivates invalid and 1056 * returns an array of deactivated ones. 1057 * 1058 * @since 2.5.0 1059 * @return WP_Error[] Array of plugin errors keyed by plugin file name. 1060 */ 1061 function validate_active_plugins() { 1062 $plugins = get_option( 'active_plugins', array() ); 1063 // Validate vartype: array. 1064 if ( ! is_array( $plugins ) ) { 1065 update_option( 'active_plugins', array() ); 1066 $plugins = array(); 1067 } 1068 1069 if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { 1070 $network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() ); 1071 $plugins = array_merge( $plugins, array_keys( $network_plugins ) ); 1072 } 1073 1074 if ( empty( $plugins ) ) { 1075 return array(); 1076 } 1077 1078 $invalid = array(); 1079 1080 // Invalid plugins get deactivated. 1081 foreach ( $plugins as $plugin ) { 1082 $result = validate_plugin( $plugin ); 1083 if ( is_wp_error( $result ) ) { 1084 $invalid[ $plugin ] = $result; 1085 deactivate_plugins( $plugin, true ); 1086 } 1087 } 1088 return $invalid; 1089 } 1090 1091 /** 1092 * Validate the plugin path. 1093 * 1094 * Checks that the main plugin file exists and is a valid plugin. See validate_file(). 1095 * 1096 * @since 2.5.0 1097 * 1098 * @param string $plugin Path to the plugin file relative to the plugins directory. 1099 * @return int|WP_Error 0 on success, WP_Error on failure. 1100 */ 1101 function validate_plugin( $plugin ) { 1102 if ( validate_file( $plugin ) ) { 1103 return new WP_Error( 'plugin_invalid', __( 'Invalid plugin path.' ) ); 1104 } 1105 if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin ) ) { 1106 return new WP_Error( 'plugin_not_found', __( 'Plugin file does not exist.' ) ); 1107 } 1108 1109 $installed_plugins = get_plugins(); 1110 if ( ! isset( $installed_plugins[ $plugin ] ) ) { 1111 return new WP_Error( 'no_plugin_header', __( 'The plugin does not have a valid header.' ) ); 1112 } 1113 return 0; 1114 } 1115 1116 /** 1117 * Validates the plugin requirements for WordPress version and PHP version. 1118 * 1119 * Uses the information from `Requires at least` and `Requires PHP` headers 1120 * defined in the plugin's main PHP file. 1121 * 1122 * @since 5.2.0 1123 * @since 5.3.0 Added support for reading the headers from the plugin's 1124 * main PHP file, with `readme.txt` as a fallback. 1125 * @since 5.8.0 Removed support for using `readme.txt` as a fallback. 1126 * 1127 * @param string $plugin Path to the plugin file relative to the plugins directory. 1128 * @return true|WP_Error True if requirements are met, WP_Error on failure. 1129 */ 1130 function validate_plugin_requirements( $plugin ) { 1131 $plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); 1132 1133 $requirements = array( 1134 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '', 1135 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '', 1136 ); 1137 1138 $compatible_wp = is_wp_version_compatible( $requirements['requires'] ); 1139 $compatible_php = is_php_version_compatible( $requirements['requires_php'] ); 1140 1141 $php_update_message = '</p><p>' . sprintf( 1142 /* translators: %s: URL to Update PHP page. */ 1143 __( '<a href="%s">Learn more about updating PHP</a>.' ), 1144 esc_url( wp_get_update_php_url() ) 1145 ); 1146 1147 $annotation = wp_get_update_php_annotation(); 1148 1149 if ( $annotation ) { 1150 $php_update_message .= '</p><p><em>' . $annotation . '</em>'; 1151 } 1152 1153 if ( ! $compatible_wp && ! $compatible_php ) { 1154 return new WP_Error( 1155 'plugin_wp_php_incompatible', 1156 '<p>' . sprintf( 1157 /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */ 1158 _x( '<strong>Error:</strong> Current versions of WordPress (%1$s) and PHP (%2$s) do not meet minimum requirements for %3$s. The plugin requires WordPress %4$s and PHP %5$s.', 'plugin' ), 1159 get_bloginfo( 'version' ), 1160 phpversion(), 1161 $plugin_headers['Name'], 1162 $requirements['requires'], 1163 $requirements['requires_php'] 1164 ) . $php_update_message . '</p>' 1165 ); 1166 } elseif ( ! $compatible_php ) { 1167 return new WP_Error( 1168 'plugin_php_incompatible', 1169 '<p>' . sprintf( 1170 /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */ 1171 _x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ), 1172 phpversion(), 1173 $plugin_headers['Name'], 1174 $requirements['requires_php'] 1175 ) . $php_update_message . '</p>' 1176 ); 1177 } elseif ( ! $compatible_wp ) { 1178 return new WP_Error( 1179 'plugin_wp_incompatible', 1180 '<p>' . sprintf( 1181 /* translators: 1: Current WordPress version, 2: Plugin name, 3: Required WordPress version. */ 1182 _x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ), 1183 get_bloginfo( 'version' ), 1184 $plugin_headers['Name'], 1185 $requirements['requires'] 1186 ) . '</p>' 1187 ); 1188 } 1189 1190 return true; 1191 } 1192 1193 /** 1194 * Whether the plugin can be uninstalled. 1195 * 1196 * @since 2.7.0 1197 * 1198 * @param string $plugin Path to the plugin file relative to the plugins directory. 1199 * @return bool Whether plugin can be uninstalled. 1200 */ 1201 function is_uninstallable_plugin( $plugin ) { 1202 $file = plugin_basename( $plugin ); 1203 1204 $uninstallable_plugins = (array) get_option( 'uninstall_plugins' ); 1205 if ( isset( $uninstallable_plugins[ $file ] ) || file_exists( WP_PLUGIN_DIR . '/' . dirname( $file ) . '/uninstall.php' ) ) { 1206 return true; 1207 } 1208 1209 return false; 1210 } 1211 1212 /** 1213 * Uninstall a single plugin. 1214 * 1215 * Calls the uninstall hook, if it is available. 1216 * 1217 * @since 2.7.0 1218 * 1219 * @param string $plugin Path to the plugin file relative to the plugins directory. 1220 * @return true|void True if a plugin's uninstall.php file has been found and included. 1221 * Void otherwise. 1222 */ 1223 function uninstall_plugin( $plugin ) { 1224 $file = plugin_basename( $plugin ); 1225 1226 $uninstallable_plugins = (array) get_option( 'uninstall_plugins' ); 1227 1228 /** 1229 * Fires in uninstall_plugin() immediately before the plugin is uninstalled. 1230 * 1231 * @since 4.5.0 1232 * 1233 * @param string $plugin Path to the plugin file relative to the plugins directory. 1234 * @param array $uninstallable_plugins Uninstallable plugins. 1235 */ 1236 do_action( 'pre_uninstall_plugin', $plugin, $uninstallable_plugins ); 1237 1238 if ( file_exists( WP_PLUGIN_DIR . '/' . dirname( $file ) . '/uninstall.php' ) ) { 1239 if ( isset( $uninstallable_plugins[ $file ] ) ) { 1240 unset( $uninstallable_plugins[ $file ] ); 1241 update_option( 'uninstall_plugins', $uninstallable_plugins ); 1242 } 1243 unset( $uninstallable_plugins ); 1244 1245 define( 'WP_UNINSTALL_PLUGIN', $file ); 1246 1247 wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $file ); 1248 include_once WP_PLUGIN_DIR . '/' . dirname( $file ) . '/uninstall.php'; 1249 1250 return true; 1251 } 1252 1253 if ( isset( $uninstallable_plugins[ $file ] ) ) { 1254 $callable = $uninstallable_plugins[ $file ]; 1255 unset( $uninstallable_plugins[ $file ] ); 1256 update_option( 'uninstall_plugins', $uninstallable_plugins ); 1257 unset( $uninstallable_plugins ); 1258 1259 wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $file ); 1260 include_once WP_PLUGIN_DIR . '/' . $file; 1261 1262 add_action( "uninstall_{$file}", $callable ); 1263 1264 /** 1265 * Fires in uninstall_plugin() once the plugin has been uninstalled. 1266 * 1267 * The action concatenates the 'uninstall_' prefix with the basename of the 1268 * plugin passed to uninstall_plugin() to create a dynamically-named action. 1269 * 1270 * @since 2.7.0 1271 */ 1272 do_action( "uninstall_{$file}" ); 1273 } 1274 } 1275 1276 // 1277 // Menu. 1278 // 1279 1280 /** 1281 * Add a top-level menu page. 1282 * 1283 * This function takes a capability which will be used to determine whether 1284 * or not a page is included in the menu. 1285 * 1286 * The function which is hooked in to handle the output of the page must check 1287 * that the user has the required capability as well. 1288 * 1289 * @since 1.5.0 1290 * 1291 * @global array $menu 1292 * @global array $admin_page_hooks 1293 * @global array $_registered_pages 1294 * @global array $_parent_pages 1295 * 1296 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1297 * @param string $menu_title The text to be used for the menu. 1298 * @param string $capability The capability required for this menu to be displayed to the user. 1299 * @param string $menu_slug The slug name to refer to this menu by. Should be unique for this menu page and only 1300 * include lowercase alphanumeric, dashes, and underscores characters to be compatible 1301 * with sanitize_key(). 1302 * @param callable $function The function to be called to output the content for this page. 1303 * @param string $icon_url The URL to the icon to be used for this menu. 1304 * * Pass a base64-encoded SVG using a data URI, which will be colored to match 1305 * the color scheme. This should begin with 'data:image/svg+xml;base64,'. 1306 * * Pass the name of a Dashicons helper class to use a font icon, 1307 * e.g. 'dashicons-chart-pie'. 1308 * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS. 1309 * @param int $position The position in the menu order this item should appear. 1310 * @return string The resulting page's hook_suffix. 1311 */ 1312 function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) { 1313 global $menu, $admin_page_hooks, $_registered_pages, $_parent_pages; 1314 1315 $menu_slug = plugin_basename( $menu_slug ); 1316 1317 $admin_page_hooks[ $menu_slug ] = sanitize_title( $menu_title ); 1318 1319 $hookname = get_plugin_page_hookname( $menu_slug, '' ); 1320 1321 if ( ! empty( $function ) && ! empty( $hookname ) && current_user_can( $capability ) ) { 1322 add_action( $hookname, $function ); 1323 } 1324 1325 if ( empty( $icon_url ) ) { 1326 $icon_url = 'dashicons-admin-generic'; 1327 $icon_class = 'menu-icon-generic '; 1328 } else { 1329 $icon_url = set_url_scheme( $icon_url ); 1330 $icon_class = ''; 1331 } 1332 1333 $new_menu = array( $menu_title, $capability, $menu_slug, $page_title, 'menu-top ' . $icon_class . $hookname, $hookname, $icon_url ); 1334 1335 if ( null === $position ) { 1336 $menu[] = $new_menu; 1337 } elseif ( isset( $menu[ "$position" ] ) ) { 1338 $position = $position + substr( base_convert( md5( $menu_slug . $menu_title ), 16, 10 ), -5 ) * 0.00001; 1339 $menu[ "$position" ] = $new_menu; 1340 } else { 1341 $menu[ $position ] = $new_menu; 1342 } 1343 1344 $_registered_pages[ $hookname ] = true; 1345 1346 // No parent as top level. 1347 $_parent_pages[ $menu_slug ] = false; 1348 1349 return $hookname; 1350 } 1351 1352 /** 1353 * Add a submenu page. 1354 * 1355 * This function takes a capability which will be used to determine whether 1356 * or not a page is included in the menu. 1357 * 1358 * The function which is hooked in to handle the output of the page must check 1359 * that the user has the required capability as well. 1360 * 1361 * @since 1.5.0 1362 * @since 5.3.0 Added the `$position` parameter. 1363 * 1364 * @global array $submenu 1365 * @global array $menu 1366 * @global array $_wp_real_parent_file 1367 * @global bool $_wp_submenu_nopriv 1368 * @global array $_registered_pages 1369 * @global array $_parent_pages 1370 * 1371 * @param string $parent_slug The slug name for the parent menu (or the file name of a standard 1372 * WordPress admin page). 1373 * @param string $page_title The text to be displayed in the title tags of the page when the menu 1374 * is selected. 1375 * @param string $menu_title The text to be used for the menu. 1376 * @param string $capability The capability required for this menu to be displayed to the user. 1377 * @param string $menu_slug The slug name to refer to this menu by. Should be unique for this menu 1378 * and only include lowercase alphanumeric, dashes, and underscores characters 1379 * to be compatible with sanitize_key(). 1380 * @param callable $function The function to be called to output the content for this page. 1381 * @param int $position The position in the menu order this item should appear. 1382 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1383 */ 1384 function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1385 global $submenu, $menu, $_wp_real_parent_file, $_wp_submenu_nopriv, 1386 $_registered_pages, $_parent_pages; 1387 1388 $menu_slug = plugin_basename( $menu_slug ); 1389 $parent_slug = plugin_basename( $parent_slug ); 1390 1391 if ( isset( $_wp_real_parent_file[ $parent_slug ] ) ) { 1392 $parent_slug = $_wp_real_parent_file[ $parent_slug ]; 1393 } 1394 1395 if ( ! current_user_can( $capability ) ) { 1396 $_wp_submenu_nopriv[ $parent_slug ][ $menu_slug ] = true; 1397 return false; 1398 } 1399 1400 /* 1401 * If the parent doesn't already have a submenu, add a link to the parent 1402 * as the first item in the submenu. If the submenu file is the same as the 1403 * parent file someone is trying to link back to the parent manually. In 1404 * this case, don't automatically add a link back to avoid duplication. 1405 */ 1406 if ( ! isset( $submenu[ $parent_slug ] ) && $menu_slug !== $parent_slug ) { 1407 foreach ( (array) $menu as $parent_menu ) { 1408 if ( $parent_menu[2] === $parent_slug && current_user_can( $parent_menu[1] ) ) { 1409 $submenu[ $parent_slug ][] = array_slice( $parent_menu, 0, 4 ); 1410 } 1411 } 1412 } 1413 1414 $new_sub_menu = array( $menu_title, $capability, $menu_slug, $page_title ); 1415 if ( ! is_int( $position ) ) { 1416 if ( null !== $position ) { 1417 _doing_it_wrong( 1418 __FUNCTION__, 1419 sprintf( 1420 /* translators: %s: add_submenu_page() */ 1421 __( 'The seventh parameter passed to %s should be an integer representing menu position.' ), 1422 '<code>add_submenu_page()</code>' 1423 ), 1424 '5.3.0' 1425 ); 1426 } 1427 1428 $submenu[ $parent_slug ][] = $new_sub_menu; 1429 } else { 1430 // Append the submenu if the parent item is not present in the submenu, 1431 // or if position is equal or higher than the number of items in the array. 1432 if ( ! isset( $submenu[ $parent_slug ] ) || $position >= count( $submenu[ $parent_slug ] ) ) { 1433 $submenu[ $parent_slug ][] = $new_sub_menu; 1434 } else { 1435 // Test for a negative position. 1436 $position = max( $position, 0 ); 1437 if ( 0 === $position ) { 1438 // For negative or `0` positions, prepend the submenu. 1439 array_unshift( $submenu[ $parent_slug ], $new_sub_menu ); 1440 } else { 1441 // Grab all of the items before the insertion point. 1442 $before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true ); 1443 // Grab all of the items after the insertion point. 1444 $after_items = array_slice( $submenu[ $parent_slug ], $position, null, true ); 1445 // Add the new item. 1446 $before_items[] = $new_sub_menu; 1447 // Merge the items. 1448 $submenu[ $parent_slug ] = array_merge( $before_items, $after_items ); 1449 } 1450 } 1451 } 1452 // Sort the parent array. 1453 ksort( $submenu[ $parent_slug ] ); 1454 1455 $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); 1456 if ( ! empty( $function ) && ! empty( $hookname ) ) { 1457 add_action( $hookname, $function ); 1458 } 1459 1460 $_registered_pages[ $hookname ] = true; 1461 1462 /* 1463 * Backward-compatibility for plugins using add_management_page(). 1464 * See wp-admin/admin.php for redirect from edit.php to tools.php. 1465 */ 1466 if ( 'tools.php' === $parent_slug ) { 1467 $_registered_pages[ get_plugin_page_hookname( $menu_slug, 'edit.php' ) ] = true; 1468 } 1469 1470 // No parent as top level. 1471 $_parent_pages[ $menu_slug ] = $parent_slug; 1472 1473 return $hookname; 1474 } 1475 1476 /** 1477 * Add submenu page to the Tools main menu. 1478 * 1479 * This function takes a capability which will be used to determine whether 1480 * or not a page is included in the menu. 1481 * 1482 * The function which is hooked in to handle the output of the page must check 1483 * that the user has the required capability as well. 1484 * 1485 * @since 1.5.0 1486 * @since 5.3.0 Added the `$position` parameter. 1487 * 1488 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1489 * @param string $menu_title The text to be used for the menu. 1490 * @param string $capability The capability required for this menu to be displayed to the user. 1491 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1492 * @param callable $function The function to be called to output the content for this page. 1493 * @param int $position The position in the menu order this item should appear. 1494 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1495 */ 1496 function add_management_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1497 return add_submenu_page( 'tools.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1498 } 1499 1500 /** 1501 * Add submenu page to the Settings main menu. 1502 * 1503 * This function takes a capability which will be used to determine whether 1504 * or not a page is included in the menu. 1505 * 1506 * The function which is hooked in to handle the output of the page must check 1507 * that the user has the required capability as well. 1508 * 1509 * @since 1.5.0 1510 * @since 5.3.0 Added the `$position` parameter. 1511 * 1512 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1513 * @param string $menu_title The text to be used for the menu. 1514 * @param string $capability The capability required for this menu to be displayed to the user. 1515 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1516 * @param callable $function The function to be called to output the content for this page. 1517 * @param int $position The position in the menu order this item should appear. 1518 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1519 */ 1520 function add_options_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1521 return add_submenu_page( 'options-general.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1522 } 1523 1524 /** 1525 * Add submenu page to the Appearance main menu. 1526 * 1527 * This function takes a capability which will be used to determine whether 1528 * or not a page is included in the menu. 1529 * 1530 * The function which is hooked in to handle the output of the page must check 1531 * that the user has the required capability as well. 1532 * 1533 * @since 2.0.0 1534 * @since 5.3.0 Added the `$position` parameter. 1535 * 1536 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1537 * @param string $menu_title The text to be used for the menu. 1538 * @param string $capability The capability required for this menu to be displayed to the user. 1539 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1540 * @param callable $function The function to be called to output the content for this page. 1541 * @param int $position The position in the menu order this item should appear. 1542 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1543 */ 1544 function add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1545 return add_submenu_page( 'themes.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1546 } 1547 1548 /** 1549 * Add submenu page to the Plugins main menu. 1550 * 1551 * This function takes a capability which will be used to determine whether 1552 * or not a page is included in the menu. 1553 * 1554 * The function which is hooked in to handle the output of the page must check 1555 * that the user has the required capability as well. 1556 * 1557 * @since 3.0.0 1558 * @since 5.3.0 Added the `$position` parameter. 1559 * 1560 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1561 * @param string $menu_title The text to be used for the menu. 1562 * @param string $capability The capability required for this menu to be displayed to the user. 1563 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1564 * @param callable $function The function to be called to output the content for this page. 1565 * @param int $position The position in the menu order this item should appear. 1566 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1567 */ 1568 function add_plugins_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1569 return add_submenu_page( 'plugins.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1570 } 1571 1572 /** 1573 * Add submenu page to the Users/Profile main menu. 1574 * 1575 * This function takes a capability which will be used to determine whether 1576 * or not a page is included in the menu. 1577 * 1578 * The function which is hooked in to handle the output of the page must check 1579 * that the user has the required capability as well. 1580 * 1581 * @since 2.1.3 1582 * @since 5.3.0 Added the `$position` parameter. 1583 * 1584 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1585 * @param string $menu_title The text to be used for the menu. 1586 * @param string $capability The capability required for this menu to be displayed to the user. 1587 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1588 * @param callable $function The function to be called to output the content for this page. 1589 * @param int $position The position in the menu order this item should appear. 1590 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1591 */ 1592 function add_users_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1593 if ( current_user_can( 'edit_users' ) ) { 1594 $parent = 'users.php'; 1595 } else { 1596 $parent = 'profile.php'; 1597 } 1598 return add_submenu_page( $parent, $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1599 } 1600 1601 /** 1602 * Add submenu page to the Dashboard main menu. 1603 * 1604 * This function takes a capability which will be used to determine whether 1605 * or not a page is included in the menu. 1606 * 1607 * The function which is hooked in to handle the output of the page must check 1608 * that the user has the required capability as well. 1609 * 1610 * @since 2.7.0 1611 * @since 5.3.0 Added the `$position` parameter. 1612 * 1613 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1614 * @param string $menu_title The text to be used for the menu. 1615 * @param string $capability The capability required for this menu to be displayed to the user. 1616 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1617 * @param callable $function The function to be called to output the content for this page. 1618 * @param int $position The position in the menu order this item should appear. 1619 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1620 */ 1621 function add_dashboard_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1622 return add_submenu_page( 'index.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1623 } 1624 1625 /** 1626 * Add submenu page to the Posts main menu. 1627 * 1628 * This function takes a capability which will be used to determine whether 1629 * or not a page is included in the menu. 1630 * 1631 * The function which is hooked in to handle the output of the page must check 1632 * that the user has the required capability as well. 1633 * 1634 * @since 2.7.0 1635 * @since 5.3.0 Added the `$position` parameter. 1636 * 1637 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1638 * @param string $menu_title The text to be used for the menu. 1639 * @param string $capability The capability required for this menu to be displayed to the user. 1640 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1641 * @param callable $function The function to be called to output the content for this page. 1642 * @param int $position The position in the menu order this item should appear. 1643 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1644 */ 1645 function add_posts_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1646 return add_submenu_page( 'edit.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1647 } 1648 1649 /** 1650 * Add submenu page to the Media main menu. 1651 * 1652 * This function takes a capability which will be used to determine whether 1653 * or not a page is included in the menu. 1654 * 1655 * The function which is hooked in to handle the output of the page must check 1656 * that the user has the required capability as well. 1657 * 1658 * @since 2.7.0 1659 * @since 5.3.0 Added the `$position` parameter. 1660 * 1661 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1662 * @param string $menu_title The text to be used for the menu. 1663 * @param string $capability The capability required for this menu to be displayed to the user. 1664 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1665 * @param callable $function The function to be called to output the content for this page. 1666 * @param int $position The position in the menu order this item should appear. 1667 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1668 */ 1669 function add_media_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1670 return add_submenu_page( 'upload.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1671 } 1672 1673 /** 1674 * Add submenu page to the Links main menu. 1675 * 1676 * This function takes a capability which will be used to determine whether 1677 * or not a page is included in the menu. 1678 * 1679 * The function which is hooked in to handle the output of the page must check 1680 * that the user has the required capability as well. 1681 * 1682 * @since 2.7.0 1683 * @since 5.3.0 Added the `$position` parameter. 1684 * 1685 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1686 * @param string $menu_title The text to be used for the menu. 1687 * @param string $capability The capability required for this menu to be displayed to the user. 1688 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1689 * @param callable $function The function to be called to output the content for this page. 1690 * @param int $position The position in the menu order this item should appear. 1691 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1692 */ 1693 function add_links_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1694 return add_submenu_page( 'link-manager.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1695 } 1696 1697 /** 1698 * Add submenu page to the Pages main menu. 1699 * 1700 * This function takes a capability which will be used to determine whether 1701 * or not a page is included in the menu. 1702 * 1703 * The function which is hooked in to handle the output of the page must check 1704 * that the user has the required capability as well. 1705 * 1706 * @since 2.7.0 1707 * @since 5.3.0 Added the `$position` parameter. 1708 * 1709 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1710 * @param string $menu_title The text to be used for the menu. 1711 * @param string $capability The capability required for this menu to be displayed to the user. 1712 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1713 * @param callable $function The function to be called to output the content for this page. 1714 * @param int $position The position in the menu order this item should appear. 1715 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1716 */ 1717 function add_pages_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1718 return add_submenu_page( 'edit.php?post_type=page', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1719 } 1720 1721 /** 1722 * Add submenu page to the Comments main menu. 1723 * 1724 * This function takes a capability which will be used to determine whether 1725 * or not a page is included in the menu. 1726 * 1727 * The function which is hooked in to handle the output of the page must check 1728 * that the user has the required capability as well. 1729 * 1730 * @since 2.7.0 1731 * @since 5.3.0 Added the `$position` parameter. 1732 * 1733 * @param string $page_title The text to be displayed in the title tags of the page when the menu is selected. 1734 * @param string $menu_title The text to be used for the menu. 1735 * @param string $capability The capability required for this menu to be displayed to the user. 1736 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1737 * @param callable $function The function to be called to output the content for this page. 1738 * @param int $position The position in the menu order this item should appear. 1739 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required. 1740 */ 1741 function add_comments_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $position = null ) { 1742 return add_submenu_page( 'edit-comments.php', $page_title, $menu_title, $capability, $menu_slug, $function, $position ); 1743 } 1744 1745 /** 1746 * Remove a top-level admin menu. 1747 * 1748 * @since 3.1.0 1749 * 1750 * @global array $menu 1751 * 1752 * @param string $menu_slug The slug of the menu. 1753 * @return array|false The removed menu on success, false if not found. 1754 */ 1755 function remove_menu_page( $menu_slug ) { 1756 global $menu; 1757 1758 foreach ( $menu as $i => $item ) { 1759 if ( $menu_slug === $item[2] ) { 1760 unset( $menu[ $i ] ); 1761 return $item; 1762 } 1763 } 1764 1765 return false; 1766 } 1767 1768 /** 1769 * Remove an admin submenu. 1770 * 1771 * @since 3.1.0 1772 * 1773 * @global array $submenu 1774 * 1775 * @param string $menu_slug The slug for the parent menu. 1776 * @param string $submenu_slug The slug of the submenu. 1777 * @return array|false The removed submenu on success, false if not found. 1778 */ 1779 function remove_submenu_page( $menu_slug, $submenu_slug ) { 1780 global $submenu; 1781 1782 if ( ! isset( $submenu[ $menu_slug ] ) ) { 1783 return false; 1784 } 1785 1786 foreach ( $submenu[ $menu_slug ] as $i => $item ) { 1787 if ( $submenu_slug === $item[2] ) { 1788 unset( $submenu[ $menu_slug ][ $i ] ); 1789 return $item; 1790 } 1791 } 1792 1793 return false; 1794 } 1795 1796 /** 1797 * Get the URL to access a particular menu page based on the slug it was registered with. 1798 * 1799 * If the slug hasn't been registered properly, no URL will be returned. 1800 * 1801 * @since 3.0.0 1802 * 1803 * @global array $_parent_pages 1804 * 1805 * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). 1806 * @param bool $echo Whether or not to echo the URL. Default true. 1807 * @return string The menu page URL. 1808 */ 1809 function menu_page_url( $menu_slug, $echo = true ) { 1810 global $_parent_pages; 1811 1812 if ( isset( $_parent_pages[ $menu_slug ] ) ) { 1813 $parent_slug = $_parent_pages[ $menu_slug ]; 1814 1815 if ( $parent_slug && ! isset( $_parent_pages[ $parent_slug ] ) ) { 1816 $url = admin_url( add_query_arg( 'page', $menu_slug, $parent_slug ) ); 1817 } else { 1818 $url = admin_url( 'admin.php?page=' . $menu_slug ); 1819 } 1820 } else { 1821 $url = ''; 1822 } 1823 1824 $url = esc_url( $url ); 1825 1826 if ( $echo ) { 1827 echo $url; 1828 } 1829 1830 return $url; 1831 } 1832 1833 // 1834 // Pluggable Menu Support -- Private. 1835 // 1836 /** 1837 * Gets the parent file of the current admin page. 1838 * 1839 * @since 1.5.0 1840 * 1841 * @global string $parent_file 1842 * @global array $menu 1843 * @global array $submenu 1844 * @global string $pagenow 1845 * @global string $typenow 1846 * @global string $plugin_page 1847 * @global array $_wp_real_parent_file 1848 * @global array $_wp_menu_nopriv 1849 * @global array $_wp_submenu_nopriv 1850 * 1851 * @param string $parent The slug name for the parent menu (or the file name of a standard 1852 * WordPress admin page). Default empty string. 1853 * @return string The parent file of the current admin page. 1854 */ 1855 function get_admin_page_parent( $parent = '' ) { 1856 global $parent_file, $menu, $submenu, $pagenow, $typenow, 1857 $plugin_page, $_wp_real_parent_file, $_wp_menu_nopriv, $_wp_submenu_nopriv; 1858 1859 if ( ! empty( $parent ) && 'admin.php' !== $parent ) { 1860 if ( isset( $_wp_real_parent_file[ $parent ] ) ) { 1861 $parent = $_wp_real_parent_file[ $parent ]; 1862 } 1863 1864 return $parent; 1865 } 1866 1867 if ( 'admin.php' === $pagenow && isset( $plugin_page ) ) { 1868 foreach ( (array) $menu as $parent_menu ) { 1869 if ( $parent_menu[2] === $plugin_page ) { 1870 $parent_file = $plugin_page; 1871 1872 if ( isset( $_wp_real_parent_file[ $parent_file ] ) ) { 1873 $parent_file = $_wp_real_parent_file[ $parent_file ]; 1874 } 1875 1876 return $parent_file; 1877 } 1878 } 1879 if ( isset( $_wp_menu_nopriv[ $plugin_page ] ) ) { 1880 $parent_file = $plugin_page; 1881 1882 if ( isset( $_wp_real_parent_file[ $parent_file ] ) ) { 1883 $parent_file = $_wp_real_parent_file[ $parent_file ]; 1884 } 1885 1886 return $parent_file; 1887 } 1888 } 1889 1890 if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $pagenow ][ $plugin_page ] ) ) { 1891 $parent_file = $pagenow; 1892 1893 if ( isset( $_wp_real_parent_file[ $parent_file ] ) ) { 1894 $parent_file = $_wp_real_parent_file[ $parent_file ]; 1895 } 1896 1897 return $parent_file; 1898 } 1899 1900 foreach ( array_keys( (array) $submenu ) as $parent ) { 1901 foreach ( $submenu[ $parent ] as $submenu_array ) { 1902 if ( isset( $_wp_real_parent_file[ $parent ] ) ) { 1903 $parent = $_wp_real_parent_file[ $parent ]; 1904 } 1905 1906 if ( ! empty( $typenow ) && "$pagenow?post_type=$typenow" === $submenu_array[2] ) { 1907 $parent_file = $parent; 1908 return $parent; 1909 } elseif ( empty( $typenow ) && $pagenow === $submenu_array[2] 1910 && ( empty( $parent_file ) || false === strpos( $parent_file, '?' ) ) 1911 ) { 1912 $parent_file = $parent; 1913 return $parent; 1914 } elseif ( isset( $plugin_page ) && $plugin_page === $submenu_array[2] ) { 1915 $parent_file = $parent; 1916 return $parent; 1917 } 1918 } 1919 } 1920 1921 if ( empty( $parent_file ) ) { 1922 $parent_file = ''; 1923 } 1924 return ''; 1925 } 1926 1927 /** 1928 * Gets the title of the current admin page. 1929 * 1930 * @since 1.5.0 1931 * 1932 * @global string $title 1933 * @global array $menu 1934 * @global array $submenu 1935 * @global string $pagenow 1936 * @global string $plugin_page 1937 * @global string $typenow 1938 * 1939 * @return string The title of the current admin page. 1940 */ 1941 function get_admin_page_title() { 1942 global $title, $menu, $submenu, $pagenow, $plugin_page, $typenow; 1943 1944 if ( ! empty( $title ) ) { 1945 return $title; 1946 } 1947 1948 $hook = get_plugin_page_hook( $plugin_page, $pagenow ); 1949 1950 $parent = get_admin_page_parent(); 1951 $parent1 = $parent; 1952 1953 if ( empty( $parent ) ) { 1954 foreach ( (array) $menu as $menu_array ) { 1955 if ( isset( $menu_array[3] ) ) { 1956 if ( $menu_array[2] === $pagenow ) { 1957 $title = $menu_array[3]; 1958 return $menu_array[3]; 1959 } elseif ( isset( $plugin_page ) && $plugin_page === $menu_array[2] && $hook === $menu_array[5] ) { 1960 $title = $menu_array[3]; 1961 return $menu_array[3]; 1962 } 1963 } else { 1964 $title = $menu_array[0]; 1965 return $title; 1966 } 1967 } 1968 } else { 1969 foreach ( array_keys( $submenu ) as $parent ) { 1970 foreach ( $submenu[ $parent ] as $submenu_array ) { 1971 if ( isset( $plugin_page ) 1972 && $plugin_page === $submenu_array[2] 1973 && ( $pagenow === $parent 1974 || $plugin_page === $parent 1975 || $plugin_page === $hook 1976 || 'admin.php' === $pagenow && $parent1 !== $submenu_array[2] 1977 || ! empty( $typenow ) && "$pagenow?post_type=$typenow" === $parent ) 1978 ) { 1979 $title = $submenu_array[3]; 1980 return $submenu_array[3]; 1981 } 1982 1983 if ( $submenu_array[2] !== $pagenow || isset( $_GET['page'] ) ) { // Not the current page. 1984 continue; 1985 } 1986 1987 if ( isset( $submenu_array[3] ) ) { 1988 $title = $submenu_array[3]; 1989 return $submenu_array[3]; 1990 } else { 1991 $title = $submenu_array[0]; 1992 return $title; 1993 } 1994 } 1995 } 1996 if ( empty( $title ) ) { 1997 foreach ( $menu as $menu_array ) { 1998 if ( isset( $plugin_page ) 1999 && $plugin_page === $menu_array[2] 2000 && 'admin.php' === $pagenow 2001 && $parent1 === $menu_array[2] 2002 ) { 2003 $title = $menu_array[3]; 2004 return $menu_array[3]; 2005 } 2006 } 2007 } 2008 } 2009 2010 return $title; 2011 } 2012 2013 /** 2014 * Gets the hook attached to the administrative page of a plugin. 2015 * 2016 * @since 1.5.0 2017 * 2018 * @param string $plugin_page The slug name of the plugin page. 2019 * @param string $parent_page The slug name for the parent menu (or the file name of a standard 2020 * WordPress admin page). 2021 * @return string|null Hook attached to the plugin page, null otherwise. 2022 */ 2023 function get_plugin_page_hook( $plugin_page, $parent_page ) { 2024 $hook = get_plugin_page_hookname( $plugin_page, $parent_page ); 2025 if ( has_action( $hook ) ) { 2026 return $hook; 2027 } else { 2028 return null; 2029 } 2030 } 2031 2032 /** 2033 * Gets the hook name for the administrative page of a plugin. 2034 * 2035 * @since 1.5.0 2036 * 2037 * @global array $admin_page_hooks 2038 * 2039 * @param string $plugin_page The slug name of the plugin page. 2040 * @param string $parent_page The slug name for the parent menu (or the file name of a standard 2041 * WordPress admin page). 2042 * @return string Hook name for the plugin page. 2043 */ 2044 function get_plugin_page_hookname( $plugin_page, $parent_page ) { 2045 global $admin_page_hooks; 2046 2047 $parent = get_admin_page_parent( $parent_page ); 2048 2049 $page_type = 'admin'; 2050 if ( empty( $parent_page ) || 'admin.php' === $parent_page || isset( $admin_page_hooks[ $plugin_page ] ) ) { 2051 if ( isset( $admin_page_hooks[ $plugin_page ] ) ) { 2052 $page_type = 'toplevel'; 2053 } elseif ( isset( $admin_page_hooks[ $parent ] ) ) { 2054 $page_type = $admin_page_hooks[ $parent ]; 2055 } 2056 } elseif ( isset( $admin_page_hooks[ $parent ] ) ) { 2057 $page_type = $admin_page_hooks[ $parent ]; 2058 } 2059 2060 $plugin_name = preg_replace( '!\.php!', '', $plugin_page ); 2061 2062 return $page_type . '_page_' . $plugin_name; 2063 } 2064 2065 /** 2066 * Determines whether the current user can access the current admin page. 2067 * 2068 * @since 1.5.0 2069 * 2070 * @global string $pagenow 2071 * @global array $menu 2072 * @global array $submenu 2073 * @global array $_wp_menu_nopriv 2074 * @global array $_wp_submenu_nopriv 2075 * @global string $plugin_page 2076 * @global array $_registered_pages 2077 * 2078 * @return bool True if the current user can access the admin page, false otherwise. 2079 */ 2080 function user_can_access_admin_page() { 2081 global $pagenow, $menu, $submenu, $_wp_menu_nopriv, $_wp_submenu_nopriv, 2082 $plugin_page, $_registered_pages; 2083 2084 $parent = get_admin_page_parent(); 2085 2086 if ( ! isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $parent ][ $pagenow ] ) ) { 2087 return false; 2088 } 2089 2090 if ( isset( $plugin_page ) ) { 2091 if ( isset( $_wp_submenu_nopriv[ $parent ][ $plugin_page ] ) ) { 2092 return false; 2093 } 2094 2095 $hookname = get_plugin_page_hookname( $plugin_page, $parent ); 2096 2097 if ( ! isset( $_registered_pages[ $hookname ] ) ) { 2098 return false; 2099 } 2100 } 2101 2102 if ( empty( $parent ) ) { 2103 if ( isset( $_wp_menu_nopriv[ $pagenow ] ) ) { 2104 return false; 2105 } 2106 if ( isset( $_wp_submenu_nopriv[ $pagenow ][ $pagenow ] ) ) { 2107 return false; 2108 } 2109 if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $pagenow ][ $plugin_page ] ) ) { 2110 return false; 2111 } 2112 if ( isset( $plugin_page ) && isset( $_wp_menu_nopriv[ $plugin_page ] ) ) { 2113 return false; 2114 } 2115 2116 foreach ( array_keys( $_wp_submenu_nopriv ) as $key ) { 2117 if ( isset( $_wp_submenu_nopriv[ $key ][ $pagenow ] ) ) { 2118 return false; 2119 } 2120 if ( isset( $plugin_page ) && isset( $_wp_submenu_nopriv[ $key ][ $plugin_page ] ) ) { 2121 return false; 2122 } 2123 } 2124 2125 return true; 2126 } 2127 2128 if ( isset( $plugin_page ) && $plugin_page === $parent && isset( $_wp_menu_nopriv[ $plugin_page ] ) ) { 2129 return false; 2130 } 2131 2132 if ( isset( $submenu[ $parent ] ) ) { 2133 foreach ( $submenu[ $parent ] as $submenu_array ) { 2134 if ( isset( $plugin_page ) && $submenu_array[2] === $plugin_page ) { 2135 return current_user_can( $submenu_array[1] ); 2136 } elseif ( $submenu_array[2] === $pagenow ) { 2137 return current_user_can( $submenu_array[1] ); 2138 } 2139 } 2140 } 2141 2142 foreach ( $menu as $menu_array ) { 2143 if ( $menu_array[2] === $parent ) { 2144 return current_user_can( $menu_array[1] ); 2145 } 2146 } 2147 2148 return true; 2149 } 2150 2151 /* Allowed list functions */ 2152 2153 /** 2154 * Refreshes the value of the allowed options list available via the 'allowed_options' hook. 2155 * 2156 * See the {@see 'allowed_options'} filter. 2157 * 2158 * @since 2.7.0 2159 * @since 5.5.0 `$new_whitelist_options` was renamed to `$new_allowed_options`. 2160 * Please consider writing more inclusive code. 2161 * 2162 * @global array $new_allowed_options 2163 * 2164 * @param array $options 2165 * @return array 2166 */ 2167 function option_update_filter( $options ) { 2168 global $new_allowed_options; 2169 2170 if ( is_array( $new_allowed_options ) ) { 2171 $options = add_allowed_options( $new_allowed_options, $options ); 2172 } 2173 2174 return $options; 2175 } 2176 2177 /** 2178 * Adds an array of options to the list of allowed options. 2179 * 2180 * @since 5.5.0 2181 * 2182 * @global array $allowed_options 2183 * 2184 * @param array $new_options 2185 * @param string|array $options 2186 * @return array 2187 */ 2188 function add_allowed_options( $new_options, $options = '' ) { 2189 if ( '' === $options ) { 2190 global $allowed_options; 2191 } else { 2192 $allowed_options = $options; 2193 } 2194 2195 foreach ( $new_options as $page => $keys ) { 2196 foreach ( $keys as $key ) { 2197 if ( ! isset( $allowed_options[ $page ] ) || ! is_array( $allowed_options[ $page ] ) ) { 2198 $allowed_options[ $page ] = array(); 2199 $allowed_options[ $page ][] = $key; 2200 } else { 2201 $pos = array_search( $key, $allowed_options[ $page ], true ); 2202 if ( false === $pos ) { 2203 $allowed_options[ $page ][] = $key; 2204 } 2205 } 2206 } 2207 } 2208 2209 return $allowed_options; 2210 } 2211 2212 /** 2213 * Removes a list of options from the allowed options list. 2214 * 2215 * @since 5.5.0 2216 * 2217 * @global array $allowed_options 2218 * 2219 * @param array $del_options 2220 * @param string|array $options 2221 * @return array 2222 */ 2223 function remove_allowed_options( $del_options, $options = '' ) { 2224 if ( '' === $options ) { 2225 global $allowed_options; 2226 } else { 2227 $allowed_options = $options; 2228 } 2229 2230 foreach ( $del_options as $page => $keys ) { 2231 foreach ( $keys as $key ) { 2232 if ( isset( $allowed_options[ $page ] ) && is_array( $allowed_options[ $page ] ) ) { 2233 $pos = array_search( $key, $allowed_options[ $page ], true ); 2234 if ( false !== $pos ) { 2235 unset( $allowed_options[ $page ][ $pos ] ); 2236 } 2237 } 2238 } 2239 } 2240 2241 return $allowed_options; 2242 } 2243 2244 /** 2245 * Output nonce, action, and option_page fields for a settings page. 2246 * 2247 * @since 2.7.0 2248 * 2249 * @param string $option_group A settings group name. This should match the group name 2250 * used in register_setting(). 2251 */ 2252 function settings_fields( $option_group ) { 2253 echo "<input type='hidden' name='option_page' value='" . esc_attr( $option_group ) . "' />"; 2254 echo '<input type="hidden" name="action" value="update" />'; 2255 wp_nonce_field( "$option_group-options" ); 2256 } 2257 2258 /** 2259 * Clears the plugins cache used by get_plugins() and by default, the plugin updates cache. 2260 * 2261 * @since 3.7.0 2262 * 2263 * @param bool $clear_update_cache Whether to clear the plugin updates cache. Default true. 2264 */ 2265 function wp_clean_plugins_cache( $clear_update_cache = true ) { 2266 if ( $clear_update_cache ) { 2267 delete_site_transient( 'update_plugins' ); 2268 } 2269 wp_cache_delete( 'plugins', 'plugins' ); 2270 } 2271 2272 /** 2273 * Load a given plugin attempt to generate errors. 2274 * 2275 * @since 3.0.0 2276 * @since 4.4.0 Function was moved into the `wp-admin/includes/plugin.php` file. 2277 * 2278 * @param string $plugin Path to the plugin file relative to the plugins directory. 2279 */ 2280 function plugin_sandbox_scrape( $plugin ) { 2281 if ( ! defined( 'WP_SANDBOX_SCRAPING' ) ) { 2282 define( 'WP_SANDBOX_SCRAPING', true ); 2283 } 2284 2285 wp_register_plugin_realpath( WP_PLUGIN_DIR . '/' . $plugin ); 2286 include_once WP_PLUGIN_DIR . '/' . $plugin; 2287 } 2288 2289 /** 2290 * Helper function for adding content to the Privacy Policy Guide. 2291 * 2292 * Plugins and themes should suggest text for inclusion in the site's privacy policy. 2293 * The suggested text should contain information about any functionality that affects user privacy, 2294 * and will be shown on the Privacy Policy Guide screen. 2295 * 2296 * A plugin or theme can use this function multiple times as long as it will help to better present 2297 * the suggested policy content. For example modular plugins such as WooCommerse or Jetpack 2298 * can add or remove suggested content depending on the modules/extensions that are enabled. 2299 * For more information see the Plugin Handbook: 2300 * https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/. 2301 * 2302 * The HTML contents of the `$policy_text` supports use of a specialized `.privacy-policy-tutorial` 2303 * CSS class which can be used to provide supplemental information. Any content contained within 2304 * HTML elements that have the `.privacy-policy-tutorial` CSS class applied will be omitted 2305 * from the clipboard when the section content is copied. 2306 * 2307 * Intended for use with the `'admin_init'` action. 2308 * 2309 * @since 4.9.6 2310 * 2311 * @param string $plugin_name The name of the plugin or theme that is suggesting content 2312 * for the site's privacy policy. 2313 * @param string $policy_text The suggested content for inclusion in the policy. 2314 */ 2315 function wp_add_privacy_policy_content( $plugin_name, $policy_text ) { 2316 if ( ! is_admin() ) { 2317 _doing_it_wrong( 2318 __FUNCTION__, 2319 sprintf( 2320 /* translators: %s: admin_init */ 2321 __( 'The suggested privacy policy content should be added only in wp-admin by using the %s (or later) action.' ), 2322 '<code>admin_init</code>' 2323 ), 2324 '4.9.7' 2325 ); 2326 return; 2327 } elseif ( ! doing_action( 'admin_init' ) && ! did_action( 'admin_init' ) ) { 2328 _doing_it_wrong( 2329 __FUNCTION__, 2330 sprintf( 2331 /* translators: %s: admin_init */ 2332 __( 'The suggested privacy policy content should be added by using the %s (or later) action. Please see the inline documentation.' ), 2333 '<code>admin_init</code>' 2334 ), 2335 '4.9.7' 2336 ); 2337 return; 2338 } 2339 2340 if ( ! class_exists( 'WP_Privacy_Policy_Content' ) ) { 2341 require_once ABSPATH . 'wp-admin/includes/class-wp-privacy-policy-content.php'; 2342 } 2343 2344 WP_Privacy_Policy_Content::add( $plugin_name, $policy_text ); 2345 } 2346 2347 /** 2348 * Determines whether a plugin is technically active but was paused while 2349 * loading. 2350 * 2351 * For more information on this and similar theme functions, check out 2352 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ 2353 * Conditional Tags} article in the Theme Developer Handbook. 2354 * 2355 * @since 5.2.0 2356 * 2357 * @param string $plugin Path to the plugin file relative to the plugins directory. 2358 * @return bool True, if in the list of paused plugins. False, if not in the list. 2359 */ 2360 function is_plugin_paused( $plugin ) { 2361 if ( ! isset( $GLOBALS['_paused_plugins'] ) ) { 2362 return false; 2363 } 2364 2365 if ( ! is_plugin_active( $plugin ) ) { 2366 return false; 2367 } 2368 2369 list( $plugin ) = explode( '/', $plugin ); 2370 2371 return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ); 2372 } 2373 2374 /** 2375 * Gets the error that was recorded for a paused plugin. 2376 * 2377 * @since 5.2.0 2378 * 2379 * @param string $plugin Path to the plugin file relative to the plugins directory. 2380 * @return array|false Array of error information as returned by `error_get_last()`, 2381 * or false if none was recorded. 2382 */ 2383 function wp_get_plugin_error( $plugin ) { 2384 if ( ! isset( $GLOBALS['_paused_plugins'] ) ) { 2385 return false; 2386 } 2387 2388 list( $plugin ) = explode( '/', $plugin ); 2389 2390 if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) { 2391 return false; 2392 } 2393 2394 return $GLOBALS['_paused_plugins'][ $plugin ]; 2395 } 2396 2397 /** 2398 * Tries to resume a single plugin. 2399 * 2400 * If a redirect was provided, we first ensure the plugin does not throw fatal 2401 * errors anymore. 2402 * 2403 * The way it works is by setting the redirection to the error before trying to 2404 * include the plugin file. If the plugin fails, then the redirection will not 2405 * be overwritten with the success message and the plugin will not be resumed. 2406 * 2407 * @since 5.2.0 2408 * 2409 * @param string $plugin Single plugin to resume. 2410 * @param string $redirect Optional. URL to redirect to. Default empty string. 2411 * @return bool|WP_Error True on success, false if `$plugin` was not paused, 2412 * `WP_Error` on failure. 2413 */ 2414 function resume_plugin( $plugin, $redirect = '' ) { 2415 /* 2416 * We'll override this later if the plugin could be resumed without 2417 * creating a fatal error. 2418 */ 2419 if ( ! empty( $redirect ) ) { 2420 wp_redirect( 2421 add_query_arg( 2422 '_error_nonce', 2423 wp_create_nonce( 'plugin-resume-error_' . $plugin ), 2424 $redirect 2425 ) 2426 ); 2427 2428 // Load the plugin to test whether it throws a fatal error. 2429 ob_start(); 2430 plugin_sandbox_scrape( $plugin ); 2431 ob_clean(); 2432 } 2433 2434 list( $extension ) = explode( '/', $plugin ); 2435 2436 $result = wp_paused_plugins()->delete( $extension ); 2437 2438 if ( ! $result ) { 2439 return new WP_Error( 2440 'could_not_resume_plugin', 2441 __( 'Could not resume the plugin.' ) 2442 ); 2443 } 2444 2445 return true; 2446 } 2447 2448 /** 2449 * Renders an admin notice in case some plugins have been paused due to errors. 2450 * 2451 * @since 5.2.0 2452 * 2453 * @global string $pagenow 2454 */ 2455 function paused_plugins_notice() { 2456 if ( 'plugins.php' === $GLOBALS['pagenow'] ) { 2457 return; 2458 } 2459 2460 if ( ! current_user_can( 'resume_plugins' ) ) { 2461 return; 2462 } 2463 2464 if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) { 2465 return; 2466 } 2467 2468 printf( 2469 '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>', 2470 __( 'One or more plugins failed to load properly.' ), 2471 __( 'You can find more details and make changes on the Plugins screen.' ), 2472 esc_url( admin_url( 'plugins.php?plugin_status=paused' ) ), 2473 __( 'Go to the Plugins screen' ) 2474 ); 2475 } 2476 2477 /** 2478 * Renders an admin notice when a plugin was deactivated during an update. 2479 * 2480 * Displays an admin notice in case a plugin has been deactivated during an 2481 * upgrade due to incompatibility with the current version of WordPress. 2482 * 2483 * @since 5.8.0 2484 * @access private 2485 * 2486 * @global string $pagenow 2487 * @global string $wp_version 2488 */ 2489 function deactivated_plugins_notice() { 2490 if ( 'plugins.php' === $GLOBALS['pagenow'] ) { 2491 return; 2492 } 2493 2494 if ( ! current_user_can( 'activate_plugins' ) ) { 2495 return; 2496 } 2497 2498 $blog_deactivated_plugins = get_option( 'wp_force_deactivated_plugins' ); 2499 $site_deactivated_plugins = array(); 2500 2501 if ( false === $blog_deactivated_plugins ) { 2502 // Option not in database, add an empty array to avoid extra DB queries on subsequent loads. 2503 update_option( 'wp_force_deactivated_plugins', array() ); 2504 } 2505 2506 if ( is_multisite() ) { 2507 $site_deactivated_plugins = get_site_option( 'wp_force_deactivated_plugins' ); 2508 if ( false === $site_deactivated_plugins ) { 2509 // Option not in database, add an empty array to avoid extra DB queries on subsequent loads. 2510 update_site_option( 'wp_force_deactivated_plugins', array() ); 2511 } 2512 } 2513 2514 if ( empty( $blog_deactivated_plugins ) && empty( $site_deactivated_plugins ) ) { 2515 // No deactivated plugins. 2516 return; 2517 } 2518 2519 $deactivated_plugins = array_merge( $blog_deactivated_plugins, $site_deactivated_plugins ); 2520 2521 foreach ( $deactivated_plugins as $plugin ) { 2522 if ( ! empty( $plugin['version_compatible'] ) && ! empty( $plugin['version_deactivated'] ) ) { 2523 $explanation = sprintf( 2524 /* translators: 1: Name of deactivated plugin, 2: Plugin version deactivated, 3: Current WP version, 4: Compatible plugin version */ 2525 __( '%1$s %2$s was deactivated due to incompatibility with WordPress %3$s, please upgrade to %1$s %4$s or later.' ), 2526 $plugin['plugin_name'], 2527 $plugin['version_deactivated'], 2528 $GLOBALS['wp_version'], 2529 $plugin['version_compatible'] 2530 ); 2531 } else { 2532 $explanation = sprintf( 2533 /* translators: 1: Name of deactivated plugin, 2: Plugin version deactivated, 3: Current WP version */ 2534 __( '%1$s %2$s was deactivated due to incompatibility with WordPress %3$s.' ), 2535 $plugin['plugin_name'], 2536 ! empty( $plugin['version_deactivated'] ) ? $plugin['version_deactivated'] : '', 2537 $GLOBALS['wp_version'], 2538 $plugin['version_compatible'] 2539 ); 2540 } 2541 2542 printf( 2543 '<div class="notice notice-warning"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>', 2544 sprintf( 2545 /* translators: %s: Name of deactivated plugin */ 2546 __( '%s plugin deactivated during WordPress upgrade.' ), 2547 $plugin['plugin_name'] 2548 ), 2549 $explanation, 2550 esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ), 2551 __( 'Go to the Plugins screen' ) 2552 ); 2553 } 2554 2555 // Empty the options. 2556 update_option( 'wp_force_deactivated_plugins', array() ); 2557 if ( is_multisite() ) { 2558 update_site_option( 'wp_force_deactivated_plugins', array() ); 2559 } 2560 }