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.

git-svn-id: https://develop.svn.wordpress.org/trunk@57545 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Colin Stewart
2024-02-06 23:44:09 +00:00
parent 3eb7bd1b29
commit 2b08a77f5c
30 changed files with 4168 additions and 352 deletions
+4
View File
@@ -117,6 +117,7 @@ $core_actions_post = array(
'parse-media-shortcode',
'destroy-sessions',
'install-plugin',
'activate-plugin',
'update-plugin',
'crop-image',
'generate-password',
@@ -169,6 +170,9 @@ add_action( 'wp_ajax_nopriv_generate-password', 'wp_ajax_nopriv_generate_passwor
add_action( 'wp_ajax_nopriv_heartbeat', 'wp_ajax_nopriv_heartbeat', 1 );
// Register Plugin Dependencies Ajax calls.
add_action( 'wp_ajax_check_plugin_dependencies', array( 'WP_Plugin_Dependencies', 'check_plugin_dependencies_during_ajax' ) );
$action = $_REQUEST['action'];
if ( is_user_logged_in() ) {
+47 -7
View File
@@ -1500,6 +1500,22 @@ div.error {
background-color: #f0f6fc;
}
#plugin-information-footer .update-now:not(.button-disabled):before {
color: #d63638;
content: "\f463";
display: inline-block;
font: normal 20px/1 dashicons;
margin: -3px 5px 0 -2px;
speak: never;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
vertical-align: middle;
}
#plugin-information-footer .notice {
margin-top: -5px;
}
.update-message p:before,
.updating-message p:before,
.updated-message p:before,
@@ -1507,7 +1523,9 @@ div.error {
.button.updating-message:before,
.button.updated-message:before,
.button.installed:before,
.button.installing:before {
.button.installing:before,
.button.activating-message:before,
.button.activated-message:before {
display: inline-block;
font: normal 20px/1 'dashicons';
-webkit-font-smoothing: antialiased;
@@ -1544,7 +1562,8 @@ div.error {
.updating-message p:before,
.import-php .updating-message:before,
.button.updating-message:before,
.button.installing:before {
.button.installing:before,
.button.activating-message:before {
color: #d63638;
content: "\f463";
}
@@ -1554,6 +1573,7 @@ div.error {
.import-php .updating-message:before,
.button.updating-message:before,
.button.installing:before,
.button.activating-message:before,
.plugins .column-auto-updates .dashicons-update.spin,
.theme-overlay .theme-autoupdate .dashicons-update.spin {
animation: rotation 2s infinite linear;
@@ -1564,6 +1584,7 @@ div.error {
.import-php .updating-message:before,
.button.updating-message:before,
.button.installing:before,
.button.activating-message:before,
.plugins .column-auto-updates .dashicons-update.spin,
.theme-overlay .theme-autoupdate .dashicons-update.spin {
animation: none;
@@ -1577,7 +1598,8 @@ div.error {
/* Updated icon (check mark). */
.updated-message p:before,
.installed p:before,
.button.updated-message:before {
.button.updated-message:before,
.button.activated-message:before {
color: #68de7c;
content: "\f147";
}
@@ -1662,19 +1684,37 @@ p.auto-update-status {
.button.updating-message:before,
.button.updated-message:before,
.button.installed:before,
.button.installing:before {
.button.installing:before,
.button.activated-message:before,
.button.activating-message:before {
margin: 3px 5px 0 -2px;
}
.button-primary.updating-message:before {
#plugin-information-footer .button.installed:before,
#plugin-information-footer .button.installing:before,
#plugin-information-footer .button.updating-message:before,
#plugin-information-footer .button.updated-message:before,
#plugin-information-footer .button.activated-message:before,
#plugin-information-footer .button.activating-message:before {
margin: 9px 5px 0 -2px;
}
#plugin-information-footer .button.update-now.updating-message:before {
margin: -3px 5px 0 -2px;
}
.button-primary.updating-message:before,
.button-primary.activating-message:before {
color: #fff;
}
.button-primary.updated-message:before {
.button-primary.updated-message:before,
.button-primary.activated-message:before {
color: #9ec2e6;
}
.button.updated-message {
.button.updated-message,
.button.activated-message {
transition-property: border, background, color;
transition-duration: .05s;
transition-timing-function: ease-in-out;
+90 -5
View File
@@ -585,8 +585,7 @@ th.sorted.desc:hover .sorting-indicator.asc:before {
z-index: 1;
}
.check-column input:where(:not(:disabled)):hover,
.check-column:hover input:where(:not(:disabled)) {
.check-column .label-covers-full-cell:hover + input:not(:disabled) {
box-shadow: 0 0 0 1px #2271b1;
}
@@ -1548,10 +1547,96 @@ div.action-links,
line-height: 1.3;
}
.plugin-card .name,
.plugin-card .desc {
margin-left: 148px; /* icon + margin */
margin-right: 128px; /* action links + margin */
margin-inline: 0;
}
.plugin-card .name, .plugin-card .desc > p {
margin-left: 148px;
}
@media (min-width: 1101px) {
.plugin-card .name, .plugin-card .desc > p {
margin-right: 128px;
}
}
@media (min-width: 481px) and (max-width: 781px) {
.plugin-card .name, .plugin-card .desc > p {
margin-right: 128px;
}
}
.plugin-card .column-description {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.plugin-card .column-description > p {
margin-top: 0;
}
.plugin-card .column-description .authors {
order: 1;
}
.plugin-card .column-description .plugin-dependencies {
order: 2;
}
.plugin-card .column-description p:empty {
display: none;
}
.plugin-card .plugin-dependencies {
background-color: #e5f5fa;
border-left: 3px solid #72aee6;
margin-bottom: .5em;
padding: 15px;
}
.plugin-card .plugin-dependencies-explainer-text {
margin-block: 0;
}
.plugin-card .plugin-dependency {
align-items: center;
display: flex;
flex-wrap: wrap;
margin-top: .5em;
column-gap: 1%;
row-gap: .5em;
}
.plugin-card .plugin-dependency:nth-child(2),
.plugin-card .plugin-dependency:last-child {
margin-top: 1em;
}
.plugin-card .plugin-dependency-name {
flex-basis: 74%;
}
.plugin-card .plugin-dependency .more-details-link {
margin-left: auto;
}
.rtl .plugin-card .plugin-dependency .more-details-link {
margin-right: auto;
}
@media (max-width: 939px) {
.plugin-card .plugin-dependency-name {
flex-basis: 69%;
}
.plugin-card .plugin-dependency .more-details-link {
}
}
.plugins #the-list .required-by,
.plugins #the-list .requires {
margin-top: 1em;
}
.plugin-card .action-links {
+50
View File
@@ -4578,6 +4578,56 @@ function wp_ajax_install_plugin() {
wp_send_json_success( $status );
}
/**
* Handles activating a plugin via AJAX.
*
* @since 6.5.0
*/
function wp_ajax_activate_plugin() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'pluginName' => '',
'plugin' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => __( 'No plugin specified.' ),
)
);
}
$status = array(
'activate' => 'plugin',
'slug' => wp_unslash( $_POST['slug'] ),
'pluginName' => wp_unslash( $_POST['name'] ),
'plugin' => wp_unslash( $_POST['plugin'] ),
);
if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' );
wp_send_json_error( $status );
}
if ( is_plugin_active( $status['plugin'] ) ) {
$status['errorMessage'] = sprintf(
/* translators: %s: Plugin name. */
__( '%s is already active.' ),
$status['pluginName']
);
}
$activated = activate_plugin( $status['plugin'] );
if ( is_wp_error( $activated ) ) {
$status['errorMessage'] = $activated->get_error_message();
wp_send_json_error( $status );
}
wp_send_json_success( $status );
}
/**
* Handles updating a plugin via AJAX.
*
@@ -155,6 +155,12 @@ class Plugin_Upgrader extends WP_Upgrader {
// Force refresh of plugin update information.
wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
$all_plugin_data = get_option( 'plugin_data', array() );
$plugin_file = $this->new_plugin_data['file'];
unset( $this->new_plugin_data['file'] );
$all_plugin_data[ $plugin_file ] = $this->new_plugin_data;
update_option( 'plugin_data', $all_plugin_data );
if ( $parsed_args['overwrite_package'] ) {
/**
* Fires when the upgrader has successfully overwritten a currently installed
@@ -482,7 +488,16 @@ class Plugin_Upgrader extends WP_Upgrader {
foreach ( $files as $file ) {
$info = get_plugin_data( $file, false, false );
if ( ! empty( $info['Name'] ) ) {
$this->new_plugin_data = $info;
$basename = basename( $file );
$dirname = basename( dirname( $file ) );
if ( '.' === $dirname ) {
$plugin_file = $basename;
} else {
$plugin_file = "$dirname/$basename";
}
$this->new_plugin_data = ( $info );
$this->new_plugin_data['file'] = $plugin_file;
break;
}
}
@@ -525,6 +525,8 @@ class WP_Plugin_Install_List_Table extends WP_List_Table {
// Remove any HTML from the description.
$description = strip_tags( $plugin['short_description'] );
$description .= $this->get_dependencies_notice( $plugin );
/**
* Filters the plugin card description on the Add Plugins screen.
*
@@ -555,102 +557,7 @@ class WP_Plugin_Install_List_Table extends WP_List_Table {
$action_links = array();
if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
$status = install_plugin_install_status( $plugin );
switch ( $status['status'] ) {
case 'install':
if ( $status['url'] ) {
if ( $compatible_php && $compatible_wp ) {
$action_links[] = sprintf(
'<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
esc_attr( $plugin['slug'] ),
esc_url( $status['url'] ),
/* translators: %s: Plugin name and version. */
esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
esc_attr( $name ),
__( 'Install Now' )
);
} else {
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Cannot Install', 'plugin' )
);
}
}
break;
case 'update_available':
if ( $status['url'] ) {
if ( $compatible_php && $compatible_wp ) {
$action_links[] = sprintf(
'<a class="update-now button aria-button-if-js" data-plugin="%s" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
esc_attr( $status['file'] ),
esc_attr( $plugin['slug'] ),
esc_url( $status['url'] ),
/* translators: %s: Plugin name and version. */
esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ),
esc_attr( $name ),
__( 'Update Now' )
);
} else {
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Cannot Update', 'plugin' )
);
}
}
break;
case 'latest_installed':
case 'newer_installed':
if ( is_plugin_active( $status['file'] ) ) {
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Active', 'plugin' )
);
} elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) {
if ( $compatible_php && $compatible_wp ) {
$button_text = __( 'Activate' );
/* translators: %s: Plugin name. */
$button_label = _x( 'Activate %s', 'plugin' );
$activate_url = add_query_arg(
array(
'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
'action' => 'activate',
'plugin' => $status['file'],
),
network_admin_url( 'plugins.php' )
);
if ( is_network_admin() ) {
$button_text = __( 'Network Activate' );
/* translators: %s: Plugin name. */
$button_label = _x( 'Network Activate %s', 'plugin' );
$activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
}
$action_links[] = sprintf(
'<a href="%1$s" class="button activate-now" aria-label="%2$s">%3$s</a>',
esc_url( $activate_url ),
esc_attr( sprintf( $button_label, $plugin['name'] ) ),
$button_text
);
} else {
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Cannot Activate', 'plugin' )
);
}
} else {
$action_links[] = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Installed', 'plugin' )
);
}
break;
}
}
$action_links[] = wp_get_plugin_action_button( $name, $plugin, $compatible_php, $compatible_wp );
$details_link = self_admin_url(
'plugin-install.php?tab=plugin-information&amp;plugin=' . $plugin['slug'] .
@@ -828,4 +735,90 @@ class WP_Plugin_Install_List_Table extends WP_List_Table {
echo '</div></div>';
}
}
/**
* Returns a notice containing a list of dependencies required by the plugin.
*
* @since 6.5.0
*
* @param array $plugin_data An array of plugin data. See {@see plugins_api()}
* for the list of possible values.
* @return string A notice containing a list of dependencies required by the plugin,
* or an empty string if none is required.
*/
protected function get_dependencies_notice( $plugin_data ) {
if ( empty( $plugin_data['requires_plugins'] ) ) {
return '';
}
$no_name_markup = '<div class="plugin-dependency"><span class="plugin-dependency-name">%s</span></div>';
$has_name_markup = '<div class="plugin-dependency"><span class="plugin-dependency-name">%s</span> %s</div>';
$dependencies_list = '';
foreach ( $plugin_data['requires_plugins'] as $dependency ) {
$dependency_data = WP_Plugin_Dependencies::get_dependency_data( $dependency );
if (
false !== $dependency_data &&
! empty( $dependency_data['name'] ) &&
! empty( $dependency_data['slug'] ) &&
! empty( $dependency_data['version'] )
) {
$more_details_link = $this->get_more_details_link( $dependency_data['name'], $dependency_data['slug'] );
$dependencies_list .= sprintf( $has_name_markup, esc_html( $dependency_data['name'] ), $more_details_link );
continue;
}
$result = plugins_api( 'plugin_information', array( 'slug' => $dependency ) );
if ( ! empty( $result->name ) ) {
$more_details_link = $this->get_more_details_link( $result->name, $result->slug );
$dependencies_list .= sprintf( $has_name_markup, esc_html( $result->name ), $more_details_link );
continue;
}
$dependencies_list .= sprintf( $no_name_markup, esc_html( $dependency ) );
}
$dependencies_notice = sprintf(
'<div class="plugin-dependencies"><p class="plugin-dependencies-explainer-text">%s</p> %s</div>',
'<strong>' . __( 'Additional plugins are required' ) . '</strong>',
$dependencies_list
);
return $dependencies_notice;
}
/**
* Creates a 'More details' link for the plugin.
*
* @since 6.5.0
*
* @param string $name The plugin's name.
* @param string $slug The plugin's slug.
* @return string The 'More details' link for the plugin.
*/
protected function get_more_details_link( $name, $slug ) {
$url = add_query_arg(
array(
'tab' => 'plugin-information',
'plugin' => $slug,
'TB_iframe' => 'true',
'width' => '600',
'height' => '550',
),
network_admin_url( 'plugin-install.php' )
);
$more_details_link = sprintf(
'<a href="%1$s" class="more-details-link thickbox open-plugin-details-modal" aria-label="%2$s" data-title="%3$s">%4$s</a>',
esc_url( $url ),
/* translators: %s: Plugin name. */
sprintf( __( 'More information about %s' ), esc_html( $name ) ),
esc_attr( $name ),
__( 'More Details' )
);
return $more_details_link;
}
}
@@ -754,6 +754,11 @@ class WP_Plugins_List_Table extends WP_List_Table {
$compatible_php = is_php_version_compatible( $requires_php );
$compatible_wp = is_wp_version_compatible( $requires_wp );
$has_dependents = WP_Plugin_Dependencies::has_dependents( $plugin_file );
$has_active_dependents = WP_Plugin_Dependencies::has_active_dependents( $plugin_file );
$has_unmet_dependencies = WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file );
$has_circular_dependency = WP_Plugin_Dependencies::has_circular_dependency( $plugin_file );
if ( 'mustuse' === $context ) {
$is_active = true;
} elseif ( 'dropins' === $context ) {
@@ -796,26 +801,53 @@ class WP_Plugins_List_Table extends WP_List_Table {
if ( $screen->in_admin( 'network' ) ) {
if ( $is_active ) {
if ( current_user_can( 'manage_network_plugins' ) ) {
$actions['deactivate'] = sprintf(
'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Network Deactivate' )
);
if ( $has_active_dependents ) {
$actions['deactivate'] = __( 'Deactivate' ) .
'<span class="screen-reader-text">' .
__( 'You cannot deactivate this plugin as other plugins require it.' ) .
'</span>';
} else {
$deactivate_url = 'plugins.php?action=deactivate' .
'&amp;plugin=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['deactivate'] = sprintf(
'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Network Deactivate' )
);
}
}
} else {
if ( current_user_can( 'manage_network_plugins' ) ) {
if ( $compatible_php && $compatible_wp ) {
$actions['activate'] = sprintf(
'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Network Activate' )
);
if ( $has_unmet_dependencies ) {
$actions['activate'] = __( 'Network Activate' ) .
'<span class="screen-reader-text">' .
__( 'You cannot activate this plugin as it has unmet requirements.' ) .
'</span>';
} else {
$activate_url = 'plugins.php?action=activate' .
'&amp;plugin=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['activate'] = sprintf(
'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Network Activate' )
);
}
} else {
$actions['activate'] = sprintf(
'<span>%s</span>',
@@ -825,14 +857,27 @@ class WP_Plugins_List_Table extends WP_List_Table {
}
if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
$actions['delete'] = sprintf(
'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Delete' )
);
if ( $has_dependents && ! $has_circular_dependency ) {
$actions['delete'] = __( 'Delete' ) .
'<span class="screen-reader-text">' .
__( 'You cannot delete this plugin as other plugins require it.' ) .
'</span>';
} else {
$delete_url = 'plugins.php?action=delete-selected' .
'&amp;checked[]=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['delete'] = sprintf(
'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
wp_nonce_url( $delete_url, 'bulk-plugins' ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Delete' )
);
}
}
}
} else {
@@ -846,20 +891,39 @@ class WP_Plugins_List_Table extends WP_List_Table {
);
} elseif ( $is_active ) {
if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
$actions['deactivate'] = sprintf(
'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Deactivate' )
);
if ( $has_active_dependents ) {
$actions['deactivate'] = __( 'Deactivate' ) .
'<span class="screen-reader-text">' .
__( 'You cannot deactivate this plugin as other plugins depend on it.' ) .
'</span>';
} else {
$deactivate_url = 'plugins.php?action=deactivate' .
'&amp;plugin=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['deactivate'] = sprintf(
'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Deactivate' )
);
}
}
if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
$resume_url = 'plugins.php?action=resume' .
'&amp;plugin=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['resume'] = sprintf(
'<a href="%s" id="resume-%s" class="resume-link" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=resume&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'resume-plugin_' . $plugin_file ),
wp_nonce_url( $resume_url, 'resume-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ),
@@ -869,14 +933,27 @@ class WP_Plugins_List_Table extends WP_List_Table {
} else {
if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
if ( $compatible_php && $compatible_wp ) {
$actions['activate'] = sprintf(
'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Activate' )
);
if ( $has_unmet_dependencies ) {
$actions['activate'] = __( 'Activate' ) .
'<span class="screen-reader-text">' .
__( 'You cannot activate this plugin as it has unmet requirements.' ) .
'</span>';
} else {
$activate_url = 'plugins.php?action=activate' .
'&amp;plugin=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['activate'] = sprintf(
'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Activate' )
);
}
} else {
$actions['activate'] = sprintf(
'<span>%s</span>',
@@ -886,14 +963,27 @@ class WP_Plugins_List_Table extends WP_List_Table {
}
if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
$actions['delete'] = sprintf(
'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Delete' )
);
if ( $has_dependents && ! $has_circular_dependency ) {
$actions['delete'] = __( 'Delete' ) .
'<span class="screen-reader-text">' .
__( 'You cannot delete this plugin as other plugins require it.' ) .
'</span>';
} else {
$delete_url = 'plugins.php?action=delete-selected' .
'&amp;checked[]=' . urlencode( $plugin_file ) .
'&amp;plugin_status=' . $context .
'&amp;paged=' . $page .
'&amp;s=' . $s;
$actions['delete'] = sprintf(
'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
wp_nonce_url( $delete_url, 'bulk-plugins' ),
esc_attr( $plugin_id_attr ),
/* translators: %s: Plugin name. */
esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
__( 'Delete' )
);
}
}
} // End if $is_active.
} // End if $screen->in_admin( 'network' ).
@@ -988,17 +1078,28 @@ class WP_Plugins_List_Table extends WP_List_Table {
$class = $is_active ? 'active' : 'inactive';
$checkbox_id = 'checkbox_' . md5( $plugin_file );
$disabled = '';
if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ), true ) || ! $compatible_php ) {
if ( $has_active_dependents || $has_unmet_dependencies ) {
$disabled = 'disabled';
}
if (
$restrict_network_active ||
$restrict_network_only ||
in_array( $status, array( 'mustuse', 'dropins' ), true ) ||
! $compatible_php
) {
$checkbox = '';
} else {
$checkbox = sprintf(
'<input type="checkbox" name="checked[]" value="%1$s" id="%2$s" />' .
'<label for="%2$s"><span class="screen-reader-text">%3$s</span></label>',
esc_attr( $plugin_file ),
'<label class="label-covers-full-cell" for="%1$s">' .
'<span class="screen-reader-text">%2$s</span></label>' .
'<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" ' . $disabled . '/>',
$checkbox_id,
/* translators: Hidden accessibility text. %s: Plugin name. */
sprintf( __( 'Select %s' ), $plugin_data['Name'] )
sprintf( __( 'Select %s' ), $plugin_data['Name'] ),
esc_attr( $plugin_file )
);
}
@@ -1007,8 +1108,11 @@ class WP_Plugins_List_Table extends WP_List_Table {
$plugin_name = $plugin_data['Name'];
}
if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] )
|| ! $compatible_php || ! $compatible_wp
if (
! empty( $totals['upgrade'] ) &&
! empty( $plugin_data['update'] ) ||
! $compatible_php ||
! $compatible_wp
) {
$class .= ' update';
}
@@ -1057,15 +1161,19 @@ class WP_Plugins_List_Table extends WP_List_Table {
<div class='$class second plugin-version-author-uri'>";
$plugin_meta = array();
if ( ! empty( $plugin_data['Version'] ) ) {
/* translators: %s: Plugin version number. */
$plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
}
if ( ! empty( $plugin_data['Author'] ) ) {
$author = $plugin_data['Author'];
if ( ! empty( $plugin_data['AuthorURI'] ) ) {
$author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
}
/* translators: %s: Plugin author name. */
$plugin_meta[] = sprintf( __( 'By %s' ), $author );
}
@@ -1149,6 +1257,24 @@ class WP_Plugins_List_Table extends WP_List_Table {
echo '</div>';
if ( $has_dependents ) {
$this->add_dependents_to_dependency_plugin_row( $plugin_file );
}
if ( WP_Plugin_Dependencies::has_dependencies( $plugin_file ) ) {
$this->add_dependencies_to_dependent_plugin_row( $plugin_file );
}
/**
* Fires after plugin row meta.
*
* @since 6.5.0
*
* @param string $plugin_file Refer to {@see 'plugin_row_meta'} filter.
* @param array $plugin_data Refer to {@see 'plugin_row_meta'} filter.
*/
do_action( 'after_plugin_row_meta', $plugin_file, $plugin_data );
if ( $paused ) {
$notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' );
@@ -1391,4 +1517,111 @@ class WP_Plugins_List_Table extends WP_List_Table {
protected function get_primary_column_name() {
return 'name';
}
/**
* Prints a list of other plugins that depend on the plugin.
*
* @since 6.5.0
*
* @param string $dependency The dependency's filepath, relative to the plugins directory.
*/
protected function add_dependents_to_dependency_plugin_row( $dependency ) {
$dependent_names = WP_Plugin_Dependencies::get_dependent_names( $dependency );
if ( empty( $dependent_names ) ) {
return;
}
$dependency_note = __( 'Note: this plugin cannot be deactivated or deleted until the plugins that require it are deactivated or deleted.' );
printf(
'<div class="required-by"><p><strong>%1$s</strong> %2$s</p><p>%3$s</p></div>',
__( 'Required by:' ),
esc_html( implode( ' | ', $dependent_names ) ),
$dependency_note
);
}
/**
* Prints a list of other plugins that the plugin depends on.
*
* @since 6.5.0
*
* @param string $dependent The dependent plugin's filepath, relative to the plugins directory.
*/
protected function add_dependencies_to_dependent_plugin_row( $dependent ) {
$dependency_names = WP_Plugin_Dependencies::get_dependency_names( $dependent );
if ( array() === $dependency_names ) {
return;
}
$links = array();
foreach ( $dependency_names as $slug => $name ) {
$links[] = $this->get_dependency_view_details_link( $name, $slug );
}
$dependency_note = __( 'Note: this plugin cannot be activated until the plugins that are required by it are activated.' );
printf(
'<div class="requires"><p><strong>%1$s</strong> %2$s</p><p>%3$s</p></div>',
__( 'Requires:' ),
implode( ' | ', $links ),
$dependency_note
);
}
/**
* Returns a 'View details' like link for a dependency.
*
* @since 6.5.0
*
* @param string $name The dependency's name.
* @param string $slug The dependency's slug.
* @return string A 'View details' link for the dependency.
*/
protected function get_dependency_view_details_link( $name, $slug ) {
$dependency_data = WP_Plugin_Dependencies::get_dependency_data( $slug );
if ( false === $dependency_data
|| $name === $slug
|| $name !== $dependency_data['name']
|| empty( $dependency_data['version'] )
) {
return $name;
}
return $this->get_view_details_link( $name, $slug );
}
/**
* Returns a 'View details' link for the plugin.
*
* @since 6.5.0
*
* @param string $name The plugin's name.
* @param string $slug The plugin's slug.
* @return string A 'View details' link for the plugin.
*/
protected function get_view_details_link( $name, $slug ) {
$url = add_query_arg(
array(
'tab' => 'plugin-information',
'plugin' => $slug,
'TB_iframe' => 'true',
'width' => '600',
'height' => '550',
),
network_admin_url( 'plugin-install.php' )
);
$name_attr = esc_attr( $name );
return sprintf(
"<a href='%s' class='thickbox open-plugin-details-modal' aria-label='%s' data-title='%s'>%s</a>",
esc_url( $url ),
/* translators: %s: Plugin name. */
sprintf( __( 'More information about %s' ), $name_attr ),
$name_attr,
esc_html( $name )
);
}
}
+166 -33
View File
@@ -884,43 +884,176 @@ function install_plugin_information() {
echo "</div>\n"; // #plugin-information-scrollable
echo "<div id='$tab-footer'>\n";
if ( ! empty( $api->download_link ) && ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) ) {
$status = install_plugin_install_status( $api );
switch ( $status['status'] ) {
case 'install':
if ( $status['url'] ) {
if ( $compatible_php && $compatible_wp ) {
echo '<a data-slug="' . esc_attr( $api->slug ) . '" id="plugin_install_from_iframe" class="button button-primary right" href="' . $status['url'] . '" target="_parent">' . __( 'Install Now' ) . '</a>';
} else {
printf(
'<button type="button" class="button button-primary button-disabled right" disabled="disabled">%s</button>',
_x( 'Cannot Install', 'plugin' )
);
}
}
break;
case 'update_available':
if ( $status['url'] ) {
if ( $compatible_php ) {
echo '<a data-slug="' . esc_attr( $api->slug ) . '" data-plugin="' . esc_attr( $status['file'] ) . '" id="plugin_update_from_iframe" class="button button-primary right" href="' . $status['url'] . '" target="_parent">' . __( 'Install Update Now' ) . '</a>';
} else {
printf(
'<button type="button" class="button button-primary button-disabled right" disabled="disabled">%s</button>',
_x( 'Cannot Update', 'plugin' )
);
}
}
break;
case 'newer_installed':
/* translators: %s: Plugin version. */
echo '<a class="button button-primary right disabled">' . sprintf( __( 'Newer Version (%s) Installed' ), esc_html( $status['version'] ) ) . '</a>';
break;
case 'latest_installed':
echo '<a class="button button-primary right disabled">' . __( 'Latest Version Installed' ) . '</a>';
break;
$button = wp_get_plugin_action_button( $api->name, $api, $compatible_php, $compatible_wp );
$button = str_replace( 'class="', 'class="right ', $button );
if ( ! str_contains( $button, __( 'Activate' ) ) ) {
$button = str_replace( 'class="', 'id="plugin_install_from_iframe" class="', $button );
}
echo wp_kses_post( $button );
}
echo "</div>\n";
wp_print_request_filesystem_credentials_modal();
wp_print_admin_notice_templates();
iframe_footer();
exit;
}
/**
* Gets the markup for the plugin install action button.
*
* @since 6.5.0
*
* @param string $name Plugin name.
* @param array|object $data {
* An array or object of plugin data. Can be retrieved from the API.
*
* @type string $slug The plugin slug.
* @type string[] $requires_plugins An array of plugin dependency slugs.
* @type string $version The plugin's version string. Used when getting the install status.
* }
* @param bool $compatible_php The result of a PHP compatibility check.
* @param bool $compatible_wp The result of a WP compatibility check.
* @return string $button The markup for the dependency row button.
*/
function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible_wp ) {
$button = '';
$data = (object) $data;
$status = install_plugin_install_status( $data );
$requires_plugins = $data->requires_plugins ?? array();
// Determine the status of plugin dependencies.
$installed_plugins = get_plugins();
$active_plugins = get_option( 'active_plugins' );
$plugin_dependencies_count = count( $requires_plugins );
$installed_plugin_dependencies_count = 0;
$active_plugin_dependencies_count = 0;
foreach ( $requires_plugins as $dependency ) {
foreach ( array_keys( $installed_plugins ) as $installed_plugin_file ) {
if ( str_contains( $installed_plugin_file, '/' ) && explode( '/', $installed_plugin_file )[0] === $dependency ) {
++$installed_plugin_dependencies_count;
}
}
foreach ( $active_plugins as $active_plugin_file ) {
if ( str_contains( $active_plugin_file, '/' ) && explode( '/', $active_plugin_file )[0] === $dependency ) {
++$active_plugin_dependencies_count;
}
}
}
$all_plugin_dependencies_installed = $installed_plugin_dependencies_count === $plugin_dependencies_count;
$all_plugin_dependencies_active = $active_plugin_dependencies_count === $plugin_dependencies_count;
sprintf(
'<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
esc_attr( $data->slug ),
esc_url( $status['url'] ),
/* translators: %s: Plugin name and version. */
esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
esc_attr( $name ),
__( 'Install Now' )
);
if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
switch ( $status['status'] ) {
case 'install':
if ( $status['url'] ) {
if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_installed && ! empty( $data->download_link ) ) {
$button = sprintf(
'<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
esc_attr( $data->slug ),
esc_url( $status['url'] ),
/* translators: %s: Plugin name and version. */
esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
esc_attr( $name ),
__( 'Install Now' )
);
} else {
$button = sprintf(
'<button type="button" class="install-now button button-disabled" disabled="disabled">%s</button>',
_x( 'Install Now', 'plugin' )
);
}
}
break;
case 'update_available':
if ( $status['url'] ) {
if ( $compatible_php && $compatible_wp ) {
$button = sprintf(
'<a class="update-now button aria-button-if-js" data-plugin="%s" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
esc_attr( $status['file'] ),
esc_attr( $data->slug ),
esc_url( $status['url'] ),
/* translators: %s: Plugin name and version. */
esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ),
esc_attr( $name ),
__( 'Update Now' )
);
} else {
$button = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Update Now', 'plugin' )
);
}
}
break;
case 'latest_installed':
case 'newer_installed':
if ( is_plugin_active( $status['file'] ) ) {
$button = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Active', 'plugin' )
);
} elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) {
if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_active ) {
$button_text = __( 'Activate' );
/* translators: %s: Plugin name. */
$button_label = _x( 'Activate %s', 'plugin' );
$activate_url = add_query_arg(
array(
'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
'action' => 'activate',
'plugin' => $status['file'],
),
network_admin_url( 'plugins.php' )
);
if ( is_network_admin() ) {
$button_text = __( 'Network Activate' );
/* translators: %s: Plugin name. */
$button_label = _x( 'Network Activate %s', 'plugin' );
$activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
}
$button = sprintf(
'<a href="%1$s" data-name="%2$s" data-slug="%3$s" data-plugin="%4$s" class="button button-primary activate-now" aria-label="%5$s">%6$s</a>',
esc_url( $activate_url ),
esc_attr( $name ),
esc_attr( $data->slug ),
esc_attr( $status['file'] ),
esc_attr( sprintf( $button_label, $name ) ),
$button_text
);
} else {
$button = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
is_network_admin() ? _x( 'Network Activate %s', 'plugin' ) : _x( 'Activate', 'plugin' )
);
}
} else {
$button = sprintf(
'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
_x( 'Installed', 'plugin' )
);
}
break;
}
return $button;
}
}
+72 -30
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;
}
+4
View File
@@ -134,6 +134,10 @@ get_current_screen()->set_screen_reader_content(
* WordPress Administration Template Header.
*/
require_once ABSPATH . 'wp-admin/admin-header.php';
WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies();
WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents();
WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies();
?>
<div class="wrap <?php echo esc_attr( "plugin-install-tab-$tab" ); ?>">
<h1 class="wp-heading-inline">
+3
View File
@@ -739,6 +739,9 @@ if ( isset( $_GET['error'] ) ) {
}
?>
<?php WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies(); ?>
<?php WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents(); ?>
<?php WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies(); ?>
<div class="wrap">
<h1 class="wp-heading-inline">
<?php