wp/wp-admin/includes/class-wp-plugins-list-table.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
--- 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.' ), '<strong>' . $s . '</strong>' );
@@ -443,8 +453,8 @@
 		}
 		?>
 		<p class="search-box">
-			<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
-			<input type="search" id="<?php echo esc_attr( $input_id ); ?>" class="wp-filter-search" name="s" value="<?php _admin_search_query(); ?>" placeholder="<?php esc_attr_e( 'Search installed plugins...' ); ?>" />
+			<label for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?></label>
+			<input type="search" id="<?php echo esc_attr( $input_id ); ?>" class="wp-filter-search" name="s" value="<?php _admin_search_query(); ?>" />
 			<?php submit_button( $text, 'hide-if-js', '', false, array( 'id' => 'search-submit' ) ); ?>
 		</p>
 		<?php
@@ -452,7 +462,8 @@
 
 	/**
 	 * @global string $status
-	 * @return array
+	 *
+	 * @return string[] Array of column titles keyed by their column name.
 	 */
 	public function get_columns() {
 		global $status;
@@ -463,7 +474,7 @@
 			'description' => __( '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(
-					"<a href='%s'%s>%s</a>",
-					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 .= '<br/>' . $plugin_data['Name'];
+				$plugin_name .= '<br />' . $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(
-							'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
-							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 ),
-							esc_attr( $plugin_id_attr ),
-							/* translators: %s: Plugin name. */
-							esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
-							__( 'Network Deactivate' )
-						);
+						if ( $has_active_dependents ) {
+							$actions['deactivate'] = __( 'Deactivate' ) .
+								'<span class="screen-reader-text">' .
+								__( 'You cannot deactivate this plugin as other plugins require it.' ) .
+								'</span>';
+
+						} else {
+							$deactivate_url = 'plugins.php?action=deactivate' .
+								'&amp;plugin=' . urlencode( $plugin_file ) .
+								'&amp;plugin_status=' . $context .
+								'&amp;paged=' . $page .
+								'&amp;s=' . $s;
+
+							$actions['deactivate'] = sprintf(
+								'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
+								wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ),
+								esc_attr( $plugin_id_attr ),
+								/* translators: %s: Plugin name. */
+								esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
+								_x( 'Network Deactivate', 'plugin' )
+							);
+						}
 					}
 				} else {
 					if ( current_user_can( 'manage_network_plugins' ) ) {
 						if ( $compatible_php && $compatible_wp ) {
-							$actions['activate'] = sprintf(
-								'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
-								wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;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' ) .
+									'<span class="screen-reader-text">' .
+									__( 'You cannot activate this plugin as it has unmet requirements.' ) .
+									'</span>';
+							} else {
+								$activate_url = 'plugins.php?action=activate' .
+									'&amp;plugin=' . urlencode( $plugin_file ) .
+									'&amp;plugin_status=' . $context .
+									'&amp;paged=' . $page .
+									'&amp;s=' . $s;
+
+								$actions['activate'] = sprintf(
+									'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
+									wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ),
+									esc_attr( $plugin_id_attr ),
+									/* translators: %s: Plugin name. */
+									esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
+									_x( 'Network Activate', 'plugin' )
+								);
+							}
 						} else {
 							$actions['activate'] = sprintf(
 								'<span>%s</span>',
@@ -816,14 +858,27 @@
 					}
 
 					if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
-						$actions['delete'] = sprintf(
-							'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
-							wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
-							esc_attr( $plugin_id_attr ),
-							/* translators: %s: Plugin name. */
-							esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
-							__( 'Delete' )
-						);
+						if ( $has_dependents && ! $has_circular_dependency ) {
+							$actions['delete'] = __( 'Delete' ) .
+								'<span class="screen-reader-text">' .
+								__( 'You cannot delete this plugin as other plugins require it.' ) .
+								'</span>';
+						} else {
+							$delete_url = 'plugins.php?action=delete-selected' .
+								'&amp;checked[]=' . urlencode( $plugin_file ) .
+								'&amp;plugin_status=' . $context .
+								'&amp;paged=' . $page .
+								'&amp;s=' . $s;
+
+							$actions['delete'] = sprintf(
+								'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
+								wp_nonce_url( $delete_url, 'bulk-plugins' ),
+								esc_attr( $plugin_id_attr ),
+								/* translators: %s: Plugin name. */
+								esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
+								__( 'Delete' )
+							);
+						}
 					}
 				}
 			} else {
@@ -837,20 +892,39 @@
 					);
 				} elseif ( $is_active ) {
 					if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
-						$actions['deactivate'] = sprintf(
-							'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
-							wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
-							esc_attr( $plugin_id_attr ),
-							/* translators: %s: Plugin name. */
-							esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
-							__( 'Deactivate' )
-						);
+						if ( $has_active_dependents ) {
+							$actions['deactivate'] = __( 'Deactivate' ) .
+								'<span class="screen-reader-text">' .
+								__( 'You cannot deactivate this plugin as other plugins depend on it.' ) .
+								'</span>';
+						} else {
+							$deactivate_url = 'plugins.php?action=deactivate' .
+								'&amp;plugin=' . urlencode( $plugin_file ) .
+								'&amp;plugin_status=' . $context .
+								'&amp;paged=' . $page .
+								'&amp;s=' . $s;
+
+							$actions['deactivate'] = sprintf(
+								'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
+								wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ),
+								esc_attr( $plugin_id_attr ),
+								/* translators: %s: Plugin name. */
+								esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
+								__( 'Deactivate' )
+							);
+						}
 					}
 
 					if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
