wp/wp-admin/includes/plugin-install.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
--- a/wp/wp-admin/includes/plugin-install.php	Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-admin/includes/plugin-install.php	Fri Sep 05 18:40:08 2025 +0200
@@ -174,7 +174,8 @@
 
 		if ( $ssl && is_wp_error( $request ) ) {
 			if ( ! wp_is_json_request() ) {
-				trigger_error(
+				wp_trigger_error(
+					__FUNCTION__,
 					sprintf(
 						/* translators: %s: Support forums URL. */
 						__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
@@ -316,18 +317,23 @@
  */
 function install_search_form( $deprecated = true ) {
 	$type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term';
-	$term = isset( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : '';
+	$term = isset( $_REQUEST['s'] ) ? urldecode( wp_unslash( $_REQUEST['s'] ) ) : '';
 	?>
 	<form class="search-form search-plugins" method="get">
 		<input type="hidden" name="tab" value="search" />
-		<label class="screen-reader-text" for="typeselector"><?php _e( 'Search plugins by:' ); ?></label>
+		<label for="search-plugins"><?php _e( 'Search Plugins' ); ?></label>
+		<input type="search" name="s" id="search-plugins" value="<?php echo esc_attr( $term ); ?>" class="wp-filter-search" />
+		<label class="screen-reader-text" for="typeselector">
+			<?php
+			/* translators: Hidden accessibility text. */
+			_e( 'Search plugins by:' );
+			?>
+		</label>
 		<select name="type" id="typeselector">
 			<option value="term"<?php selected( 'term', $type ); ?>><?php _e( 'Keyword' ); ?></option>
 			<option value="author"<?php selected( 'author', $type ); ?>><?php _e( 'Author' ); ?></option>
 			<option value="tag"<?php selected( 'tag', $type ); ?>><?php _ex( 'Tag', 'Plugin Installer' ); ?></option>
 		</select>
-		<label class="screen-reader-text" for="search-plugins"><?php _e( 'Search Plugins' ); ?></label>
-		<input type="search" name="s" id="search-plugins" value="<?php echo esc_attr( $term ); ?>" class="wp-filter-search" placeholder="<?php esc_attr_e( 'Search plugins...' ); ?>" />
 		<?php submit_button( __( 'Search Plugins' ), 'hide-if-js', false, false, array( 'id' => 'search-submit' ) ); ?>
 	</form>
 	<?php
@@ -342,11 +348,16 @@
 	?>
 <div class="upload-plugin">
 	<p class="install-help"><?php _e( 'If you have a plugin in a .zip format, you may install or update it by uploading it here.' ); ?></p>
-	<form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url( 'update.php?action=upload-plugin' ); ?>">
+	<form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo esc_url( self_admin_url( 'update.php?action=upload-plugin' ) ); ?>">
 		<?php wp_nonce_field( 'plugin-upload' ); ?>
-		<label class="screen-reader-text" for="pluginzip"><?php _e( 'Plugin zip file' ); ?></label>
+		<label class="screen-reader-text" for="pluginzip">
+			<?php
+			/* translators: Hidden accessibility text. */
+			_e( 'Plugin zip file' );
+			?>
+		</label>
 		<input type="file" id="pluginzip" name="pluginzip" accept=".zip" />
-		<?php submit_button( __( 'Install Now' ), '', 'install-plugin-submit', false ); ?>
+		<?php submit_button( _x( 'Install Now', 'plugin' ), '', 'install-plugin-submit', false ); ?>
 	</form>
 </div>
 	<?php
@@ -395,7 +406,7 @@
 		case 'install_plugins_featured':
 			printf(
 				/* translators: %s: https://wordpress.org/plugins/ */
-				'<p>' . __( 'Plugins extend and expand the functionality of WordPress. You may automatically install plugins from the <a href="%s">WordPress Plugin Directory</a> or upload a plugin in .zip format by clicking the button at the top of this page.' ) . '</p>',
+				'<p>' . __( 'Plugins extend and expand the functionality of WordPress. You may install plugins in the <a href="%s">WordPress Plugin Directory</a> right from here, or upload a plugin in .zip format by clicking the button at the top of this page.' ) . '</p>',
 				__( 'https://wordpress.org/plugins/' )
 			);
 			break;
@@ -471,8 +482,10 @@
 				}
 			} else {
 				$key = array_keys( $installed_plugin );
-				// Use the first plugin regardless of the name.
-				// Could have issues for multiple plugins in one directory if they share different version numbers.
+				/*
+				 * Use the first plugin regardless of the name.
+				 * Could have issues for multiple plugins in one directory if they share different version numbers.
+				 */
 				$key = reset( $key );
 
 				$update_file = $api->slug . '/' . $key;
@@ -800,37 +813,54 @@
 	$tested_wp      = ( empty( $api->tested ) || version_compare( get_bloginfo( 'version' ), $api->tested, '<=' ) );
 
 	if ( ! $compatible_php ) {
-		echo '<div class="notice notice-error notice-alt"><p>';
-		_e( '<strong>Error:</strong> This plugin <strong>requires a newer version of PHP</strong>.' );
+		$compatible_php_notice_message  = '<p>';
+		$compatible_php_notice_message .= __( '<strong>Error:</strong> This plugin <strong>requires a newer version of PHP</strong>.' );
+
 		if ( current_user_can( 'update_php' ) ) {
-			printf(
+			$compatible_php_notice_message .= sprintf(
 				/* translators: %s: URL to Update PHP page. */
 				' ' . __( '<a href="%s" target="_blank">Click here to learn more about updating PHP</a>.' ),
 				esc_url( wp_get_update_php_url() )
-			);
-
-			wp_update_php_annotation( '</p><p><em>', '</em>' );
+			) . wp_update_php_annotation( '</p><p><em>', '</em>', false );
 		} else {
-			echo '</p>';
+			$compatible_php_notice_message .= '</p>';
 		}
-		echo '</div>';
+
+		wp_admin_notice(
+			$compatible_php_notice_message,
+			array(
+				'type'               => 'error',
+				'additional_classes' => array( 'notice-alt' ),
+				'paragraph_wrap'     => false,
+			)
+		);
 	}
 
 	if ( ! $tested_wp ) {
-		echo '<div class="notice notice-warning notice-alt"><p>';
-		_e( '<strong>Warning:</strong> This plugin <strong>has not been tested</strong> with your current version of WordPress.' );
-		echo '</p></div>';
+		wp_admin_notice(
+			__( '<strong>Warning:</strong> This plugin <strong>has not been tested</strong> with your current version of WordPress.' ),
+			array(
+				'type'               => 'warning',
+				'additional_classes' => array( 'notice-alt' ),
+			)
+		);
 	} elseif ( ! $compatible_wp ) {
-		echo '<div class="notice notice-error notice-alt"><p>';
-		_e( '<strong>Error:</strong> This plugin <strong>requires a newer version of WordPress</strong>.' );
+		$compatible_wp_notice_message = __( '<strong>Error:</strong> This plugin <strong>requires a newer version of WordPress</strong>.' );
 		if ( current_user_can( 'update_core' ) ) {
-			printf(
+			$compatible_wp_notice_message .= sprintf(
 				/* translators: %s: URL to WordPress Updates screen. */
 				' ' . __( '<a href="%s" target="_parent">Click here to update WordPress</a>.' ),
-				self_admin_url( 'update-core.php' )
+				esc_url( self_admin_url( 'update-core.php' ) )
 			);
 		}
-		echo '</p></div>';
+
+		wp_admin_notice(
+			$compatible_wp_notice_message,
+			array(
+				'type'               => 'error',
+				'additional_classes' => array( 'notice-alt' ),
+			)
+		);
 	}
 
 	foreach ( (array) $api->sections as $section_name => $content ) {
@@ -850,43 +880,166 @@
 	echo "</div>\n"; // #plugin-information-scrollable
 	echo "<div id='$tab-footer'>\n";
 	if ( ! empty( $api->download_link ) && ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) ) {
-		$status = install_plugin_install_status( $api );
+		$button = wp_get_plugin_action_button( $api->name, $api, $compatible_php, $compatible_wp );
+		$button = str_replace( 'class="', 'class="right ', $button );
+
+		if ( ! str_contains( $button, _x( 'Activate', 'plugin' ) ) ) {
+			$button = str_replace( 'class="', 'id="plugin_install_from_iframe" class="', $button );
+		}
+
+		echo wp_kses_post( $button );
+	}
+	echo "</div>\n";
+
+	wp_print_request_filesystem_credentials_modal();
+	wp_print_admin_notice_templates();
+
+	iframe_footer();
+	exit;
+}
+
+/**
+ * Gets the markup for the plugin install action button.
+ *
+ * @since 6.5.0
+ *
+ * @param string       $name           Plugin name.
+ * @param array|object $data           {
+ *     An array or object of plugin data. Can be retrieved from the API.
+ *
+ *     @type string   $slug             The plugin slug.
+ *     @type string[] $requires_plugins An array of plugin dependency slugs.
+ *     @type string   $version          The plugin's version string. Used when getting the install status.
+ * }
+ * @param bool         $compatible_php   The result of a PHP compatibility check.
+ * @param bool         $compatible_wp    The result of a WP compatibility check.
+ * @return string The markup for the dependency row button. An empty string if the user does not have capabilities.
+ */
+function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible_wp ) {
+	$button           = '';
+	$data             = (object) $data;
+	$status           = install_plugin_install_status( $data );
+	$requires_plugins = $data->requires_plugins ?? array();
+
+	// Determine the status of plugin dependencies.
+	$installed_plugins                   = get_plugins();
+	$active_plugins                      = get_option( 'active_plugins', array() );
+	$plugin_dependencies_count           = count( $requires_plugins );
+	$installed_plugin_dependencies_count = 0;
+	$active_plugin_dependencies_count    = 0;
+	foreach ( $requires_plugins as $dependency ) {
+		foreach ( array_keys( $installed_plugins ) as $installed_plugin_file ) {
+			if ( str_contains( $installed_plugin_file, '/' ) && explode( '/', $installed_plugin_file )[0] === $dependency ) {
+				++$installed_plugin_dependencies_count;
+			}
+		}
+
+		foreach ( $active_plugins as $active_plugin_file ) {
+			if ( str_contains( $active_plugin_file, '/' ) && explode( '/', $active_plugin_file )[0] === $dependency ) {
+				++$active_plugin_dependencies_count;
+			}
+		}
+	}
+	$all_plugin_dependencies_installed = $installed_plugin_dependencies_count === $plugin_dependencies_count;
+	$all_plugin_dependencies_active    = $active_plugin_dependencies_count === $plugin_dependencies_count;
+
+	if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
 		switch ( $status['status'] ) {
 			case 'install':
 				if ( $status['url'] ) {
-					if ( $compatible_php && $compatible_wp ) {
-						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>';
+					if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_installed && ! empty( $data->download_link ) ) {
+						$button = sprintf(
+							'<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s" role="button">%s</a>',
+							esc_attr( $data->slug ),
+							esc_url( $status['url'] ),
+							/* translators: %s: Plugin name and version. */
+							esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
+							esc_attr( $name ),
+							_x( 'Install Now', 'plugin' )
+						);
 					} else {
-						printf(
-							'<button type="button" class="button button-primary button-disabled right" disabled="disabled">%s</button>',
-							_x( 'Cannot Install', 'plugin' )
+						$button = sprintf(
+							'<button type="button" class="install-now button button-disabled" disabled="disabled">%s</button>',
+							_x( 'Install Now', 'plugin' )
 						);
 					}
 				}
 				break;
+
 			case 'update_available':
 				if ( $status['url'] ) {
-					if ( $compatible_php ) {
-						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>';
+					if ( $compatible_php && $compatible_wp ) {
+						$button = sprintf(
+							'<a class="update-now button aria-button-if-js" data-plugin="%s" data-slug="%s" href="%s" aria-label="%s" data-name="%s" role="button">%s</a>',
+							esc_attr( $status['file'] ),
+							esc_attr( $data->slug ),
+							esc_url( $status['url'] ),
+							/* translators: %s: Plugin name and version. */
+							esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ),
+							esc_attr( $name ),
+							_x( 'Update Now', 'plugin' )
+						);
 					} else {
-						printf(
-							'<button type="button" class="button button-primary button-disabled right" disabled="disabled">%s</button>',
-							_x( 'Cannot Update', 'plugin' )
+						$button = sprintf(
+							'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
+							_x( 'Update Now', 'plugin' )
 						);
 					}
 				}
 				break;
+
+			case 'latest_installed':
 			case 'newer_installed':
-				/* translators: %s: Plugin version. */
-				echo '<a class="button button-primary right disabled">' . sprintf( __( 'Newer Version (%s) Installed' ), esc_html( $status['version'] ) ) . '</a>';
-				break;
-			case 'latest_installed':
-				echo '<a class="button button-primary right disabled">' . __( 'Latest Version Installed' ) . '</a>';
+				if ( is_plugin_active( $status['file'] ) ) {
+					$button = sprintf(
+						'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
+						_x( 'Active', 'plugin' )
+					);
+				} elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) {
+					if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_active ) {
+						$button_text = _x( 'Activate', 'plugin' );
+						/* translators: %s: Plugin name. */
+						$button_label = _x( 'Activate %s', 'plugin' );
+						$activate_url = add_query_arg(
+							array(
+								'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
+								'action'   => 'activate',
+								'plugin'   => $status['file'],
+							),
+							network_admin_url( 'plugins.php' )
+						);
+
+						if ( is_network_admin() ) {
+							$button_text = _x( 'Network Activate', 'plugin' );
+							/* translators: %s: Plugin name. */
+							$button_label = _x( 'Network Activate %s', 'plugin' );
+							$activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
+						}
+
+						$button = sprintf(
+							'<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" role="button">%6$s</a>',
+							esc_url( $activate_url ),
+							esc_attr( $name ),
+							esc_attr( $data->slug ),
+							esc_attr( $status['file'] ),
+							esc_attr( sprintf( $button_label, $name ) ),
+							$button_text
+						);
+					} else {
+						$button = sprintf(
+							'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
+							is_network_admin() ? _x( 'Network Activate', 'plugin' ) : _x( 'Activate', 'plugin' )
+						);
+					}
+				} else {
+					$button = sprintf(
+						'<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
+						_x( 'Installed', 'plugin' )
+					);
+				}
 				break;
 		}
 	}
-	echo "</div>\n";
 
-	iframe_footer();
-	exit;
+	return $button;
 }