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