mirror of
git://develop.git.wordpress.org/
synced 2025-01-18 05:18:42 +01:00
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:
parent
3eb7bd1b29
commit
2b08a77f5c
@ -413,6 +413,31 @@
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message from a modal to the main screen to update buttons in plugin cards.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} data An object of data to use for the button.
|
||||
* @param {string} data.slug The plugin's slug.
|
||||
* @param {string} data.text The text to use for the button.
|
||||
* @param {string} data.ariaLabel The value for the button's aria-label attribute. An empty string removes the attribute.
|
||||
* @param {string=} data.status Optional. An identifier for the status.
|
||||
* @param {string=} data.removeClasses Optional. A space-separated list of classes to remove from the button.
|
||||
* @param {string=} data.addClasses Optional. A space-separated list of classes to add to the button.
|
||||
* @param {string=} data.href Optional. The button's URL.
|
||||
* @param {string=} data.pluginName Optional. The plugin's name.
|
||||
* @param {string=} data.plugin Optional. The plugin file, relative to the plugins directory.
|
||||
*/
|
||||
wp.updates.setCardButtonStatus = function( data ) {
|
||||
var target = window.parent === window ? null : window.parent;
|
||||
|
||||
$.support.postMessage = !! window.postMessage;
|
||||
if ( false !== $.support.postMessage && null !== target && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) {
|
||||
target.postMessage( JSON.stringify( data ), window.location.origin );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrements the update counts throughout the various menus.
|
||||
*
|
||||
@ -452,7 +477,8 @@
|
||||
*/
|
||||
wp.updates.updatePlugin = function( args ) {
|
||||
var $updateRow, $card, $message, message,
|
||||
$adminBarUpdates = $( '#wp-admin-bar-updates' );
|
||||
$adminBarUpdates = $( '#wp-admin-bar-updates' ),
|
||||
buttonText = __( 'Updating...' );
|
||||
|
||||
args = _.extend( {
|
||||
success: wp.updates.updatePluginSuccess,
|
||||
@ -468,7 +494,7 @@
|
||||
$updateRow.find( '.plugin-title strong' ).text()
|
||||
);
|
||||
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
|
||||
$card = $( '.plugin-card-' + args.slug );
|
||||
$card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' );
|
||||
$message = $card.find( '.update-now' ).addClass( 'updating-message' );
|
||||
message = sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
@ -488,10 +514,22 @@
|
||||
|
||||
$message
|
||||
.attr( 'aria-label', message )
|
||||
.text( __( 'Updating...' ) );
|
||||
.text( buttonText );
|
||||
|
||||
$document.trigger( 'wp-plugin-updating', args );
|
||||
|
||||
if ( 'plugin-information-footer' === $card.attr('id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'updating-plugin',
|
||||
slug: args.slug,
|
||||
addClasses: 'updating-message',
|
||||
text: buttonText,
|
||||
ariaLabel: message
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return wp.updates.ajax( 'update-plugin', args );
|
||||
};
|
||||
|
||||
@ -511,7 +549,13 @@
|
||||
*/
|
||||
wp.updates.updatePluginSuccess = function( response ) {
|
||||
var $pluginRow, $updateMessage, newText,
|
||||
$adminBarUpdates = $( '#wp-admin-bar-updates' );
|
||||
$adminBarUpdates = $( '#wp-admin-bar-updates' ),
|
||||
buttonText = _x( 'Updated!', 'plugin' ),
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s updated!', 'plugin' ),
|
||||
response.pluginName
|
||||
);
|
||||
|
||||
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
|
||||
$pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' )
|
||||
@ -528,7 +572,7 @@
|
||||
// Clear the "time to next auto-update" text.
|
||||
$pluginRow.find( '.auto-update-time' ).empty();
|
||||
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
|
||||
$updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
|
||||
$updateMessage = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.update-now' )
|
||||
.removeClass( 'updating-message' )
|
||||
.addClass( 'button-disabled updated-message' );
|
||||
}
|
||||
@ -536,19 +580,25 @@
|
||||
$adminBarUpdates.removeClass( 'spin' );
|
||||
|
||||
$updateMessage
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s updated!', 'plugin' ),
|
||||
response.pluginName
|
||||
)
|
||||
)
|
||||
.text( _x( 'Updated!', 'plugin' ) );
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
wp.a11y.speak( __( 'Update completed successfully.' ) );
|
||||
|
||||
wp.updates.decrementCount( 'plugin' );
|
||||
if ( 'plugin_install_from_iframe' !== $updateMessage.attr( 'id' ) ) {
|
||||
wp.updates.decrementCount( 'plugin' );
|
||||
} else {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'updated-plugin',
|
||||
slug: response.slug,
|
||||
removeClasses: 'updating-message',
|
||||
addClasses: 'button-disabled updated-message',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$document.trigger( 'wp-plugin-update-success', response );
|
||||
};
|
||||
@ -567,7 +617,7 @@
|
||||
* @param {string} response.errorMessage The error that occurred.
|
||||
*/
|
||||
wp.updates.updatePluginError = function( response ) {
|
||||
var $pluginRow, $card, $message, errorMessage,
|
||||
var $pluginRow, $card, $message, errorMessage, buttonText, ariaLabel,
|
||||
$adminBarUpdates = $( '#wp-admin-bar-updates' );
|
||||
|
||||
if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
|
||||
@ -608,28 +658,32 @@
|
||||
$message.find( 'p' ).removeAttr( 'aria-label' );
|
||||
}
|
||||
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
|
||||
$card = $( '.plugin-card-' + response.slug )
|
||||
.addClass( 'plugin-card-update-failed' )
|
||||
buttonText = __( 'Update failed.' );
|
||||
|
||||
$card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' )
|
||||
.append( wp.updates.adminNotice( {
|
||||
className: 'update-message notice-error notice-alt is-dismissible',
|
||||
message: errorMessage
|
||||
} ) );
|
||||
|
||||
if ( $card.hasClass( 'plugin-card-' + response.slug ) ) {
|
||||
$card.addClass( 'plugin-card-update-failed' );
|
||||
}
|
||||
|
||||
$card.find( '.update-now' )
|
||||
.text( __( 'Update failed.' ) )
|
||||
.text( buttonText )
|
||||
.removeClass( 'updating-message' );
|
||||
|
||||
if ( response.pluginName ) {
|
||||
$card.find( '.update-now' )
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s update failed.', 'plugin' ),
|
||||
response.pluginName
|
||||
)
|
||||
);
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s update failed.', 'plugin' ),
|
||||
response.pluginName
|
||||
);
|
||||
|
||||
$card.find( '.update-now' ).attr( 'aria-label', ariaLabel );
|
||||
} else {
|
||||
ariaLabel = '';
|
||||
$card.find( '.update-now' ).removeAttr( 'aria-label' );
|
||||
}
|
||||
|
||||
@ -652,6 +706,18 @@
|
||||
|
||||
wp.a11y.speak( errorMessage, 'assertive' );
|
||||
|
||||
if ( 'plugin-information-footer' === $card.attr('id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'plugin-update-failed',
|
||||
slug: response.slug,
|
||||
removeClasses: 'updating-message',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$document.trigger( 'wp-plugin-update-error', response );
|
||||
};
|
||||
|
||||
@ -668,8 +734,10 @@
|
||||
* decorated with an abort() method.
|
||||
*/
|
||||
wp.updates.installPlugin = function( args ) {
|
||||
var $card = $( '.plugin-card-' + args.slug ),
|
||||
$message = $card.find( '.install-now' );
|
||||
var $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ),
|
||||
$message = $card.find( '.install-now' ),
|
||||
buttonText = __( 'Installing...' ),
|
||||
ariaLabel;
|
||||
|
||||
args = _.extend( {
|
||||
success: wp.updates.installPluginSuccess,
|
||||
@ -684,17 +752,16 @@
|
||||
$message.data( 'originaltext', $message.html() );
|
||||
}
|
||||
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( 'Installing %s...', 'plugin' ),
|
||||
$message.data( 'name' )
|
||||
);
|
||||
|
||||
$message
|
||||
.addClass( 'updating-message' )
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( 'Installing %s...', 'plugin' ),
|
||||
$message.data( 'name' )
|
||||
)
|
||||
)
|
||||
.text( __( 'Installing...' ) );
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
wp.a11y.speak( __( 'Installing... please wait.' ) );
|
||||
|
||||
@ -703,6 +770,18 @@
|
||||
|
||||
$document.trigger( 'wp-plugin-installing', args );
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'installing-plugin',
|
||||
slug: args.slug,
|
||||
addClasses: 'updating-message',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return wp.updates.ajax( 'install-plugin', args );
|
||||
};
|
||||
|
||||
@ -717,20 +796,19 @@
|
||||
* @param {string} response.activateUrl URL to activate the just installed plugin.
|
||||
*/
|
||||
wp.updates.installPluginSuccess = function( response ) {
|
||||
var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' );
|
||||
var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
|
||||
buttonText = _x( 'Installed!', 'plugin' ),
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s installed!', 'plugin' ),
|
||||
response.pluginName
|
||||
);
|
||||
|
||||
$message
|
||||
.removeClass( 'updating-message' )
|
||||
.addClass( 'updated-message installed button-disabled' )
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s installed!', 'plugin' ),
|
||||
response.pluginName
|
||||
)
|
||||
)
|
||||
.text( _x( 'Installed!', 'plugin' ) );
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
wp.a11y.speak( __( 'Installation completed successfully.' ) );
|
||||
|
||||
@ -738,37 +816,24 @@
|
||||
|
||||
if ( response.activateUrl ) {
|
||||
setTimeout( function() {
|
||||
|
||||
// Transform the 'Install' button into an 'Activate' button.
|
||||
$message.removeClass( 'install-now installed button-disabled updated-message' )
|
||||
.addClass( 'activate-now button-primary' )
|
||||
.attr( 'href', response.activateUrl );
|
||||
|
||||
if ( 'plugins-network' === pagenow ) {
|
||||
$message
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( 'Network Activate %s', 'plugin' ),
|
||||
response.pluginName
|
||||
)
|
||||
)
|
||||
.text( __( 'Network Activate' ) );
|
||||
} else {
|
||||
$message
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( 'Activate %s', 'plugin' ),
|
||||
response.pluginName
|
||||
)
|
||||
)
|
||||
.text( __( 'Activate' ) );
|
||||
}
|
||||
wp.updates.checkPluginDependencies( {
|
||||
slug: response.slug
|
||||
} );
|
||||
}, 1000 );
|
||||
}
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'installed-plugin',
|
||||
slug: response.slug,
|
||||
removeClasses: 'updating-message',
|
||||
addClasses: 'updated-message installed button-disabled',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -783,8 +848,14 @@
|
||||
* @param {string} response.errorMessage The error that occurred.
|
||||
*/
|
||||
wp.updates.installPluginError = function( response ) {
|
||||
var $card = $( '.plugin-card-' + response.slug ),
|
||||
var $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ),
|
||||
$button = $card.find( '.install-now' ),
|
||||
buttonText = __( 'Installation failed.' ),
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s installation failed', 'plugin' ),
|
||||
$button.data( 'name' )
|
||||
),
|
||||
errorMessage;
|
||||
|
||||
if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
|
||||
@ -817,21 +888,334 @@
|
||||
|
||||
$button
|
||||
.removeClass( 'updating-message' ).addClass( 'button-disabled' )
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name and version. */
|
||||
_x( '%s installation failed', 'plugin' ),
|
||||
$button.data( 'name' )
|
||||
)
|
||||
)
|
||||
.text( __( 'Installation failed.' ) );
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
wp.a11y.speak( errorMessage, 'assertive' );
|
||||
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'plugin-install-failed',
|
||||
slug: response.slug,
|
||||
removeClasses: 'updating-message',
|
||||
addClasses: 'button-disabled',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
|
||||
$document.trigger( 'wp-plugin-install-error', response );
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends an Ajax request to the server to check a plugin's dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} args Arguments.
|
||||
* @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
|
||||
* @param {checkPluginDependenciesSuccess=} args.success Optional. Success callback. Default: wp.updates.checkPluginDependenciesSuccess
|
||||
* @param {checkPluginDependenciesError=} args.error Optional. Error callback. Default: wp.updates.checkPluginDependenciesError
|
||||
* @return {$.promise} A jQuery promise that represents the request,
|
||||
* decorated with an abort() method.
|
||||
*/
|
||||
wp.updates.checkPluginDependencies = function( args ) {
|
||||
args = _.extend( {
|
||||
success: wp.updates.checkPluginDependenciesSuccess,
|
||||
error: wp.updates.checkPluginDependenciesError
|
||||
}, args );
|
||||
|
||||
wp.a11y.speak( __( 'Checking plugin dependencies... please wait.' ) );
|
||||
$document.trigger( 'wp-checking-plugin-dependencies', args );
|
||||
|
||||
return wp.updates.ajax( 'check_plugin_dependencies', args );
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI appropriately after a successful plugin dependencies check.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} response Response from the server.
|
||||
* @param {string} response.slug Slug of the checked plugin.
|
||||
* @param {string} response.pluginName Name of the checked plugin.
|
||||
* @param {string} response.plugin The plugin file, relative to the plugins directory.
|
||||
* @param {string} response.activateUrl URL to activate the just checked plugin.
|
||||
*/
|
||||
wp.updates.checkPluginDependenciesSuccess = function( response ) {
|
||||
var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
|
||||
buttonText, ariaLabel;
|
||||
|
||||
// Transform the 'Install' button into an 'Activate' button.
|
||||
$message
|
||||
.removeClass( 'install-now installed button-disabled updated-message' )
|
||||
.addClass( 'activate-now button-primary' )
|
||||
.attr( 'href', response.activateUrl );
|
||||
|
||||
wp.a11y.speak( __( 'Plugin dependencies check completed successfully.' ) );
|
||||
$document.trigger( 'wp-check-plugin-dependencies-success', response );
|
||||
|
||||
if ( 'plugins-network' === pagenow ) {
|
||||
buttonText = _x( 'Network Activate' );
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( 'Network Activate %s', 'plugin' ),
|
||||
response.pluginName
|
||||
);
|
||||
|
||||
$message
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
} else {
|
||||
buttonText = _x( 'Activate', 'plugin' );
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( 'Activate %s', 'plugin' ),
|
||||
response.pluginName
|
||||
);
|
||||
|
||||
$message
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.attr( 'data-name', response.pluginName )
|
||||
.attr( 'data-slug', response.slug )
|
||||
.attr( 'data-plugin', response.plugin )
|
||||
.text( buttonText );
|
||||
}
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'dependencies-check-success',
|
||||
slug: response.slug,
|
||||
removeClasses: 'install-now installed button-disabled updated-message',
|
||||
addClasses: 'activate-now button-primary',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel,
|
||||
pluginName: response.pluginName,
|
||||
plugin: response.plugin,
|
||||
href: response.activateUrl
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI appropriately after a failed plugin dependencies check.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} response Response from the server.
|
||||
* @param {string} response.slug Slug of the plugin to be checked.
|
||||
* @param {string=} response.pluginName Optional. Name of the plugin to be checked.
|
||||
* @param {string} response.errorCode Error code for the error that occurred.
|
||||
* @param {string} response.errorMessage The error that occurred.
|
||||
*/
|
||||
wp.updates.checkPluginDependenciesError = function( response ) {
|
||||
var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
|
||||
buttonText = __( 'Activate' ),
|
||||
ariaLabel = sprintf(
|
||||
/* translators: 1: Plugin name, 2. The reason the plugin cannot be activated. */
|
||||
_x( 'Cannot activate %1$s. %2$s', 'plugin' ),
|
||||
response.pluginName,
|
||||
response.errorMessage
|
||||
),
|
||||
errorMessage;
|
||||
|
||||
if ( ! wp.updates.isValidResponse( response, 'check-dependencies' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorMessage = sprintf(
|
||||
/* translators: %s: Error string for a failed activation. */
|
||||
__( 'Activation failed: %s' ),
|
||||
response.errorMessage
|
||||
);
|
||||
|
||||
wp.a11y.speak( errorMessage, 'assertive' );
|
||||
$document.trigger( 'wp-check-plugin-dependencies-error', response );
|
||||
|
||||
$message
|
||||
.removeClass( 'install-now installed updated-message' )
|
||||
.addClass( 'activate-now button-primary' )
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr('id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'dependencies-check-failed',
|
||||
slug: response.slug,
|
||||
removeClasses: 'install-now installed updated-message',
|
||||
addClasses: 'activate-now button-primary',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends an Ajax request to the server to activate a plugin.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} args Arguments.
|
||||
* @param {string} args.name The name of the plugin.
|
||||
* @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
|
||||
* @param {string} args.plugin The plugin file, relative to the plugins directory.
|
||||
* @param {activatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.activatePluginSuccess
|
||||
* @param {activatePluginError=} args.error Optional. Error callback. Default: wp.updates.activatePluginError
|
||||
* @return {$.promise} A jQuery promise that represents the request,
|
||||
* decorated with an abort() method.
|
||||
*/
|
||||
wp.updates.activatePlugin = function( args ) {
|
||||
var $message = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ).find( '.activate-now, .activating-message' );
|
||||
|
||||
args = _.extend( {
|
||||
success: wp.updates.activatePluginSuccess,
|
||||
error: wp.updates.activatePluginError
|
||||
}, args );
|
||||
|
||||
wp.a11y.speak( __( 'Activating... please wait.' ) );
|
||||
$document.trigger( 'wp-activating-plugin', args );
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'activating-plugin',
|
||||
slug: args.slug,
|
||||
removeClasses: 'installed updated-message button-primary',
|
||||
addClasses: 'activating-message',
|
||||
text: _x( 'Activating...', 'plugin' ),
|
||||
ariaLabel: sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( 'Activating %s', 'plugin' ),
|
||||
args.name
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return wp.updates.ajax( 'activate-plugin', args );
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI appropriately after a successful plugin activation.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} response Response from the server.
|
||||
* @param {string} response.slug Slug of the activated plugin.
|
||||
* @param {string} response.pluginName Name of the activated plugin.
|
||||
* @param {string} response.plugin The plugin file, relative to the plugins directory.
|
||||
*/
|
||||
wp.updates.activatePluginSuccess = function( response ) {
|
||||
var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
|
||||
buttonText = _x( 'Activated!', 'plugin' ),
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: The plugin name. */
|
||||
'%s activated successfully.',
|
||||
response.pluginName
|
||||
);
|
||||
|
||||
wp.a11y.speak( __( 'Activation completed successfully.' ) );
|
||||
$document.trigger( 'wp-plugin-activate-success', response );
|
||||
|
||||
$message
|
||||
.removeClass( 'activating-message' )
|
||||
.addClass( 'activated-message button-disabled' )
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'activated-plugin',
|
||||
slug: response.slug,
|
||||
removeClasses: 'activating-message',
|
||||
addClasses: 'activated-message button-disabled',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setTimeout( function() {
|
||||
$message.removeClass( 'activated-message' )
|
||||
.text( _x( 'Active', 'plugin' ) );
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'plugin-active',
|
||||
slug: response.slug,
|
||||
removeClasses: 'activated-message',
|
||||
text: _x( 'Active', 'plugin' ),
|
||||
ariaLabel: sprintf(
|
||||
/* translators: %s: The plugin name. */
|
||||
'%s is active.',
|
||||
response.pluginName
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
}, 1000 );
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI appropriately after a failed plugin activation.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Object} response Response from the server.
|
||||
* @param {string} response.slug Slug of the plugin to be activated.
|
||||
* @param {string=} response.pluginName Optional. Name of the plugin to be activated.
|
||||
* @param {string} response.errorCode Error code for the error that occurred.
|
||||
* @param {string} response.errorMessage The error that occurred.
|
||||
*/
|
||||
wp.updates.activatePluginError = function( response ) {
|
||||
var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
|
||||
buttonText = __( 'Activation failed.' ),
|
||||
ariaLabel = sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( '%s activation failed', 'plugin' ),
|
||||
response.pluginName
|
||||
),
|
||||
errorMessage;
|
||||
|
||||
if ( ! wp.updates.isValidResponse( response, 'activate' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorMessage = sprintf(
|
||||
/* translators: %s: Error string for a failed activation. */
|
||||
__( 'Activation failed: %s' ),
|
||||
response.errorMessage
|
||||
);
|
||||
|
||||
wp.a11y.speak( errorMessage, 'assertive' );
|
||||
$document.trigger( 'wp-plugin-activate-error', response );
|
||||
|
||||
$message
|
||||
.removeClass( 'install-now installed activating-message' )
|
||||
.addClass( 'button-disabled' )
|
||||
.attr( 'aria-label', ariaLabel )
|
||||
.text( buttonText );
|
||||
|
||||
if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
|
||||
wp.updates.setCardButtonStatus(
|
||||
{
|
||||
status: 'plugin-activation-failed',
|
||||
slug: response.slug,
|
||||
removeClasses: 'install-now installed activating-message',
|
||||
addClasses: 'button-disabled',
|
||||
text: buttonText,
|
||||
ariaLabel: ariaLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI appropriately after a successful importer install.
|
||||
*
|
||||
@ -1970,6 +2354,16 @@
|
||||
errorMessage = __( 'Installation failed: %s' );
|
||||
break;
|
||||
|
||||
case 'check-dependencies':
|
||||
/* translators: %s: Error string for a failed dependencies check. */
|
||||
errorMessage = __( 'Dependencies check failed: %s' );
|
||||
break;
|
||||
|
||||
case 'activate':
|
||||
/* translators: %s: Error string for a failed activation. */
|
||||
errorMessage = __( 'Activation failed: %s' );
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
/* translators: %s: Error string for a failed deletion. */
|
||||
errorMessage = __( 'Deletion failed: %s' );
|
||||
@ -2025,7 +2419,7 @@
|
||||
};
|
||||
|
||||
$( function() {
|
||||
var $pluginFilter = $( '#plugin-filter' ),
|
||||
var $pluginFilter = $( '#plugin-filter, #plugin-information-footer' ),
|
||||
$bulkActionForm = $( '#bulk-action-form' ),
|
||||
$filesystemForm = $( '#request-filesystem-credentials-form' ),
|
||||
$filesystemModal = $( '#request-filesystem-credentials-dialog' ),
|
||||
@ -2241,6 +2635,44 @@
|
||||
} );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Click handler for plugin activations in plugin activation view.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param {Event} event Event interface.
|
||||
*/
|
||||
$pluginFilter.on( 'click', '.activate-now', function( event ) {
|
||||
var $activateButton = $( event.target );
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if ( $activateButton.hasClass( 'activating-message' ) || $activateButton.hasClass( 'button-disabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activateButton
|
||||
.removeClass( 'activate-now button-primary' )
|
||||
.addClass( 'activating-message' )
|
||||
.attr(
|
||||
'aria-label',
|
||||
sprintf(
|
||||
/* translators: %s: Plugin name. */
|
||||
_x( 'Activating %s', 'plugin' ),
|
||||
$activateButton.data( 'name' )
|
||||
)
|
||||
)
|
||||
.text( _x( 'Activating...', 'plugin' ) );
|
||||
|
||||
wp.updates.activatePlugin(
|
||||
{
|
||||
name: $activateButton.data( 'name' ),
|
||||
slug: $activateButton.data( 'slug' ),
|
||||
plugin: $activateButton.data( 'plugin' )
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Click handler for importer plugins installs in the Import screen.
|
||||
*
|
||||
@ -2766,35 +3198,6 @@
|
||||
target.postMessage( JSON.stringify( update ), window.location.origin );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Click handler for installing a plugin from the details modal on `plugin-install.php`.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param {Event} event Event interface.
|
||||
*/
|
||||
$( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
|
||||
var target = window.parent === window ? null : window.parent,
|
||||
install;
|
||||
|
||||
$.support.postMessage = !! window.postMessage;
|
||||
|
||||
if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
install = {
|
||||
action: 'install-plugin',
|
||||
data: {
|
||||
slug: $( this ).data( 'slug' )
|
||||
}
|
||||
};
|
||||
|
||||
target.postMessage( JSON.stringify( install ), window.location.origin );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Handles postMessage events.
|
||||
*
|
||||
@ -2818,7 +3221,45 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! message || 'undefined' === typeof message.action ) {
|
||||
if ( ! message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
'undefined' !== typeof message.status &&
|
||||
'undefined' !== typeof message.slug &&
|
||||
'undefined' !== typeof message.text &&
|
||||
'undefined' !== typeof message.ariaLabel
|
||||
) {
|
||||
var $card = $( '.plugin-card-' + message.slug ),
|
||||
$message = $card.find( '[data-slug="' + message.slug + '"]' );
|
||||
|
||||
if ( 'undefined' !== typeof message.removeClasses ) {
|
||||
$message.removeClass( message.removeClasses );
|
||||
}
|
||||
|
||||
if ( 'undefined' !== typeof message.addClasses ) {
|
||||
$message.addClass( message.addClasses );
|
||||
}
|
||||
|
||||
if ( '' === message.ariaLabel ) {
|
||||
$message.removeAttr( 'aria-label' );
|
||||
} else {
|
||||
$message.attr( 'aria-label', message.ariaLabel );
|
||||
}
|
||||
|
||||
if ( 'dependencies-check-success' === message.status ) {
|
||||
$message
|
||||
.attr( 'data-name', message.pluginName )
|
||||
.attr( 'data-slug', message.slug )
|
||||
.attr( 'data-plugin', message.plugin )
|
||||
.attr( 'href', message.href );
|
||||
}
|
||||
|
||||
$message.text( message.text );
|
||||
}
|
||||
|
||||
if ( 'undefined' === typeof message.action || 'undefined' === typeof message.data.slug ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2832,10 +3273,6 @@
|
||||
|
||||
case 'install-plugin':
|
||||
case 'update-plugin':
|
||||
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
|
||||
window.tb_remove();
|
||||
/* jscs:enable */
|
||||
|
||||
message.data = wp.updates._addCallbacks( message.data, message.action );
|
||||
|
||||
wp.updates.queue.push( message );
|
||||
|
@ -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() ) {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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&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&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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' .
|
||||
'&plugin=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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' .
|
||||
'&plugin=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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' .
|
||||
'&checked[]=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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' .
|
||||
'&plugin=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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' .
|
||||
'&plugin=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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' .
|
||||
'&plugin=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&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' .
|
||||
'&checked[]=' . urlencode( $plugin_file ) .
|
||||
'&plugin_status=' . $context .
|
||||
'&paged=' . $page .
|
||||
'&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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
949
src/wp-includes/class-wp-plugin-dependencies.php
Normal file
949
src/wp-includes/class-wp-plugin-dependencies.php
Normal file
@ -0,0 +1,949 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress Plugin Administration API: WP_Plugin_Dependencies class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage Administration
|
||||
* @since 6.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class for installing plugin dependencies.
|
||||
*
|
||||
* It is designed to add plugin dependencies as designated in the
|
||||
* `Requires Plugins` header to a new view in the plugins install page.
|
||||
*/
|
||||
class WP_Plugin_Dependencies {
|
||||
|
||||
/**
|
||||
* Holds 'get_plugins()'.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $plugins;
|
||||
|
||||
/**
|
||||
* Holds plugin directory names to compare with cache.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $plugin_dirnames;
|
||||
|
||||
/**
|
||||
* Holds sanitized plugin dependency slugs.
|
||||
*
|
||||
* Keyed on the dependent plugin's filepath,
|
||||
* relative to the plugins directory.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $dependencies;
|
||||
|
||||
/**
|
||||
* Holds an array of sanitized plugin dependency slugs.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $dependency_slugs;
|
||||
|
||||
/**
|
||||
* Holds an array of dependent plugin slugs.
|
||||
*
|
||||
* Keyed on the dependent plugin's filepath,
|
||||
* relative to the plugins directory.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $dependent_slugs;
|
||||
|
||||
/**
|
||||
* Holds 'plugins_api()' data for plugin dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $dependency_api_data;
|
||||
|
||||
/**
|
||||
* Holds plugin dependency filepaths, relative to the plugins directory.
|
||||
*
|
||||
* Keyed on the dependency's slug.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $dependency_filepaths;
|
||||
|
||||
/**
|
||||
* An array of circular dependency pairings.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
protected static $circular_dependencies_pairs;
|
||||
|
||||
/**
|
||||
* An array of circular dependency slugs.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $circular_dependencies_slugs;
|
||||
|
||||
/**
|
||||
* Initializes by fetching plugin header and plugin API data,
|
||||
* and deactivating dependents with unmet dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public static function initialize() {
|
||||
self::read_dependencies_from_plugin_headers();
|
||||
self::get_dependency_api_data();
|
||||
self::deactivate_dependents_with_unmet_dependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the plugin has plugins that depend on it.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return bool Whether the plugin has plugins that depend on it.
|
||||
*/
|
||||
public static function has_dependents( $plugin_file ) {
|
||||
return in_array( self::convert_to_slug( $plugin_file ), self::$dependency_slugs, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the plugin has plugin dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return bool Whether a plugin has plugin dependencies.
|
||||
*/
|
||||
public static function has_dependencies( $plugin_file ) {
|
||||
return isset( self::$dependencies[ $plugin_file ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the plugin has active dependents.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return bool Whether the plugin has active dependents.
|
||||
*/
|
||||
public static function has_active_dependents( $plugin_file ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
|
||||
$dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) );
|
||||
foreach ( $dependents as $dependent ) {
|
||||
if ( is_plugin_active( $dependent ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets filepaths of plugins that require the dependency.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $slug The dependency's slug.
|
||||
* @return array An array of dependent plugin filepaths, relative to the plugins directory.
|
||||
*/
|
||||
public static function get_dependents( $slug ) {
|
||||
$dependents = array();
|
||||
|
||||
foreach ( self::$dependencies as $dependent => $dependencies ) {
|
||||
if ( in_array( $slug, $dependencies, true ) ) {
|
||||
$dependents[] = $dependent;
|
||||
}
|
||||
}
|
||||
|
||||
return $dependents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the slugs of plugins that the dependent requires.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory.
|
||||
* @return array An array of dependency plugin slugs.
|
||||
*/
|
||||
public static function get_dependencies( $plugin_file ) {
|
||||
if ( isset( self::$dependencies[ $plugin_file ] ) ) {
|
||||
return self::$dependencies[ $plugin_file ];
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dependent plugin's filepath.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $slug The dependent plugin's slug.
|
||||
* @return string|false The dependent plugin's filepath, relative to the plugins directory,
|
||||
* or false if the plugin has no dependencies.
|
||||
*/
|
||||
public static function get_dependent_filepath( $slug ) {
|
||||
$filepath = array_search( $slug, self::$dependent_slugs, true );
|
||||
|
||||
return $filepath ? $filepath : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the plugin has unmet dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return bool Whether the plugin has unmet dependencies.
|
||||
*/
|
||||
public static function has_unmet_dependencies( $plugin_file ) {
|
||||
if ( ! isset( self::$dependencies[ $plugin_file ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
|
||||
foreach ( self::$dependencies[ $plugin_file ] as $dependency ) {
|
||||
$dependency_filepath = self::get_dependency_filepath( $dependency );
|
||||
|
||||
if ( false === $dependency_filepath || is_plugin_inactive( $dependency_filepath ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the plugin has a circular dependency.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return bool Whether the plugin has a circular dependency.
|
||||
*/
|
||||
public static function has_circular_dependency( $plugin_file ) {
|
||||
if ( ! is_array( self::$circular_dependencies_slugs ) ) {
|
||||
self::get_circular_dependencies();
|
||||
}
|
||||
|
||||
if ( ! empty( self::$circular_dependencies_slugs ) ) {
|
||||
$slug = self::convert_to_slug( $plugin_file );
|
||||
|
||||
if ( in_array( $slug, self::$circular_dependencies_slugs, true ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of plugins that require the plugin.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return array An array of dependent names.
|
||||
*/
|
||||
public static function get_dependent_names( $plugin_file ) {
|
||||
$dependent_names = array();
|
||||
$plugins = self::get_plugins();
|
||||
$slug = self::convert_to_slug( $plugin_file );
|
||||
|
||||
foreach ( self::get_dependents( $slug ) as $dependent ) {
|
||||
$dependent_names[ $dependent ] = $plugins[ $dependent ]['Name'];
|
||||
}
|
||||
sort( $dependent_names );
|
||||
|
||||
return $dependent_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of plugins required by the plugin.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory.
|
||||
* @return array An array of dependency names.
|
||||
*/
|
||||
public static function get_dependency_names( $plugin_file ) {
|
||||
$dependency_api_data = self::get_dependency_api_data();
|
||||
$dependencies = self::get_dependencies( $plugin_file );
|
||||
$plugins = self::get_plugins();
|
||||
|
||||
$dependency_names = array();
|
||||
foreach ( $dependencies as $dependency ) {
|
||||
// Use the name if it's available, otherwise fall back to the slug.
|
||||
if ( isset( $dependency_api_data[ $dependency ]['name'] ) ) {
|
||||
$name = $dependency_api_data[ $dependency ]['name'];
|
||||
} else {
|
||||
$dependency_filepath = self::get_dependency_filepath( $dependency );
|
||||
if ( false !== $dependency_filepath ) {
|
||||
$name = $plugins[ $dependency_filepath ]['Name'];
|
||||
} else {
|
||||
$name = $dependency;
|
||||
}
|
||||
}
|
||||
|
||||
$dependency_names[ $dependency ] = $name;
|
||||
}
|
||||
|
||||
return $dependency_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filepath for a dependency, relative to the plugin's directory.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $slug The dependency's slug.
|
||||
* @return string|false If installed, the dependency's filepath relative to the plugins directory, otherwise false.
|
||||
*/
|
||||
public static function get_dependency_filepath( $slug ) {
|
||||
$dependency_filepaths = self::get_dependency_filepaths();
|
||||
|
||||
if ( ! isset( $dependency_filepaths[ $slug ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $dependency_filepaths[ $slug ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns API data for the dependency.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $slug The dependency's slug.
|
||||
* @return array|false The dependency's API data on success, otherwise false.
|
||||
*/
|
||||
public static function get_dependency_data( $slug ) {
|
||||
$dependency_api_data = self::get_dependency_api_data();
|
||||
|
||||
if ( isset( $dependency_api_data[ $slug ] ) ) {
|
||||
return $dependency_api_data[ $slug ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an admin notice if dependencies are not installed.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public static function display_admin_notice_for_unmet_dependencies() {
|
||||
if ( in_array( false, self::get_dependency_filepaths(), true ) ) {
|
||||
wp_admin_notice(
|
||||
__( 'There are additional plugin dependencies that must be installed.' ),
|
||||
array(
|
||||
'type' => 'info',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an admin notice if dependencies have been deactivated.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public static function display_admin_notice_for_deactivated_dependents() {
|
||||
/*
|
||||
* Plugin deactivated if dependencies not met.
|
||||
* Transient on a 10 second timeout.
|
||||
*/
|
||||
$deactivate_requires = get_site_transient( 'wp_plugin_dependencies_deactivated_plugins' );
|
||||
if ( ! empty( $deactivate_requires ) ) {
|
||||
$deactivated_plugins = '';
|
||||
foreach ( $deactivate_requires as $deactivated ) {
|
||||
$deactivated_plugins .= '<li>' . esc_html( self::$plugins[ $deactivated ]['Name'] ) . '</li>';
|
||||
}
|
||||
wp_admin_notice(
|
||||
sprintf(
|
||||
/* translators: 1: plugin names */
|
||||
__( 'The following plugin(s) have been deactivated due to uninstalled or inactive dependencies: %s' ),
|
||||
"<ul>$deactivated_plugins</ul>"
|
||||
),
|
||||
array(
|
||||
'type' => 'error',
|
||||
'dismissible' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an admin notice if circular dependencies are installed.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public static function display_admin_notice_for_circular_dependencies() {
|
||||
$circular_dependencies = self::get_circular_dependencies();
|
||||
if ( ! empty( $circular_dependencies ) && count( $circular_dependencies ) > 1 ) {
|
||||
$circular_dependencies = array_unique( $circular_dependencies, SORT_REGULAR );
|
||||
$plugins = self::get_plugins();
|
||||
$plugin_dirnames = self::get_plugin_dirnames();
|
||||
|
||||
// Build output lines.
|
||||
$circular_dependency_lines = '';
|
||||
foreach ( $circular_dependencies as $circular_dependency ) {
|
||||
$first_filepath = $plugin_dirnames[ $circular_dependency[0] ];
|
||||
$second_filepath = $plugin_dirnames[ $circular_dependency[1] ];
|
||||
$circular_dependency_lines .= sprintf(
|
||||
/* translators: 1: First plugin name, 2: Second plugin name. */
|
||||
'<li>' . _x( '%1$s requires %2$s', 'The first plugin requires the second plugin.' ) . '</li>',
|
||||
'<strong>' . esc_html( $plugins[ $first_filepath ]['Name'] ) . '</strong>',
|
||||
'<strong>' . esc_html( $plugins[ $second_filepath ]['Name'] ) . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
wp_admin_notice(
|
||||
sprintf(
|
||||
'<p>%1$s</p><ul>%2$s</ul><p>%3$s</p>',
|
||||
__( 'These plugins cannot be activated because their requirements are invalid. ' ),
|
||||
$circular_dependency_lines,
|
||||
__( 'Please contact the plugin authors for more information.' )
|
||||
),
|
||||
array(
|
||||
'type' => 'warning',
|
||||
'paragraph_wrap' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks plugin dependencies after a plugin is installed via AJAX.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public static function check_plugin_dependencies_during_ajax() {
|
||||
check_ajax_referer( 'updates' );
|
||||
|
||||
if ( empty( $_POST['slug'] ) ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'slug' => '',
|
||||
'pluginName' => '',
|
||||
'errorCode' => 'no_plugin_specified',
|
||||
'errorMessage' => __( 'No plugin specified.' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
|
||||
$status = array( 'slug' => $slug );
|
||||
|
||||
self::get_plugins();
|
||||
self::get_plugin_dirnames();
|
||||
|
||||
if ( ! isset( self::$plugin_dirnames[ $slug ] ) ) {
|
||||
$status['errorCode'] = 'plugin_not_installed';
|
||||
$status['errorMessage'] = __( 'The plugin is not installed.' );
|
||||
wp_send_json_error( $status );
|
||||
}
|
||||
|
||||
$plugin_file = self::$plugin_dirnames[ $slug ];
|
||||
$status['pluginName'] = self::$plugins[ $plugin_file ]['Name'];
|
||||
$status['plugin'] = $plugin_file;
|
||||
|
||||
if ( current_user_can( 'activate_plugin', $plugin_file ) && is_plugin_inactive( $plugin_file ) ) {
|
||||
$status['activateUrl'] = add_query_arg(
|
||||
array(
|
||||
'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ),
|
||||
'action' => 'activate',
|
||||
'plugin' => $plugin_file,
|
||||
),
|
||||
is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) {
|
||||
$status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
|
||||
}
|
||||
|
||||
$dependencies = self::get_dependencies( $plugin_file );
|
||||
if ( empty( $dependencies ) ) {
|
||||
$status['message'] = __( 'The plugin has no required plugins.' );
|
||||
wp_send_json_success( $status );
|
||||
}
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
|
||||
$inactive_dependencies = array();
|
||||
foreach ( $dependencies as $dependency ) {
|
||||
if ( false === self::$plugin_dirnames[ $dependency ] || is_plugin_inactive( self::$plugin_dirnames[ $dependency ] ) ) {
|
||||
$inactive_dependencies[] = $dependency;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $inactive_dependencies ) ) {
|
||||
$inactive_dependency_names = array_map(
|
||||
function ( $dependency ) {
|
||||
if ( isset( self::$dependency_api_data[ $dependency ]['Name'] ) ) {
|
||||
$inactive_dependency_name = self::$dependency_api_data[ $dependency ]['Name'];
|
||||
} else {
|
||||
$inactive_dependency_name = $dependency;
|
||||
}
|
||||
return $inactive_dependency_name;
|
||||
},
|
||||
$inactive_dependencies
|
||||
);
|
||||
|
||||
$status['errorCode'] = 'inactive_dependencies';
|
||||
$status['errorMessage'] = sprintf(
|
||||
/* translators: %s: A list of inactive dependency plugin names. */
|
||||
__( 'The following plugins must be activated first: %s.' ),
|
||||
implode( ', ', $inactive_dependency_names )
|
||||
);
|
||||
$status['errorData'] = array_combine( $inactive_dependencies, $inactive_dependency_names );
|
||||
|
||||
wp_send_json_error( $status );
|
||||
}
|
||||
|
||||
$status['message'] = __( 'All required plugins are installed and activated.' );
|
||||
wp_send_json_success( $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data for installed plugins.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array An array of plugin data.
|
||||
*/
|
||||
protected static function get_plugins() {
|
||||
if ( is_array( self::$plugins ) ) {
|
||||
return self::$plugins;
|
||||
}
|
||||
|
||||
$all_plugin_data = get_option( 'plugin_data', array() );
|
||||
|
||||
if ( empty( $all_plugin_data ) ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
$all_plugin_data = get_plugins();
|
||||
}
|
||||
|
||||
self::$plugins = $all_plugin_data;
|
||||
|
||||
return self::$plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and stores dependency slugs from a plugin's 'Requires Plugins' header.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
|
||||
*/
|
||||
protected static function read_dependencies_from_plugin_headers() {
|
||||
self::$dependencies = array();
|
||||
self::$dependency_slugs = array();
|
||||
self::$dependent_slugs = array();
|
||||
$plugins = self::get_plugins();
|
||||
foreach ( $plugins as $plugin => $header ) {
|
||||
if ( '' === $header['RequiresPlugins'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependency_slugs = self::sanitize_dependency_slugs( $header['RequiresPlugins'] );
|
||||
self::$dependencies[ $plugin ] = $dependency_slugs;
|
||||
self::$dependency_slugs = array_merge( self::$dependency_slugs, $dependency_slugs );
|
||||
|
||||
$dependent_slug = self::convert_to_slug( $plugin );
|
||||
self::$dependent_slugs[ $plugin ] = $dependent_slug;
|
||||
}
|
||||
self::$dependency_slugs = array_unique( self::$dependency_slugs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes slugs.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $slugs A comma-separated string of plugin dependency slugs.
|
||||
* @return array An array of sanitized plugin dependency slugs.
|
||||
*/
|
||||
protected static function sanitize_dependency_slugs( $slugs ) {
|
||||
$sanitized_slugs = array();
|
||||
$slugs = explode( ',', $slugs );
|
||||
|
||||
foreach ( $slugs as $slug ) {
|
||||
$slug = trim( $slug );
|
||||
|
||||
/**
|
||||
* Filters a plugin dependency's slug before matching to
|
||||
* the WordPress.org slug format.
|
||||
*
|
||||
* Can be used to switch between free and premium plugin slugs, for example.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $slug The slug.
|
||||
*/
|
||||
$slug = apply_filters( 'wp_plugin_dependencies_slug', $slug );
|
||||
|
||||
// Match to WordPress.org slug format.
|
||||
if ( preg_match( '/^[a-z0-9]+(-[a-z0-9]+)*$/mu', $slug ) ) {
|
||||
$sanitized_slugs[] = $slug;
|
||||
}
|
||||
}
|
||||
$sanitized_slugs = array_unique( $sanitized_slugs );
|
||||
sort( $sanitized_slugs );
|
||||
|
||||
return $sanitized_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets plugin filepaths for active plugins that depend on the dependency.
|
||||
*
|
||||
* Recurses for each dependent that is also a dependency.
|
||||
*
|
||||
* @param string $plugin_file The dependency's filepath, relative to the plugin directory.
|
||||
* @return string[] An array of active dependent plugin filepaths, relative to the plugin directory.
|
||||
*/
|
||||
protected static function get_active_dependents_in_dependency_tree( $plugin_file ) {
|
||||
$all_dependents = array();
|
||||
$dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) );
|
||||
|
||||
if ( empty( $dependents ) ) {
|
||||
return $all_dependents;
|
||||
}
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
foreach ( $dependents as $dependent ) {
|
||||
if ( is_plugin_active( $dependent ) ) {
|
||||
$all_dependents[] = $dependent;
|
||||
$all_dependents = array_merge(
|
||||
$all_dependents,
|
||||
self::get_active_dependents_in_dependency_tree( $dependent )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $all_dependents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates dependent plugins with unmet dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
protected static function deactivate_dependents_with_unmet_dependencies() {
|
||||
$dependents_to_deactivate = array();
|
||||
$circular_dependencies = array_reduce(
|
||||
self::get_circular_dependencies(),
|
||||
function ( $all_circular, $circular_pair ) {
|
||||
return array_merge( $all_circular, $circular_pair );
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
foreach ( self::$dependencies as $dependent => $dependencies ) {
|
||||
// Skip dependents that are no longer installed or aren't active.
|
||||
if ( ! array_key_exists( $dependent, self::$plugins ) || is_plugin_inactive( $dependent ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip plugins within a circular dependency tree or plugins that have no unmet dependencies.
|
||||
if ( in_array( $dependent, $circular_dependencies, true ) || ! self::has_unmet_dependencies( $dependent ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependents_to_deactivate[] = $dependent;
|
||||
|
||||
// Also add any plugins that rely on any of this plugin's dependents.
|
||||
$dependents_to_deactivate = array_merge(
|
||||
$dependents_to_deactivate,
|
||||
self::get_active_dependents_in_dependency_tree( $dependent )
|
||||
);
|
||||
}
|
||||
|
||||
$dependents_to_deactivate = array_unique( $dependents_to_deactivate );
|
||||
|
||||
deactivate_plugins( $dependents_to_deactivate );
|
||||
set_site_transient( 'wp_plugin_dependencies_deactivated_plugins', $dependents_to_deactivate, 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filepath of installed dependencies.
|
||||
* If a dependency is not installed, the filepath defaults to false.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array An array of install dependencies filepaths, relative to the plugins directory.
|
||||
*/
|
||||
protected static function get_dependency_filepaths() {
|
||||
if ( is_array( self::$dependency_filepaths ) ) {
|
||||
return self::$dependency_filepaths;
|
||||
}
|
||||
|
||||
self::$dependency_filepaths = array();
|
||||
|
||||
$plugin_dirnames = self::get_plugin_dirnames();
|
||||
foreach ( self::$dependency_slugs as $slug ) {
|
||||
if ( isset( $plugin_dirnames[ $slug ] ) ) {
|
||||
self::$dependency_filepaths[ $slug ] = $plugin_dirnames[ $slug ];
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$dependency_filepaths[ $slug ] = false;
|
||||
}
|
||||
|
||||
return self::$dependency_filepaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and stores dependency plugin data from the WordPress.org Plugin API.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*
|
||||
* @return array|void An array of dependency API data, or void on early exit.
|
||||
*/
|
||||
protected static function get_dependency_api_data() {
|
||||
global $pagenow;
|
||||
|
||||
if ( ! is_admin() || ( 'plugins.php' !== $pagenow && 'plugin-install.php' !== $pagenow ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_array( self::$dependency_api_data ) ) {
|
||||
return self::$dependency_api_data;
|
||||
}
|
||||
|
||||
$plugins = self::get_plugins();
|
||||
self::$dependency_api_data = (array) get_site_transient( 'wp_plugin_dependencies_plugin_data' );
|
||||
foreach ( self::$dependency_slugs as $slug ) {
|
||||
// Set transient for individual data, remove from self::$dependency_api_data if transient expired.
|
||||
if ( ! get_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}" ) ) {
|
||||
unset( self::$dependency_api_data[ $slug ] );
|
||||
set_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}", true, 12 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
if ( isset( self::$dependency_api_data[ $slug ] ) ) {
|
||||
if ( false === self::$dependency_api_data[ $slug ] ) {
|
||||
$dependency_file = self::get_dependency_filepath( $slug );
|
||||
|
||||
if ( false === $dependency_file ) {
|
||||
self::$dependency_api_data[ $slug ] = array( 'Name' => $slug );
|
||||
} else {
|
||||
self::$dependency_api_data[ $slug ] = array( 'Name' => $plugins[ $dependency_file ]['Name'] );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't hit the Plugin API if data exists.
|
||||
if ( ! empty( self::$dependency_api_data[ $slug ]['last_updated'] ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'plugins_api' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
}
|
||||
|
||||
$information = plugins_api(
|
||||
'plugin_information',
|
||||
array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'short_description' => true,
|
||||
'icons' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $information ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$dependency_api_data[ $slug ] = (array) $information;
|
||||
// plugins_api() returns 'name' not 'Name'.
|
||||
self::$dependency_api_data[ $information->slug ]['Name'] = self::$dependency_api_data[ $information->slug ]['name'];
|
||||
set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 );
|
||||
}
|
||||
|
||||
// Remove from self::$dependency_api_data if slug no longer a dependency.
|
||||
$differences = array_diff( array_keys( self::$dependency_api_data ), self::$dependency_slugs );
|
||||
foreach ( $differences as $difference ) {
|
||||
unset( self::$dependency_api_data[ $difference ] );
|
||||
}
|
||||
|
||||
ksort( self::$dependency_api_data );
|
||||
// Remove empty elements.
|
||||
self::$dependency_api_data = array_filter( self::$dependency_api_data );
|
||||
set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 );
|
||||
|
||||
return self::$dependency_api_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets plugin directory names.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array An array of plugin directory names.
|
||||
*/
|
||||
protected static function get_plugin_dirnames() {
|
||||
if ( is_array( self::$plugin_dirnames ) ) {
|
||||
return self::$plugin_dirnames;
|
||||
}
|
||||
|
||||
self::$plugin_dirnames = array();
|
||||
|
||||
$plugin_files = array_keys( self::get_plugins() );
|
||||
foreach ( $plugin_files as $plugin_file ) {
|
||||
$slug = self::convert_to_slug( $plugin_file );
|
||||
self::$plugin_dirnames[ $slug ] = $plugin_file;
|
||||
}
|
||||
|
||||
return self::$plugin_dirnames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets circular dependency data.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array[] An array of circular dependency pairings.
|
||||
*/
|
||||
protected static function get_circular_dependencies() {
|
||||
if ( is_array( self::$circular_dependencies_pairs ) ) {
|
||||
return self::$circular_dependencies_pairs;
|
||||
}
|
||||
|
||||
self::$circular_dependencies_slugs = array();
|
||||
|
||||
self::$circular_dependencies_pairs = array();
|
||||
foreach ( self::$dependencies as $dependent => $dependencies ) {
|
||||
/*
|
||||
* $dependent is in 'a/a.php' format. Dependencies are stored as slugs, i.e. 'a'.
|
||||
*
|
||||
* Convert $dependent to slug format for checking.
|
||||
*/
|
||||
$dependent_slug = self::convert_to_slug( $dependent );
|
||||
|
||||
self::$circular_dependencies_pairs = array_merge(
|
||||
self::$circular_dependencies_pairs,
|
||||
self::check_for_circular_dependencies( array( $dependent_slug ), $dependencies )
|
||||
);
|
||||
}
|
||||
|
||||
return self::$circular_dependencies_pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for circular dependencies.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $dependents Array of dependent plugins.
|
||||
* @param array $dependencies Array of plugins dependencies.
|
||||
* @return array A circular dependency pairing, or an empty array if none exists.
|
||||
*/
|
||||
protected static function check_for_circular_dependencies( $dependents, $dependencies ) {
|
||||
$circular_dependencies_pairs = array();
|
||||
|
||||
// Check for a self-dependency.
|
||||
$dependents_location_in_its_own_dependencies = array_intersect( $dependents, $dependencies );
|
||||
if ( ! empty( $dependents_location_in_its_own_dependencies ) ) {
|
||||
foreach ( $dependents_location_in_its_own_dependencies as $self_dependency ) {
|
||||
self::$circular_dependencies_slugs[] = $self_dependency;
|
||||
$circular_dependencies_pairs[] = array( $self_dependency, $self_dependency );
|
||||
|
||||
// No need to check for itself again.
|
||||
unset( $dependencies[ array_search( $self_dependency, $dependencies, true ) ] );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check each dependency to see:
|
||||
* 1. If it has dependencies.
|
||||
* 2. If its list of dependencies includes one of its own dependents.
|
||||
*/
|
||||
foreach ( $dependencies as $dependency ) {
|
||||
// Check if the dependency is also a dependent.
|
||||
$dependency_location_in_dependents = array_search( $dependency, self::$dependent_slugs, true );
|
||||
|
||||
if ( false !== $dependency_location_in_dependents ) {
|
||||
$dependencies_of_the_dependency = self::$dependencies[ $dependency_location_in_dependents ];
|
||||
|
||||
foreach ( $dependents as $dependent ) {
|
||||
// Check if its dependencies includes one of its own dependents.
|
||||
$dependent_location_in_dependency_dependencies = array_search(
|
||||
$dependent,
|
||||
$dependencies_of_the_dependency,
|
||||
true
|
||||
);
|
||||
|
||||
if ( false !== $dependent_location_in_dependency_dependencies ) {
|
||||
self::$circular_dependencies_slugs[] = $dependent;
|
||||
self::$circular_dependencies_slugs[] = $dependency;
|
||||
$circular_dependencies_pairs[] = array( $dependent, $dependency );
|
||||
|
||||
// Remove the dependent from its dependency's dependencies.
|
||||
unset( $dependencies_of_the_dependency[ $dependent_location_in_dependency_dependencies ] );
|
||||
}
|
||||
}
|
||||
|
||||
$dependents[] = $dependency;
|
||||
|
||||
/*
|
||||
* Now check the dependencies of the dependency's dependencies for the dependent.
|
||||
*
|
||||
* Yes, that does make sense.
|
||||
*/
|
||||
$circular_dependencies_pairs = array_merge(
|
||||
$circular_dependencies_pairs,
|
||||
self::check_for_circular_dependencies( $dependents, array_unique( $dependencies_of_the_dependency ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $circular_dependencies_pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a plugin filepath to a slug.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $plugin_file The plugin's filepath, relative to the plugins directory.
|
||||
* @return string The plugin's slug.
|
||||
*/
|
||||
protected static function convert_to_slug( $plugin_file ) {
|
||||
if ( 'hello.php' === $plugin_file ) {
|
||||
return 'hello-dolly';
|
||||
}
|
||||
return str_contains( $plugin_file, '/' ) ? dirname( $plugin_file ) : str_replace( '.php', '', $plugin_file );
|
||||
}
|
||||
}
|
@ -3359,7 +3359,7 @@ function wp_get_image_mime( $file ) {
|
||||
* specification and the AV1-AVIF spec, see https://aomediacodec.github.io/av1-avif/v1.1.0.html#brands.
|
||||
*/
|
||||
|
||||
// Divide the header string into 4 byte groups.
|
||||
// Divide the header string into 4 byte groups.
|
||||
$magic = str_split( $magic, 8 );
|
||||
|
||||
if (
|
||||
|
@ -986,6 +986,7 @@ function wp_get_active_and_valid_plugins() {
|
||||
|
||||
$network_plugins = is_multisite() ? wp_get_active_network_plugins() : false;
|
||||
|
||||
$invalid_plugins = array();
|
||||
foreach ( $active_plugins as $plugin ) {
|
||||
if ( ! validate_file( $plugin ) // $plugin must validate as file.
|
||||
&& str_ends_with( $plugin, '.php' ) // $plugin must end with '.php'.
|
||||
@ -994,6 +995,20 @@ function wp_get_active_and_valid_plugins() {
|
||||
&& ( ! $network_plugins || ! in_array( WP_PLUGIN_DIR . '/' . $plugin, $network_plugins, true ) )
|
||||
) {
|
||||
$plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
|
||||
} else {
|
||||
$invalid_plugins[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $invalid_plugins ) ) {
|
||||
$all_plugin_data = get_option( 'plugin_data', array() );
|
||||
|
||||
if ( ! empty( $all_plugin_data ) ) {
|
||||
foreach ( $invalid_plugins as $invalid_plugin ) {
|
||||
unset( $all_plugin_data[ $invalid_plugin ] );
|
||||
}
|
||||
|
||||
update_option( 'plugin_data', $all_plugin_data );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1190,6 +1205,7 @@ function is_protected_ajax_action() {
|
||||
'search-install-plugins', // Searching for a plugin in the plugin install screen.
|
||||
'update-plugin', // Update an existing plugin.
|
||||
'update-theme', // Update an existing theme.
|
||||
'activate-plugin', // Activating an existing plugin.
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -412,6 +412,12 @@ wp_plugin_directory_constants();
|
||||
|
||||
$GLOBALS['wp_plugin_paths'] = array();
|
||||
|
||||
// Load and initialize WP_Plugin_Dependencies.
|
||||
require_once ABSPATH . WPINC . '/class-wp-plugin-dependencies.php';
|
||||
if ( ! defined( 'WP_RUN_CORE_TESTS' ) ) {
|
||||
WP_Plugin_Dependencies::initialize();
|
||||
}
|
||||
|
||||
// Load must-use plugins.
|
||||
foreach ( wp_get_mu_plugins() as $mu_plugin ) {
|
||||
$_wp_plugin_file = $mu_plugin;
|
||||
@ -486,7 +492,91 @@ if ( ! is_multisite() && wp_is_fatal_error_handler_enabled() ) {
|
||||
}
|
||||
|
||||
// Load active plugins.
|
||||
$all_plugin_data = get_option( 'plugin_data', array() );
|
||||
$failed_plugins = array();
|
||||
$update_plugin_data = false;
|
||||
foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
|
||||
$plugin_file = str_replace( trailingslashit( WP_PLUGIN_DIR ), '', $plugin );
|
||||
if ( ! isset( $all_plugin_data[ $plugin_file ] ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$all_plugin_data[ $plugin_file ] = get_plugin_data( WP_PLUGIN_DIR . "/$plugin_file" );
|
||||
|
||||
$update_plugin_data = true;
|
||||
}
|
||||
|
||||
$plugin_headers = $all_plugin_data[ $plugin_file ];
|
||||
$errors = array();
|
||||
$requirements = array(
|
||||
'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'] );
|
||||
$compatible_php = is_php_version_compatible( $requirements['requires_php'] );
|
||||
$dependencies_met = ! WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file );
|
||||
|
||||
$php_update_message = '</p><p>' . sprintf(
|
||||
/* translators: %s: URL to Update PHP page. */
|
||||
__( '<a href="%s">Learn more about updating PHP</a>.' ),
|
||||
esc_url( wp_get_update_php_url() )
|
||||
);
|
||||
|
||||
$annotation = wp_get_update_php_annotation();
|
||||
|
||||
if ( $annotation ) {
|
||||
$php_update_message .= '</p><p><em>' . $annotation . '</em>';
|
||||
}
|
||||
|
||||
if ( ! $dependencies_met ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: %s: The plugin's name. */
|
||||
_x( '%s has unmet dependencies.', 'plugin' ),
|
||||
$plugin_headers['Name']
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $compatible_wp && ! $compatible_php ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */
|
||||
_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' ),
|
||||
get_bloginfo( 'version' ),
|
||||
PHP_VERSION,
|
||||
$plugin_headers['Name'],
|
||||
$requirements['requires'],
|
||||
$requirements['requires_php']
|
||||
) . $php_update_message;
|
||||
} elseif ( ! $compatible_php ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */
|
||||
_x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ),
|
||||
PHP_VERSION,
|
||||
$plugin_headers['Name'],
|
||||
$requirements['requires_php']
|
||||
) . $php_update_message;
|
||||
} elseif ( ! $compatible_wp ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: 1: Current WordPress version, 2: Plugin name, 3: Required WordPress version. */
|
||||
_x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ),
|
||||
get_bloginfo( 'version' ),
|
||||
$plugin_headers['Name'],
|
||||
$requirements['requires']
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $errors ) ) {
|
||||
$failed_plugins[ $plugin_file ] = '';
|
||||
foreach ( $errors as $error ) {
|
||||
$failed_plugins[ $plugin_file ] .= wp_get_admin_notice(
|
||||
$error,
|
||||
array(
|
||||
'type' => 'error',
|
||||
'dismissible' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
wp_register_plugin_realpath( $plugin );
|
||||
|
||||
$_wp_plugin_file = $plugin;
|
||||
@ -504,6 +594,24 @@ foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
|
||||
}
|
||||
unset( $plugin, $_wp_plugin_file );
|
||||
|
||||
if ( $update_plugin_data ) {
|
||||
update_option( 'plugin_data', $all_plugin_data );
|
||||
}
|
||||
|
||||
if ( ! empty( $failed_plugins ) ) {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function () use ( $failed_plugins ) {
|
||||
global $pagenow;
|
||||
|
||||
if ( 'index.php' === $pagenow || 'plugins.php' === $pagenow ) {
|
||||
echo implode( '', $failed_plugins );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
unset( $failed_plugins );
|
||||
|
||||
// Load pluggable functions.
|
||||
require ABSPATH . WPINC . '/pluggable.php';
|
||||
require ABSPATH . WPINC . '/pluggable-deprecated.php';
|
||||
|
126
tests/phpunit/tests/admin/plugin-dependencies/base.php
Normal file
126
tests/phpunit/tests/admin/plugin-dependencies/base.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Test case for the Plugin Dependencies tests.
|
||||
*
|
||||
* @package WP_Plugin_Dependencies
|
||||
*
|
||||
* Abstracts the common properties and tasks for the Plugin Dependencies tests.
|
||||
*/
|
||||
abstract class WP_PluginDependencies_UnitTestCase extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Stores an instance of WP_Plugin_Dependencies
|
||||
* for managing property visibility during tests.
|
||||
*
|
||||
* @var WP_Plugin_Dependencies
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Stores a list of static properties and their default values.
|
||||
* for resetting after each test runs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $static_properties = array(
|
||||
'plugins' => null,
|
||||
'plugin_dirnames' => null,
|
||||
'dependencies' => null,
|
||||
'dependency_slugs' => null,
|
||||
'dependent_slugs' => null,
|
||||
'dependency_api_data' => null,
|
||||
'dependency_filepaths' => null,
|
||||
'circular_dependencies_pairs' => null,
|
||||
'circular_dependencies_slugs' => null,
|
||||
);
|
||||
|
||||
/**
|
||||
* An array of reflected class members.
|
||||
*
|
||||
* @var ReflectionMethod[]|ReflectionProperty[]
|
||||
*/
|
||||
protected static $reflected_members = array();
|
||||
|
||||
/**
|
||||
* Sets up the WP_Plugin_Dependencies instance before any tests run.
|
||||
*/
|
||||
public static function set_up_before_class() {
|
||||
parent::set_up_before_class();
|
||||
|
||||
self::$instance = new WP_Plugin_Dependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the '$reflected_members' property after all tests run.
|
||||
*/
|
||||
public static function tear_down_after_class() {
|
||||
self::$reflected_members = array();
|
||||
|
||||
parent::tear_down_after_class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all static properties to a default value after each test.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
foreach ( self::$static_properties as $name => $default_value ) {
|
||||
$this->set_property_value( $name, $default_value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily modifies the accessibility of a property to change its value.
|
||||
*
|
||||
* @param string $property The property's name.
|
||||
* @param mixed $value The new value.
|
||||
*/
|
||||
public function set_property_value( $property, $value ) {
|
||||
if ( ! isset( self::$reflected_members[ $property ] ) ) {
|
||||
self::$reflected_members[ $property ] = new ReflectionProperty( self::$instance, $property );
|
||||
}
|
||||
|
||||
self::$reflected_members[ $property ]->setAccessible( true );
|
||||
self::$reflected_members[ $property ]->setValue( self::$instance, $value );
|
||||
self::$reflected_members[ $property ]->setAccessible( false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily modifies the accessibility of a property to get its value.
|
||||
*
|
||||
* @param string $property The property's name.
|
||||
* @return mixed The value of the property.
|
||||
*/
|
||||
public function get_property_value( $property ) {
|
||||
if ( ! isset( self::$reflected_members[ $property ] ) ) {
|
||||
self::$reflected_members[ $property ] = new ReflectionProperty( self::$instance, $property );
|
||||
}
|
||||
|
||||
self::$reflected_members[ $property ]->setAccessible( true );
|
||||
$value = self::$reflected_members[ $property ]->getValue( self::$instance );
|
||||
self::$reflected_members[ $property ]->setAccessible( false );
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily modifies the accessibility of a method to invoke it
|
||||
* and return its result.
|
||||
*
|
||||
* @param string $method The method's name.
|
||||
* @param mixed ...$args Arguments for the method.
|
||||
* @return mixed The result of the method call.
|
||||
*/
|
||||
protected function call_method( $method, ...$args ) {
|
||||
if ( ! isset( self::$reflected_members[ $method ] ) ) {
|
||||
self::$reflected_members[ $method ] = new ReflectionMethod( self::$instance, $method );
|
||||
}
|
||||
|
||||
self::$reflected_members[ $method ]->setAccessible( true );
|
||||
$value = self::$reflected_members[ $method ]->invokeArgs( self::$instance, $args );
|
||||
self::$reflected_members[ $method ]->setAccessible( false );
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependencies() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependencies
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependencies extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependencies will return an empty array.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_an_empty_array_when_a_plugin_has_no_dependencies() {
|
||||
$this->assertSame( array(), self::$instance::get_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with dependencies will return an array of dependencies.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_an_array_of_dependencies_when_a_plugin_has_dependencies() {
|
||||
$expected = array( 'dependency', 'dependency2' );
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => $expected )
|
||||
);
|
||||
$this->assertSame( $expected, self::$instance::get_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependency_data() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_data
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_api_data
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependencyData extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that dependency data is retrieved.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*/
|
||||
public function test_should_get_dependency_data() {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = 'plugins.php';
|
||||
set_current_screen( 'plugins.php' );
|
||||
|
||||
$expected = array( 'name' => 'Dependency 1' );
|
||||
$this->set_property_value( 'dependency_api_data', array( 'dependency' => $expected ) );
|
||||
|
||||
$actual = self::$instance::get_dependency_data( 'dependency' );
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$this->assertSame( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that false is returned when no dependency data exists.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*/
|
||||
public function test_should_return_false_when_no_dependency_data_exists() {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = 'plugins.php';
|
||||
set_current_screen( 'plugins.php' );
|
||||
|
||||
$this->set_property_value( 'dependency_api_data', array() );
|
||||
|
||||
$actual = self::$instance::get_dependency_data( 'dependency' );
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$this->assertFalse( $actual );
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependency_filepath() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_filepath
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_filepaths
|
||||
* @covers WP_Plugin_Dependencies::get_plugin_dirnames
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependencyFilepath extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that the expected dependency filepaths are retrieved for installed dependencies.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @dataProvider data_get_dependency_filepath
|
||||
*
|
||||
* @param string[] $dependency_slug The dependency slug.
|
||||
* @param string[] $plugins An array of plugin paths.
|
||||
* @param string|false $expected The expected result.
|
||||
*/
|
||||
public function test_should_return_filepaths_for_installed_dependencies( $dependency_slug, $plugins, $expected ) {
|
||||
$this->set_property_value( 'plugins', $plugins );
|
||||
$this->assertNull( $this->get_property_value( 'dependency_filepaths' ) );
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
self::$instance::get_dependency_filepath( $dependency_slug ),
|
||||
'The incorrect filepath was returned.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_get_dependency_filepath() {
|
||||
return array(
|
||||
'no plugins' => array(
|
||||
'dependency_slug' => 'dependency',
|
||||
'plugins' => array(),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that starts with slug/' => array(
|
||||
'dependency_slug' => 'dependency',
|
||||
'plugins' => array(
|
||||
'dependency-pro/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that ends with slug/' => array(
|
||||
'dependency_slugs' => 'dependency',
|
||||
'plugins' => array(
|
||||
'addon-for-dependency/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that does not exist' => array(
|
||||
'dependency_slugs' => 'dependency2',
|
||||
'plugins' => array(
|
||||
'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency2' ),
|
||||
),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that exists' => array(
|
||||
'dependency_slugs' => 'dependency',
|
||||
'plugins' => array(
|
||||
'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
),
|
||||
'expected' => 'dependency/dependency.php',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an existing value for dependency filepaths is returned.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_existing_value_for_dependency_filepaths() {
|
||||
$expected = 'dependency/dependency.php';
|
||||
|
||||
$this->set_property_value( 'dependency_filepaths', array( 'dependency' => $expected ) );
|
||||
|
||||
/*
|
||||
* If existing dependency filepaths are not returned,
|
||||
* they'll be built from this data.
|
||||
*
|
||||
* This data is explicitly set to ensure that no
|
||||
* test plugins ever interfere with this test.
|
||||
*/
|
||||
$this->set_property_value(
|
||||
'dependency_slugs',
|
||||
array( 'dependency', 'dependency2', 'dependency3' )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array(
|
||||
// This is flipped as paths are stored in the keys.
|
||||
'dependency/dependency.php' => array(),
|
||||
'dependency2/dependency2.php' => array(),
|
||||
'dependency3/dependency3.php' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertSame( $expected, self::$instance::get_dependency_filepath( 'dependency' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an empty array is returned when
|
||||
* no plugin directory names are stored.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_empty_array_for_no_plugin_dirnames() {
|
||||
$this->set_property_value( 'dependency_slugs', array() );
|
||||
$this->assertFalse( self::$instance::get_dependency_filepath( 'dependency' ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependency_names() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_names
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_api_data
|
||||
* @covers WP_Plugin_Dependencies::get_dependencies
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_filepaths
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependencyNames extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that dependency names are retrieved.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*/
|
||||
public function test_should_get_dependency_names() {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = 'plugins.php';
|
||||
set_current_screen( 'plugins.php' );
|
||||
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array( 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ) )
|
||||
);
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array(
|
||||
'dependency' => 'dependency/dependency.php',
|
||||
'dependency2' => 'dependency2/dependency2.php',
|
||||
)
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_api_data',
|
||||
array(
|
||||
'dependency' => array(
|
||||
'name' => 'Dependency 1',
|
||||
),
|
||||
'dependency2' => array(
|
||||
'name' => 'Dependency 2',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$actual = self::$instance::get_dependency_names( 'dependent/dependent.php' );
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$this->assertSame(
|
||||
array(
|
||||
'dependency' => 'Dependency 1',
|
||||
'dependency2' => 'Dependency 2',
|
||||
),
|
||||
$actual
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependency slugs are used if their name is not available.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*/
|
||||
public function test_should_use_dependency_name_from_file() {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = 'plugins.php';
|
||||
set_current_screen( 'plugins.php' );
|
||||
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ),
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => '',
|
||||
),
|
||||
'dependency2/dependency2.php' => array(
|
||||
'Name' => 'Dependency 2',
|
||||
'RequiresPlugins' => '',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array(
|
||||
'dependency' => 'dependency/dependency.php',
|
||||
'dependency2' => 'dependency2/dependency2.php',
|
||||
)
|
||||
);
|
||||
|
||||
// The plugins are not in the Plugins repository.
|
||||
$this->set_property_value( 'dependency_api_data', array() );
|
||||
|
||||
$actual = self::$instance::get_dependency_names( 'dependent/dependent.php' );
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$this->assertSame(
|
||||
array(
|
||||
'dependency' => 'Dependency 1',
|
||||
'dependency2' => 'Dependency 2',
|
||||
),
|
||||
$actual
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependency slugs are used if their name is not available.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*/
|
||||
public function test_should_use_dependency_slugs() {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = 'plugins.php';
|
||||
set_current_screen( 'plugins.php' );
|
||||
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array( 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ) )
|
||||
);
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
// The plugins are not in the Plugins repository.
|
||||
$this->set_property_value( 'dependency_api_data', array() );
|
||||
|
||||
$actual = self::$instance::get_dependency_names( 'dependent/dependent.php' );
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$this->assertSame(
|
||||
array(
|
||||
'dependency' => 'dependency',
|
||||
'dependency2' => 'dependency2',
|
||||
),
|
||||
$actual
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that `$dependency_api_data` is set when it's not already available.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*/
|
||||
public function test_should_set_dependency_data_when_not_already_available() {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = 'plugins.php';
|
||||
set_current_screen( 'plugins.php' );
|
||||
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array(
|
||||
'dependent/dependent.php' => array(
|
||||
'Name' => 'Dependent 1',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => '',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$this->set_property_value( 'dependency_slugs', array( 'dependency' ) );
|
||||
|
||||
set_site_transient( 'wp_plugin_dependencies_plugin_data', array( 'dependency' => false ) );
|
||||
set_site_transient( 'wp_plugin_dependencies_plugin_timeout_dependency', true, 12 * HOUR_IN_SECONDS );
|
||||
self::$instance::get_dependency_names( 'dependent' );
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$this->assertSame(
|
||||
array( 'dependency' => array( 'Name' => 'Dependency 1' ) ),
|
||||
$this->get_property_value( 'dependency_api_data' )
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependent_filepath() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependent_filepath
|
||||
* @covers WP_Plugin_Dependencies::get_plugin_dirnames
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependentFilepath extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that the expected dependent filepath is retrieved.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @dataProvider data_get_dependent_filepath
|
||||
*
|
||||
* @param string $dependent_slug The dependent slug.
|
||||
* @param string[] $plugins An array of plugin data.
|
||||
* @param string|false $expected The expected result.
|
||||
*/
|
||||
public function test_should_return_filepaths_for_installed_dependents( $dependent_slug, $plugins, $expected ) {
|
||||
$this->set_property_value( 'plugins', $plugins );
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
self::$instance::get_dependent_filepath( $dependent_slug ),
|
||||
'The incorrect filepath was returned.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_get_dependent_filepath() {
|
||||
return array(
|
||||
'a plugin that exists' => array(
|
||||
'dependent_slug' => 'dependent',
|
||||
'plugins' => array( 'dependent/dependent.php' => array( 'RequiresPlugins' => 'woocommerce' ) ),
|
||||
'expected' => 'dependent/dependent.php',
|
||||
),
|
||||
'no plugins' => array(
|
||||
'dependent_slug' => 'dependent',
|
||||
'plugins' => array(),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that starts with slug/' => array(
|
||||
'dependent_slug' => 'dependent',
|
||||
'plugins' => array( 'dependent-pro/dependent.php' => array( 'RequiresPlugins' => 'woocommerce' ) ),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that ends with slug/' => array(
|
||||
'dependent_slug' => 'dependent',
|
||||
'plugins' => array( 'not-dependent/not-dependent.php' => array( 'RequiresPlugins' => 'woocommerce' ) ),
|
||||
'expected' => false,
|
||||
),
|
||||
'a plugin that does not exist' => array(
|
||||
'dependent_slug' => 'dependent2',
|
||||
'plugins' => array( 'dependent/dependent.php' => array( 'RequiresPlugins' => 'woocommerce' ) ),
|
||||
'expected' => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependent_names() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependent_names
|
||||
* @covers WP_Plugin_Dependencies::get_plugins
|
||||
* @covers WP_Plugin_Dependencies::convert_to_slug
|
||||
* @covers WP_Plugin_Dependencies::get_dependents
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependentNames extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that dependent names are retrieved.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_get_dependent_names() {
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array(
|
||||
'dependent/dependent.php' => array(
|
||||
'Name' => 'Dependent 1',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
'dependent2/dependent2.php' => array(
|
||||
'Name' => 'Dependent 2',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertSame(
|
||||
array( 'Dependent 1', 'Dependent 2' ),
|
||||
self::$instance::get_dependent_names( 'dependency/dependency.php' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependent names are sorted.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_sort_dependent_names() {
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array(
|
||||
'dependent2/dependent2.php' => array(
|
||||
'Name' => 'Dependent 2',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
'dependent/dependent.php' => array(
|
||||
'Name' => 'Dependent 1',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertSame(
|
||||
array( 'Dependent 1', 'Dependent 2' ),
|
||||
self::$instance::get_dependent_names( 'dependency/dependency.php' )
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::get_dependents() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependents
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_GetDependents extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependents will return an empty array.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_an_empty_array_when_a_plugin_has_no_dependents() {
|
||||
self::$instance::initialize();
|
||||
$this->assertSame(
|
||||
array(),
|
||||
self::$instance::get_dependents( 'dependency' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with dependents will return an array of dependents.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_an_array_of_dependents_when_a_plugin_has_dependents() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'dependency' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
array( 'dependent/dependent.php', 'dependent2/dependent2.php' ),
|
||||
self::$instance::get_dependents( 'dependency' )
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::has_active_dependents() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::has_active_dependents
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_HasActiveDependents extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependents will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_dependents() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent/dependent.php' ) );
|
||||
|
||||
$this->assertFalse( self::$instance::has_active_dependents( 'dependency2/dependency2.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with active dependents will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_active_dependents() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent/dependent.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_active_dependents( 'dependency/dependency.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with one inactive and one active dependent will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_one_inactive_and_one_active_dependent() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent2/dependent2.php' => array( 'dependency' ),
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent/dependent.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_active_dependents( 'dependency/dependency.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with one active and one inactive dependent will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_one_active_and_one_inactive_dependent() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'dependency' ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent/dependent.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_active_dependents( 'dependency/dependency.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when a plugin with active dependents is earlier in the list,
|
||||
* it will return true if a later plugin has no active dependents.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_the_earlier_plugin_has_active_dependents_but_the_later_plugin_does_not() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent2/dependent2.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'dependency2' ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent/dependent.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_active_dependents( 'dependency/dependency.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when a plugin with active dependents is later in the list,
|
||||
* it will return true if an earlier plugin has no active dependents.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_the_later_plugin_has_active_dependents_but_the_earlier_plugin_does_not() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent2/dependent2.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'dependency2' ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent2/dependent2.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_active_dependents( 'dependency2/dependency2.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependents will return false.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_active_dependents() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->assertFalse( self::$instance::has_active_dependents( 'dependency/dependency.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when a plugin with no active dependents is earlier in the list,
|
||||
* it will return false if a later plugin has active dependents.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_the_earlier_plugin_has_no_active_dependents_but_the_later_plugin_does() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent2/dependent2.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'dependency2' ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent2/dependent2.php' ) );
|
||||
|
||||
$this->assertFalse( self::$instance::has_active_dependents( 'dependency/dependency.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when a plugin with no active dependents is later in the list,
|
||||
* it will return false if an earlier plugin has active dependents.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_the_later_plugin_has_no_active_dependents_but_the_earlier_plugin_does() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent2/dependent2.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array(
|
||||
'dependent/dependent.php' => array( 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'dependency2' ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependent/dependent.php' ) );
|
||||
|
||||
$this->assertFalse( self::$instance::has_active_dependents( 'dependency2/dependency2.php' ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::has_circular_dependency() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::has_circular_dependency
|
||||
* @covers WP_Plugin_Dependencies::get_circular_dependencies
|
||||
* @covers WP_Plugin_Dependencies::check_for_circular_dependencies
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_HasCircularDependency extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with a circular dependency will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @dataProvider data_circular_dependencies
|
||||
*
|
||||
* @param string $plugin_to_check The plugin file of the plugin to check.
|
||||
* @param array[] $plugins An array of plugins.
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_circular_dependency( $plugin_to_check, $plugins ) {
|
||||
$this->set_property_value( 'plugins', $plugins );
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertTrue( self::$instance::has_circular_dependency( $plugin_to_check ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_circular_dependencies() {
|
||||
return array(
|
||||
'a plugin that depends on itself' => array(
|
||||
'plugin_to_check' => 'dependency/dependency.php',
|
||||
'plugins' => array(
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
),
|
||||
),
|
||||
'two plugins' => array(
|
||||
'plugin_to_check' => 'dependency/dependency.php',
|
||||
'plugins' => array(
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => 'dependency2',
|
||||
),
|
||||
'dependency2/dependency2.php' => array(
|
||||
'Name' => 'Dependency 2',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
),
|
||||
),
|
||||
'three plugins' => array(
|
||||
'plugin_to_check' => 'dependency/dependency.php',
|
||||
'plugins' => array(
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => 'dependency2',
|
||||
),
|
||||
'dependency2/dependency2.php' => array(
|
||||
'Name' => 'Dependency 2',
|
||||
'RequiresPlugins' => 'dependency3',
|
||||
),
|
||||
'dependency3/dependency3.php' => array(
|
||||
'Name' => 'Dependency 3',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
),
|
||||
),
|
||||
'four plugins' => array(
|
||||
'plugin_to_check' => 'dependency/dependency.php',
|
||||
'plugins' => array(
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => 'dependency4',
|
||||
),
|
||||
'dependency2/dependency2.php' => array(
|
||||
'Name' => 'Dependency 2',
|
||||
'RequiresPlugins' => 'dependency3',
|
||||
),
|
||||
'dependency3/dependency3.php' => array(
|
||||
'Name' => 'Dependency 3',
|
||||
'RequiresPlugins' => 'dependency',
|
||||
),
|
||||
'dependency4/dependency4.php' => array(
|
||||
'Name' => 'Dependency 4',
|
||||
'RequiresPlugins' => 'dependency2',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no circular dependencies will return false.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_circular_dependency() {
|
||||
$this->set_property_value(
|
||||
'plugins',
|
||||
array(
|
||||
'dependency/dependency.php' => array(
|
||||
'Name' => 'Dependency 1',
|
||||
'RequiresPlugins' => 'dependency2',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertFalse( self::$instance::has_circular_dependency( 'dependent/dependent.php' ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::has_dependencies() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::has_dependencies
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_HasDependencies extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with dependencies will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_dependencies() {
|
||||
$this->set_property_value( 'dependencies', array( 'dependent/dependent.php' => array() ) );
|
||||
$this->assertTrue( self::$instance::has_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependencies will return false.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_dependencies() {
|
||||
$this->set_property_value( 'dependencies', array( 'dependent2/dependent2.php' => array() ) );
|
||||
$this->assertFalse( self::$instance::has_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::has_dependents() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::has_dependents
|
||||
* @covers WP_Plugin_Dependencies::convert_to_slug
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_HasDependents extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with dependents will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_dependents() {
|
||||
$this->set_property_value( 'dependency_slugs', array( 'dependent' ) );
|
||||
$this->assertTrue( self::$instance::has_dependents( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a single file plugin with dependents will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_single_file_plugin_has_dependents() {
|
||||
$this->set_property_value( 'dependency_slugs', array( 'dependent' ) );
|
||||
$this->assertTrue( self::$instance::has_dependents( 'dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependents will return false.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_dependents() {
|
||||
$this->set_property_value( 'dependency_slugs', array( 'dependent2' ) );
|
||||
$this->assertFalse( self::$instance::has_dependents( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that 'hello.php' is converted to 'hello-dolly'.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_convert_hellophp_to_hello_dolly() {
|
||||
$this->set_property_value( 'dependency_slugs', array( 'hello-dolly' ) );
|
||||
$this->assertTrue( self::$instance::has_dependents( 'hello.php' ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::has_unmet_dependencies() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::has_unmet_dependencies
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_HasUnmetDependencies extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that a plugin with no dependencies will return false.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_dependencies() {
|
||||
$this->set_property_value( 'dependencies', array( 'dependent/dependent.php' => array( 'dependency' ) ) );
|
||||
$this->assertFalse( self::$instance::has_unmet_dependencies( 'dependent2/dependent2.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin whose dependencies are installed and active will return false.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_false_when_a_plugin_has_no_unmet_dependencies() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array( 'dependency' => 'dependency/dependency.php' )
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependency/dependency.php' ) );
|
||||
|
||||
$this->assertFalse( self::$instance::has_unmet_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with a dependency that is not installed will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_a_dependency_that_is_not_installed() {
|
||||
self::$instance::initialize();
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->assertTrue( self::$instance::has_unmet_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with a dependency that is inactive will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_a_dependency_that_is_inactive() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array( 'dependency' => 'dependency/dependency.php' )
|
||||
);
|
||||
|
||||
$this->assertTrue( self::$instance::has_unmet_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with one dependency that is active and one dependency that is inactive will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_one_active_dependency_and_one_inactive_dependency() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency', 'dependency2' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array(
|
||||
'dependency' => 'dependency/dependency.php',
|
||||
'dependency2' => 'dependency2/dependency2.php',
|
||||
)
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependency/dependency.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_unmet_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with one dependency that is active and one dependency that is not installed will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_one_active_dependency_and_one_that_is_not_installed() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency', 'dependency2' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array( 'dependency' => 'dependency/dependency.php' )
|
||||
);
|
||||
|
||||
update_option( 'active_plugins', array( 'dependency/dependency.php' ) );
|
||||
|
||||
$this->assertTrue( self::$instance::has_unmet_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a plugin with one dependency that is inactive and one dependency that is not installed will return true.
|
||||
*
|
||||
* @ticket 22316
|
||||
*/
|
||||
public function test_should_return_true_when_a_plugin_has_one_inactive_dependency_and_one_that_is_not_installed() {
|
||||
$this->set_property_value(
|
||||
'dependencies',
|
||||
array( 'dependent/dependent.php' => array( 'dependency', 'dependency2' ) )
|
||||
);
|
||||
|
||||
$this->set_property_value(
|
||||
'dependency_filepaths',
|
||||
array( 'dependency' => 'dependency/dependency.php' )
|
||||
);
|
||||
|
||||
$this->assertTrue( self::$instance::has_unmet_dependencies( 'dependent/dependent.php' ) );
|
||||
}
|
||||
}
|
336
tests/phpunit/tests/admin/plugin-dependencies/initialize.php
Normal file
336
tests/phpunit/tests/admin/plugin-dependencies/initialize.php
Normal file
@ -0,0 +1,336 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the WP_Plugin_Dependencies::initialize() method.
|
||||
*
|
||||
* @package WordPress
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
* @group plugins
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::initialize
|
||||
*/
|
||||
class Tests_Admin_WPPluginDependencies_Initialize extends WP_PluginDependencies_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that `$dependency_api_data` is set on certain screens.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_api_data
|
||||
* @covers WP_Plugin_Dependencies::get_plugins
|
||||
*
|
||||
* @dataProvider data_screens
|
||||
*
|
||||
* @global string $pagenow The filename of the current screen.
|
||||
*
|
||||
* @param string $screen The screen file.
|
||||
*/
|
||||
public function test_should_set_dependency_api_data_on_certain_screens( $screen ) {
|
||||
global $pagenow;
|
||||
|
||||
// Backup $pagenow.
|
||||
$old_pagenow = $pagenow;
|
||||
|
||||
// Ensure is_admin() and screen checks pass.
|
||||
$pagenow = $screen;
|
||||
set_current_screen( $screen );
|
||||
|
||||
self::$instance::initialize();
|
||||
|
||||
// Restore $pagenow.
|
||||
$pagenow = $old_pagenow;
|
||||
|
||||
$dependency_api_data = $this->get_property_value( 'dependency_api_data' );
|
||||
|
||||
$this->assertIsArray( $dependency_api_data, '$dependency_api_data is not an array.' );
|
||||
$this->assertEmpty( $dependency_api_data, '$dependency_api_data is not empty.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_screens() {
|
||||
return array(
|
||||
'plugins.php' => array(
|
||||
'screen' => 'plugins.php',
|
||||
),
|
||||
'plugin-install.php' => array(
|
||||
'screen' => 'plugin-install.php',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that `$dependency_api_data` is not set by default.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::get_dependency_api_data
|
||||
*/
|
||||
public function test_should_not_set_dependency_api_data() {
|
||||
self::$instance::initialize();
|
||||
|
||||
$dependency_api_data = $this->get_property_value( 'dependency_api_data' );
|
||||
|
||||
$this->assertNull( $dependency_api_data, '$dependency_api_data was set.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependency slugs are loaded and sanitized.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::read_dependencies_from_plugin_headers
|
||||
* @covers WP_Plugin_Dependencies::sanitize_dependency_slugs
|
||||
*
|
||||
* @dataProvider data_should_sanitize_slugs
|
||||
*
|
||||
* @param string $requires_plugins The unsanitized dependency slug(s).
|
||||
* @param array $expected Optional. The sanitized dependency slug(s). Default empty array.
|
||||
*/
|
||||
public function test_initialize_should_load_and_sanitize_dependency_slugs_from_plugin_headers( $requires_plugins, $expected = array() ) {
|
||||
$this->set_property_value( 'plugins', array( 'dependent/dependent.php' => array( 'RequiresPlugins' => $requires_plugins ) ) );
|
||||
self::$instance->initialize();
|
||||
$this->assertSame( $expected, $this->get_property_value( 'dependency_slugs' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_should_sanitize_slugs() {
|
||||
return array(
|
||||
// Valid slugs.
|
||||
'one dependency' => array(
|
||||
'requires_plugins' => 'hello-dolly',
|
||||
'expected' => array( 'hello-dolly' ),
|
||||
),
|
||||
'two dependencies in alphabetical order' => array(
|
||||
'requires_plugins' => 'hello-dolly, woocommerce',
|
||||
'expected' => array(
|
||||
'hello-dolly',
|
||||
'woocommerce',
|
||||
),
|
||||
),
|
||||
'two dependencies in reverse alphabetical order' => array(
|
||||
'requires_plugins' => 'woocommerce, hello-dolly',
|
||||
'expected' => array(
|
||||
'hello-dolly',
|
||||
'woocommerce',
|
||||
),
|
||||
),
|
||||
'two dependencies with a space' => array(
|
||||
'requires_plugins' => 'hello-dolly , woocommerce',
|
||||
'expected' => array(
|
||||
'hello-dolly',
|
||||
'woocommerce',
|
||||
),
|
||||
),
|
||||
'a repeated dependency' => array(
|
||||
'requires_plugins' => 'hello-dolly, woocommerce, hello-dolly',
|
||||
'expected' => array(
|
||||
'hello-dolly',
|
||||
'woocommerce',
|
||||
),
|
||||
),
|
||||
'a dependency with multiple dashes' => array(
|
||||
'requires_plugins' => 'this-is-a-valid-slug',
|
||||
'expected' => array( 'this-is-a-valid-slug' ),
|
||||
),
|
||||
'a dependency starting with numbers' => array(
|
||||
'requires_plugins' => '123slug',
|
||||
'expected' => array( '123slug' ),
|
||||
),
|
||||
'a dependency with a trailing comma' => array(
|
||||
'requires_plugins' => 'hello-dolly,',
|
||||
'expected' => array( 'hello-dolly' ),
|
||||
),
|
||||
'a dependency with a leading comma' => array(
|
||||
'requires_plugins' => ',hello-dolly',
|
||||
'expected' => array( 'hello-dolly' ),
|
||||
),
|
||||
'a dependency with leading and trailing commas' => array(
|
||||
'requires_plugins' => ',hello-dolly,',
|
||||
'expected' => array( 'hello-dolly' ),
|
||||
),
|
||||
'a dependency with a trailing comma and a space' => array(
|
||||
'requires_plugins' => 'hello-dolly, ',
|
||||
'expected' => array( 'hello-dolly' ),
|
||||
),
|
||||
|
||||
// Invalid or empty slugs.
|
||||
'no dependencies' => array(
|
||||
'requires_plugins' => '',
|
||||
),
|
||||
'a dependency with an underscore' => array(
|
||||
'requires_plugins' => 'hello_dolly',
|
||||
),
|
||||
'a dependency with a space' => array(
|
||||
'requires_plugins' => 'hello dolly',
|
||||
),
|
||||
'a dependency in quotes' => array(
|
||||
'requires_plugins' => '"hello-dolly"',
|
||||
),
|
||||
'two dependencies in quotes' => array(
|
||||
'requires_plugins' => '"hello-dolly, woocommerce"',
|
||||
),
|
||||
'a dependency with trailing dash' => array(
|
||||
'requires_plugins' => 'ending-dash-',
|
||||
),
|
||||
'a dependency with leading dash' => array(
|
||||
'requires_plugins' => '-slug',
|
||||
),
|
||||
'a dependency with double dashes' => array(
|
||||
'requires_plugins' => 'abc--123',
|
||||
),
|
||||
'cyrillic dependencies' => array(
|
||||
'requires_plugins' => 'я-делюсь',
|
||||
),
|
||||
'arabic dependencies' => array(
|
||||
'requires_plugins' => 'لينوكس-ويكى',
|
||||
),
|
||||
'chinese dependencies' => array(
|
||||
'requires_plugins' => '唐诗宋词chinese-poem,社交登录,腾讯微博一键登录,豆瓣秀-for-wordpress',
|
||||
),
|
||||
'symbol dependencies' => array(
|
||||
'requires_plugins' => '★-wpsymbols-★',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependent files are loaded and slugified.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::read_dependencies_from_plugin_headers
|
||||
* @covers WP_Plugin_Dependencies::convert_to_slug
|
||||
*/
|
||||
public function test_should_slugify_dependent_files() {
|
||||
$plugins = get_plugins();
|
||||
|
||||
$expected_slugs = array();
|
||||
foreach ( $plugins as $plugin_file => &$headers ) {
|
||||
// Create the expected slugs.
|
||||
if ( 'hello.php' === $plugin_file ) {
|
||||
$slug = 'hello-dolly';
|
||||
} else {
|
||||
$slug = str_replace( '.php', '', explode( '/', $plugin_file )[0] );
|
||||
}
|
||||
|
||||
$expected_slugs[ $plugin_file ] = $slug;
|
||||
|
||||
// While here, ensure the plugins are all dependents.
|
||||
$headers['RequiresPlugins'] = 'dependency';
|
||||
}
|
||||
unset( $headers );
|
||||
|
||||
// Set the plugins property with the plugin data modified to make them dependents.
|
||||
$this->set_property_value( 'plugins', $plugins );
|
||||
|
||||
self::$instance->initialize();
|
||||
$this->assertSame( $expected_slugs, $this->get_property_value( 'dependent_slugs' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependents with unmet dependencies are deactivated.
|
||||
*
|
||||
* @ticket 22316
|
||||
*
|
||||
* @covers WP_Plugin_Dependencies::deactivate_dependents_with_unmet_dependencies
|
||||
* @covers WP_Plugin_Dependencies::has_unmet_dependencies
|
||||
* @covers WP_Plugin_Dependencies::get_active_dependents_in_dependency_tree
|
||||
*
|
||||
* @dataProvider data_should_only_deactivate_dependents_with_unmet_dependencies
|
||||
*
|
||||
* @param array $active_plugins An array of active plugin paths.
|
||||
* @param array $plugins An array of installed plugins.
|
||||
* @param array $expected The expected value of 'active_plugins' after initialization.
|
||||
*/
|
||||
public function test_should_deactivate_dependents_with_uninstalled_dependencies( $active_plugins, $plugins, $expected ) {
|
||||
update_option( 'active_plugins', $active_plugins );
|
||||
|
||||
$this->set_property_value( 'plugins', $plugins );
|
||||
self::$instance::initialize();
|
||||
|
||||
$this->assertSame( $expected, array_values( get_option( 'active_plugins', array() ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_should_only_deactivate_dependents_with_unmet_dependencies() {
|
||||
return array(
|
||||
'a dependent with an uninstalled dependency' => array(
|
||||
'active_plugins' => array( 'dependent/dependent.php' ),
|
||||
'plugins' => array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
),
|
||||
'expected' => array(),
|
||||
),
|
||||
'a dependent with an inactive dependency' => array(
|
||||
'active_plugins' => array( 'dependent/dependent.php' ),
|
||||
'plugins' => array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
),
|
||||
'expected' => array(),
|
||||
),
|
||||
'a dependent with two dependencies, one uninstalled, one inactive' => array(
|
||||
'active_plugins' => array( 'dependent/dependent.php' ),
|
||||
'plugins' => array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ),
|
||||
'dependency2/dependency2.php' => array( 'RequiresPlugins' => '' ),
|
||||
),
|
||||
'expected' => array(),
|
||||
),
|
||||
'a dependent with a dependency that is installed and active' => array(
|
||||
'active_plugins' => array( 'dependent/dependent.php', 'dependency/dependency.php' ),
|
||||
'plugins' => array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
),
|
||||
'expected' => array( 'dependent/dependent.php', 'dependency/dependency.php' ),
|
||||
),
|
||||
'one dependent with two dependencies that are installed and active' => array(
|
||||
'active_plugins' => array(
|
||||
'dependent/dependent.php',
|
||||
'dependency/dependency.php',
|
||||
'dependency2/dependency2.php',
|
||||
),
|
||||
'plugins' => array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ),
|
||||
'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),
|
||||
'dependency2/dependency2.php' => array( 'RequiresPlugins' => '' ),
|
||||
),
|
||||
'expected' => array(
|
||||
'dependent/dependent.php',
|
||||
'dependency/dependency.php',
|
||||
'dependency2/dependency2.php',
|
||||
),
|
||||
),
|
||||
'two dependents, one with an uninstalled dependency, and one with an active dependency' => array(
|
||||
'active_plugins' => array(
|
||||
'dependent/dependent.php',
|
||||
'dependent2/dependent2.php',
|
||||
'dependency2/dependency2.php',
|
||||
),
|
||||
'plugins' => array(
|
||||
'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),
|
||||
'dependent2/dependent2.php' => array( 'RequiresPlugins' => 'dependency2' ),
|
||||
'dependency2/dependency2.php' => array( 'RequiresPlugins' => '' ),
|
||||
),
|
||||
'expected' => array( 'dependent2/dependent2.php', 'dependency2/dependency2.php' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user