+						$resume_url = 'plugins.php?action=resume' .
+							'&amp;plugin=' . urlencode( $plugin_file ) .
+							'&amp;plugin_status=' . $context .
+							'&amp;paged=' . $page .
+							'&amp;s=' . $s;
+
 						$actions['resume'] = sprintf(
 							'<a href="%s" id="resume-%s" class="resume-link" aria-label="%s">%s</a>',
-							wp_nonce_url( 'plugins.php?action=resume&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;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(
-								'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
-								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 ),
-								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' ) .
+									'<span class="screen-reader-text">' .
+									__( 'You cannot activate this plugin as it has unmet requirements.' ) .
+									'</span>';
+							} else {
+								$activate_url = 'plugins.php?action=activate' .
+									'&amp;plugin=' . urlencode( $plugin_file ) .
+									'&amp;plugin_status=' . $context .
+									'&amp;paged=' . $page .
+									'&amp;s=' . $s;
+
+								$actions['activate'] = sprintf(
+									'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
+									wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ),
+									esc_attr( $plugin_id_attr ),
+									/* translators: %s: Plugin name. */
+									esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
+									_x( 'Activate', 'plugin' )
+								);
+							}
 						} else {
 							$actions['activate'] = sprintf(
 								'<span>%s</span>',
@@ -877,14 +964,27 @@
 					}
 
 					if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
-						$actions['delete'] = sprintf(
-							'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
-							wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
-							esc_attr( $plugin_id_attr ),
-							/* translators: %s: Plugin name. */
-							esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
-							__( 'Delete' )
-						);
+						if ( $has_dependents && ! $has_circular_dependency ) {
+							$actions['delete'] = __( 'Delete' ) .
+								'<span class="screen-reader-text">' .
+								__( 'You cannot delete this plugin as other plugins require it.' ) .
+								'</span>';
+						} else {
+							$delete_url = 'plugins.php?action=delete-selected' .
+								'&amp;checked[]=' . urlencode( $plugin_file ) .
+								'&amp;plugin_status=' . $context .
+								'&amp;paged=' . $page .
+								'&amp;s=' . $s;
+
+							$actions['delete'] = sprintf(
+								'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
+								wp_nonce_url( $delete_url, 'bulk-plugins' ),
+								esc_attr( $plugin_id_attr ),
+								/* translators: %s: Plugin name. */
+								esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
+								__( 'Delete' )
+							);
+						}
 					}
 				} // End if $is_active.
 			} // End if $screen->in_admin( 'network' ).
@@ -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(
-				'<label class="screen-reader-text" for="%1$s">%2$s</label>' .
-				'<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" />',
+				'<label class="label-covers-full-cell" for="%1$s">' .
+				'<span class="screen-reader-text">%2$s</span></label>' .
+				'<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" ' . $disabled . '/>',
 				$checkbox_id,
-				/* translators: %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 @@
 						<div class='$class second plugin-version-author-uri'>";
 
 					$plugin_meta = array();
+
 					if ( ! empty( $plugin_data['Version'] ) ) {
 						/* translators: %s: Plugin version number. */
 						$plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
 					}
