Upgrade/Install: Introduce Plugin Dependencies.

Introduces a new "Requires Plugins" plugin header so that plugin developers can list the slugs of the plugins theirs depends on.

This will inform users of the requirements, and provide links to the WordPress.org Plugins Repository that they can click to install and activate the dependencies first.

Plugins whose requirements are not met cannot be installed or activated, and they will be deactivated automatically if their requirements become unmet.
Plugins that others rely on cannot be deactivated or deleted until their dependent plugins are deactivated or deleted.

In memory of Alex Mills and Alex King.
WordPress Remembers.

Props ahoereth, afragen, alanfuller, alexkingorg, amykamala, anonymized_10690803, apeatling, ashfame, atimmer, audrasjb, aristath, azaozz, batmoo, beaulebens, blobaugh, bobbingwide, boonebgorges, brianhenryie, chanthaboune, chrisdavidmiles, coolmann, costdev, courane01, danielbachhuber, davidperez, dd32, Denis-de-Bernardy, dingo_d, DJPaul, dougal, DrewAPicture, ethitter, filosofo, georgestephanis, giuseppemazzapica-1, goldenapples, griffinjt, hellofromTonya, husobj, ideag, jarednova, jbobich, jbrinley, jltallon, joedolson, johnciacia, johnjamesjacoby, joppuyo, jsmoriss, karmatosed, kebbet, knutsp, kraftbj, kraftner, kurtpayne, lkraav, logikal16, luisherranz, man4toman, markjaquith, matt, mbijon, megphillips91, mikeschinkel, mordauk, morehawes, mrwweb, mte90, mukesh27, mzaweb, nacin, norcross, nvwd, nwjames, obliviousharmony, ocean90, oglekler, paaljoachim, pauldewouters, pbaylies, pbiron, peterwilsoncc, Philipp15b, poena, pogidude, retlehs, rmccue, ryan, sabreuse, sc0ttkclark, scribu, sereedmedia, SergeyBiryukov, ShaneF, shidouhikari, soean, spacedmonkey, stephenh1988, swissspidy, taylorde, tazotodua, threadi, TimothyBlynJacobs, TJNowell, tollmanz, toscho, tropicalista, Viper007Bond, westi, whiteshadow, williamsba1, wpsmith, ZaneMatthew.
Fixes #22316.
Built from https://develop.svn.wordpress.org/trunk@57545


git-svn-id: http://core.svn.wordpress.org/trunk@57046 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
costdev
2024-02-06 23:46:14 +00:00
parent 15007f8b47
commit c60fc98b33
24 changed files with 2607 additions and 370 deletions

View File

