Skip to content

Commit

Permalink
Add scheduled updates pause feature (#37110)
Browse files Browse the repository at this point in the history
* Initial changes

* Add new unit tests

* changelog

* Updated version

* Update versions

* Fix: typo

* Fix: typo.
  • Loading branch information
zaerl authored Apr 29, 2024
1 parent 106b217 commit 2b79cbf
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add scheduled updates active flag
2 changes: 1 addition & 1 deletion projects/packages/scheduled-updates/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"autotagger": true,
"branch-alias": {
"dev-trunk": "0.10.x-dev"
"dev-trunk": "0.11.x-dev"
},
"textdomain": "jetpack-scheduled-updates",
"version-constants": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php
/**
* Scheduled Updates Active class
*
* @package automattic/scheduled-updates
*/

namespace Automattic\Jetpack;

/**
* Scheduled_Updates_Active class
*
* This class provides static methods to get/save active for scheduled updates.
*/
class Scheduled_Updates_Active {

/**
* The name of the WordPress option where the active option is stored.
*/
const OPTION_NAME = 'jetpack_scheduled_update_active';

/**
* Get the active value for a scheduled update.
*
* @param string $schedule_id Request ID.
* @return bool Active value.
*/
public static function get( $schedule_id ) {
$option = get_option( self::OPTION_NAME, array() );

return $option[ $schedule_id ] ?? true;
}

/**
* Update the active value for a scheduled update.
*
* @param string $schedule_id Request ID.
* @param bool $active Active value.
* @return bool
*/
public static function update( $schedule_id, $active ) {
$option = get_option( self::OPTION_NAME, array() );

if ( ! is_array( $option ) ) {
$option = array();
}

$option[ $schedule_id ] = $active;

return update_option( self::OPTION_NAME, $option );
}

/**
* Clear the active value for a scheduled update.
*
* @param string|null $schedule_id Request ID.
* @return bool
*/
public static function clear( $schedule_id ) {
$option = get_option( self::OPTION_NAME, array() );

if ( isset( $option[ $schedule_id ] ) ) {
unset( $option[ $schedule_id ] );
}

if ( count( $option ) ) {
return update_option( self::OPTION_NAME, $option );
} else {
return delete_option( self::OPTION_NAME );
}
}

/**
* Update the active value for a scheduled update hook.
*
* @param string $id The ID of the schedule.
* @param object $event The event object.
* @param \WP_REST_Request $request The request object.
* @return bool
*/
public static function updates_active( $id, $event, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$schedule = $request['schedule'];
$active = $schedule['active'] ?? true;

return self::update( $id, $active );
}

/**
* REST prepare_item_for_response filter.
*
* @param array $item WP Cron event.
* @param \WP_REST_Request $request Request object.
* @return array Response array on success.
*/
public static function response_filter( $item, $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$item['active'] = self::get( $item['schedule_id'] );

return $item;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,22 @@ public static function validate( $path ) {
* @param \WP_REST_Request $request The request object.
* @return bool
*/
public static function updates_health_paths( $id, $event, $request ) {
public static function updates_health_paths( $id, $event, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$schedule = $request['schedule'];
$paths = $schedule['health_check_paths'] ?? array();
return self::update( $id, $paths );
}

/**
* REST prepare_item_for_response filter.
*
* @param array $item WP Cron event.
* @param \WP_REST_Request $request Request object.
* @return array Response array on success.
*/
public static function response_filter( $item, $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$item['health_check_paths'] = self::get( $item['schedule_id'] );

return $item;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Scheduled_Updates {
*
* @var string
*/
const PACKAGE_VERSION = '0.10.0';
const PACKAGE_VERSION = '0.11.0-alpha';

/**
* The cron event hook for the scheduled plugins update.
Expand Down Expand Up @@ -58,11 +58,19 @@ public static function init() {
add_filter( 'plugin_auto_update_setting_html', array( Scheduled_Updates_Admin::class, 'show_scheduled_updates' ), 10, 2 );

add_action( 'jetpack_scheduled_update_created', array( __CLASS__, 'maybe_disable_autoupdates' ), 10, 3 );
add_action( 'jetpack_scheduled_update_deleted', array( __CLASS__, 'enable_autoupdates' ), 10, 2 );
add_action( 'jetpack_scheduled_update_updated', array( Scheduled_Updates_Logs::class, 'replace_logs_schedule_id' ), 10, 2 );
add_action( 'jetpack_scheduled_update_deleted', array( Scheduled_Updates_Logs::class, 'delete_logs_schedule_id' ), 10, 3 );
add_action( 'jetpack_scheduled_update_created', array( Scheduled_Updates_Active::class, 'updates_active' ), 10, 3 );
add_action( 'jetpack_scheduled_update_created', array( Scheduled_Updates_Health_Paths::class, 'updates_health_paths' ), 10, 3 );

add_action( 'jetpack_scheduled_update_updated', array( Scheduled_Updates_Logs::class, 'replace_logs_schedule_id' ), 10, 2 );

add_action( 'jetpack_scheduled_update_deleted', array( __CLASS__, 'enable_autoupdates' ), 10, 2 );
add_action( 'jetpack_scheduled_update_deleted', array( Scheduled_Updates_Active::class, 'clear' ) );
add_action( 'jetpack_scheduled_update_deleted', array( Scheduled_Updates_Health_Paths::class, 'clear' ) );
add_action( 'jetpack_scheduled_update_deleted', array( Scheduled_Updates_Logs::class, 'delete_logs_schedule_id' ), 10, 3 );

add_filter( 'jetpack_scheduled_response_item', array( __CLASS__, 'response_filter' ), 10, 2 );
add_filter( 'jetpack_scheduled_response_item', array( Scheduled_Updates_Active::class, 'response_filter' ), 10, 2 );
add_filter( 'jetpack_scheduled_response_item', array( Scheduled_Updates_Health_Paths::class, 'response_filter' ), 10, 2 );

// Update cron sync option after options update.
$callback = array( __CLASS__, 'update_option_cron' );
Expand Down Expand Up @@ -99,7 +107,13 @@ public static function load_rest_api_endpoints() {
* @param string ...$plugins List of plugins to update.
*/
public static function run_scheduled_update( ...$plugins ) {
$schedule_id = self::generate_schedule_id( $plugins );
$schedule_id = self::generate_schedule_id( $plugins );

if ( ! Scheduled_Updates_Active::get( $schedule_id ) ) {
// The schedule is not active, return.
return false;
}

$available_updates = get_site_transient( 'update_plugins' );
$plugins_to_update = $available_updates->response ?? array();
$plugins_to_update = array_intersect_key( $plugins_to_update, array_flip( $plugins ) );
Expand All @@ -118,10 +132,10 @@ public static function run_scheduled_update( ...$plugins ) {
'no_plugins_to_update'
);

return;
return false;
}

( new Connection\Client() )->wpcom_json_api_request_as_blog(
$response = ( new Connection\Client() )->wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/hosting/scheduled-update', \Jetpack_Options::get_option( 'id' ) ),
'2',
array( 'method' => 'POST' ),
Expand All @@ -132,6 +146,8 @@ public static function run_scheduled_update( ...$plugins ) {
),
'wpcom'
);

return is_array( $response );
}

/**
Expand Down Expand Up @@ -460,6 +476,28 @@ public static function deleted_plugin( $plugin_file, $deleted ) {
}
}

/**
* REST prepare_item_for_response filter.
*
* @param array $item WP Cron event.
* @param \WP_REST_Request $request Request object.
* @return array Response array on success.
*/
public static function response_filter( $item, $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$status = self::get_scheduled_update_status( $item['schedule_id'] );

if ( ! $status ) {
$status = array(
'last_run_timestamp' => null,
'last_run_status' => null,
);
}

$item = array_merge( $item, $status );

return $item;
}

/**
* Generates a unique schedule ID.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,18 +541,7 @@ public function prepare_response_for_collection( $response ) {
*/
public function prepare_item_for_response( $item, $request ) {
$item = (array) $item;

$status = Scheduled_Updates::get_scheduled_update_status( $item['schedule_id'] );
if ( ! $status ) {
$status = array(
'last_run_timestamp' => null,
'last_run_status' => null,
);
}

$item = array_merge( $item, $status );
$item['health_check_paths'] = Scheduled_Updates_Health_Paths::get( $item['schedule_id'] );

$item = apply_filters( 'jetpack_scheduled_response_item', $item, $request );
$item = $this->add_additional_fields_to_object( $item, $request );

// Remove schedule ID, not needed in the response.
Expand Down Expand Up @@ -771,6 +760,10 @@ public function get_item_schema() {
'type' => 'string',
'enum' => array( 'success', 'failure-and-rollback', 'failure-and-rollback-fail' ),
),
'active' => array(
'description' => 'Whether the schedule is active.',
'type' => 'boolean',
),
'health_check_paths' => array(
'description' => 'Paths to check for site health.',
'type' => 'array',
Expand Down Expand Up @@ -818,6 +811,12 @@ public function get_object_params() {
'type' => 'integer',
'required' => true,
),
'active' => array(
'description' => 'Whether the schedule is active.',
'type' => 'boolean',
'required' => false,
'default' => true,
),
'health_check_paths' => array(
'description' => 'List of paths to check for site health after the update.',
'type' => 'array',
Expand Down
Loading

0 comments on commit 2b79cbf

Please sign in to comment.