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