Make WordPress Core

Changeset 57545


Ignore:
Timestamp:
02/06/2024 11:44:09 PM (3 years ago)
Author:
costdev
Message:

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.

Location:
trunk
Files:
16 added
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/wp/updates.js

    r56011 r57545  
    415415
    416416        /**
     417         * Sends a message from a modal to the main screen to update buttons in plugin cards.
     418         *
     419         * @since 6.5.0
     420         *
     421         * @param {Object}  data               An object of data to use for the button.
     422         * @param {string}  data.slug          The plugin's slug.
     423         * @param {string}  data.text          The text to use for the button.
     424         * @param {string}  data.ariaLabel     The value for the button's aria-label attribute. An empty string removes the attribute.
     425         * @param {string=} data.status        Optional. An identifier for the status.
     426         * @param {string=} data.removeClasses Optional. A space-separated list of classes to remove from the button.
     427         * @param {string=} data.addClasses    Optional. A space-separated list of classes to add to the button.
     428         * @param {string=} data.href          Optional. The button's URL.
     429         * @param {string=} data.pluginName    Optional. The plugin's name.
     430         * @param {string=} data.plugin        Optional. The plugin file, relative to the plugins directory.
     431         */
     432        wp.updates.setCardButtonStatus = function( data ) {
     433                var target = window.parent === window ? null : window.parent;
     434
     435                $.support.postMessage = !! window.postMessage;
     436                if ( false !== $.support.postMessage && null !== target && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) {
     437                        target.postMessage( JSON.stringify( data ), window.location.origin );
     438                }
     439        };
     440
     441        /**
    417442         * Decrements the update counts throughout the various menus.
    418443         *
    … …  
    453478        wp.updates.updatePlugin = function( args ) {
    454479                var $updateRow, $card, $message, message,
    455                         $adminBarUpdates = $( '#wp-admin-bar-updates' );
     480                        $adminBarUpdates = $( '#wp-admin-bar-updates' ),
     481                        buttonText = __( 'Updating...' );
    456482
    457483                args = _.extend( {
    … …  
    469495                        );
    470496                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
    471                         $card    = $( '.plugin-card-' + args.slug );
     497                        $card    = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' );
    472498                        $message = $card.find( '.update-now' ).addClass( 'updating-message' );
    473499                        message    = sprintf(
    … …  
    489515                $message
    490516                        .attr( 'aria-label', message )
    491                         .text( __( 'Updating...' ) );
     517                        .text( buttonText );
    492518
    493519                $document.trigger( 'wp-plugin-updating', args );
     520
     521                if ( 'plugin-information-footer' === $card.attr('id' ) ) {
     522                        wp.updates.setCardButtonStatus(
     523                                {
     524                                        status: 'updating-plugin',
     525                                        slug: args.slug,
     526                                        addClasses: 'updating-message',
     527                                        text: buttonText,
     528                                        ariaLabel: message
     529                                }
     530                        );
     531                }
    494532
    495533                return wp.updates.ajax( 'update-plugin', args );
    … …  
    512550        wp.updates.updatePluginSuccess = function( response ) {
    513551                var $pluginRow, $updateMessage, newText,
    514                         $adminBarUpdates = $( '#wp-admin-bar-updates' );
     552                        $adminBarUpdates = $( '#wp-admin-bar-updates' ),
     553                        buttonText = _x( 'Updated!', 'plugin' ),
     554                        ariaLabel = sprintf(
     555                                /* translators: %s: Plugin name and version. */
     556                                _x( '%s updated!', 'plugin' ),
     557                                response.pluginName
     558                        );
    515559
    516560                if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
    … …  
    529573                        $pluginRow.find( '.auto-update-time' ).empty();
    530574                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
    531                         $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
     575                        $updateMessage = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.update-now' )
    532576                                .removeClass( 'updating-message' )
    533577                                .addClass( 'button-disabled updated-message' );
    … …  
    537581
    538582                $updateMessage
    539                         .attr(
    540                                 'aria-label',
    541                                 sprintf(
    542                                         /* translators: %s: Plugin name and version. */
    543                                         _x( '%s updated!', 'plugin' ),
    544                                         response.pluginName
    545                                 )
    546                         )
    547                         .text( _x( 'Updated!', 'plugin' ) );
     583                        .attr( 'aria-label', ariaLabel )
     584                        .text( buttonText );
    548585
    549586                wp.a11y.speak( __( 'Update completed successfully.' ) );
    550587
    551                 wp.updates.decrementCount( 'plugin' );
     588                if ( 'plugin_install_from_iframe' !== $updateMessage.attr( 'id' ) ) {
     589                        wp.updates.decrementCount( 'plugin' );
     590                } else {
     591                        wp.updates.setCardButtonStatus(
     592                                {
     593                                        status: 'updated-plugin',
     594                                        slug: response.slug,
     595                                        removeClasses: 'updating-message',
     596                                        addClasses: 'button-disabled updated-message',
     597                                        text: buttonText,
     598                                        ariaLabel: ariaLabel
     599                                }
     600                        );
     601                }
    552602
    553603                $document.trigger( 'wp-plugin-update-success', response );
    … …  
    568618         */
    569619        wp.updates.updatePluginError = function( response ) {
    570                 var $pluginRow, $card, $message, errorMessage,
     620                var $pluginRow, $card, $message, errorMessage, buttonText, ariaLabel,
    571621                        $adminBarUpdates = $( '#wp-admin-bar-updates' );
    572622
    … …  
    609659                        }
    610660                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
    611                         $card = $( '.plugin-card-' + response.slug )
    612                                 .addClass( 'plugin-card-update-failed' )
     661                        buttonText = __( 'Update failed.' );
     662
     663                        $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' )
    613664                                .append( wp.updates.adminNotice( {
    614665                                        className: 'update-message notice-error notice-alt is-dismissible',
    … …  
    616667                                } ) );
    617668
     669                        if ( $card.hasClass( 'plugin-card-' + response.slug ) ) {
     670                                $card.addClass( 'plugin-card-update-failed' );
     671                        }
     672
    618673                        $card.find( '.update-now' )
    619                                 .text(  __( 'Update failed.' ) )
     674                                .text( buttonText )
    620675                                .removeClass( 'updating-message' );
    621676
    622677                        if ( response.pluginName ) {
    623                                 $card.find( '.update-now' )
    624                                         .attr(
    625                                                 'aria-label',
    626                                                 sprintf(
    627                                                         /* translators: %s: Plugin name and version. */
    628                                                         _x( '%s update failed.', 'plugin' ),
    629                                                         response.pluginName
    630                                                 )
    631                                         );
     678                                ariaLabel = sprintf(
     679                                        /* translators: %s: Plugin name and version. */
     680                                        _x( '%s update failed.', 'plugin' ),
     681                                        response.pluginName
     682                                );
     683
     684                                $card.find( '.update-now' ).attr( 'aria-label', ariaLabel );
    632685                        } else {
     686                                ariaLabel = '';
    633687                                $card.find( '.update-now' ).removeAttr( 'aria-label' );
    634688                        }
    … …  
    652706
    653707                wp.a11y.speak( errorMessage, 'assertive' );
     708
     709                if ( 'plugin-information-footer' === $card.attr('id' ) ) {
     710                        wp.updates.setCardButtonStatus(
     711                                {
     712                                        status: 'plugin-update-failed',
     713                                        slug: response.slug,
     714                                        removeClasses: 'updating-message',
     715                                        text: buttonText,
     716                                        ariaLabel: ariaLabel
     717                                }
     718                        );
     719                }
    654720
    655721                $document.trigger( 'wp-plugin-update-error', response );
    … …  
    669735         */
    670736        wp.updates.installPlugin = function( args ) {
    671                 var $card    = $( '.plugin-card-' + args.slug ),
    672                         $message = $card.find( '.install-now' );
     737                var $card    = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ),
     738                        $message = $card.find( '.install-now' ),
     739                        buttonText = __( 'Installing...' ),
     740                        ariaLabel;
    673741
    674742                args = _.extend( {
    … …  
    685753                }
    686754
     755                ariaLabel = sprintf(
     756                        /* translators: %s: Plugin name and version. */
     757                        _x( 'Installing %s...', 'plugin' ),
     758                        $message.data( 'name' )
     759                );
     760
    687761                $message
    688762                        .addClass( 'updating-message' )
    689                         .attr(
    690                                 'aria-label',
    691                                 sprintf(
    692                                         /* translators: %s: Plugin name and version. */
    693                                         _x( 'Installing %s...', 'plugin' ),
    694                                         $message.data( 'name' )
    695                                 )
    696                         )
    697                         .text( __( 'Installing...' ) );
     763                        .attr( 'aria-label', ariaLabel )
     764                        .text( buttonText );
    698765
    699766                wp.a11y.speak( __( 'Installing... please wait.' ) );
    … …  
    703770
    704771                $document.trigger( 'wp-plugin-installing', args );
     772
     773                if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     774                        wp.updates.setCardButtonStatus(
     775                                {
     776                                        status: 'installing-plugin',
     777                                        slug: args.slug,
     778                                        addClasses: 'updating-message',
     779                                        text: buttonText,
     780                                        ariaLabel: ariaLabel
     781                                }
     782                        );
     783                }
    705784
    706785                return wp.updates.ajax( 'install-plugin', args );
    … …  
    718797         */
    719798        wp.updates.installPluginSuccess = function( response ) {
    720                 var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' );
     799                var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
     800                        buttonText = _x( 'Installed!', 'plugin' ),
     801                        ariaLabel = sprintf(
     802                                /* translators: %s: Plugin name and version. */
     803                                _x( '%s installed!', 'plugin' ),
     804                                response.pluginName
     805                        );
    721806
    722807                $message
    723808                        .removeClass( 'updating-message' )
    724809                        .addClass( 'updated-message installed button-disabled' )
    725                         .attr(
    726                                 'aria-label',
    727                                 sprintf(
    728                                         /* translators: %s: Plugin name and version. */
    729                                         _x( '%s installed!', 'plugin' ),
    730                                         response.pluginName
    731                                 )
    732                         )
    733                         .text( _x( 'Installed!', 'plugin' ) );
     810                        .attr( 'aria-label', ariaLabel )
     811                        .text( buttonText );
    734812
    735813                wp.a11y.speak( __( 'Installation completed successfully.' ) );
    … …  
    739817                if ( response.activateUrl ) {
    740818                        setTimeout( function() {
    741 
    742                                 // Transform the 'Install' button into an 'Activate' button.
    743                                 $message.removeClass( 'install-now installed button-disabled updated-message' )
    744                                         .addClass( 'activate-now button-primary' )
    745                                         .attr( 'href', response.activateUrl );
    746 
    747                                 if ( 'plugins-network' === pagenow ) {
    748                                         $message
    749                                                 .attr(
    750                                                         'aria-label',
    751                                                         sprintf(
    752                                                                 /* translators: %s: Plugin name. */
    753                                                                 _x( 'Network Activate %s', 'plugin' ),
    754                                                                 response.pluginName
    755                                                         )
    756                                                 )
    757                                                 .text( __( 'Network Activate' ) );
    758                                 } else {
    759                                         $message
    760                                                 .attr(
    761                                                         'aria-label',
    762                                                         sprintf(
    763                                                                 /* translators: %s: Plugin name. */
    764                                                                 _x( 'Activate %s', 'plugin' ),
    765                                                                 response.pluginName
    766                                                         )
    767                                                 )
    768                                                 .text( __( 'Activate' ) );
    769                                 }
     819                                wp.updates.checkPluginDependencies( {
     820                                        slug: response.slug
     821                                } );
    770822                        }, 1000 );
     823                }
     824
     825                if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     826                        wp.updates.setCardButtonStatus(
     827                                {
     828                                        status: 'installed-plugin',
     829                                        slug: response.slug,
     830                                        removeClasses: 'updating-message',
     831                                        addClasses: 'updated-message installed button-disabled',
     832                                        text: buttonText,
     833                                        ariaLabel: ariaLabel
     834                                }
     835                        );
    771836                }
    772837        };
    … …  
    784849         */
    785850        wp.updates.installPluginError = function( response ) {
    786                 var $card   = $( '.plugin-card-' + response.slug ),
     851                var $card   = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ),
    787852                        $button = $card.find( '.install-now' ),
     853                        buttonText = __( 'Installation failed.' ),
     854                        ariaLabel = sprintf(
     855                                /* translators: %s: Plugin name and version. */
     856                                _x( '%s installation failed', 'plugin' ),
     857                                $button.data( 'name' )
     858                        ),
    788859                        errorMessage;
    789860
    … …  
    818889                $button
    819890                        .removeClass( 'updating-message' ).addClass( 'button-disabled' )
    820                         .attr(
    821                                 'aria-label',
    822                                 sprintf(
    823                                         /* translators: %s: Plugin name and version. */
    824                                         _x( '%s installation failed', 'plugin' ),
    825                                         $button.data( 'name' )
    826                                 )
    827                         )
    828                         .text( __( 'Installation failed.' ) );
     891                        .attr( 'aria-label', ariaLabel )
     892                        .text( buttonText );
    829893
    830894                wp.a11y.speak( errorMessage, 'assertive' );
    831895
     896                wp.updates.setCardButtonStatus(
     897                        {
     898                                status: 'plugin-install-failed',
     899                                slug: response.slug,
     900                                removeClasses: 'updating-message',
     901                                addClasses: 'button-disabled',
     902                                text: buttonText,
     903                                ariaLabel: ariaLabel
     904                        }
     905                );
     906
    832907                $document.trigger( 'wp-plugin-install-error', response );
     908        };
     909
     910        /**
     911         * Sends an Ajax request to the server to check a plugin's dependencies.
     912         *
     913         * @since 6.5.0
     914         *
     915         * @param {Object}                          args         Arguments.
     916         * @param {string}                          args.slug    Plugin identifier in the WordPress.org Plugin repository.
     917         * @param {checkPluginDependenciesSuccess=} args.success Optional. Success callback. Default: wp.updates.checkPluginDependenciesSuccess
     918         * @param {checkPluginDependenciesError=}   args.error   Optional. Error callback. Default: wp.updates.checkPluginDependenciesError
     919         * @return {$.promise} A jQuery promise that represents the request,
     920         *                     decorated with an abort() method.
     921         */
     922        wp.updates.checkPluginDependencies = function( args ) {
     923                args = _.extend( {
     924                        success: wp.updates.checkPluginDependenciesSuccess,
     925                        error: wp.updates.checkPluginDependenciesError
     926                }, args );
     927
     928                wp.a11y.speak( __( 'Checking plugin dependencies... please wait.' ) );
     929                $document.trigger( 'wp-checking-plugin-dependencies', args );
     930
     931                return wp.updates.ajax( 'check_plugin_dependencies', args );
     932        };
     933
     934        /**
     935         * Updates the UI appropriately after a successful plugin dependencies check.
     936         *
     937         * @since 6.5.0
     938         *
     939         * @param {Object} response             Response from the server.
     940         * @param {string} response.slug        Slug of the checked plugin.
     941         * @param {string} response.pluginName  Name of the checked plugin.
     942         * @param {string} response.plugin      The plugin file, relative to the plugins directory.
     943         * @param {string} response.activateUrl URL to activate the just checked plugin.
     944         */
     945        wp.updates.checkPluginDependenciesSuccess = function( response ) {
     946                var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
     947                        buttonText, ariaLabel;
     948
     949                // Transform the 'Install' button into an 'Activate' button.
     950                $message
     951                        .removeClass( 'install-now installed button-disabled updated-message' )
     952                        .addClass( 'activate-now button-primary' )
     953                        .attr( 'href', response.activateUrl );
     954
     955                wp.a11y.speak( __( 'Plugin dependencies check completed successfully.' ) );
     956                $document.trigger( 'wp-check-plugin-dependencies-success', response );
     957
     958                if ( 'plugins-network' === pagenow ) {
     959                        buttonText = _x( 'Network Activate' );
     960                        ariaLabel  = sprintf(
     961                                /* translators: %s: Plugin name. */
     962                                _x( 'Network Activate %s', 'plugin' ),
     963                                response.pluginName
     964                        );
     965
     966                        $message
     967                                .attr( 'aria-label', ariaLabel )
     968                                .text( buttonText );
     969                } else {
     970                        buttonText = _x( 'Activate', 'plugin' );
     971                        ariaLabel = sprintf(
     972                                /* translators: %s: Plugin name. */
     973                                _x( 'Activate %s', 'plugin' ),
     974                                response.pluginName
     975                        );
     976
     977                        $message
     978                                .attr( 'aria-label', ariaLabel )
     979                                .attr( 'data-name', response.pluginName )
     980                                .attr( 'data-slug', response.slug )
     981                                .attr( 'data-plugin', response.plugin )
     982                                .text( buttonText );
     983                }
     984
     985                if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     986                        wp.updates.setCardButtonStatus(
     987                                {
     988                                        status: 'dependencies-check-success',
     989                                        slug: response.slug,
     990                                        removeClasses: 'install-now installed button-disabled updated-message',
     991                                        addClasses: 'activate-now button-primary',
     992                                        text: buttonText,
     993                                        ariaLabel: ariaLabel,
     994                                        pluginName: response.pluginName,
     995                                        plugin: response.plugin,
     996                                        href: response.activateUrl
     997                                }
     998                        );
     999                }
     1000        };
     1001
     1002        /**
     1003         * Updates the UI appropriately after a failed plugin dependencies check.
     1004         *
     1005         * @since 6.5.0
     1006         *
     1007         * @param {Object}  response              Response from the server.
     1008         * @param {string}  response.slug         Slug of the plugin to be checked.
     1009         * @param {string=} response.pluginName   Optional. Name of the plugin to be checked.
     1010         * @param {string}  response.errorCode    Error code for the error that occurred.
     1011         * @param {string}  response.errorMessage The error that occurred.
     1012         */
     1013        wp.updates.checkPluginDependenciesError = function( response ) {
     1014                var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ),
     1015                        buttonText = __( 'Activate' ),
     1016                        ariaLabel = sprintf(
     1017                                /* translators: 1: Plugin name, 2. The reason the plugin cannot be activated. */
     1018                                _x( 'Cannot activate %1$s. %2$s', 'plugin' ),
     1019                                response.pluginName,
     1020                                response.errorMessage
     1021                        ),
     1022                        errorMessage;
     1023
     1024                if ( ! wp.updates.isValidResponse( response, 'check-dependencies' ) ) {
     1025                        return;
     1026                }
     1027
     1028                errorMessage = sprintf(
     1029                        /* translators: %s: Error string for a failed activation. */
     1030                        __( 'Activation failed: %s' ),
     1031                        response.errorMessage
     1032                );
     1033
     1034                wp.a11y.speak( errorMessage, 'assertive' );
     1035                $document.trigger( 'wp-check-plugin-dependencies-error', response );
     1036
     1037                $message
     1038                        .removeClass( 'install-now installed updated-message' )
     1039                        .addClass( 'activate-now button-primary' )
     1040                        .attr( 'aria-label', ariaLabel )
     1041                        .text( buttonText );
     1042
     1043                if ( 'plugin-information-footer' === $message.parent().attr('id' ) ) {
     1044                        wp.updates.setCardButtonStatus(
     1045                                {
     1046                                        status: 'dependencies-check-failed',
     1047                                        slug: response.slug,
     1048                                        removeClasses: 'install-now installed updated-message',
     1049                                        addClasses: 'activate-now button-primary',
     1050                                        text: buttonText,
     1051                                        ariaLabel: ariaLabel
     1052                                }
     1053                        );
     1054                }
     1055        };
     1056
     1057        /**
     1058         * Sends an Ajax request to the server to activate a plugin.
     1059         *
     1060         * @since 6.5.0
     1061         *
     1062         * @param {Object}                 args         Arguments.
     1063         * @param {string}                 args.name    The name of the plugin.
     1064         * @param {string}                 args.slug    Plugin identifier in the WordPress.org Plugin repository.
     1065         * @param {string}                 args.plugin  The plugin file, relative to the plugins directory.
     1066         * @param {activatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.activatePluginSuccess
     1067         * @param {activatePluginError=}   args.error   Optional. Error callback. Default: wp.updates.activatePluginError
     1068         * @return {$.promise} A jQuery promise that represents the request,
     1069         *                     decorated with an abort() method.
     1070         */
     1071        wp.updates.activatePlugin = function( args ) {
     1072                var $message = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ).find( '.activate-now, .activating-message' );
     1073
     1074                args = _.extend( {
     1075                        success: wp.updates.activatePluginSuccess,
     1076                        error: wp.updates.activatePluginError
     1077                }, args );
     1078
     1079                wp.a11y.speak( __( 'Activating... please wait.' ) );
     1080                $document.trigger( 'wp-activating-plugin', args );
     1081
     1082                if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     1083                        wp.updates.setCardButtonStatus(
     1084                                {
     1085                                        status: 'activating-plugin',
     1086                                        slug: args.slug,
     1087                                        removeClasses: 'installed updated-message button-primary',
     1088                                        addClasses: 'activating-message',
     1089                                        text: _x( 'Activating...', 'plugin' ),
     1090                                        ariaLabel: sprintf(
     1091                                                /* translators: %s: Plugin name. */
     1092                                                _x( 'Activating %s', 'plugin' ),
     1093                                                args.name
     1094                                        )
     1095                                }
     1096                        );
     1097                }
     1098
     1099                return wp.updates.ajax( 'activate-plugin', args );
     1100        };
     1101
     1102        /**
     1103         * Updates the UI appropriately after a successful plugin activation.
     1104         *
     1105         * @since 6.5.0
     1106         *
     1107         * @param {Object} response             Response from the server.
     1108         * @param {string} response.slug        Slug of the activated plugin.
     1109         * @param {string} response.pluginName  Name of the activated plugin.
     1110         * @param {string} response.plugin      The plugin file, relative to the plugins directory.
     1111         */
     1112        wp.updates.activatePluginSuccess = function( response ) {
     1113                var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
     1114                        buttonText = _x( 'Activated!', 'plugin' ),
     1115                        ariaLabel = sprintf(
     1116                                /* translators: %s: The plugin name. */
     1117                                '%s activated successfully.',
     1118                                response.pluginName
     1119                        );
     1120
     1121                wp.a11y.speak( __( 'Activation completed successfully.' ) );
     1122                $document.trigger( 'wp-plugin-activate-success', response );
     1123
     1124                $message
     1125                        .removeClass( 'activating-message' )
     1126                        .addClass( 'activated-message button-disabled' )
     1127                        .attr( 'aria-label', ariaLabel )
     1128                        .text( buttonText );
     1129
     1130                if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     1131                        wp.updates.setCardButtonStatus(
     1132                                {
     1133                                        status: 'activated-plugin',
     1134                                        slug: response.slug,
     1135                                        removeClasses: 'activating-message',
     1136                                        addClasses: 'activated-message button-disabled',
     1137                                        text: buttonText,
     1138                                        ariaLabel: ariaLabel
     1139                                }
     1140                        );
     1141                }
     1142
     1143                setTimeout( function() {
     1144                        $message.removeClass( 'activated-message' )
     1145                        .text( _x( 'Active', 'plugin' ) );
     1146
     1147                        if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     1148                                wp.updates.setCardButtonStatus(
     1149                                        {
     1150                                                status: 'plugin-active',
     1151                                                slug: response.slug,
     1152                                                removeClasses: 'activated-message',
     1153                                                text: _x( 'Active', 'plugin' ),
     1154                                                ariaLabel: sprintf(
     1155                                                        /* translators: %s: The plugin name. */
     1156                                                        '%s is active.',
     1157                                                        response.pluginName
     1158                                                )
     1159                                        }
     1160                                );
     1161                        }
     1162                }, 1000 );
     1163        };
     1164
     1165        /**
     1166         * Updates the UI appropriately after a failed plugin activation.
     1167         *
     1168         * @since 6.5.0
     1169         *
     1170         * @param {Object}  response              Response from the server.
     1171         * @param {string}  response.slug         Slug of the plugin to be activated.
     1172         * @param {string=} response.pluginName   Optional. Name of the plugin to be activated.
     1173         * @param {string}  response.errorCode    Error code for the error that occurred.
     1174         * @param {string}  response.errorMessage The error that occurred.
     1175         */
     1176        wp.updates.activatePluginError = function( response ) {
     1177                var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ),
     1178                        buttonText = __( 'Activation failed.' ),
     1179                        ariaLabel = sprintf(
     1180                                /* translators: %s: Plugin name. */
     1181                                _x( '%s activation failed', 'plugin' ),
     1182                                response.pluginName
     1183                        ),
     1184                        errorMessage;
     1185
     1186                if ( ! wp.updates.isValidResponse( response, 'activate' ) ) {
     1187                        return;
     1188                }
     1189
     1190                errorMessage = sprintf(
     1191                        /* translators: %s: Error string for a failed activation. */
     1192                        __( 'Activation failed: %s' ),
     1193                        response.errorMessage
     1194                );
     1195
     1196                wp.a11y.speak( errorMessage, 'assertive' );
     1197                $document.trigger( 'wp-plugin-activate-error', response );
     1198
     1199                $message
     1200                        .removeClass( 'install-now installed activating-message' )
     1201                        .addClass( 'button-disabled' )
     1202                        .attr( 'aria-label', ariaLabel )
     1203                        .text( buttonText );
     1204
     1205                if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) {
     1206                        wp.updates.setCardButtonStatus(
     1207                                {
     1208                                        status: 'plugin-activation-failed',
     1209                                        slug: response.slug,
     1210                                        removeClasses: 'install-now installed activating-message',
     1211                                        addClasses: 'button-disabled',
     1212                                        text: buttonText,
     1213                                        ariaLabel: ariaLabel
     1214                                }
     1215                        );
     1216                }
    8331217        };
    8341218
    … …  
    19712355                                break;
    19722356
     2357                        case 'check-dependencies':
     2358                                /* translators: %s: Error string for a failed dependencies check. */
     2359                                errorMessage = __( 'Dependencies check failed: %s' );
     2360                                break;
     2361
     2362                        case 'activate':
     2363                                /* translators: %s: Error string for a failed activation. */
     2364                                errorMessage = __( 'Activation failed: %s' );
     2365                                break;
     2366
    19732367                        case 'delete':
    19742368                                /* translators: %s: Error string for a failed deletion. */
    … …  
    20262420
    20272421        $( function() {
    2028                 var $pluginFilter        = $( '#plugin-filter' ),
     2422                var $pluginFilter        = $( '#plugin-filter, #plugin-information-footer' ),
    20292423                        $bulkActionForm      = $( '#bulk-action-form' ),
    20302424                        $filesystemForm      = $( '#request-filesystem-credentials-form' ),
    … …  
    22432637
    22442638                /**
     2639                 * Click handler for plugin activations in plugin activation view.
     2640                 *
     2641                 * @since 6.5.0
     2642                 *
     2643                 * @param {Event} event Event interface.
     2644                 */
     2645                $pluginFilter.on( 'click', '.activate-now', function( event ) {
     2646                        var $activateButton = $( event.target );
     2647
     2648                        event.preventDefault();
     2649
     2650                        if ( $activateButton.hasClass( 'activating-message' ) || $activateButton.hasClass( 'button-disabled' ) ) {
     2651                                return;
     2652                        }
     2653
     2654                        $activateButton
     2655                                .removeClass( 'activate-now button-primary' )
     2656                                .addClass( 'activating-message' )
     2657                                .attr(
     2658                                        'aria-label',
     2659                                        sprintf(
     2660                                                /* translators: %s: Plugin name. */
     2661                                                _x( 'Activating %s', 'plugin' ),
     2662                                                $activateButton.data( 'name' )
     2663                                        )
     2664                                )
     2665                                .text( _x( 'Activating...', 'plugin' ) );
     2666
     2667                        wp.updates.activatePlugin(
     2668                                {
     2669                                        name: $activateButton.data( 'name' ),
     2670                                        slug: $activateButton.data( 'slug' ),
     2671                                        plugin: $activateButton.data( 'plugin' )
     2672                                }
     2673                        );
     2674                });
     2675
     2676                /**
    22452677                 * Click handler for importer plugins installs in the Import screen.
    22462678                 *
    … …  
    27683200
    27693201                /**
    2770                  * Click handler for installing a plugin from the details modal on `plugin-install.php`.
    2771                  *
    2772                  * @since 4.6.0
    2773                  *
    2774                  * @param {Event} event Event interface.
    2775                  */
    2776                 $( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
    2777                         var target = window.parent === window ? null : window.parent,
    2778                                 install;
    2779 
    2780                         $.support.postMessage = !! window.postMessage;
    2781 
    2782                         if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
    2783                                 return;
    2784                         }
    2785 
    2786                         event.preventDefault();
    2787 
    2788                         install = {
    2789                                 action: 'install-plugin',
    2790                                 data:   {
    2791                                         slug: $( this ).data( 'slug' )
    2792                                 }
    2793                         };
    2794 
    2795                         target.postMessage( JSON.stringify( install ), window.location.origin );
    2796                 } );
    2797 
    2798                 /**
    27993202                 * Handles postMessage events.
    28003203                 *
    … …  
    28193222                        }
    28203223
    2821                         if ( ! message || 'undefined' === typeof message.action ) {
     3224                        if ( ! message ) {
     3225                                return;
     3226                        }
     3227
     3228                        if (
     3229                                'undefined' !== typeof message.status &&
     3230                                'undefined' !== typeof message.slug &&
     3231                                'undefined' !== typeof message.text &&
     3232                                'undefined' !== typeof message.ariaLabel
     3233                        ) {
     3234                                var $card = $( '.plugin-card-' + message.slug ),
     3235                                        $message = $card.find( '[data-slug="' + message.slug + '"]' );
     3236
     3237                                if ( 'undefined' !== typeof message.removeClasses ) {
     3238                                        $message.removeClass( message.removeClasses );
     3239                                }
     3240
     3241                                if ( 'undefined' !== typeof message.addClasses ) {
     3242                                        $message.addClass( message.addClasses );
     3243                                }
     3244
     3245                                if ( '' === message.ariaLabel ) {
     3246                                        $message.removeAttr( 'aria-label' );
     3247                                } else {
     3248                                        $message.attr( 'aria-label', message.ariaLabel );
     3249                                }
     3250
     3251                                if ( 'dependencies-check-success' === message.status ) {
     3252                                        $message
     3253                                                .attr( 'data-name', message.pluginName )
     3254                                                .attr( 'data-slug', message.slug )
     3255                                                .attr( 'data-plugin', message.plugin )
     3256                                                .attr( 'href', message.href );
     3257                                }
     3258
     3259                                $message.text( message.text );
     3260                        }
     3261
     3262                        if ( 'undefined' === typeof message.action || 'undefined' === typeof message.data.slug ) {
    28223263                                return;
    28233264                        }
    … …  
    28333274                                case 'install-plugin':
    28343275                                case 'update-plugin':
    2835                                         /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
    2836                                         window.tb_remove();
    2837                                         /* jscs:enable */
    2838 
    28393276                                        message.data = wp.updates._addCallbacks( message.data, message.action );
    28403277
  • trunk/src/wp-admin/admin-ajax.php

    r53363 r57545  
    118118        'destroy-sessions',
    119119        'install-plugin',
     120        'activate-plugin',
    120121        'update-plugin',
    121122        'crop-image',
    … …  
    170171add_action( 'wp_ajax_nopriv_heartbeat', 'wp_ajax_nopriv_heartbeat', 1 );
    171172
     173// Register Plugin Dependencies Ajax calls.
     174add_action( 'wp_ajax_check_plugin_dependencies', array( 'WP_Plugin_Dependencies', 'check_plugin_dependencies_during_ajax' ) );
     175
    172176$action = $_REQUEST['action'];
    173177
  • trunk/src/wp-admin/css/common.css

    r57363 r57545  
    15011501}
    15021502
     1503#plugin-information-footer .update-now:not(.button-disabled):before {
     1504        color: #d63638;
     1505        content: "\f463";
     1506        display: inline-block;
     1507        font: normal 20px/1 dashicons;
     1508        margin: -3px 5px 0 -2px;
     1509        speak: never;
     1510        -webkit-font-smoothing: antialiased;
     1511        -moz-osx-font-smoothing: grayscale;
     1512        vertical-align: middle;
     1513}
     1514
     1515#plugin-information-footer .notice {
     1516    margin-top: -5px;
     1517}
     1518
    15031519.update-message p:before,
    15041520.updating-message p:before,
    … …  
    15081524.button.updated-message:before,
    15091525.button.installed:before,
    1510 .button.installing:before {
     1526.button.installing:before,
     1527.button.activating-message:before,
     1528.button.activated-message:before {
    15111529        display: inline-block;
    15121530        font: normal 20px/1 'dashicons';
    … …  
    15451563.import-php .updating-message:before,
    15461564.button.updating-message:before,
    1547 .button.installing:before {
     1565.button.installing:before,
     1566.button.activating-message:before {
    15481567        color: #d63638;
    15491568        content: "\f463";
    … …  
    15551574.button.updating-message:before,
    15561575.button.installing:before,
     1576.button.activating-message:before,
    15571577.plugins .column-auto-updates .dashicons-update.spin,
    15581578.theme-overlay .theme-autoupdate .dashicons-update.spin {
    … …  
    15651585        .button.updating-message:before,
    15661586        .button.installing:before,
     1587        .button.activating-message:before,
    15671588        .plugins .column-auto-updates .dashicons-update.spin,
    15681589        .theme-overlay .theme-autoupdate .dashicons-update.spin {
    … …  
    15781599.updated-message p:before,
    15791600.installed p:before,
    1580 .button.updated-message:before {
     1601.button.updated-message:before,
     1602.button.activated-message:before {
    15811603        color: #68de7c;
    15821604        content: "\f147";
    … …  
    16631685.button.updated-message:before,
    16641686.button.installed:before,
    1665 .button.installing:before {
     1687.button.installing:before,
     1688.button.activated-message:before,
     1689.button.activating-message:before {
    16661690        margin: 3px 5px 0 -2px;
    16671691}
    16681692
    1669 .button-primary.updating-message:before {
     1693#plugin-information-footer .button.installed:before,
     1694#plugin-information-footer .button.installing:before,
     1695#plugin-information-footer .button.updating-message:before,
     1696#plugin-information-footer .button.updated-message:before,
     1697#plugin-information-footer .button.activated-message:before,
     1698#plugin-information-footer .button.activating-message:before {
     1699        margin: 9px 5px 0 -2px;
     1700}
     1701
     1702#plugin-information-footer .button.update-now.updating-message:before {
     1703        margin: -3px 5px 0 -2px;
     1704}
     1705
     1706.button-primary.updating-message:before,
     1707.button-primary.activating-message:before {
    16701708        color: #fff;
    16711709}
    16721710
    1673 .button-primary.updated-message:before {
     1711.button-primary.updated-message:before,
     1712.button-primary.activated-message:before {
    16741713        color: #9ec2e6;
    16751714}
    16761715
    1677 .button.updated-message {
     1716.button.updated-message,
     1717.button.activated-message {
    16781718        transition-property: border, background, color;
    16791719        transition-duration: .05s;
  • trunk/src/wp-admin/css/list-tables.css

    r57321 r57545  
    586586}
    587587
    588 .check-column input:where(:not(:disabled)):hover,
    589 .check-column:hover input:where(:not(:disabled)) {
     588.check-column .label-covers-full-cell:hover + input:not(:disabled) {
    590589        box-shadow: 0 0 0 1px #2271b1;
    591590}
    … …  
    15491548}
    15501549
    1551 .plugin-card .name,
    15521550.plugin-card .desc {
    1553         margin-left: 148px; /* icon + margin */
    1554         margin-right: 128px; /* action links + margin */
     1551        margin-inline: 0;
     1552}
     1553
     1554.plugin-card .name, .plugin-card .desc > p {
     1555        margin-left: 148px;
     1556}
     1557
     1558@media (min-width: 1101px) {
     1559        .plugin-card .name, .plugin-card .desc > p {
     1560                margin-right: 128px;
     1561        }
     1562}
     1563
     1564@media (min-width: 481px) and (max-width: 781px) {
     1565        .plugin-card .name, .plugin-card .desc > p {
     1566                margin-right: 128px;
     1567        }
     1568}
     1569
     1570.plugin-card .column-description {
     1571        display: flex;
     1572        flex-direction: column;
     1573        justify-content: flex-start;
     1574}
     1575
     1576.plugin-card .column-description > p {
     1577        margin-top: 0;
     1578}
     1579
     1580.plugin-card .column-description .authors {
     1581        order: 1;
     1582}
     1583
     1584.plugin-card .column-description .plugin-dependencies {
     1585        order: 2;
     1586}
     1587
     1588.plugin-card .column-description p:empty {
     1589        display: none;
     1590}
     1591
     1592.plugin-card .plugin-dependencies {
     1593        background-color: #e5f5fa;
     1594        border-left: 3px solid #72aee6;
     1595        margin-bottom: .5em;
     1596        padding: 15px;
     1597}
     1598
     1599.plugin-card .plugin-dependencies-explainer-text {
     1600        margin-block: 0;
     1601}
     1602
     1603.plugin-card .plugin-dependency {
     1604        align-items: center;
     1605        display: flex;
     1606        flex-wrap: wrap;
     1607        margin-top: .5em;
     1608        column-gap: 1%;
     1609        row-gap: .5em;
     1610}
     1611
     1612.plugin-card .plugin-dependency:nth-child(2),
     1613.plugin-card .plugin-dependency:last-child {
     1614        margin-top: 1em;
     1615}
     1616
     1617.plugin-card .plugin-dependency-name {
     1618        flex-basis: 74%;
     1619}
     1620
     1621.plugin-card .plugin-dependency .more-details-link {
     1622        margin-left: auto;
     1623}
     1624
     1625.rtl .plugin-card .plugin-dependency .more-details-link {
     1626        margin-right: auto;
     1627}
     1628
     1629@media (max-width: 939px) {
     1630        .plugin-card .plugin-dependency-name {
     1631                flex-basis: 69%;
     1632        }
     1633        .plugin-card .plugin-dependency .more-details-link {
     1634        }
     1635}
     1636
     1637.plugins #the-list .required-by,
     1638.plugins #the-list .requires {
     1639        margin-top: 1em;
    15551640}
    15561641
  • trunk/src/wp-admin/includes/ajax-actions.php

    r57136 r57545  
    45804580
    45814581/**
     4582 * Handles activating a plugin via AJAX.
     4583 *
     4584 * @since 6.5.0
     4585 */
     4586function wp_ajax_activate_plugin() {
     4587        check_ajax_referer( 'updates' );
     4588
     4589        if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
     4590                wp_send_json_error(
     4591                        array(
     4592                                'slug'         => '',
     4593                                'pluginName'   => '',
     4594                                'plugin'       => '',
     4595                                'errorCode'    => 'no_plugin_specified',
     4596                                'errorMessage' => __( 'No plugin specified.' ),
     4597                        )
     4598                );
     4599        }
     4600
     4601        $status = array(
     4602                'activate'   => 'plugin',
     4603                'slug'       => wp_unslash( $_POST['slug'] ),
     4604                'pluginName' => wp_unslash( $_POST['name'] ),
     4605                'plugin'     => wp_unslash( $_POST['plugin'] ),
     4606        );
     4607
     4608        if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) {
     4609                $status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' );
     4610                wp_send_json_error( $status );
     4611        }
     4612
     4613        if ( is_plugin_active( $status['plugin'] ) ) {
     4614                $status['errorMessage'] = sprintf(
     4615                        /* translators: %s: Plugin name. */
     4616                        __( '%s is already active.' ),
     4617                        $status['pluginName']
     4618                );
     4619        }
     4620
     4621        $activated = activate_plugin( $status['plugin'] );
     4622
     4623        if ( is_wp_error( $activated ) ) {
     4624                $status['errorMessage'] = $activated->get_error_message();
     4625                wp_send_json_error( $status );
     4626        }
     4627
     4628        wp_send_json_success( $status );
     4629}
     4630
     4631/**
    45824632 * Handles updating a plugin via AJAX.
    45834633 *
  • trunk/src/wp-admin/includes/class-plugin-upgrader.php

    r57239 r57545  
    155155                // Force refresh of plugin update information.
    156156                wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
     157
     158                $all_plugin_data = get_option( 'plugin_data', array() );
     159                $plugin_file     = $this->new_plugin_data['file'];
     160                unset( $this->new_plugin_data['file'] );
     161                $all_plugin_data[ $plugin_file ] = $this->new_plugin_data;
     162                update_option( 'plugin_data', $all_plugin_data );
    157163
    158164                if ( $parsed_args['overwrite_package'] ) {
    … …  
    483489                                $info = get_plugin_data( $file, false, false );
    484490                                if ( ! empty( $info['Name'] ) ) {
    485                                         $this->new_plugin_data = $info;
     491                                        $basename = basename( $file );
     492                                        $dirname  = basename( dirname( $file ) );
     493
     494                                        if ( '.' === $dirname ) {
     495                                                $plugin_file = $basename;
     496                                        } else {
     497                                                $plugin_file = "$dirname/$basename";
     498                                        }
     499                                        $this->new_plugin_data         = ( $info );
     500                                        $this->new_plugin_data['file'] = $plugin_file;
    486501                                        break;
    487502                                }
  • trunk/src/wp-admin/includes/class-wp-plugin-install-list-table.php

    r56599 r57545  
    526526                        $description = strip_tags( $plugin['short_description'] );
    527527
     528                        $description .= $this->get_dependencies_notice( $plugin );
     529
    528530                        /**
    529531                         * Filters the plugin card description on the Add Plugins screen.
    … …  
    556558                        $action_links = array();
    557559
    558                         if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
    559                                 $status = install_plugin_install_status( $plugin );
    560 
    561                                 switch ( $status['status'] ) {
    562                                         case 'install':
    563                                                 if ( $status['url'] ) {
    564                                                         if ( $compatible_php && $compatible_wp ) {
    565                                                                 $action_links[] = sprintf(
    566                                                                         '<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
    567                                                                         esc_attr( $plugin['slug'] ),
    568                                                                         esc_url( $status['url'] ),
    569                                                                         /* translators: %s: Plugin name and version. */
    570                                                                         esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
    571                                                                         esc_attr( $name ),
    572                                                                         __( 'Install Now' )
    573                                                                 );
    574                                                         } else {
    575                                                                 $action_links[] = sprintf(
    576                                                                         '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
    577                                                                         _x( 'Cannot Install', 'plugin' )
    578                                                                 );
    579                                                         }
    580                                                 }
    581                                                 break;
    582 
    583                                         case 'update_available':
    584                                                 if ( $status['url'] ) {
    585                                                         if ( $compatible_php && $compatible_wp ) {
    586                                                                 $action_links[] = sprintf(
    587                                                                         '<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>',
    588                                                                         esc_attr( $status['file'] ),
    589                                                                         esc_attr( $plugin['slug'] ),
    590                                                                         esc_url( $status['url'] ),
    591                                                                         /* translators: %s: Plugin name and version. */
    592                                                                         esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ),
    593                                                                         esc_attr( $name ),
    594                                                                         __( 'Update Now' )
    595                                                                 );
    596                                                         } else {
    597                                                                 $action_links[] = sprintf(
    598                                                                         '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
    599                                                                         _x( 'Cannot Update', 'plugin' )
    600                                                                 );
    601                                                         }
    602                                                 }
    603                                                 break;
    604 
    605                                         case 'latest_installed':
    606                                         case 'newer_installed':
    607                                                 if ( is_plugin_active( $status['file'] ) ) {
    608                                                         $action_links[] = sprintf(
    609                                                                 '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
    610                                                                 _x( 'Active', 'plugin' )
    611                                                         );
    612                                                 } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) {
    613                                                         if ( $compatible_php && $compatible_wp ) {
    614                                                                 $button_text = __( 'Activate' );
    615                                                                 /* translators: %s: Plugin name. */
    616                                                                 $button_label = _x( 'Activate %s', 'plugin' );
    617                                                                 $activate_url = add_query_arg(
    618                                                                         array(
    619                                                                                 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
    620                                                                                 'action'   => 'activate',
    621                                                                                 'plugin'   => $status['file'],
    622                                                                         ),
    623                                                                         network_admin_url( 'plugins.php' )
    624                                                                 );
    625 
    626                                                                 if ( is_network_admin() ) {
    627                                                                         $button_text = __( 'Network Activate' );
    628                                                                         /* translators: %s: Plugin name. */
    629                                                                         $button_label = _x( 'Network Activate %s', 'plugin' );
    630                                                                         $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
    631                                                                 }
    632 
    633                                                                 $action_links[] = sprintf(
    634                                                                         '<a href="%1$s" class="button activate-now" aria-label="%2$s">%3$s</a>',
    635                                                                         esc_url( $activate_url ),
    636                                                                         esc_attr( sprintf( $button_label, $plugin['name'] ) ),
    637                                                                         $button_text
    638                                                                 );
    639                                                         } else {
    640                                                                 $action_links[] = sprintf(
    641                                                                         '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
    642                                                                         _x( 'Cannot Activate', 'plugin' )
    643                                                                 );
    644                                                         }
    645                                                 } else {
    646                                                         $action_links[] = sprintf(
    647                                                                 '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
    648                                                                 _x( 'Installed', 'plugin' )
    649                                                         );
    650                                                 }
    651                                                 break;
    652                                 }
    653                         }
     560                        $action_links[] = wp_get_plugin_action_button( $name, $plugin, $compatible_php, $compatible_wp );
    654561
    655562                        $details_link = self_admin_url(
    … …  
    829736                }
    830737        }
     738
     739        /**
     740         * Returns a notice containing a list of dependencies required by the plugin.
     741         *
     742         * @since 6.5.0
     743         *
     744         * @param array  $plugin_data An array of plugin data. See {@see plugins_api()}
     745         *                            for the list of possible values.
     746         * @return string A notice containing a list of dependencies required by the plugin,
     747         *                or an empty string if none is required.
     748         */
     749        protected function get_dependencies_notice( $plugin_data ) {
     750                if ( empty( $plugin_data['requires_plugins'] ) ) {
     751                        return '';
     752                }
     753
     754                $no_name_markup  = '<div class="plugin-dependency"><span class="plugin-dependency-name">%s</span></div>';
     755                $has_name_markup = '<div class="plugin-dependency"><span class="plugin-dependency-name">%s</span> %s</div>';
     756
     757                $dependencies_list = '';
     758                foreach ( $plugin_data['requires_plugins'] as $dependency ) {
     759                        $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $dependency );
     760
     761                        if (
     762                                false !== $dependency_data &&
     763                                ! empty( $dependency_data['name'] ) &&
     764                                ! empty( $dependency_data['slug'] ) &&
     765                                ! empty( $dependency_data['version'] )
     766                        ) {
     767                                $more_details_link  = $this->get_more_details_link( $dependency_data['name'], $dependency_data['slug'] );
     768                                $dependencies_list .= sprintf( $has_name_markup, esc_html( $dependency_data['name'] ), $more_details_link );
     769                                continue;
     770                        }
     771
     772                        $result = plugins_api( 'plugin_information', array( 'slug' => $dependency ) );
     773
     774                        if ( ! empty( $result->name ) ) {
     775                                $more_details_link  = $this->get_more_details_link( $result->name, $result->slug );
     776                                $dependencies_list .= sprintf( $has_name_markup, esc_html( $result->name ), $more_details_link );
     777                                continue;
     778                        }
     779
     780                        $dependencies_list .= sprintf( $no_name_markup, esc_html( $dependency ) );
     781                }
     782
     783                $dependencies_notice = sprintf(
     784                        '<div class="plugin-dependencies"><p class="plugin-dependencies-explainer-text">%s</p> %s</div>',
     785                        '<strong>' . __( 'Additional plugins are required' ) . '</strong>',
     786                        $dependencies_list
     787                );
     788
     789                return $dependencies_notice;
     790        }
     791
     792        /**
     793         * Creates a 'More details' link for the plugin.
     794         *
     795         * @since 6.5.0
     796         *
     797         * @param string $name The plugin's name.
     798         * @param string $slug The plugin's slug.
     799         * @return string The 'More details' link for the plugin.
     800         */
     801        protected function get_more_details_link( $name, $slug ) {
     802                $url = add_query_arg(
     803                        array(
     804                                'tab'       => 'plugin-information',
     805                                'plugin'    => $slug,
     806                                'TB_iframe' => 'true',
     807                                'width'     => '600',
     808                                'height'    => '550',
     809                        ),
     810                        network_admin_url( 'plugin-install.php' )
     811                );
     812
     813                $more_details_link = sprintf(
     814                        '<a href="%1$s" class="more-details-link thickbox open-plugin-details-modal" aria-label="%2$s" data-title="%3$s">%4$s</a>',
     815                        esc_url( $url ),
     816                        /* translators: %s: Plugin name. */
     817                        sprintf( __( 'More information about %s' ), esc_html( $name ) ),
     818                        esc_attr( $name ),
     819                        __( 'More Details' )
     820                );
     821
     822                return $more_details_link;
     823        }
    831824}
  • trunk/src/wp-admin/includes/class-wp-plugins-list-table.php

    r56824 r57545  
    755755                $compatible_wp  = is_wp_version_compatible( $requires_wp );
    756756
     757                $has_dependents          = WP_Plugin_Dependencies::has_dependents( $plugin_file );
     758                $has_active_dependents   = WP_Plugin_Dependencies::has_active_dependents( $plugin_file );
     759                $has_unmet_dependencies  = WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file );
     760                $has_circular_dependency = WP_Plugin_Dependencies::has_circular_dependency( $plugin_file );
     761
    757762                if ( 'mustuse' === $context ) {
    758763                        $is_active = true;
    … …  
    797802                                if ( $is_active ) {
    798803                                        if ( current_user_can( 'manage_network_plugins' ) ) {
    799                                                 $actions['deactivate'] = sprintf(
    800                                                         '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
    801                                                         wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
    802                                                         esc_attr( $plugin_id_attr ),
    803                                                         /* translators: %s: Plugin name. */
    804                                                         esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
    805                                                         __( 'Network Deactivate' )
    806                                                 );
     804                                                if ( $has_active_dependents ) {
     805                                                        $actions['deactivate'] = __( 'Deactivate' ) .
     806                                                                '<span class="screen-reader-text">' .
     807                                                                __( 'You cannot deactivate this plugin as other plugins require it.' ) .
     808                                                                '</span>';
     809
     810                                                } else {
     811                                                        $deactivate_url = 'plugins.php?action=deactivate' .
     812                                                                '&amp;plugin=' . urlencode( $plugin_file ) .
     813                                                                '&amp;plugin_status=' . $context .
     814                                                                '&amp;paged=' . $page .
     815                                                                '&amp;s=' . $s;
     816
     817                                                        $actions['deactivate'] = sprintf(
     818                                                                '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
     819                                                                wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ),
     820                                                                esc_attr( $plugin_id_attr ),
     821                                                                /* translators: %s: Plugin name. */
     822                                                                esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
     823                                                                __( 'Network Deactivate' )
     824                                                        );
     825                                                }
    807826                                        }
    808827                                } else {
    809828                                        if ( current_user_can( 'manage_network_plugins' ) ) {
    810829                                                if ( $compatible_php && $compatible_wp ) {
    811                                                         $actions['activate'] = sprintf(
    812                                                                 '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
    813                                                                 wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ),
    814                                                                 esc_attr( $plugin_id_attr ),
    815                                                                 /* translators: %s: Plugin name. */
    816                                                                 esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
    817                                                                 __( 'Network Activate' )
    818                                                         );
     830                                                        if ( $has_unmet_dependencies ) {
     831                                                                $actions['activate'] = __( 'Network Activate' ) .
     832                                                                        '<span class="screen-reader-text">' .
     833                                                                        __( 'You cannot activate this plugin as it has unmet requirements.' ) .
     834                                                                        '</span>';
     835                                                        } else {
     836                                                                $activate_url = 'plugins.php?action=activate' .
     837                                                                        '&amp;plugin=' . urlencode( $plugin_file ) .
     838                                                                        '&amp;plugin_status=' . $context .
     839                                                                        '&amp;paged=' . $page .
     840                                                                        '&amp;s=' . $s;
     841
     842                                                                $actions['activate'] = sprintf(
     843                                                                        '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
     844                                                                        wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ),
     845                                                                        esc_attr( $plugin_id_attr ),
     846                                                                        /* translators: %s: Plugin name. */
     847                                                                        esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
     848                                                                        __( 'Network Activate' )
     849                                                                );
     850                                                        }
    819851                                                } else {
    820852                                                        $actions['activate'] = sprintf(
    … …  
    826858
    827859                                        if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
    828                                                 $actions['delete'] = sprintf(
    829                                                         '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
    830                                                         wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
    831                                                         esc_attr( $plugin_id_attr ),
    832                                                         /* translators: %s: Plugin name. */
    833                                                         esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
    834                                                         __( 'Delete' )
    835                                                 );
     860                                                if ( $has_dependents && ! $has_circular_dependency ) {
     861                                                        $actions['delete'] = __( 'Delete' ) .
     862                                                                '<span class="screen-reader-text">' .
     863                                                                __( 'You cannot delete this plugin as other plugins require it.' ) .
     864                                                                '</span>';
     865                                                } else {
     866                                                        $delete_url = 'plugins.php?action=delete-selected' .
     867                                                                '&amp;checked[]=' . urlencode( $plugin_file ) .
     868                                                                '&amp;plugin_status=' . $context .
     869                                                                '&amp;paged=' . $page .
     870                                                                '&amp;s=' . $s;
     871
     872                                                        $actions['delete'] = sprintf(
     873                                                                '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
     874                                                                wp_nonce_url( $delete_url, 'bulk-plugins' ),
     875                                                                esc_attr( $plugin_id_attr ),
     876                                                                /* translators: %s: Plugin name. */
     877                                                                esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
     878                                                                __( 'Delete' )
     879                                                        );
     880                                                }
    836881                                        }
    837882                                }
    … …  
    847892                                } elseif ( $is_active ) {
    848893                                        if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
    849                                                 $actions['deactivate'] = sprintf(
    850                                                         '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
    851                                                         wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
    852                                                         esc_attr( $plugin_id_attr ),
    853                                                         /* translators: %s: Plugin name. */
    854                                                         esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
    855                                                         __( 'Deactivate' )
    856                                                 );
     894                                                if ( $has_active_dependents ) {
     895                                                        $actions['deactivate'] = __( 'Deactivate' ) .
     896                                                                '<span class="screen-reader-text">' .
     897                                                                __( 'You cannot deactivate this plugin as other plugins depend on it.' ) .
     898                                                                '</span>';
     899                                                } else {
     900                                                        $deactivate_url = 'plugins.php?action=deactivate' .
     901                                                                '&amp;plugin=' . urlencode( $plugin_file ) .
     902                                                                '&amp;plugin_status=' . $context .
     903                                                                '&amp;paged=' . $page .
     904                                                                '&amp;s=' . $s;
     905
     906                                                        $actions['deactivate'] = sprintf(
     907                                                                '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
     908                                                                wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ),
     909                                                                esc_attr( $plugin_id_attr ),
     910                                                                /* translators: %s: Plugin name. */
     911                                                                esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
     912                                                                __( 'Deactivate' )
     913                                                        );
     914                                                }
    857915                                        }
    858916
    859917                                        if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
     918                                                $resume_url = 'plugins.php?action=resume' .
     919                                                        '&amp;plugin=' . urlencode( $plugin_file ) .
     920                                                        '&amp;plugin_status=' . $context .
     921                                                        '&amp;paged=' . $page .
     922                                                        '&amp;s=' . $s;
     923
    860924                                                $actions['resume'] = sprintf(
    861925                                                        '<a href="%s" id="resume-%s" class="resume-link" aria-label="%s">%s</a>',
    862                                                         wp_nonce_url( 'plugins.php?action=resume&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'resume-plugin_' . $plugin_file ),
     926                                                        wp_nonce_url( $resume_url, 'resume-plugin_' . $plugin_file ),
    863927                                                        esc_attr( $plugin_id_attr ),
    864928                                                        /* translators: %s: Plugin name. */
    … …  
    870934                                        if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
    871935                                                if ( $compatible_php && $compatible_wp ) {
    872                                                         $actions['activate'] = sprintf(
    873                                                                 '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
    874                                                                 wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ),
    875                                                                 esc_attr( $plugin_id_attr ),
    876                                                                 /* translators: %s: Plugin name. */
    877                                                                 esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
    878                                                                 __( 'Activate' )
    879                                                         );
     936                                                        if ( $has_unmet_dependencies ) {
     937                                                                $actions['activate'] = __( 'Activate' ) .
     938                                                                        '<span class="screen-reader-text">' .
     939                                                                        __( 'You cannot activate this plugin as it has unmet requirements.' ) .
     940                                                                        '</span>';
     941                                                        } else {
     942                                                                $activate_url = 'plugins.php?action=activate' .
     943                                                                        '&amp;plugin=' . urlencode( $plugin_file ) .
     944                                                                        '&amp;plugin_status=' . $context .
     945                                                                        '&amp;paged=' . $page .
     946                                                                        '&amp;s=' . $s;
     947
     948                                                                $actions['activate'] = sprintf(
     949                                                                        '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
     950                                                                        wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ),
     951                                                                        esc_attr( $plugin_id_attr ),
     952                                                                        /* translators: %s: Plugin name. */
     953                                                                        esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
     954                                                                        __( 'Activate' )
     955                                                                );
     956                                                        }
    880957                                                } else {
    881958                                                        $actions['activate'] = sprintf(
    … …  
    887964
    888965                                        if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
    889                                                 $actions['delete'] = sprintf(
    890                                                         '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
    891                                                         wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
    892                                                         esc_attr( $plugin_id_attr ),
    893                                                         /* translators: %s: Plugin name. */
    894                                                         esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
    895                                                         __( 'Delete' )
    896                                                 );
     966                                                if ( $has_dependents && ! $has_circular_dependency ) {
     967                                                        $actions['delete'] = __( 'Delete' ) .
     968                                                                '<span class="screen-reader-text">' .
     969                                                                __( 'You cannot delete this plugin as other plugins require it.' ) .
     970                                                                '</span>';
     971                                                } else {
     972                                                        $delete_url = 'plugins.php?action=delete-selected' .
     973                                                                '&amp;checked[]=' . urlencode( $plugin_file ) .
     974                                                                '&amp;plugin_status=' . $context .
     975                                                                '&amp;paged=' . $page .
     976                                                                '&amp;s=' . $s;
     977
     978                                                        $actions['delete'] = sprintf(
     979                                                                '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
     980                                                                wp_nonce_url( $delete_url, 'bulk-plugins' ),
     981                                                                esc_attr( $plugin_id_attr ),
     982                                                                /* translators: %s: Plugin name. */
     983                                                                esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
     984                                                                __( 'Delete' )
     985                                                        );
     986                                                }
    897987                                        }
    898988                                } // End if $is_active.
    … …  
    9891079                $class       = $is_active ? 'active' : 'inactive';
    9901080                $checkbox_id = 'checkbox_' . md5( $plugin_file );
    991 
    992                 if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ), true ) || ! $compatible_php ) {
     1081                $disabled    = '';
     1082
     1083                if ( $has_active_dependents || $has_unmet_dependencies ) {
     1084                        $disabled = 'disabled';
     1085                }
     1086
     1087                if (
     1088                        $restrict_network_active ||
     1089                        $restrict_network_only ||
     1090                        in_array( $status, array( 'mustuse', 'dropins' ), true ) ||
     1091                        ! $compatible_php
     1092                ) {
    9931093                        $checkbox = '';
    9941094                } else {
    9951095                        $checkbox = sprintf(
    996                                 '<input type="checkbox" name="checked[]" value="%1$s" id="%2$s" />' .
    997                                 '<label for="%2$s"><span class="screen-reader-text">%3$s</span></label>',
    998                                 esc_attr( $plugin_file ),
     1096                                '<label class="label-covers-full-cell" for="%1$s">' .
     1097                                '<span class="screen-reader-text">%2$s</span></label>' .
     1098                                '<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" ' . $disabled . '/>',
    9991099                                $checkbox_id,
    10001100                                /* translators: Hidden accessibility text. %s: Plugin name. */
    1001                                 sprintf( __( 'Select %s' ), $plugin_data['Name'] )
     1101                                sprintf( __( 'Select %s' ), $plugin_data['Name'] ),
     1102                                esc_attr( $plugin_file )
    10021103                        );
    10031104                }
    … …  
    10081109                }
    10091110
    1010                 if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] )
    1011                         || ! $compatible_php || ! $compatible_wp
     1111                if (
     1112                        ! empty( $totals['upgrade'] ) &&
     1113                        ! empty( $plugin_data['update'] ) ||
     1114                        ! $compatible_php ||
     1115                        ! $compatible_wp
    10121116                ) {
    10131117                        $class .= ' update';
    … …  
    10581162
    10591163                                        $plugin_meta = array();
     1164
    10601165                                        if ( ! empty( $plugin_data['Version'] ) ) {
    10611166                                                /* translators: %s: Plugin version number. */
    10621167                                                $plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
    10631168                                        }
     1169
    10641170                                        if ( ! empty( $plugin_data['Author'] ) ) {
    10651171                                                $author = $plugin_data['Author'];
     1172
    10661173                                                if ( ! empty( $plugin_data['AuthorURI'] ) ) {
    10671174                                                        $author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
    10681175                                                }
     1176
    10691177                                                /* translators: %s: Plugin author name. */
    10701178                                                $plugin_meta[] = sprintf( __( 'By %s' ), $author );
    … …  
    11501258                                        echo '</div>';
    11511259
     1260                                        if ( $has_dependents ) {
     1261                                                $this->add_dependents_to_dependency_plugin_row( $plugin_file );
     1262                                        }
     1263
     1264                                        if ( WP_Plugin_Dependencies::has_dependencies( $plugin_file ) ) {
     1265                                                $this->add_dependencies_to_dependent_plugin_row( $plugin_file );
     1266                                        }
     1267
     1268                                        /**
     1269                                         * Fires after plugin row meta.
     1270                                         *
     1271                                         * @since 6.5.0
     1272                                         *
     1273                                         * @param string $plugin_file Refer to {@see 'plugin_row_meta'} filter.
     1274                                         * @param array  $plugin_data Refer to {@see 'plugin_row_meta'} filter.
     1275                                         */
     1276                                        do_action( 'after_plugin_row_meta', $plugin_file, $plugin_data );
     1277
    11521278                                        if ( $paused ) {
    11531279                                                $notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' );
    … …  
    13921518                return 'name';
    13931519        }
     1520
     1521        /**
     1522         * Prints a list of other plugins that depend on the plugin.
     1523         *
     1524         * @since 6.5.0
     1525         *
     1526         * @param string $dependency The dependency's filepath, relative to the plugins directory.
     1527         */
     1528        protected function add_dependents_to_dependency_plugin_row( $dependency ) {
     1529                $dependent_names = WP_Plugin_Dependencies::get_dependent_names( $dependency );
     1530
     1531                if ( empty( $dependent_names ) ) {
     1532                        return;
     1533                }
     1534
     1535                $dependency_note = __( 'Note: this plugin cannot be deactivated or deleted until the plugins that require it are deactivated or deleted.' );
     1536                printf(
     1537                        '<div class="required-by"><p><strong>%1$s</strong> %2$s</p><p>%3$s</p></div>',
     1538                        __( 'Required by:' ),
     1539                        esc_html( implode( ' | ', $dependent_names ) ),
     1540                        $dependency_note
     1541                );
     1542        }
     1543
     1544        /**
     1545         * Prints a list of other plugins that the plugin depends on.
     1546         *
     1547         * @since 6.5.0
     1548         *
     1549         * @param string $dependent The dependent plugin's filepath, relative to the plugins directory.
     1550         */
     1551        protected function add_dependencies_to_dependent_plugin_row( $dependent ) {
     1552                $dependency_names = WP_Plugin_Dependencies::get_dependency_names( $dependent );
     1553
     1554                if ( array() === $dependency_names ) {
     1555                        return;
     1556                }
     1557
     1558                $links = array();
     1559                foreach ( $dependency_names as $slug => $name ) {
     1560                        $links[] = $this->get_dependency_view_details_link( $name, $slug );
     1561                }
     1562
     1563                $dependency_note = __( 'Note: this plugin cannot be activated until the plugins that are required by it are activated.' );
     1564
     1565                printf(
     1566                        '<div class="requires"><p><strong>%1$s</strong> %2$s</p><p>%3$s</p></div>',
     1567                        __( 'Requires:' ),
     1568                        implode( ' | ', $links ),
     1569                        $dependency_note
     1570                );
     1571        }
     1572
     1573        /**
     1574         * Returns a 'View details' like link for a dependency.
     1575         *
     1576         * @since 6.5.0
     1577         *
     1578         * @param string $name The dependency's name.
     1579         * @param string $slug The dependency's slug.
     1580         * @return string A 'View details' link for the dependency.
     1581         */
     1582        protected function get_dependency_view_details_link( $name, $slug ) {
     1583                $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $slug );
     1584
     1585                if ( false === $dependency_data
     1586                        || $name === $slug
     1587                        || $name !== $dependency_data['name']
     1588                        || empty( $dependency_data['version'] )
     1589                ) {
     1590                        return $name;
     1591                }
     1592
     1593                return $this->get_view_details_link( $name, $slug );
     1594        }
     1595
     1596        /**
     1597         * Returns a 'View details' link for the plugin.
     1598         *
     1599         * @since 6.5.0
     1600         *
     1601         * @param string $name The plugin's name.
     1602         * @param string $slug The plugin's slug.
     1603         * @return string A 'View details' link for the plugin.
     1604         */
     1605        protected function get_view_details_link( $name, $slug ) {
     1606                $url = add_query_arg(
     1607                        array(
     1608                                'tab'       => 'plugin-information',
     1609                                'plugin'    => $slug,
     1610                                'TB_iframe' => 'true',
     1611                                'width'     => '600',
     1612                                'height'    => '550',
     1613                        ),
     1614                        network_admin_url( 'plugin-install.php' )
     1615                );
     1616
     1617                $name_attr = esc_attr( $name );
     1618                return sprintf(
     1619                        "<a href='%s' class='thickbox open-plugin-details-modal' aria-label='%s' data-title='%s'>%s</a>",
     1620                        esc_url( $url ),
     1621                        /* translators: %s: Plugin name. */
     1622                        sprintf( __( 'More information about %s' ), $name_attr ),
     1623                        $name_attr,
     1624                        esc_html( $name )
     1625                );
     1626        }
    13941627}
  • trunk/src/wp-admin/includes/plugin-install.php

    r56571 r57545  
    885885        echo "<div id='$tab-footer'>\n";
    886886        if ( ! empty( $api->download_link ) && ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) ) {
    887                 $status = install_plugin_install_status( $api );
     887                $button = wp_get_plugin_action_button( $api->name, $api, $compatible_php, $compatible_wp );
     888                $button = str_replace( 'class="', 'class="right ', $button );
     889
     890                if ( ! str_contains( $button, __( 'Activate' ) ) ) {
     891                        $button = str_replace( 'class="', 'id="plugin_install_from_iframe" class="', $button );
     892                }
     893
     894                echo wp_kses_post( $button );
     895        }
     896        echo "</div>\n";
     897
     898        wp_print_request_filesystem_credentials_modal();
     899        wp_print_admin_notice_templates();
     900
     901        iframe_footer();
     902        exit;
     903}
     904
     905/**
     906 * Gets the markup for the plugin install action button.
     907 *
     908 * @since 6.5.0
     909 *
     910 * @param string       $name           Plugin name.
     911 * @param array|object $data           {
     912 *     An array or object of plugin data. Can be retrieved from the API.
     913 *
     914 *     @type string   $slug             The plugin slug.
     915 *     @type string[] $requires_plugins An array of plugin dependency slugs.
     916 *     @type string   $version          The plugin's version string. Used when getting the install status.
     917 * }
     918 * @param bool         $compatible_php   The result of a PHP compatibility check.
     919 * @param bool         $compatible_wp    The result of a WP compatibility check.
     920 * @return string $button The markup for the dependency row button.
     921 */
     922function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible_wp ) {
     923        $button           = '';
     924        $data             = (object) $data;
     925        $status           = install_plugin_install_status( $data );
     926        $requires_plugins = $data->requires_plugins ?? array();
     927
     928        // Determine the status of plugin dependencies.
     929        $installed_plugins                   = get_plugins();
     930        $active_plugins                      = get_option( 'active_plugins' );
     931        $plugin_dependencies_count           = count( $requires_plugins );
     932        $installed_plugin_dependencies_count = 0;
     933        $active_plugin_dependencies_count    = 0;
     934        foreach ( $requires_plugins as $dependency ) {
     935                foreach ( array_keys( $installed_plugins ) as $installed_plugin_file ) {
     936                        if ( str_contains( $installed_plugin_file, '/' ) && explode( '/', $installed_plugin_file )[0] === $dependency ) {
     937                                ++$installed_plugin_dependencies_count;
     938                        }
     939                }
     940
     941                foreach ( $active_plugins as $active_plugin_file ) {
     942                        if ( str_contains( $active_plugin_file, '/' ) && explode( '/', $active_plugin_file )[0] === $dependency ) {
     943                                ++$active_plugin_dependencies_count;
     944                        }
     945                }
     946        }
     947        $all_plugin_dependencies_installed = $installed_plugin_dependencies_count === $plugin_dependencies_count;
     948        $all_plugin_dependencies_active    = $active_plugin_dependencies_count === $plugin_dependencies_count;
     949
     950        sprintf(
     951                '<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
     952                esc_attr( $data->slug ),
     953                esc_url( $status['url'] ),
     954                /* translators: %s: Plugin name and version. */
     955                esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
     956                esc_attr( $name ),
     957                __( 'Install Now' )
     958        );
     959
     960        if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
    888961                switch ( $status['status'] ) {
    889962                        case 'install':
    890963                                if ( $status['url'] ) {
    891                                         if ( $compatible_php && $compatible_wp ) {
    892                                                 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>';
     964                                        if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_installed && ! empty( $data->download_link ) ) {
     965                                                $button = sprintf(
     966                                                        '<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
     967                                                        esc_attr( $data->slug ),
     968                                                        esc_url( $status['url'] ),
     969                                                        /* translators: %s: Plugin name and version. */
     970                                                        esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
     971                                                        esc_attr( $name ),
     972                                                        __( 'Install Now' )
     973                                                );
    893974                                        } else {
    894                                                 printf(
    895                                                         '<button type="button" class="button button-primary button-disabled right" disabled="disabled">%s</button>',
    896                                                         _x( 'Cannot Install', 'plugin' )
     975                                                $button = sprintf(
     976                                                        '<button type="button" class="install-now button button-disabled" disabled="disabled">%s</button>',
     977                                                        _x( 'Install Now', 'plugin' )
    897978                                                );
    898979                                        }
    899980                                }
    900981                                break;
     982
    901983                        case 'update_available':
    902984                                if ( $status['url'] ) {
    903                                         if ( $compatible_php ) {
    904                                                 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>';
     985                                        if ( $compatible_php && $compatible_wp ) {
     986                                                $button = sprintf(
     987                                                        '<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>',
     988                                                        esc_attr( $status['file'] ),
     989                                                        esc_attr( $data->slug ),
     990                                                        esc_url( $status['url'] ),
     991                                                        /* translators: %s: Plugin name and version. */
     992                                                        esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ),
     993                                                        esc_attr( $name ),
     994                                                        __( 'Update Now' )
     995                                                );
    905996                                        } else {
    906                                                 printf(
    907                                                         '<button type="button" class="button button-primary button-disabled right" disabled="disabled">%s</button>',
    908                                                         _x( 'Cannot Update', 'plugin' )
     997                                                $button = sprintf(
     998                                                        '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
     999                                                        _x( 'Update Now', 'plugin' )
    9091000                                                );
    9101001                                        }
    9111002                                }
    9121003                                break;
     1004
     1005                        case 'latest_installed':
    9131006                        case 'newer_installed':
    914                                 /* translators: %s: Plugin version. */
    915                                 echo '<a class="button button-primary right disabled">' . sprintf( __( 'Newer Version (%s) Installed' ), esc_html( $status['version'] ) ) . '</a>';
     1007                                if ( is_plugin_active( $status['file'] ) ) {
     1008                                        $button = sprintf(
     1009                                                '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
     1010                                                _x( 'Active', 'plugin' )
     1011                                        );
     1012                                } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) {
     1013                                        if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_active ) {
     1014                                                $button_text = __( 'Activate' );
     1015                                                /* translators: %s: Plugin name. */
     1016                                                $button_label = _x( 'Activate %s', 'plugin' );
     1017                                                $activate_url = add_query_arg(
     1018                                                        array(
     1019                                                                '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
     1020                                                                'action'   => 'activate',
     1021                                                                'plugin'   => $status['file'],
     1022                                                        ),
     1023                                                        network_admin_url( 'plugins.php' )
     1024                                                );
     1025
     1026                                                if ( is_network_admin() ) {
     1027                                                        $button_text = __( 'Network Activate' );
     1028                                                        /* translators: %s: Plugin name. */
     1029                                                        $button_label = _x( 'Network Activate %s', 'plugin' );
     1030                                                        $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
     1031                                                }
     1032
     1033                                                $button = sprintf(
     1034                                                        '<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>',
     1035                                                        esc_url( $activate_url ),
     1036                                                        esc_attr( $name ),
     1037                                                        esc_attr( $data->slug ),
     1038                                                        esc_attr( $status['file'] ),
     1039                                                        esc_attr( sprintf( $button_label, $name ) ),
     1040                                                        $button_text
     1041                                                );
     1042                                        } else {
     1043                                                $button = sprintf(
     1044                                                        '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
     1045                                                        is_network_admin() ? _x( 'Network Activate %s', 'plugin' ) : _x( 'Activate', 'plugin' )
     1046                                                );
     1047                                        }
     1048                                } else {
     1049                                        $button = sprintf(
     1050                                                '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
     1051                                                _x( 'Installed', 'plugin' )
     1052                                        );
     1053                                }
    9161054                                break;
    917                         case 'latest_installed':
    918                                 echo '<a class="button button-primary right disabled">' . __( 'Latest Version Installed' ) . '</a>';
    919                                 break;
    920                 }
    921         }
    922         echo "</div>\n";
    923 
    924         iframe_footer();
    925         exit;
     1055                }
     1056
     1057                return $button;
     1058        }
    9261059}
  • trunk/src/wp-admin/includes/plugin.php

    r57337 r57545  
    4646 * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers.
    4747 * @since 5.8.0 Added support for `Update URI` header.
     48 * @since 6.5.0 Added support for `Requires Plugins` header.
    4849 *
    4950 * @param string $plugin_file Absolute path to the main plugin file.
    … …  
    5455 *     Plugin data. Values will be empty if not supplied by the plugin.
    5556 *
    56  *     @type string $Name        Name of the plugin. Should be unique.
    57  *     @type string $PluginURI   Plugin URI.
    58  *     @type string $Version     Plugin version.
    59  *     @type string $Description Plugin description.
    60  *     @type string $Author      Plugin author's name.
    61  *     @type string $AuthorURI   Plugin author's website address (if set).
    62  *     @type string $TextDomain  Plugin textdomain.
    63  *     @type string $DomainPath  Plugin's relative directory path to .mo files.
    64  *     @type bool   $Network     Whether the plugin can only be activated network-wide.
    65  *     @type string $RequiresWP  Minimum required version of WordPress.
    66  *     @type string $RequiresPHP Minimum required version of PHP.
    67  *     @type string $UpdateURI   ID of the plugin for update purposes, should be a URI.
    68  *     @type string $Title       Title of the plugin and link to the plugin's site (if set).
    69  *     @type string $AuthorName  Plugin author's name.
     57 *     @type string $Name            Name of the plugin. Should be unique.
     58 *     @type string $PluginURI       Plugin URI.
     59 *     @type string $Version         Plugin version.
     60 *     @type string $Description     Plugin description.
     61 *     @type string $Author          Plugin author's name.
     62 *     @type string $AuthorURI       Plugin author's website address (if set).
     63 *     @type string $TextDomain      Plugin textdomain.
     64 *     @type string $DomainPath      Plugin's relative directory path to .mo files.
     65 *     @type bool   $Network         Whether the plugin can only be activated network-wide.
     66 *     @type string $RequiresWP      Minimum required version of WordPress.
     67 *     @type string $RequiresPHP     Minimum required version of PHP.
     68 *     @type string $UpdateURI       ID of the plugin for update purposes, should be a URI.
     69 *     @type string $RequiresPlugins Comma separated list of dot org plugin slugs.
     70 *     @type string $Title           Title of the plugin and link to the plugin's site (if set).
     71 *     @type string $AuthorName      Plugin author's name.
    7072 * }
    7173 */
    … …  
    7375
    7476        $default_headers = array(
    75                 'Name'        => 'Plugin Name',
    76                 'PluginURI'   => 'Plugin URI',
    77                 'Version'     => 'Version',
    78                 'Description' => 'Description',
    79                 'Author'      => 'Author',
    80                 'AuthorURI'   => 'Author URI',
    81                 'TextDomain'  => 'Text Domain',
    82                 'DomainPath'  => 'Domain Path',
    83                 'Network'     => 'Network',
    84                 'RequiresWP'  => 'Requires at least',
    85                 'RequiresPHP' => 'Requires PHP',
    86                 'UpdateURI'   => 'Update URI',
     77                'Name'            => 'Plugin Name',
     78                'PluginURI'       => 'Plugin URI',
     79                'Version'         => 'Version',
     80                'Description'     => 'Description',
     81                'Author'          => 'Author',
     82                'AuthorURI'       => 'Author URI',
     83                'TextDomain'      => 'Text Domain',
     84                'DomainPath'      => 'Domain Path',
     85                'Network'         => 'Network',
     86                'RequiresWP'      => 'Requires at least',
     87                'RequiresPHP'     => 'Requires PHP',
     88                'UpdateURI'       => 'Update URI',
     89                'RequiresPlugins' => 'Requires Plugins',
    8790                // Site Wide Only is deprecated in favor of Network.
    88                 '_sitewide'   => 'Site Wide Only',
     91                '_sitewide'       => 'Site Wide Only',
    8992        );
    9093
    … …  
    331334        }
    332335
     336        $new_plugin_data = array();
    333337        foreach ( $plugin_files as $plugin_file ) {
    334338                if ( ! is_readable( "$plugin_root/$plugin_file" ) ) {
    … …  
    343347                }
    344348
     349                $new_plugin_file = str_replace(
     350                        trailingslashit( WP_PLUGIN_DIR ),
     351                        '',
     352                        "$plugin_root/$plugin_file"
     353                );
     354
     355                $new_plugin_data[ $new_plugin_file ]           = $plugin_data;
    345356                $wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data;
    346357        }
    … …  
    350361        $cache_plugins[ $plugin_folder ] = $wp_plugins;
    351362        wp_cache_set( 'plugins', $cache_plugins, 'plugins' );
     363        update_option( 'plugin_data', $new_plugin_data );
    352364
    353365        return $wp_plugins;
    … …  
    952964
    953965        $plugin_translations = wp_get_installed_translations( 'plugins' );
     966        $all_plugin_data     = get_option( 'plugin_data', array() );
    954967
    955968        $errors = array();
    … …  
    9961009                        continue;
    9971010                }
     1011                unset( $all_plugin_data[ $plugin_file ] );
    9981012
    9991013                $plugin_slug = dirname( $plugin_file );
    … …  
    10441058                return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) );
    10451059        }
     1060        update_option( 'plugin_data', $all_plugin_data );
    10461061
    10471062        return true;
    … …  
    11151130 * Validates the plugin requirements for WordPress version and PHP version.
    11161131 *
    1117  * Uses the information from `Requires at least` and `Requires PHP` headers
     1132 * Uses the information from `Requires at least`, `Requires PHP` and `Requires Plugins` headers
    11181133 * defined in the plugin's main PHP file.
    11191134 *
    … …  
    11221137 *              main PHP file, with `readme.txt` as a fallback.
    11231138 * @since 5.8.0 Removed support for using `readme.txt` as a fallback.
     1139 * @since 6.5.0 Added support for the 'Requires Plugins' header.
    11241140 *
    11251141 * @param string $plugin Path to the plugin file relative to the plugins directory.
    … …  
    11301146
    11311147        $requirements = array(
    1132                 'requires'     => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
    1133                 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
     1148                'requires'         => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
     1149                'requires_php'     => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
     1150                'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '',
    11341151        );
    11351152
    … …  
    11861203        }
    11871204
     1205        if ( WP_Plugin_Dependencies::has_unmet_dependencies( $plugin ) ) {
     1206                $dependencies       = WP_Plugin_Dependencies::get_dependencies( $plugin );
     1207                $unmet_dependencies = array();
     1208
     1209                foreach ( $dependencies as $dependency ) {
     1210                        $dependency_file = WP_Plugin_Dependencies::get_dependency_filepath( $dependency );
     1211
     1212                        if ( false === $dependency_file ) {
     1213                                $unmet_dependencies['not_installed'][] = $dependency;
     1214                        } elseif ( is_plugin_inactive( $dependency_file ) ) {
     1215                                $unmet_dependencies['inactive'][] = $dependency;
     1216                        }
     1217                }
     1218
     1219                return new WP_Error(
     1220                        'plugin_missing_dependencies',
     1221                        '<p>' . sprintf(
     1222                                /* translators: %s: Plugin name. */
     1223                                _x( '<strong>Error:</strong> %s requires plugins that are not installed or activated.', 'plugin' ),
     1224                                $plugin_headers['Name']
     1225                        ) . '</p>',
     1226                        $unmet_dependencies
     1227                );
     1228        }
     1229
    11881230        return true;
    11891231}
  • trunk/src/wp-admin/plugin-install.php

    r55412 r57545  
    135135 */
    136136require_once ABSPATH . 'wp-admin/admin-header.php';
     137
     138WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies();
     139WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents();
     140WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies();
    137141?>
    138142<div class="wrap <?php echo esc_attr( "plugin-install-tab-$tab" ); ?>">
  • trunk/src/wp-admin/plugins.php

    r56695 r57545  
    740740?>
    741741
     742<?php WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies(); ?>
     743<?php WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents(); ?>
     744<?php WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies(); ?>
    742745<div class="wrap">
    743746<h1 class="wp-heading-inline">
  • trunk/src/wp-includes/functions.php

    r57543 r57545  
    33603360                 */
    33613361
    3362                  // Divide the header string into 4 byte groups.
     3362                // Divide the header string into 4 byte groups.
    33633363                $magic = str_split( $magic, 8 );
    33643364
  • trunk/src/wp-includes/load.php

    r57504 r57545  
    987987        $network_plugins = is_multisite() ? wp_get_active_network_plugins() : false;
    988988
     989        $invalid_plugins = array();
    989990        foreach ( $active_plugins as $plugin ) {
    990991                if ( ! validate_file( $plugin )                     // $plugin must validate as file.
    … …  
    995996                ) {
    996997                        $plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
     998                } else {
     999                        $invalid_plugins[] = $plugin;
     1000                }
     1001        }
     1002
     1003        if ( ! empty( $invalid_plugins ) ) {
     1004                $all_plugin_data = get_option( 'plugin_data', array() );
     1005
     1006                if ( ! empty( $all_plugin_data ) ) {
     1007                        foreach ( $invalid_plugins as $invalid_plugin ) {
     1008                                unset( $all_plugin_data[ $invalid_plugin ] );
     1009                        }
     1010
     1011                        update_option( 'plugin_data', $all_plugin_data );
    9971012                }
    9981013        }
    … …  
    11911206                'update-plugin',          // Update an existing plugin.
    11921207                'update-theme',           // Update an existing theme.
     1208                'activate-plugin',        // Activating an existing plugin.
    11931209        );
    11941210
  • trunk/src/wp-settings.php

    r57539 r57545  
    413413$GLOBALS['wp_plugin_paths'] = array();
    414414
     415// Load and initialize WP_Plugin_Dependencies.
     416require_once ABSPATH . WPINC . '/class-wp-plugin-dependencies.php';
     417if ( ! defined( 'WP_RUN_CORE_TESTS' ) ) {
     418        WP_Plugin_Dependencies::initialize();
     419}
     420
    415421// Load must-use plugins.
    416422foreach ( wp_get_mu_plugins() as $mu_plugin ) {
    … …  
    487493
    488494// Load active plugins.
     495$all_plugin_data    = get_option( 'plugin_data', array() );
     496$failed_plugins     = array();
     497$update_plugin_data = false;
    489498foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
     499        $plugin_file = str_replace( trailingslashit( WP_PLUGIN_DIR ), '', $plugin );
     500        if ( ! isset( $all_plugin_data[ $plugin_file ] ) ) {
     501                require_once ABSPATH . 'wp-admin/includes/plugin.php';
     502                $all_plugin_data[ $plugin_file ] = get_plugin_data( WP_PLUGIN_DIR . "/$plugin_file" );
     503
     504                $update_plugin_data = true;
     505        }
     506
     507        $plugin_headers   = $all_plugin_data[ $plugin_file ];
     508        $errors           = array();
     509        $requirements     = array(
     510                'requires'         => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',
     511                'requires_php'     => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',
     512                'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '',
     513        );
     514        $compatible_wp    = is_wp_version_compatible( $requirements['requires'] );
     515        $compatible_php   = is_php_version_compatible( $requirements['requires_php'] );
     516        $dependencies_met = ! WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file );
     517
     518        $php_update_message = '</p><p>' . sprintf(
     519                /* translators: %s: URL to Update PHP page. */
     520                __( '<a href="%s">Learn more about updating PHP</a>.' ),
     521                esc_url( wp_get_update_php_url() )
     522        );
     523
     524        $annotation = wp_get_update_php_annotation();
     525
     526        if ( $annotation ) {
     527                $php_update_message .= '</p><p><em>' . $annotation . '</em>';
     528        }
     529
     530        if ( ! $dependencies_met ) {
     531                $errors[] = sprintf(
     532                        /* translators: %s: The plugin's name. */
     533                        _x( '%s has unmet dependencies.', 'plugin' ),
     534                        $plugin_headers['Name']
     535                );
     536        }
     537
     538        if ( ! $compatible_wp && ! $compatible_php ) {
     539                $errors[] = sprintf(
     540                        /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */
     541                        _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' ),
     542                        get_bloginfo( 'version' ),
     543                        PHP_VERSION,
     544                        $plugin_headers['Name'],
     545                        $requirements['requires'],
     546                        $requirements['requires_php']
     547                ) . $php_update_message;
     548        } elseif ( ! $compatible_php ) {
     549                $errors[] = sprintf(
     550                        /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */
     551                        _x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ),
     552                        PHP_VERSION,
     553                        $plugin_headers['Name'],
     554                        $requirements['requires_php']
     555                ) . $php_update_message;
     556        } elseif ( ! $compatible_wp ) {
     557                $errors[] = sprintf(
     558                        /* translators: 1: Current WordPress version, 2: Plugin name, 3: Required WordPress version. */
     559                        _x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ),
     560                        get_bloginfo( 'version' ),
     561                        $plugin_headers['Name'],
     562                        $requirements['requires']
     563                );
     564        }
     565
     566        if ( ! empty( $errors ) ) {
     567                $failed_plugins[ $plugin_file ] = '';
     568                foreach ( $errors as $error ) {
     569                        $failed_plugins[ $plugin_file ] .= wp_get_admin_notice(
     570                                $error,
     571                                array(
     572                                        'type'        => 'error',
     573                                        'dismissible' => true,
     574                                )
     575                        );
     576                }
     577                continue;
     578        }
     579
    490580        wp_register_plugin_realpath( $plugin );
    491581
    … …  
    505595unset( $plugin, $_wp_plugin_file );
    506596
     597if ( $update_plugin_data ) {
     598        update_option( 'plugin_data', $all_plugin_data );
     599}
     600
     601if ( ! empty( $failed_plugins ) ) {
     602        add_action(
     603                'admin_notices',
     604                function () use ( $failed_plugins ) {
     605                        global $pagenow;
     606
     607                        if ( 'index.php' === $pagenow || 'plugins.php' === $pagenow ) {
     608                                echo implode( '', $failed_plugins );
     609                        }
     610                }
     611        );
     612}
     613unset( $failed_plugins );
     614
    507615// Load pluggable functions.
    508616require ABSPATH . WPINC . '/pluggable.php';
Note: See TracChangeset for help on using the changeset viewer.