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