Make WordPress Core


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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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}
Note: See TracChangeset for help on using the changeset viewer.