diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-admin/includes/class-wp-plugins-list-table.php --- a/wp/wp-admin/includes/class-wp-plugins-list-table.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-admin/includes/class-wp-plugins-list-table.php Fri Sep 05 18:40:08 2025 +0200 @@ -11,7 +11,6 @@ * Core class used to implement displaying installed plugins in a list table. * * @since 3.1.0 - * @access private * * @see WP_List_Table */ @@ -62,8 +61,7 @@ $this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'plugin' ) && current_user_can( 'update_plugins' ) - && ( ! is_multisite() || $this->screen->in_admin( 'network' ) ) - && ! in_array( $status, array( 'mustuse', 'dropins' ), true ); + && ( ! is_multisite() || $this->screen->in_admin( 'network' ) ); } /** @@ -92,7 +90,8 @@ public function prepare_items() { global $status, $plugins, $totals, $page, $orderby, $order, $s; - wp_reset_vars( array( 'orderby', 'order' ) ); + $orderby = ! empty( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : ''; + $order = ! empty( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : ''; /** * Filters the full array of plugins to list in the Plugins list table. @@ -263,8 +262,10 @@ } } elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) ) || ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) { - // On the non-network screen, populate the active list with plugins that are individually activated. - // On the network admin screen, populate the active list with plugins that are network-activated. + /* + * On the non-network screen, populate the active list with plugins that are individually activated. + * On the network admin screen, populate the active list with plugins that are network-activated. + */ $plugins['active'][ $plugin_file ] = $plugin_data; if ( ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ) ) { @@ -298,6 +299,15 @@ $plugins['search'] = array_filter( $plugins['all'], array( $this, '_search_callback' ) ); } + /** + * Filters the array of plugins for the list table. + * + * @since 6.3.0 + * + * @param array[] $plugins An array of arrays of plugin data, keyed by context. + */ + $plugins = apply_filters( 'plugins_list', $plugins ); + $totals = array(); foreach ( $plugins as $type => $list ) { $totals[ $type ] = count( $list ); @@ -404,7 +414,7 @@ global $plugins; if ( ! empty( $_REQUEST['s'] ) ) { - $s = esc_html( wp_unslash( $_REQUEST['s'] ) ); + $s = esc_html( urldecode( wp_unslash( $_REQUEST['s'] ) ) ); /* translators: %s: Plugin search term. */ printf( __( 'No plugins found for: %s.' ), '' . $s . '' ); @@ -443,8 +453,8 @@ } ?> __( 'Description' ), ); - if ( $this->show_autoupdates ) { + if ( $this->show_autoupdates && ! in_array( $status, array( 'mustuse', 'dropins' ), true ) ) { $columns['auto-updates'] = __( 'Automatic Updates' ); } @@ -576,16 +587,15 @@ } if ( 'search' !== $type ) { - $status_links[ $type ] = sprintf( - "%s", - add_query_arg( 'plugin_status', $type, 'plugins.php' ), - ( $type === $status ) ? ' class="current" aria-current="page"' : '', - sprintf( $text, number_format_i18n( $count ) ) + $status_links[ $type ] = array( + 'url' => add_query_arg( 'plugin_status', $type, 'plugins.php' ), + 'label' => sprintf( $text, number_format_i18n( $count ) ), + 'current' => $type === $status, ); } } - return $status_links; + return $this->get_views_links( $status_links ); } /** @@ -598,11 +608,11 @@ $actions = array(); if ( 'active' !== $status ) { - $actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Activate' ) : __( 'Activate' ); + $actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? _x( 'Network Activate', 'plugin' ) : _x( 'Activate', 'plugin' ); } if ( 'inactive' !== $status && 'recent' !== $status ) { - $actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Deactivate' ) : __( 'Deactivate' ); + $actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? _x( 'Network Deactivate', 'plugin' ) : _x( 'Deactivate', 'plugin' ); } if ( ! is_multisite() || $this->screen->in_admin( 'network' ) ) { @@ -719,7 +729,7 @@ $suffix = 2; while ( in_array( $plugin_id_attr, $plugin_id_attrs, true ) ) { $plugin_id_attr = "$plugin_slug-$suffix"; - $suffix++; + ++$suffix; } $plugin_id_attrs[] = $plugin_id_attr; @@ -745,6 +755,11 @@ $compatible_php = is_php_version_compatible( $requires_php ); $compatible_wp = is_wp_version_compatible( $requires_wp ); + $has_dependents = WP_Plugin_Dependencies::has_dependents( $plugin_file ); + $has_active_dependents = WP_Plugin_Dependencies::has_active_dependents( $plugin_file ); + $has_unmet_dependencies = WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file ); + $has_circular_dependency = WP_Plugin_Dependencies::has_circular_dependency( $plugin_file ); + if ( 'mustuse' === $context ) { $is_active = true; } elseif ( 'dropins' === $context ) { @@ -752,7 +767,7 @@ $plugin_name = $plugin_file; if ( $plugin_file !== $plugin_data['Name'] ) { - $plugin_name .= '
' . $plugin_data['Name']; + $plugin_name .= '
' . $plugin_data['Name']; } if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant. @@ -787,26 +802,53 @@ if ( $screen->in_admin( 'network' ) ) { if ( $is_active ) { if ( current_user_can( 'manage_network_plugins' ) ) { - $actions['deactivate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Network Deactivate' ) - ); + if ( $has_active_dependents ) { + $actions['deactivate'] = __( 'Deactivate' ) . + '' . + __( 'You cannot deactivate this plugin as other plugins require it.' ) . + ''; + + } else { + $deactivate_url = 'plugins.php?action=deactivate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['deactivate'] = sprintf( + '%s', + wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), + _x( 'Network Deactivate', 'plugin' ) + ); + } } } else { if ( current_user_can( 'manage_network_plugins' ) ) { if ( $compatible_php && $compatible_wp ) { - $actions['activate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Network Activate' ) - ); + if ( $has_unmet_dependencies ) { + $actions['activate'] = _x( 'Network Activate', 'plugin' ) . + '' . + __( 'You cannot activate this plugin as it has unmet requirements.' ) . + ''; + } else { + $activate_url = 'plugins.php?action=activate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['activate'] = sprintf( + '%s', + wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ), + _x( 'Network Activate', 'plugin' ) + ); + } } else { $actions['activate'] = sprintf( '%s', @@ -816,14 +858,27 @@ } if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) { - $actions['delete'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=delete-selected&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'bulk-plugins' ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Delete' ) - ); + if ( $has_dependents && ! $has_circular_dependency ) { + $actions['delete'] = __( 'Delete' ) . + '' . + __( 'You cannot delete this plugin as other plugins require it.' ) . + ''; + } else { + $delete_url = 'plugins.php?action=delete-selected' . + '&checked[]=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['delete'] = sprintf( + '%s', + wp_nonce_url( $delete_url, 'bulk-plugins' ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Delete' ) + ); + } } } } else { @@ -837,20 +892,39 @@ ); } elseif ( $is_active ) { if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) { - $actions['deactivate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Deactivate' ) - ); + if ( $has_active_dependents ) { + $actions['deactivate'] = __( 'Deactivate' ) . + '' . + __( 'You cannot deactivate this plugin as other plugins depend on it.' ) . + ''; + } else { + $deactivate_url = 'plugins.php?action=deactivate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['deactivate'] = sprintf( + '%s', + wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Deactivate' ) + ); + } } if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) { + $resume_url = 'plugins.php?action=resume' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + $actions['resume'] = sprintf( '%s', - wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ), + wp_nonce_url( $resume_url, 'resume-plugin_' . $plugin_file ), esc_attr( $plugin_id_attr ), /* translators: %s: Plugin name. */ esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ), @@ -860,14 +934,27 @@ } else { if ( current_user_can( 'activate_plugin', $plugin_file ) ) { if ( $compatible_php && $compatible_wp ) { - $actions['activate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Activate' ) - ); + if ( $has_unmet_dependencies ) { + $actions['activate'] = _x( 'Activate', 'plugin' ) . + '' . + __( 'You cannot activate this plugin as it has unmet requirements.' ) . + ''; + } else { + $activate_url = 'plugins.php?action=activate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['activate'] = sprintf( + '%s', + wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ), + _x( 'Activate', 'plugin' ) + ); + } } else { $actions['activate'] = sprintf( '%s', @@ -877,14 +964,27 @@ } if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) { - $actions['delete'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=delete-selected&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'bulk-plugins' ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Delete' ) - ); + if ( $has_dependents && ! $has_circular_dependency ) { + $actions['delete'] = __( 'Delete' ) . + '' . + __( 'You cannot delete this plugin as other plugins require it.' ) . + ''; + } else { + $delete_url = 'plugins.php?action=delete-selected' . + '&checked[]=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['delete'] = sprintf( + '%s', + wp_nonce_url( $delete_url, 'bulk-plugins' ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Delete' ) + ); + } } } // End if $is_active. } // End if $screen->in_admin( 'network' ). @@ -902,7 +1002,7 @@ * @param string[] $actions An array of plugin action links. By default this can include * 'activate', 'deactivate', and 'delete'. * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. * @param string $context The plugin context. By default this can include 'all', @@ -922,7 +1022,7 @@ * @param string[] $actions An array of plugin action links. By default this can include * 'activate', 'deactivate', and 'delete'. * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. * @param string $context The plugin context. By default this can include 'all', @@ -944,7 +1044,7 @@ * 'activate', 'deactivate', and 'delete'. With Multisite active * this can also include 'network_active' and 'network_only' items. * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. * @param string $context The plugin context. By default this can include 'all', @@ -966,7 +1066,7 @@ * 'activate', 'deactivate', and 'delete'. With Multisite active * this can also include 'network_active' and 'network_only' items. * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. * @param string $context The plugin context. By default this can include 'all', @@ -979,15 +1079,26 @@ $class = $is_active ? 'active' : 'inactive'; $checkbox_id = 'checkbox_' . md5( $plugin_file ); + $disabled = ''; - if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ), true ) || ! $compatible_php ) { + if ( $has_dependents || $has_unmet_dependencies ) { + $disabled = 'disabled'; + } + + if ( + $restrict_network_active || + $restrict_network_only || + in_array( $status, array( 'mustuse', 'dropins' ), true ) || + ! $compatible_php + ) { $checkbox = ''; } else { $checkbox = sprintf( - '' . - '', + '' . + '', $checkbox_id, - /* translators: %s: Plugin name. */ + /* translators: Hidden accessibility text. %s: Plugin name. */ sprintf( __( 'Select %s' ), $plugin_data['Name'] ), esc_attr( $plugin_file ) ); @@ -998,8 +1109,11 @@ $plugin_name = $plugin_data['Name']; } - if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) - || ! $compatible_php || ! $compatible_wp + if ( + ! empty( $totals['upgrade'] ) && + ! empty( $plugin_data['update'] ) || + ! $compatible_php || + ! $compatible_wp ) { $class .= ' update'; } @@ -1023,8 +1137,7 @@ list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); - $auto_updates = (array) get_site_option( 'auto_update_plugins', array() ); - $available_updates = get_site_transient( 'update_plugins' ); + $auto_updates = (array) get_site_option( 'auto_update_plugins', array() ); foreach ( $columns as $column_name => $column_display_name ) { $extra_classes = ''; @@ -1049,15 +1162,19 @@
"; $plugin_meta = array(); + if ( ! empty( $plugin_data['Version'] ) ) { /* translators: %s: Plugin version number. */ $plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); } + if ( ! empty( $plugin_data['Author'] ) ) { $author = $plugin_data['Author']; + if ( ! empty( $plugin_data['AuthorURI'] ) ) { $author = '' . $plugin_data['Author'] . ''; } + /* translators: %s: Plugin author name. */ $plugin_meta[] = sprintf( __( 'By %s' ), $author ); } @@ -1141,6 +1258,24 @@ echo '
'; + if ( $has_dependents ) { + $this->add_dependents_to_dependency_plugin_row( $plugin_file ); + } + + if ( WP_Plugin_Dependencies::has_dependencies( $plugin_file ) ) { + $this->add_dependencies_to_dependent_plugin_row( $plugin_file ); + } + + /** + * Fires after plugin row meta. + * + * @since 6.5.0 + * + * @param string $plugin_file Refer to {@see 'plugin_row_meta'} filter. + * @param array $plugin_data Refer to {@see 'plugin_row_meta'} filter. + */ + do_action( 'after_plugin_row_meta', $plugin_file, $plugin_data ); + if ( $paused ) { $notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' ); @@ -1156,7 +1291,7 @@ echo ''; break; case 'auto-updates': - if ( ! $this->show_autoupdates ) { + if ( ! $this->show_autoupdates || in_array( $status, array( 'mustuse', 'dropins' ), true ) ) { break; } @@ -1229,13 +1364,20 @@ * including toggle auto-update action links and * time to next update. * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. */ echo apply_filters( 'plugin_auto_update_setting_html', $html, $plugin_file, $plugin_data ); - echo ''; + wp_admin_notice( + '', + array( + 'type' => 'error', + 'additional_classes' => array( 'notice-alt', 'inline', 'hidden' ), + ) + ); + echo ''; break; @@ -1251,7 +1393,7 @@ * * @param string $column_name Name of the column. * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. */ @@ -1265,58 +1407,65 @@ if ( ! $compatible_php || ! $compatible_wp ) { printf( - '' . - '' . - '

', + '', esc_attr( $this->get_column_count() ) ); + $incompatible_message = ''; if ( ! $compatible_php && ! $compatible_wp ) { - _e( 'This plugin does not work with your versions of WordPress and PHP.' ); + $incompatible_message .= __( 'This plugin does not work with your versions of WordPress and PHP.' ); if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { - printf( + $incompatible_message .= sprintf( /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ ' ' . __( 'Please update WordPress, and then learn more about updating PHP.' ), self_admin_url( 'update-core.php' ), esc_url( wp_get_update_php_url() ) ); - wp_update_php_annotation( '

', '' ); + $incompatible_message .= wp_update_php_annotation( '

', '', false ); } elseif ( current_user_can( 'update_core' ) ) { - printf( + $incompatible_message .= sprintf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . __( 'Please update WordPress.' ), self_admin_url( 'update-core.php' ) ); } elseif ( current_user_can( 'update_php' ) ) { - printf( + $incompatible_message .= sprintf( /* translators: %s: URL to Update PHP page. */ ' ' . __( 'Learn more about updating PHP.' ), esc_url( wp_get_update_php_url() ) ); - wp_update_php_annotation( '

', '' ); + $incompatible_message .= wp_update_php_annotation( '

', '', false ); } } elseif ( ! $compatible_wp ) { - _e( 'This plugin does not work with your version of WordPress.' ); + $incompatible_message .= __( 'This plugin does not work with your version of WordPress.' ); if ( current_user_can( 'update_core' ) ) { - printf( + $incompatible_message .= sprintf( /* translators: %s: URL to WordPress Updates screen. */ ' ' . __( 'Please update WordPress.' ), self_admin_url( 'update-core.php' ) ); } } elseif ( ! $compatible_php ) { - _e( 'This plugin does not work with your version of PHP.' ); + $incompatible_message .= __( 'This plugin does not work with your version of PHP.' ); if ( current_user_can( 'update_php' ) ) { - printf( + $incompatible_message .= sprintf( /* translators: %s: URL to Update PHP page. */ ' ' . __( 'Learn more about updating PHP.' ), esc_url( wp_get_update_php_url() ) ); - wp_update_php_annotation( '

', '' ); + $incompatible_message .= wp_update_php_annotation( '

', '', false ); } } - echo '

'; + wp_admin_notice( + $incompatible_message, + array( + 'type' => 'error', + 'additional_classes' => array( 'notice-alt', 'inline', 'update-message' ), + ) + ); + + echo ''; } /** @@ -1327,7 +1476,7 @@ * to possible values for `$status`. * * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. * @param string $status Status filter currently applied to the plugin list. @@ -1348,7 +1497,7 @@ * to possible values for `$status`. * * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. See `get_plugin_data()` + * @param array $plugin_data An array of plugin data. See get_plugin_data() * and the {@see 'plugin_row_meta'} filter for the list * of possible values. * @param string $status Status filter currently applied to the plugin list. @@ -1369,4 +1518,140 @@ protected function get_primary_column_name() { return 'name'; } + + /** + * Prints a list of other plugins that depend on the plugin. + * + * @since 6.5.0 + * + * @param string $dependency The dependency's filepath, relative to the plugins directory. + */ + protected function add_dependents_to_dependency_plugin_row( $dependency ) { + $dependent_names = WP_Plugin_Dependencies::get_dependent_names( $dependency ); + + if ( empty( $dependent_names ) ) { + return; + } + + $dependency_note = __( 'Note: This plugin cannot be deactivated or deleted until the plugins that require it are deactivated or deleted.' ); + + $comma = wp_get_list_item_separator(); + $required_by = sprintf( + /* translators: %s: List of dependencies. */ + __( 'Required by: %s' ), + implode( $comma, $dependent_names ) + ); + + printf( + '

%1$s

%2$s

', + $required_by, + $dependency_note + ); + } + + /** + * Prints a list of other plugins that the plugin depends on. + * + * @since 6.5.0 + * + * @param string $dependent The dependent plugin's filepath, relative to the plugins directory. + */ + protected function add_dependencies_to_dependent_plugin_row( $dependent ) { + $dependency_names = WP_Plugin_Dependencies::get_dependency_names( $dependent ); + + if ( array() === $dependency_names ) { + return; + } + + $links = array(); + foreach ( $dependency_names as $slug => $name ) { + $links[] = $this->get_dependency_view_details_link( $name, $slug ); + } + + $is_active = is_multisite() ? is_plugin_active_for_network( $dependent ) : is_plugin_active( $dependent ); + $comma = wp_get_list_item_separator(); + $requires = sprintf( + /* translators: %s: List of dependency names. */ + __( 'Requires: %s' ), + implode( $comma, $links ) + ); + + $notice = ''; + $error_message = ''; + if ( WP_Plugin_Dependencies::has_unmet_dependencies( $dependent ) ) { + if ( $is_active ) { + $error_message = __( 'This plugin is active but may not function correctly because required plugins are missing or inactive.' ); + } else { + $error_message = __( 'This plugin cannot be activated because required plugins are missing or inactive.' ); + } + $notice = wp_get_admin_notice( + $error_message, + array( + 'type' => 'error', + 'additional_classes' => array( 'inline', 'notice-alt' ), + ) + ); + } + + printf( + '

%1$s

%2$s

', + $requires, + $notice + ); + } + + /** + * Returns a 'View details' like link for a dependency. + * + * @since 6.5.0 + * + * @param string $name The dependency's name. + * @param string $slug The dependency's slug. + * @return string A 'View details' link for the dependency. + */ + protected function get_dependency_view_details_link( $name, $slug ) { + $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $slug ); + + if ( false === $dependency_data + || $name === $slug + || $name !== $dependency_data['name'] + || empty( $dependency_data['version'] ) + ) { + return $name; + } + + return $this->get_view_details_link( $name, $slug ); + } + + /** + * Returns a 'View details' link for the plugin. + * + * @since 6.5.0 + * + * @param string $name The plugin's name. + * @param string $slug The plugin's slug. + * @return string A 'View details' link for the plugin. + */ + protected function get_view_details_link( $name, $slug ) { + $url = add_query_arg( + array( + 'tab' => 'plugin-information', + 'plugin' => $slug, + 'TB_iframe' => 'true', + 'width' => '600', + 'height' => '550', + ), + network_admin_url( 'plugin-install.php' ) + ); + + $name_attr = esc_attr( $name ); + return sprintf( + "%s", + esc_url( $url ), + /* translators: %s: Plugin name. */ + sprintf( __( 'More information about %s' ), $name_attr ), + $name_attr, + esc_html( $name ) + ); + } }