@@ -45,6 +45,7 @@
* @since 1.5.0
* @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers.
* @since 5.8.0 Added support for `Update URI` header.
* @since 6.5.0 Added support for `Requires Plugins` header.
*
* @param string $plugin_file Absolute path to the main plugin file.
* @param bool $markup Optional. If the returned data should have HTML markup applied.
@@ -53,39 +54,41 @@
* @return array {
* Plugin data. Values will be empty if not supplied by the plugin.
*
* @type string $Name Name of the plugin. Should be unique.
* @type string $PluginURI Plugin URI.
* @type string $Version Plugin version.
* @type string $Description Plugin description.
* @type string $Author Plugin author's name.
* @type string $AuthorURI Plugin author's website address (if set).
* @type string $TextDomain Plugin textdomain.
* @type string $DomainPath Plugin's relative directory path to .mo files.
* @type bool $Network Whether the plugin can only be activated network-wide.
* @type string $RequiresWP Minimum required version of WordPress.
* @type string $RequiresPHP Minimum required version of PHP.
* @type string $UpdateURI ID of the plugin for update purposes, should be a URI.
* @type string $Title Title of the plugin and link to the plugin's site (if set).
* @type string $AuthorName Plugin author's name.
* @type string $Name Name of the plugin. Should be unique.
* @type string $PluginURI Plugin URI.
* @type string $Version Plugin version.
* @type string $Description Plugin description.
* @type string $Author Plugin author's name.
* @type string $AuthorURI Plugin author's website address (if set).
* @type string $TextDomain Plugin textdomain.
* @type string $DomainPath Plugin's relative directory path to .mo files.
* @type bool $Network Whether the plugin can only be activated network-wide.
* @type string $RequiresWP Minimum required version of WordPress.
* @type string $RequiresPHP Minimum required version of PHP.
* @type string $UpdateURI ID of the plugin for update purposes, should be a URI.
* @type string $RequiresPlugins Comma separated list of dot org plugin slugs.
* @type string $Title Title of the plugin and link to the plugin's site (if set).
* @type string $AuthorName Plugin author's name.
* }
*/
function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
$default_headers = array(
'Name' => 'Plugin Name',
'PluginURI' => 'Plugin URI',
'Version' => 'Version',
'Description' => 'Description',
'Author' => 'Author',
'AuthorURI' => 'Author URI',
'TextDomain' => 'Text Domain',
'DomainPath' => 'Domain Path',
'Network' => 'Network',
'RequiresWP' => 'Requires at least',
'RequiresPHP' => 'Requires PHP',
'UpdateURI' => 'Update URI',
'Name' => 'Plugin Name',
'PluginURI' => 'Plugin URI',
'Version' => 'Version',
'Description' => 'Description',
'Author' => 'Author',
'AuthorURI' => 'Author URI',
'TextDomain' => 'Text Domain',
'DomainPath' => 'Domain Path',
'Network' => 'Network',
'RequiresWP' => 'Requires at least',
'RequiresPHP' => 'Requires PHP',
'UpdateURI' => 'Update URI',
'RequiresPlugins' => 'Requires Plugins',
// Site Wide Only is deprecated in favor of Network.
'_sitewide' => 'Site Wide Only',
'_sitewide' => 'Site Wide Only',
);
$plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );
@@ -330,6 +333,7 @@ function get_plugins( $plugin_folder = '' ) {
return $wp_plugins;
}
$new_plugin_data = array();
foreach ( $plugin_files as $plugin_file ) {
if ( ! is_readable( "$plugin_root/$plugin_file" ) ) {
continue;
@@ -342,6 +346,13 @@ function get_plugins( $plugin_folder = '' ) {
continue;
}
$new_plugin_file = str_replace(
trailingslashit( WP_PLUGIN_DIR ),
'',
"$plugin_root/$plugin_file"
);
$new_plugin_data[ $new_plugin_file ] = $plugin_data;
$wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data;
}
@@ -349,6 +360,7 @@ function get_plugins( $plugin_folder = '' ) {
$cache_plugins[ $plugin_folder ] = $wp_plugins;
wp_cache_set( 'plugins', $cache_plugins, 'plugins' );
update_option( 'plugin_data', $new_plugin_data );
return $wp_plugins;
}
@@ -951,6 +963,7 @@ function delete_plugins( $plugins, $deprecated = '' ) {
$plugins_dir = trailingslashit( $plugins_dir );
$plugin_translations = wp_get_installed_translations( 'plugins' );
$all_plugin_data = get_option( 'plugin_data', array() );
$errors = array();
@@ -995,6 +1008,7 @@ function delete_plugins( $plugins, $deprecated = '' ) {
$errors[] = $plugin_file;
continue;
}
unset( $all_plugin_data[ $plugin_file ] );
$plugin_slug = dirname( $plugin_file );
@@ -1043,6 +1057,7 @@ function delete_plugins( $plugins, $deprecated = '' ) {
return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) );
}
update_option( 'plugin_data', $all_plugin_data );
return true;
}
@@ -1114,13 +1129,14 @@ function validate_plugin( $plugin ) {
/**
* Validates the plugin requirements for WordPress version and PHP version.
*
* Uses the information from `Requires at least` and `Requires PHP` headers
* Uses the information from `Requires at least`, `Requires PHP` and `Requires Plugins` headers
* defined in the plugin's main PHP file.
*
* @since 5.2.0
* @since 5.3.0 Added support for reading the headers from the plugin's
* main PHP file, with `readme.txt` as a fallback.
* @since 5.8.0 Removed support for using `readme.txt` as a fallback.
* @since 6.5.0 Added support for the 'Requires Plugins' header.
*
* @param string $plugin Path to the plugin file relative to the plugins directory.
* @return true|WP_Error True if requirements are met, WP_Error on failure.
@@ -1129,8 +1145,9 @@ function validate_plugin_requirements( $plugin ) {
$plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$requirements = array(
'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '',
);
$compatible_wp = is_wp_version_compatible( $requirements['requires'] );
@@ -1185,6 +1202,31 @@ function validate_plugin_requirements( $plugin ) {
);
}
if ( WP_Plugin_Dependencies::has_unmet_dependencies( $plugin ) ) {
$dependencies = WP_Plugin_Dependencies::get_dependencies( $plugin );
$unmet_dependencies = array();
foreach ( $dependencies as $dependency ) {
$dependency_file = WP_Plugin_Dependencies::get_dependency_filepath( $dependency );
if ( false === $dependency_file ) {
$unmet_dependencies['not_installed'][] = $dependency;
} elseif ( is_plugin_inactive( $dependency_file ) ) {
$unmet_dependencies['inactive'][] = $dependency;
}
}
return new WP_Error(
'plugin_missing_dependencies',
'<p>' . sprintf(
/* translators: %s: Plugin name. */
_x( '<strong>Error:</strong> %s requires plugins that are not installed or activated.', 'plugin' ),
$plugin_headers['Name']
) . '</p>',
$unmet_dependencies
);
}
return true;
}