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