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