wp/wp-admin/includes/plugin.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    43  * reading.
    43  * reading.
    44  *
    44  *
    45  * @since 1.5.0
    45  * @since 1.5.0
    46  * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers.
    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.
    47  * @since 5.8.0 Added support for `Update URI` header.
       
    48  * @since 6.5.0 Added support for `Requires Plugins` header.
    48  *
    49  *
    49  * @param string $plugin_file Absolute path to the main plugin file.
    50  * @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  * @param bool   $markup      Optional. If the returned data should have HTML markup applied.
    51  *                            Default true.
    52  *                            Default true.
    52  * @param bool   $translate   Optional. If the returned data should be translated. Default true.
    53  * @param bool   $translate   Optional. If the returned data should be translated. Default true.
    53  * @return array {
    54  * @return array {
    54  *     Plugin data. Values will be empty if not supplied by the plugin.
    55  *     Plugin data. Values will be empty if not supplied by the plugin.
    55  *
    56  *
    56  *     @type string $Name        Name of the plugin. Should be unique.
    57  *     @type string $Name            Name of the plugin. Should be unique.
    57  *     @type string $PluginURI   Plugin URI.
    58  *     @type string $PluginURI       Plugin URI.
    58  *     @type string $Version     Plugin version.
    59  *     @type string $Version         Plugin version.
    59  *     @type string $Description Plugin description.
    60  *     @type string $Description     Plugin description.
    60  *     @type string $Author      Plugin author's name.
    61  *     @type string $Author          Plugin author's name.
    61  *     @type string $AuthorURI   Plugin author's website address (if set).
    62  *     @type string $AuthorURI       Plugin author's website address (if set).
    62  *     @type string $TextDomain  Plugin textdomain.
    63  *     @type string $TextDomain      Plugin textdomain.
    63  *     @type string $DomainPath  Plugin's relative directory path to .mo files.
    64  *     @type string $DomainPath      Plugin's relative directory path to .mo files.
    64  *     @type bool   $Network     Whether the plugin can only be activated network-wide.
    65  *     @type bool   $Network         Whether the plugin can only be activated network-wide.
    65  *     @type string $RequiresWP  Minimum required version of WordPress.
    66  *     @type string $RequiresWP      Minimum required version of WordPress.
    66  *     @type string $RequiresPHP Minimum required version of PHP.
    67  *     @type string $RequiresPHP     Minimum required version of PHP.
    67  *     @type string $UpdateURI   ID of the plugin for update purposes, should be a URI.
    68  *     @type string $UpdateURI       ID of the plugin for update purposes, should be a URI.
    68  *     @type string $Title       Title of the plugin and link to the plugin's site (if set).
    69  *     @type string $RequiresPlugins Comma separated list of dot org plugin slugs.
    69  *     @type string $AuthorName  Plugin author's name.
    70  *     @type string $Title           Title of the plugin and link to the plugin's site (if set).
       
    71  *     @type string $AuthorName      Plugin author's name.
    70  * }
    72  * }
    71  */
    73  */
    72 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
    74 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
    73 
    75 
    74 	$default_headers = array(
    76 	$default_headers = array(
    75 		'Name'        => 'Plugin Name',
    77 		'Name'            => 'Plugin Name',
    76 		'PluginURI'   => 'Plugin URI',
    78 		'PluginURI'       => 'Plugin URI',
    77 		'Version'     => 'Version',
    79 		'Version'         => 'Version',
    78 		'Description' => 'Description',
    80 		'Description'     => 'Description',
    79 		'Author'      => 'Author',
    81 		'Author'          => 'Author',
    80 		'AuthorURI'   => 'Author URI',
    82 		'AuthorURI'       => 'Author URI',
    81 		'TextDomain'  => 'Text Domain',
    83 		'TextDomain'      => 'Text Domain',
    82 		'DomainPath'  => 'Domain Path',
    84 		'DomainPath'      => 'Domain Path',
    83 		'Network'     => 'Network',
    85 		'Network'         => 'Network',
    84 		'RequiresWP'  => 'Requires at least',
    86 		'RequiresWP'      => 'Requires at least',
    85 		'RequiresPHP' => 'Requires PHP',
    87 		'RequiresPHP'     => 'Requires PHP',
    86 		'UpdateURI'   => 'Update URI',
    88 		'UpdateURI'       => 'Update URI',
       
    89 		'RequiresPlugins' => 'Requires Plugins',
    87 		// Site Wide Only is deprecated in favor of Network.
    90 		// Site Wide Only is deprecated in favor of Network.
    88 		'_sitewide'   => 'Site Wide Only',
    91 		'_sitewide'       => 'Site Wide Only',
    89 	);
    92 	);
    90 
    93 
    91 	$plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );
    94 	$plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );
    92 
    95 
    93 	// Site Wide Only is the old header for Network.
    96 	// Site Wide Only is the old header for Network.
   100 	unset( $plugin_data['_sitewide'] );
   103 	unset( $plugin_data['_sitewide'] );
   101 
   104 
   102 	// If no text domain is defined fall back to the plugin slug.
   105 	// If no text domain is defined fall back to the plugin slug.
   103 	if ( ! $plugin_data['TextDomain'] ) {
   106 	if ( ! $plugin_data['TextDomain'] ) {
   104 		$plugin_slug = dirname( plugin_basename( $plugin_file ) );
   107 		$plugin_slug = dirname( plugin_basename( $plugin_file ) );
   105 		if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) {
   108 		if ( '.' !== $plugin_slug && ! str_contains( $plugin_slug, '/' ) ) {
   106 			$plugin_data['TextDomain'] = $plugin_slug;
   109 			$plugin_data['TextDomain'] = $plugin_slug;
   107 		}
   110 		}
   108 	}
   111 	}
   109 
   112 
   110 	if ( $markup || $translate ) {
   113 	if ( $markup || $translate ) {
   125  * @see get_plugin_data()
   128  * @see get_plugin_data()
   126  *
   129  *
   127  * @access private
   130  * @access private
   128  *
   131  *
   129  * @param string $plugin_file Path to the main plugin file.
   132  * @param string $plugin_file Path to the main plugin file.
   130  * @param array  $plugin_data An array of plugin data. See `get_plugin_data()`.
   133  * @param array  $plugin_data An array of plugin data. See get_plugin_data().
   131  * @param bool   $markup      Optional. If the returned data should have HTML markup applied.
   134  * @param bool   $markup      Optional. If the returned data should have HTML markup applied.
   132  *                            Default true.
   135  *                            Default true.
   133  * @param bool   $translate   Optional. If the returned data should be translated. Default true.
   136  * @param bool   $translate   Optional. If the returned data should be translated. Default true.
   134  * @return array Plugin data. Values will be empty if not supplied by the plugin.
   137  * @return array Plugin data. Values will be empty if not supplied by the plugin.
   135  *               See get_plugin_data() for the list of possible values.
   138  *               See get_plugin_data() for the list of possible values.
   176 	$allowed_tags['a'] = array(
   179 	$allowed_tags['a'] = array(
   177 		'href'  => true,
   180 		'href'  => true,
   178 		'title' => true,
   181 		'title' => true,
   179 	);
   182 	);
   180 
   183 
   181 	// Name is marked up inside <a> tags. Don't allow these.
   184 	/*
   182 	// Author is too, but some plugins have used <a> here (omitting Author URI).
   185 	 * Name is marked up inside <a> tags. Don't allow these.
       
   186 	 * Author is too, but some plugins have used <a> here (omitting Author URI).
       
   187 	 */
   183 	$plugin_data['Name']   = wp_kses( $plugin_data['Name'], $allowed_tags_in_links );
   188 	$plugin_data['Name']   = wp_kses( $plugin_data['Name'], $allowed_tags_in_links );
   184 	$plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags );
   189 	$plugin_data['Author'] = wp_kses( $plugin_data['Author'], $allowed_tags );
   185 
   190 
   186 	$plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags );
   191 	$plugin_data['Description'] = wp_kses( $plugin_data['Description'], $allowed_tags );
   187 	$plugin_data['Version']     = wp_kses( $plugin_data['Version'], $allowed_tags );
   192 	$plugin_data['Version']     = wp_kses( $plugin_data['Version'], $allowed_tags );
   267  * optimization purposes.
   272  * optimization purposes.
   268  *
   273  *
   269  * @since 1.5.0
   274  * @since 1.5.0
   270  *
   275  *
   271  * @param string $plugin_folder Optional. Relative path to single plugin folder.
   276  * @param string $plugin_folder Optional. Relative path to single plugin folder.
   272  * @return array[] Array of arrays of plugin data, keyed by plugin file name. See `get_plugin_data()`.
   277  * @return array[] Array of arrays of plugin data, keyed by plugin file name. See get_plugin_data().
   273  */
   278  */
   274 function get_plugins( $plugin_folder = '' ) {
   279 function get_plugins( $plugin_folder = '' ) {
   275 
   280 
   276 	$cache_plugins = wp_cache_get( 'plugins', 'plugins' );
   281 	$cache_plugins = wp_cache_get( 'plugins', 'plugins' );
   277 	if ( ! $cache_plugins ) {
   282 	if ( ! $cache_plugins ) {
   292 	$plugins_dir  = @opendir( $plugin_root );
   297 	$plugins_dir  = @opendir( $plugin_root );
   293 	$plugin_files = array();
   298 	$plugin_files = array();
   294 
   299 
   295 	if ( $plugins_dir ) {
   300 	if ( $plugins_dir ) {
   296 		while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
   301 		while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
   297 			if ( '.' === substr( $file, 0, 1 ) ) {
   302 			if ( str_starts_with( $file, '.' ) ) {
   298 				continue;
   303 				continue;
   299 			}
   304 			}
   300 
   305 
   301 			if ( is_dir( $plugin_root . '/' . $file ) ) {
   306 			if ( is_dir( $plugin_root . '/' . $file ) ) {
   302 				$plugins_subdir = @opendir( $plugin_root . '/' . $file );
   307 				$plugins_subdir = @opendir( $plugin_root . '/' . $file );
   303 
   308 
   304 				if ( $plugins_subdir ) {
   309 				if ( $plugins_subdir ) {
   305 					while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) {
   310 					while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) {
   306 						if ( '.' === substr( $subfile, 0, 1 ) ) {
   311 						if ( str_starts_with( $subfile, '.' ) ) {
   307 							continue;
   312 							continue;
   308 						}
   313 						}
   309 
   314 
   310 						if ( '.php' === substr( $subfile, -4 ) ) {
   315 						if ( str_ends_with( $subfile, '.php' ) ) {
   311 							$plugin_files[] = "$file/$subfile";
   316 							$plugin_files[] = "$file/$subfile";
   312 						}
   317 						}
   313 					}
   318 					}
   314 
   319 
   315 					closedir( $plugins_subdir );
   320 					closedir( $plugins_subdir );
   316 				}
   321 				}
   317 			} else {
   322 			} elseif ( str_ends_with( $file, '.php' ) ) {
   318 				if ( '.php' === substr( $file, -4 ) ) {
   323 				$plugin_files[] = $file;
   319 					$plugin_files[] = $file;
       
   320 				}
       
   321 			}
   324 			}
   322 		}
   325 		}
   323 
   326 
   324 		closedir( $plugins_dir );
   327 		closedir( $plugins_dir );
   325 	}
   328 	}
   355  * Checks the mu-plugins directory and retrieve all mu-plugin files with any plugin data.
   358  * Checks the mu-plugins directory and retrieve all mu-plugin files with any plugin data.
   356  *
   359  *
   357  * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins).
   360  * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins).
   358  *
   361  *
   359  * @since 3.0.0
   362  * @since 3.0.0
   360  * @return array[] Array of arrays of mu-plugin data, keyed by plugin file name. See `get_plugin_data()`.
   363  * @return array[] Array of arrays of mu-plugin data, keyed by plugin file name. See get_plugin_data().
   361  */
   364  */
   362 function get_mu_plugins() {
   365 function get_mu_plugins() {
   363 	$wp_plugins   = array();
   366 	$wp_plugins   = array();
   364 	$plugin_files = array();
   367 	$plugin_files = array();
   365 
   368 
   369 
   372 
   370 	// Files in wp-content/mu-plugins directory.
   373 	// Files in wp-content/mu-plugins directory.
   371 	$plugins_dir = @opendir( WPMU_PLUGIN_DIR );
   374 	$plugins_dir = @opendir( WPMU_PLUGIN_DIR );
   372 	if ( $plugins_dir ) {
   375 	if ( $plugins_dir ) {
   373 		while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
   376 		while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
   374 			if ( '.php' === substr( $file, -4 ) ) {
   377 			if ( str_ends_with( $file, '.php' ) ) {
   375 				$plugin_files[] = $file;
   378 				$plugin_files[] = $file;
   376 			}
   379 			}
   377 		}
   380 		}
   378 	} else {
   381 	} else {
   379 		return $wp_plugins;
   382 		return $wp_plugins;
   427 
   430 
   428 /**
   431 /**
   429  * Checks the wp-content directory and retrieve all drop-ins with any plugin data.
   432  * Checks the wp-content directory and retrieve all drop-ins with any plugin data.
   430  *
   433  *
   431  * @since 3.0.0
   434  * @since 3.0.0
   432  * @return array[] Array of arrays of dropin plugin data, keyed by plugin file name. See `get_plugin_data()`.
   435  * @return array[] Array of arrays of dropin plugin data, keyed by plugin file name. See get_plugin_data().
   433  */
   436  */
   434 function get_dropins() {
   437 function get_dropins() {
   435 	$dropins      = array();
   438 	$dropins      = array();
   436 	$plugin_files = array();
   439 	$plugin_files = array();
   437 
   440 
   474 
   477 
   475 	return $dropins;
   478 	return $dropins;
   476 }
   479 }
   477 
   480 
   478 /**
   481 /**
   479  * Returns drop-ins that WordPress uses.
   482  * Returns drop-in plugins that WordPress uses.
   480  *
   483  *
   481  * Includes Multisite drop-ins only when is_multisite()
   484  * Includes Multisite drop-ins only when is_multisite()
   482  *
   485  *
   483  * @since 3.0.0
   486  * @since 3.0.0
   484  * @return array[] Key is file name. The value is an array, with the first value the
   487  *
   485  *  purpose of the drop-in and the second value the name of the constant that must be
   488  * @return array[] {
   486  *  true for the drop-in to be used, or true if no constant is required.
   489  *     Key is file name. The value is an array of data about the drop-in.
       
   490  *
       
   491  *     @type array ...$0 {
       
   492  *         Data about the drop-in.
       
   493  *
       
   494  *         @type string      $0 The purpose of the drop-in.
       
   495  *         @type string|true $1 Name of the constant that must be true for the drop-in
       
   496  *                              to be used, or true if no constant is required.
       
   497  *     }
       
   498  * }
   487  */
   499  */
   488 function _get_dropins() {
   500 function _get_dropins() {
   489 	$dropins = array(
   501 	$dropins = array(
   490 		'advanced-cache.php'      => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ),  // WP_CACHE
   502 		'advanced-cache.php'      => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ),  // WP_CACHE
   491 		'db.php'                  => array( __( 'Custom database class.' ), true ),          // Auto on load.
   503 		'db.php'                  => array( __( 'Custom database class.' ), true ),          // Auto on load.
   850  * @param string|string[] $plugins      Single plugin or list of plugins to activate.
   862  * @param string|string[] $plugins      Single plugin or list of plugins to activate.
   851  * @param string          $redirect     Redirect to page after successful activation.
   863  * @param string          $redirect     Redirect to page after successful activation.
   852  * @param bool            $network_wide Whether to enable the plugin for all sites in the network.
   864  * @param bool            $network_wide Whether to enable the plugin for all sites in the network.
   853  *                                      Default false.
   865  *                                      Default false.
   854  * @param bool            $silent       Prevent calling activation hooks. Default false.
   866  * @param bool            $silent       Prevent calling activation hooks. Default false.
   855  * @return bool|WP_Error True when finished or WP_Error if there were errors during a plugin activation.
   867  * @return true|WP_Error True when finished or WP_Error if there were errors during a plugin activation.
   856  */
   868  */
   857 function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) {
   869 function activate_plugins( $plugins, $redirect = '', $network_wide = false, $silent = false ) {
   858 	if ( ! is_array( $plugins ) ) {
   870 	if ( ! is_array( $plugins ) ) {
   859 		$plugins = array( $plugins );
   871 		$plugins = array( $plugins );
   860 	}
   872 	}
   967 		 */
   979 		 */
   968 		do_action( 'delete_plugin', $plugin_file );
   980 		do_action( 'delete_plugin', $plugin_file );
   969 
   981 
   970 		$this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) );
   982 		$this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) );
   971 
   983 
   972 		// If plugin is in its own directory, recursively delete the directory.
   984 		/*
   973 		// Base check on if plugin includes directory separator AND that it's not the root plugin folder.
   985 		 * If plugin is in its own directory, recursively delete the directory.
       
   986 		 * Base check on if plugin includes directory separator AND that it's not the root plugin folder.
       
   987 		 */
   974 		if ( strpos( $plugin_file, '/' ) && $this_plugin_dir !== $plugins_dir ) {
   988 		if ( strpos( $plugin_file, '/' ) && $this_plugin_dir !== $plugins_dir ) {
   975 			$deleted = $wp_filesystem->delete( $this_plugin_dir, true );
   989 			$deleted = $wp_filesystem->delete( $this_plugin_dir, true );
   976 		} else {
   990 		} else {
   977 			$deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file );
   991 			$deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file );
   978 		}
   992 		}
  1003 			$translations = $plugin_translations[ $plugin_slug ];
  1017 			$translations = $plugin_translations[ $plugin_slug ];
  1004 
  1018 
  1005 			foreach ( $translations as $translation => $data ) {
  1019 			foreach ( $translations as $translation => $data ) {
  1006 				$wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' );
  1020 				$wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' );
  1007 				$wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' );
  1021 				$wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' );
       
  1022 				$wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.l10n.php' );
  1008 
  1023 
  1009 				$json_translation_files = glob( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '-*.json' );
  1024 				$json_translation_files = glob( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '-*.json' );
  1010 				if ( $json_translation_files ) {
  1025 				if ( $json_translation_files ) {
  1011 					array_map( array( $wp_filesystem, 'delete' ), $json_translation_files );
  1026 					array_map( array( $wp_filesystem, 'delete' ), $json_translation_files );
  1012 				}
  1027 				}
  1107 }
  1122 }
  1108 
  1123 
  1109 /**
  1124 /**
  1110  * Validates the plugin requirements for WordPress version and PHP version.
  1125  * Validates the plugin requirements for WordPress version and PHP version.
  1111  *
  1126  *
  1112  * Uses the information from `Requires at least` and `Requires PHP` headers
  1127  * Uses the information from `Requires at least`, `Requires PHP` and `Requires Plugins` headers
  1113  * defined in the plugin's main PHP file.
  1128  * defined in the plugin's main PHP file.
  1114  *
  1129  *
  1115  * @since 5.2.0
  1130  * @since 5.2.0
  1116  * @since 5.3.0 Added support for reading the headers from the plugin's
  1131  * @since 5.3.0 Added support for reading the headers from the plugin's
  1117  *              main PHP file, with `readme.txt` as a fallback.
  1132  *              main PHP file, with `readme.txt` as a fallback.
  1118  * @since 5.8.0 Removed support for using `readme.txt` as a fallback.
  1133  * @since 5.8.0 Removed support for using `readme.txt` as a fallback.
       
  1134  * @since 6.5.0 Added support for the 'Requires Plugins' header.
  1119  *
  1135  *
  1120  * @param string $plugin Path to the plugin file relative to the plugins directory.
  1136  * @param string $plugin Path to the plugin file relative to the plugins directory.
  1121  * @return true|WP_Error True if requirements are met, WP_Error on failure.
  1137  * @return true|WP_Error True if requirements are met, WP_Error on failure.
  1122  */
  1138  */
  1123 function validate_plugin_requirements( $plugin ) {
  1139 function validate_plugin_requirements( $plugin ) {
  1124 	$plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
  1140 	$plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
  1125 
  1141 
  1126 	$requirements = array(
  1142 	$requirements = array(
  1127 		'requires'     => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
  1143 		'requires'         => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
  1128 		'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
  1144 		'requires_php'     => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
       
  1145 		'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '',
  1129 	);
  1146 	);
  1130 
  1147 
  1131 	$compatible_wp  = is_wp_version_compatible( $requirements['requires'] );
  1148 	$compatible_wp  = is_wp_version_compatible( $requirements['requires'] );
  1132 	$compatible_php = is_php_version_compatible( $requirements['requires_php'] );
  1149 	$compatible_php = is_php_version_compatible( $requirements['requires_php'] );
  1133 
  1150 
  1148 			'plugin_wp_php_incompatible',
  1165 			'plugin_wp_php_incompatible',
  1149 			'<p>' . sprintf(
  1166 			'<p>' . sprintf(
  1150 				/* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */
  1167 				/* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */
  1151 				_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' ),
  1168 				_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' ),
  1152 				get_bloginfo( 'version' ),
  1169 				get_bloginfo( 'version' ),
  1153 				phpversion(),
  1170 				PHP_VERSION,
  1154 				$plugin_headers['Name'],
  1171 				$plugin_headers['Name'],
  1155 				$requirements['requires'],
  1172 				$requirements['requires'],
  1156 				$requirements['requires_php']
  1173 				$requirements['requires_php']
  1157 			) . $php_update_message . '</p>'
  1174 			) . $php_update_message . '</p>'
  1158 		);
  1175 		);
  1160 		return new WP_Error(
  1177 		return new WP_Error(
  1161 			'plugin_php_incompatible',
  1178 			'plugin_php_incompatible',
  1162 			'<p>' . sprintf(
  1179 			'<p>' . sprintf(
  1163 				/* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */
  1180 				/* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */
  1164 				_x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ),
  1181 				_x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ),
  1165 				phpversion(),
  1182 				PHP_VERSION,
  1166 				$plugin_headers['Name'],
  1183 				$plugin_headers['Name'],
  1167 				$requirements['requires_php']
  1184 				$requirements['requires_php']
  1168 			) . $php_update_message . '</p>'
  1185 			) . $php_update_message . '</p>'
  1169 		);
  1186 		);
  1170 	} elseif ( ! $compatible_wp ) {
  1187 	} elseif ( ! $compatible_wp ) {
  1175 				_x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ),
  1192 				_x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ),
  1176 				get_bloginfo( 'version' ),
  1193 				get_bloginfo( 'version' ),
  1177 				$plugin_headers['Name'],
  1194 				$plugin_headers['Name'],
  1178 				$requirements['requires']
  1195 				$requirements['requires']
  1179 			) . '</p>'
  1196 			) . '</p>'
       
  1197 		);
       
  1198 	}
       
  1199 
       
  1200 	WP_Plugin_Dependencies::initialize();
       
  1201 
       
  1202 	if ( WP_Plugin_Dependencies::has_unmet_dependencies( $plugin ) ) {
       
  1203 		$dependency_names       = WP_Plugin_Dependencies::get_dependency_names( $plugin );
       
  1204 		$unmet_dependencies     = array();
       
  1205 		$unmet_dependency_names = array();
       
  1206 
       
  1207 		foreach ( $dependency_names as $dependency => $dependency_name ) {
       
  1208 			$dependency_file = WP_Plugin_Dependencies::get_dependency_filepath( $dependency );
       
  1209 
       
  1210 			if ( false === $dependency_file ) {
       
  1211 				$unmet_dependencies['not_installed'][ $dependency ] = $dependency_name;
       
  1212 				$unmet_dependency_names[]                           = $dependency_name;
       
  1213 			} elseif ( is_plugin_inactive( $dependency_file ) ) {
       
  1214 				$unmet_dependencies['inactive'][ $dependency ] = $dependency_name;
       
  1215 				$unmet_dependency_names[]                      = $dependency_name;
       
  1216 			}
       
  1217 		}
       
  1218 
       
  1219 		$error_message = sprintf(
       
  1220 			/* translators: 1: Plugin name, 2: Number of plugins, 3: A comma-separated list of plugin names. */
       
  1221 			_n(
       
  1222 				'<strong>Error:</strong> %1$s requires %2$d plugin to be installed and activated: %3$s.',
       
  1223 				'<strong>Error:</strong> %1$s requires %2$d plugins to be installed and activated: %3$s.',
       
  1224 				count( $unmet_dependency_names )
       
  1225 			),
       
  1226 			$plugin_headers['Name'],
       
  1227 			count( $unmet_dependency_names ),
       
  1228 			implode( wp_get_list_item_separator(), $unmet_dependency_names )
       
  1229 		);
       
  1230 
       
  1231 		if ( is_multisite() ) {
       
  1232 			if ( current_user_can( 'manage_network_plugins' ) ) {
       
  1233 				$error_message .= ' ' . sprintf(
       
  1234 					/* translators: %s: Link to the plugins page. */
       
  1235 					__( '<a href="%s">Manage plugins</a>.' ),
       
  1236 					esc_url( network_admin_url( 'plugins.php' ) )
       
  1237 				);
       
  1238 			} else {
       
  1239 				$error_message .= ' ' . __( 'Please contact your network administrator.' );
       
  1240 			}
       
  1241 		} else {
       
  1242 			$error_message .= ' ' . sprintf(
       
  1243 				/* translators: %s: Link to the plugins page. */
       
  1244 				__( '<a href="%s">Manage plugins</a>.' ),
       
  1245 				esc_url( admin_url( 'plugins.php' ) )
       
  1246 			);
       
  1247 		}
       
  1248 
       
  1249 		return new WP_Error(
       
  1250 			'plugin_missing_dependencies',
       
  1251 			"<p>{$error_message}</p>",
       
  1252 			$unmet_dependencies
  1180 		);
  1253 		);
  1181 	}
  1254 	}
  1182 
  1255 
  1183 	return true;
  1256 	return true;
  1184 }
  1257 }
  1452 		$position = max( $position, 0 );
  1525 		$position = max( $position, 0 );
  1453 		if ( 0 === $position ) {
  1526 		if ( 0 === $position ) {
  1454 			// For negative or `0` positions, prepend the submenu.
  1527 			// For negative or `0` positions, prepend the submenu.
  1455 			array_unshift( $submenu[ $parent_slug ], $new_sub_menu );
  1528 			array_unshift( $submenu[ $parent_slug ], $new_sub_menu );
  1456 		} else {
  1529 		} else {
       
  1530 			$position = absint( $position );
  1457 			// Grab all of the items before the insertion point.
  1531 			// Grab all of the items before the insertion point.
  1458 			$before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true );
  1532 			$before_items = array_slice( $submenu[ $parent_slug ], 0, $position, true );
  1459 			// Grab all of the items after the insertion point.
  1533 			// Grab all of the items after the insertion point.
  1460 			$after_items = array_slice( $submenu[ $parent_slug ], $position, null, true );
  1534 			$after_items = array_slice( $submenu[ $parent_slug ], $position, null, true );
  1461 			// Add the new item.
  1535 			// Add the new item.
  1932 
  2006 
  1933 			if ( ! empty( $typenow ) && "$pagenow?post_type=$typenow" === $submenu_array[2] ) {
  2007 			if ( ! empty( $typenow ) && "$pagenow?post_type=$typenow" === $submenu_array[2] ) {
  1934 				$parent_file = $parent_page;
  2008 				$parent_file = $parent_page;
  1935 				return $parent_page;
  2009 				return $parent_page;
  1936 			} elseif ( empty( $typenow ) && $pagenow === $submenu_array[2]
  2010 			} elseif ( empty( $typenow ) && $pagenow === $submenu_array[2]
  1937 				&& ( empty( $parent_file ) || false === strpos( $parent_file, '?' ) )
  2011 				&& ( empty( $parent_file ) || ! str_contains( $parent_file, '?' ) )
  1938 			) {
  2012 			) {
  1939 				$parent_file = $parent_page;
  2013 				$parent_file = $parent_page;
  1940 				return $parent_page;
  2014 				return $parent_page;
  1941 			} elseif ( isset( $plugin_page ) && $plugin_page === $submenu_array[2] ) {
  2015 			} elseif ( isset( $plugin_page ) && $plugin_page === $submenu_array[2] ) {
  1942 				$parent_file = $parent_page;
  2016 				$parent_file = $parent_page;
  1954 /**
  2028 /**
  1955  * Gets the title of the current admin page.
  2029  * Gets the title of the current admin page.
  1956  *
  2030  *
  1957  * @since 1.5.0
  2031  * @since 1.5.0
  1958  *
  2032  *
  1959  * @global string $title
  2033  * @global string $title       The title of the current screen.
  1960  * @global array  $menu
  2034  * @global array  $menu
  1961  * @global array  $submenu
  2035  * @global array  $submenu
  1962  * @global string $pagenow     The filename of the current screen.
  2036  * @global string $pagenow     The filename of the current screen.
  1963  * @global string $typenow     The post type of the current screen.
  2037  * @global string $typenow     The post type of the current screen.
  1964  * @global string $plugin_page
  2038  * @global string $plugin_page
  2379  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
  2453  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
  2380  * Conditional Tags} article in the Theme Developer Handbook.
  2454  * Conditional Tags} article in the Theme Developer Handbook.
  2381  *
  2455  *
  2382  * @since 5.2.0
  2456  * @since 5.2.0
  2383  *
  2457  *
       
  2458  * @global WP_Paused_Extensions_Storage $_paused_plugins
       
  2459  *
  2384  * @param string $plugin Path to the plugin file relative to the plugins directory.
  2460  * @param string $plugin Path to the plugin file relative to the plugins directory.
  2385  * @return bool True, if in the list of paused plugins. False, if not in the list.
  2461  * @return bool True, if in the list of paused plugins. False, if not in the list.
  2386  */
  2462  */
  2387 function is_plugin_paused( $plugin ) {
  2463 function is_plugin_paused( $plugin ) {
  2388 	if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
  2464 	if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
  2401 /**
  2477 /**
  2402  * Gets the error that was recorded for a paused plugin.
  2478  * Gets the error that was recorded for a paused plugin.
  2403  *
  2479  *
  2404  * @since 5.2.0
  2480  * @since 5.2.0
  2405  *
  2481  *
       
  2482  * @global WP_Paused_Extensions_Storage $_paused_plugins
       
  2483  *
  2406  * @param string $plugin Path to the plugin file relative to the plugins directory.
  2484  * @param string $plugin Path to the plugin file relative to the plugins directory.
  2407  * @return array|false Array of error information as returned by `error_get_last()`,
  2485  * @return array|false Array of error information as returned by `error_get_last()`,
  2408  *                     or false if none was recorded.
  2486  *                     or false if none was recorded.
  2409  */
  2487  */
  2410 function wp_get_plugin_error( $plugin ) {
  2488 function wp_get_plugin_error( $plugin ) {
  2433  *
  2511  *
  2434  * @since 5.2.0
  2512  * @since 5.2.0
  2435  *
  2513  *
  2436  * @param string $plugin   Single plugin to resume.
  2514  * @param string $plugin   Single plugin to resume.
  2437  * @param string $redirect Optional. URL to redirect to. Default empty string.
  2515  * @param string $redirect Optional. URL to redirect to. Default empty string.
  2438  * @return bool|WP_Error True on success, false if `$plugin` was not paused,
  2516  * @return true|WP_Error True on success, false if `$plugin` was not paused,
  2439  *                       `WP_Error` on failure.
  2517  *                       `WP_Error` on failure.
  2440  */
  2518  */
  2441 function resume_plugin( $plugin, $redirect = '' ) {
  2519 function resume_plugin( $plugin, $redirect = '' ) {
  2442 	/*
  2520 	/*
  2443 	 * We'll override this later if the plugin could be resumed without
  2521 	 * We'll override this later if the plugin could be resumed without
  2475 /**
  2553 /**
  2476  * Renders an admin notice in case some plugins have been paused due to errors.
  2554  * Renders an admin notice in case some plugins have been paused due to errors.
  2477  *
  2555  *
  2478  * @since 5.2.0
  2556  * @since 5.2.0
  2479  *
  2557  *
  2480  * @global string $pagenow The filename of the current screen.
  2558  * @global string                       $pagenow         The filename of the current screen.
       
  2559  * @global WP_Paused_Extensions_Storage $_paused_plugins
  2481  */
  2560  */
  2482 function paused_plugins_notice() {
  2561 function paused_plugins_notice() {
  2483 	if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
  2562 	if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
  2484 		return;
  2563 		return;
  2485 	}
  2564 	}
  2490 
  2569 
  2491 	if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
  2570 	if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
  2492 		return;
  2571 		return;
  2493 	}
  2572 	}
  2494 
  2573 
  2495 	printf(
  2574 	$message = sprintf(
  2496 		'<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>',
  2575 		'<strong>%s</strong><br>%s</p><p><a href="%s">%s</a>',
  2497 		__( 'One or more plugins failed to load properly.' ),
  2576 		__( 'One or more plugins failed to load properly.' ),
  2498 		__( 'You can find more details and make changes on the Plugins screen.' ),
  2577 		__( 'You can find more details and make changes on the Plugins screen.' ),
  2499 		esc_url( admin_url( 'plugins.php?plugin_status=paused' ) ),
  2578 		esc_url( admin_url( 'plugins.php?plugin_status=paused' ) ),
  2500 		__( 'Go to the Plugins screen' )
  2579 		__( 'Go to the Plugins screen' )
       
  2580 	);
       
  2581 	wp_admin_notice(
       
  2582 		$message,
       
  2583 		array( 'type' => 'error' )
  2501 	);
  2584 	);
  2502 }
  2585 }
  2503 
  2586 
  2504 /**
  2587 /**
  2505  * Renders an admin notice when a plugin was deactivated during an update.
  2588  * Renders an admin notice when a plugin was deactivated during an update.
  2564 				$GLOBALS['wp_version'],
  2647 				$GLOBALS['wp_version'],
  2565 				$plugin['version_compatible']
  2648 				$plugin['version_compatible']
  2566 			);
  2649 			);
  2567 		}
  2650 		}
  2568 
  2651 
  2569 		printf(
  2652 		$message = sprintf(
  2570 			'<div class="notice notice-warning"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>',
  2653 			'<strong>%s</strong><br>%s</p><p><a href="%s">%s</a>',
  2571 			sprintf(
  2654 			sprintf(
  2572 				/* translators: %s: Name of deactivated plugin. */
  2655 				/* translators: %s: Name of deactivated plugin. */
  2573 				__( '%s plugin deactivated during WordPress upgrade.' ),
  2656 				__( '%s plugin deactivated during WordPress upgrade.' ),
  2574 				$plugin['plugin_name']
  2657 				$plugin['plugin_name']
  2575 			),
  2658 			),
  2576 			$explanation,
  2659 			$explanation,
  2577 			esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ),
  2660 			esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ),
  2578 			__( 'Go to the Plugins screen' )
  2661 			__( 'Go to the Plugins screen' )
  2579 		);
  2662 		);
       
  2663 		wp_admin_notice( $message, array( 'type' => 'warning' ) );
  2580 	}
  2664 	}
  2581 
  2665 
  2582 	// Empty the options.
  2666 	// Empty the options.
  2583 	update_option( 'wp_force_deactivated_plugins', array() );
  2667 	update_option( 'wp_force_deactivated_plugins', array() );
  2584 	if ( is_multisite() ) {
  2668 	if ( is_multisite() ) {