angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

plugin.php (88385B)


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