+
 					if ( ! empty( $plugin_data['Author'] ) ) {
 						$author = $plugin_data['Author'];
+
 						if ( ! empty( $plugin_data['AuthorURI'] ) ) {
 							$author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
 						}
+
 						/* translators: %s: Plugin author name. */
 						$plugin_meta[] = sprintf( __( 'By %s' ), $author );
 					}
@@ -1141,6 +1258,24 @@
 
 					echo '</div>';
 
+					if ( $has_dependents ) {
+						$this->add_dependents_to_dependency_plugin_row( $plugin_file );
+					}
+
+					if ( WP_Plugin_Dependencies::has_dependencies( $plugin_file ) ) {
+						$this->add_dependencies_to_dependent_plugin_row( $plugin_file );
+					}
+
+					/**
+					 * Fires after plugin row meta.
+					 *
+					 * @since 6.5.0
+					 *
+					 * @param string $plugin_file Refer to {@see 'plugin_row_meta'} filter.
+					 * @param array  $plugin_data Refer to {@see 'plugin_row_meta'} filter.
+					 */
+					do_action( 'after_plugin_row_meta', $plugin_file, $plugin_data );
+
 					if ( $paused ) {
 						$notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' );
 
@@ -1156,7 +1291,7 @@
 					echo '</td>';
 					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 '<div class="notice notice-error notice-alt inline hidden"><p></p></div>';
+					wp_admin_notice(
+						'',
+						array(
+							'type'               => 'error',
+							'additional_classes' => array( 'notice-alt', 'inline', 'hidden' ),
+						)
+					);
+
 					echo '</td>';
 
 					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(
-				'<tr class="plugin-update-tr">' .
-				'<td colspan="%s" class="plugin-update colspanchange">' .
-				'<div class="update-message notice inline notice-error notice-alt"><p>',
+				'<tr class="plugin-update-tr"><td colspan="%s" class="plugin-update colspanchange">',
 				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. */
 						' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ),
 						self_admin_url( 'update-core.php' ),
 						esc_url( wp_get_update_php_url() )
 					);
-					wp_update_php_annotation( '</p><p><em>', '</em>' );
+					$incompatible_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false );
 				} elseif ( current_user_can( 'update_core' ) ) {
-					printf(
+					$incompatible_message .= sprintf(
 						/* translators: %s: URL to WordPress Updates screen. */
 						' ' . __( '<a href="%s">Please update WordPress</a>.' ),
 						self_admin_url( 'update-core.php' )
 					);
 				} elseif ( current_user_can( 'update_php' ) ) {
-					printf(
+					$incompatible_message .= sprintf(
 						/* translators: %s: URL to Update PHP page. */
 						' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
 						esc_url( wp_get_update_php_url() )
 					);
-					wp_update_php_annotation( '</p><p><em>', '</em>' );
+					$incompatible_message .= wp_update_php_annotation( '</p><p><em>', '</em>', 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. */
 						' ' . __( '<a href="%s">Please update WordPress</a>.' ),
 						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. */
 						' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
 						esc_url( wp_get_update_php_url() )
 					);
-					wp_update_php_annotation( '</p><p><em>', '</em>' );
+					$incompatible_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false );
 				}
 			}
 
-			echo '</p></div></td></tr>';
+			wp_admin_notice(
+				$incompatible_message,
+				array(
+					'type'               => 'error',
+					'additional_classes' => array( 'notice-alt', 'inline', 'update-message' ),
+				)
+			);
+
+			echo '</td></tr>';
 		}
 
 		/**
@@ -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. */
+			__( '<strong>Required by:</strong> %s' ),
+			implode( $comma, $dependent_names )
+		);
+
+		printf(
+			'<div class="required-by"><p>%1$s</p><p>%2$s</p></div>',
+			$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. */
+			__( '<strong>Requires:</strong> %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(
+			'<div class="requires"><p>%1$s</p><p>%2$s</p></div>',
+			$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(
+			"<a href='%s' class='thickbox open-plugin-details-modal' aria-label='%s' data-title='%s'>%s</a>",
+			esc_url( $url ),
+			/* translators: %s: Plugin name. */
+			sprintf( __( 'More information about %s' ), $name_attr ),
+			$name_attr,
+			esc_html( $name )
+		);
+	}
 }