$this->slug(), 'risk' => 'none', 'summary' => 'Lists the pending updates: each plugin and theme with an update available ' . '(current and new version, whether it is active and auto-updated) and the core update ' . 'status. Call before planning maintenance; pair with update-plugin to apply one. ' . 'Set refresh=true to force a fresh check against WordPress.org first. Read-only.', 'annotations' => array( 'readonly' => true, // Calls wordpress.org (MCP openWorldHint; v1.6.0). 'openWorldHint' => true, ), // Prefill for the admin Run panel (v1.4); agents may copy it too. 'example_input' => array( 'type' => 'plugins', 'refresh' => true ), 'input_schema' => array( // Plain 'object' with an empty default: a parameterless REST GET run // passes input=null and core fills the schema default before validating // (WP_Ability::normalize_input, since 6.9). The former ['object','null'] // made the MCP Adapter wrap this tool's input in a required "input" // object (SchemaTransformer wraps any non-'object' type) — v1.6.0. // The default spells out dry_run=false (what an absent input means // for a read-only ability) rather than an empty array: the adapter's // schema normalizer renders an empty array as [] on an object schema. 'type' => 'object', 'default' => array( 'dry_run' => false ), 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'plugins', 'themes', 'core', 'all' ), 'default' => 'all', 'description' => 'Restrict the list to one update kind.', ), 'refresh' => array( 'type' => 'boolean', 'default' => false, 'description' => 'Force a fresh update check (network call to WordPress.org) instead of the cached result.', ), ), ), ); } public function dry_run( $atb_input ) { return $this->output( true, true, $this->gather( $atb_input ) ); } public function run( $atb_input ) { return $this->output( true, false, $this->gather( $atb_input ) ); } /** * @param array|null $atb_input * @return array details shape per v1.3 plan §3.2. */ protected function gather( $atb_input ) { $atb_type = ( is_array( $atb_input ) && isset( $atb_input['type'] ) ) ? (string) $atb_input['type'] : 'all'; $atb_refresh = is_array( $atb_input ) && ! empty( $atb_input['refresh'] ); if ( ! in_array( $atb_type, array( 'plugins', 'themes', 'core' ), true ) ) { $atb_type = 'all'; } if ( ! function_exists( 'get_plugin_updates' ) ) { require_once ABSPATH . 'wp-admin/includes/update.php'; } if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $atb_details = array( 'checked_gmt' => gmdate( 'Y-m-d H:i:s' ), 'refreshed' => $atb_refresh, ); if ( 'all' === $atb_type || 'plugins' === $atb_type ) { if ( $atb_refresh && function_exists( 'wp_update_plugins' ) ) { wp_update_plugins(); } $atb_auto_plugins = (array) get_site_option( 'auto_update_plugins', array() ); $atb_plugins = array(); foreach ( (array) get_plugin_updates() as $atb_file => $atb_plugin ) { $atb_plugins[] = array( 'plugin' => (string) $atb_file, 'name' => isset( $atb_plugin->Name ) ? (string) $atb_plugin->Name : (string) $atb_file, // phpcs:ignore WordPress.NamingConventions.ValidVariableName 'current_version' => isset( $atb_plugin->Version ) ? (string) $atb_plugin->Version : '', // phpcs:ignore WordPress.NamingConventions.ValidVariableName 'new_version' => isset( $atb_plugin->update->new_version ) ? (string) $atb_plugin->update->new_version : '', 'is_active' => is_plugin_active( $atb_file ), 'auto_update' => in_array( $atb_file, $atb_auto_plugins, true ), ); } $atb_details['plugins'] = $atb_plugins; $atb_details['plugins_count'] = count( $atb_plugins ); } if ( 'all' === $atb_type || 'themes' === $atb_type ) { if ( $atb_refresh && function_exists( 'wp_update_themes' ) ) { wp_update_themes(); } $atb_auto_themes = (array) get_site_option( 'auto_update_themes', array() ); $atb_current = function_exists( 'get_stylesheet' ) ? get_stylesheet() : ''; $atb_themes = array(); foreach ( (array) get_theme_updates() as $atb_stylesheet => $atb_theme ) { $atb_themes[] = array( 'theme' => (string) $atb_stylesheet, 'name' => method_exists( $atb_theme, 'get' ) ? (string) $atb_theme->get( 'Name' ) : (string) $atb_stylesheet, 'current_version' => method_exists( $atb_theme, 'get' ) ? (string) $atb_theme->get( 'Version' ) : '', 'new_version' => isset( $atb_theme->update['new_version'] ) ? (string) $atb_theme->update['new_version'] : '', 'is_active' => (string) $atb_stylesheet === $atb_current, 'auto_update' => in_array( (string) $atb_stylesheet, $atb_auto_themes, true ), ); } $atb_details['themes'] = $atb_themes; $atb_details['themes_count'] = count( $atb_themes ); } if ( 'all' === $atb_type || 'core' === $atb_type ) { if ( $atb_refresh && function_exists( 'wp_version_check' ) ) { wp_version_check(); } $atb_current_core = get_bloginfo( 'version' ); $atb_core = array( 'current_version' => (string) $atb_current_core, 'update_available' => false, 'new_version' => null, // Whether core would apply updates on its own (v1.4 F8): // the owner-facing "enable automatic updates" toggle sets // the auto_update_core_major option; the constant (true / // false / 'minor' / 'beta' / 'rc') overrides everything // and is reported raw when defined. 'auto_update_major' => 'enabled' === get_site_option( 'auto_update_core_major', 'unset' ), 'auto_update_constant' => defined( 'WP_AUTO_UPDATE_CORE' ) ? WP_AUTO_UPDATE_CORE : null, ); // get_core_updates() lists 'upgrade' responses first; 'latest' // means the site is already current. foreach ( (array) get_core_updates() as $atb_core_update ) { if ( is_object( $atb_core_update ) && isset( $atb_core_update->response ) && 'upgrade' === $atb_core_update->response ) { $atb_core['update_available'] = true; $atb_core['new_version'] = isset( $atb_core_update->version ) ? (string) $atb_core_update->version : null; break; } } $atb_details['core'] = $atb_core; } return $atb_details; } } }