Make WordPress Core

Changeset 57545


Ignore:
Timestamp:
02/06/2024 11:44:09 PM (5 months 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.