File: /var/www/html/wp-content/plugins/wp-graphql/src/Admin/Updates/Updates.php
<?php
/**
* Initializes Plugin Update functionality.
*
* @package WPGraphQL\Admin\Updates
* @since 1.30.0
*/
namespace WPGraphQL\Admin\Updates;
/**
* Class Updates
*/
final class Updates {
/**
* Initialize the Updates functionality.
*/
public function init(): void {
// Expose the plugin headers.
add_filter( 'extra_plugin_headers', [ $this, 'enable_plugin_headers' ] );
add_filter( 'extra_theme_headers', [ $this, 'enable_plugin_headers' ] );
// Prevent autoupdates for untested WPGraphQL Extensions.
add_filter( 'auto_update_plugin', [ $this, 'maybe_allow_autoupdates' ], 10, 2 );
// Load the Update Checker for the current screen.
add_action( 'current_screen', [ $this, 'load_screen_checker' ] );
// Disable incompatible plugins.
add_action( 'admin_init', [ $this, 'disable_incompatible_plugins' ] );
add_action( 'graphql_activate', [ $this, 'disable_incompatible_plugins' ] );
add_action( 'admin_notices', [ $this, 'disable_incompatible_plugins_notice' ] );
// Register admin assets.
add_action( 'admin_enqueue_scripts', [ $this, 'register_assets' ] );
}
/**
* Enable the plugin headers.
*
* @param string[] $headers The headers.
* @return string[]
*/
public function enable_plugin_headers( $headers ) {
$headers[] = UpdateChecker::VERSION_HEADER;
$headers[] = UpdateChecker::TESTED_UP_TO_HEADER;
return $headers;
}
/**
* Prevent autoupdates when there is an untested WPGraphQL Extension.
*
* @param bool $should_update Whether the plugin should autoupdate.
* @param object $plugin The plugin data object.
*
* @return bool Whether the plugin should autoupdate.
*/
public function maybe_allow_autoupdates( $should_update, $plugin ) {
// Bail if it's not our plugin.
if ( ! isset( $plugin->plugin ) || ! isset( $plugin->new_version ) || 'wp-graphql/wp-graphql.php' !== $plugin->plugin ) {
return $should_update;
}
// If autoupdates are already disabled we don't need to check further.
if ( false === $should_update ) {
return $should_update;
}
$new_version = sanitize_text_field( $plugin->new_version );
if ( '' === $new_version ) {
return $should_update;
}
// Store the sanitized version in the plugin object.
$plugin->new_version = $new_version;
$plugin_updates = new UpdateChecker( $plugin );
return $plugin_updates->should_autoupdate( (bool) $should_update );
}
/**
* Maybe loads the Update Checker for the current admin screen.
*/
public function load_screen_checker(): void {
$screen = get_current_screen();
// Bail if we're not on a screen.
if ( ! $screen ) {
return;
}
// Loaders for the different WPAdmin Screens.
$loaders = [
'plugins' => PluginsScreenLoader::class,
'update-core' => UpdatesScreenLoader::class,
];
// Bail if the current screen doesn't need an update checker.
if ( ! in_array( $screen->id, array_keys( $loaders ), true ) ) {
return;
}
// Load the update checker for the current screen.
new $loaders[ $screen->id ]();
}
/**
* Registers the admin assets for update-related screens.
*
* The /build directory is gitignored and only generated via `npm run build`.
* Users who install via WordPress.org or GitHub releases have pre-built assets,
* but those who clone the repo or install via Composer need to build manually.
* We check for asset existence to prevent fatal errors in development environments.
*/
public function register_assets(): void {
$screen = get_current_screen();
$allowed_screens = [
'plugins',
'update-core',
];
// Bail if we're not on a screen.
if ( ! $screen || ! in_array( $screen->id, $allowed_screens, true ) ) {
return;
}
$asset_path = WPGRAPHQL_PLUGIN_DIR . 'build/updates.asset.php';
$css_path = WPGRAPHQL_PLUGIN_DIR . 'build/updates.css';
// Bail if build assets don't exist (e.g., dev install without running npm build)
if ( ! file_exists( $asset_path ) || ! file_exists( $css_path ) ) {
return;
}
// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path is constructed from WPGRAPHQL_PLUGIN_DIR constant + hardcoded string, validated with file_exists()
$asset_file = include $asset_path;
wp_enqueue_style(
'wp-graphql-admin-updates',
WPGRAPHQL_PLUGIN_URL . 'build/updates.css',
$asset_file['dependencies'],
$asset_file['version']
);
}
/**
* Disables plugins that don't meet the minimum `Requires WPGraphQL` version.
*/
public function disable_incompatible_plugins(): void {
// Get the plugin data.
$plugin_data = get_plugin_data( WPGRAPHQL_PLUGIN_DIR . '/wp-graphql.php' );
// Initialize the Update Checker.
$update_checker = new UpdateChecker( (object) $plugin_data );
// Get the incompatible plugins.
$incompatible_plugins = $update_checker->get_incompatible_plugins( WPGRAPHQL_VERSION, true );
// Deactivate the incompatible plugins.
$notice_data = [];
foreach ( $incompatible_plugins as $file => $plugin ) {
$notice_data[] = [
'name' => $plugin['Name'],
'version' => $plugin[ UpdateChecker::VERSION_HEADER ],
];
deactivate_plugins( $file );
}
// Display a notice to the user.
if ( ! empty( $notice_data ) ) {
set_transient( 'wpgraphql_incompatible_plugins', $notice_data );
}
}
/**
* Displays a one-time notice to the user if incompatible plugins were deactivated.
*/
public function disable_incompatible_plugins_notice(): void {
$incompatible_plugins = get_transient( 'wpgraphql_incompatible_plugins' );
if ( empty( $incompatible_plugins ) ) {
return;
}
$notice = sprintf(
'<p>%s</p>',
__( 'The following plugins were deactivated because they require a newer version of WPGraphQL. Please update WPGraphQL to a newer version to reactivate these plugins.', 'wp-graphql' )
);
$notice .= '<ul class="ul-disc">';
foreach ( $incompatible_plugins as $plugin ) {
$notice .= sprintf(
'<li><strong>%s</strong> (requires at least WPGraphQL: v%s)</li>',
esc_html( $plugin['name'] ),
esc_html( $plugin['version'] )
);
}
$notice .= '</ul>';
echo wp_kses_post( sprintf( '<div class="notice notice-error is-dismissable">%s</div>', $notice ) );
// Delete once the notice is displayed.
delete_transient( 'wpgraphql_incompatible_plugins' );
}
}