diff --git a/projects/packages/scheduled-updates/.phan/baseline.php b/projects/packages/scheduled-updates/.phan/baseline.php
index 74832f049ae01..7bedf7098cdde 100644
--- a/projects/packages/scheduled-updates/.phan/baseline.php
+++ b/projects/packages/scheduled-updates/.phan/baseline.php
@@ -17,11 +17,10 @@
// PhanUndeclaredClassMethod : 2 occurrences
// PhanNoopNew : 1 occurrence
// PhanTypeMismatchArgumentProbablyReal : 1 occurrence
- // PhanTypeMismatchDimFetch : 1 occurrence
// Currently, file_suppressions and directory_suppressions are the only supported suppressions
'file_suppressions' => [
- 'src/class-scheduled-updates.php' => ['PhanRedundantCondition', 'PhanTypeMismatchArgumentProbablyReal', 'PhanTypeMismatchDimFetch', 'PhanUndeclaredClassMethod'],
+ 'src/class-scheduled-updates.php' => ['PhanRedundantCondition', 'PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredClassMethod'],
'src/pluggable.php' => ['PhanTypeArraySuspiciousNullable'],
'src/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-update-schedules.php' => ['PhanPluginMixedKeyNoKey'],
'tests/php/class-scheduled-updates-logs-test.php' => ['PhanCompatibleAccessMethodOnTraitDefinition', 'PhanUndeclaredProperty'],
diff --git a/projects/packages/scheduled-updates/changelog/add-plugin-list-filter b/projects/packages/scheduled-updates/changelog/add-plugin-list-filter
new file mode 100644
index 0000000000000..dc1d1c2af92c9
--- /dev/null
+++ b/projects/packages/scheduled-updates/changelog/add-plugin-list-filter
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Scheduled Updates: Added a views link to filter plugins that are part of a scheduled update.
diff --git a/projects/packages/scheduled-updates/src/class-scheduled-updates-admin.php b/projects/packages/scheduled-updates/src/class-scheduled-updates-admin.php
new file mode 100644
index 0000000000000..f879ca5369ea7
--- /dev/null
+++ b/projects/packages/scheduled-updates/src/class-scheduled-updates-admin.php
@@ -0,0 +1,164 @@
+ 0,
+ * 'wordpress-importer/wordpress-importer.php' => 1,
+ * 'wp-last-login/wp-last-login.php' => 2,
+ * );
+ */
+ $scheduled = array_flip(
+ array_unique(
+ array_merge(
+ ...array_values(
+ wp_list_pluck( $events, 'args' )
+ )
+ )
+ )
+ );
+
+ // Removing from the auto-update-disabled list since they are scheduled.
+ $plugins['auto-update-disabled'] = array_diff_key( (array) $plugins['auto-update-disabled'], $scheduled );
+ $plugins['scheduled-updates'] = array_intersect_key( (array) $plugins['all'], $scheduled );
+ }
+
+ return $plugins;
+ }
+
+ /**
+ * Add a view for scheduled updates in the Plugins list table.
+ *
+ * @param array $views An array of available views.
+ * @return array
+ */
+ public static function add_scheduled_updates_view( $views ) {
+ global $totals;
+
+ if ( ! empty( $totals['scheduled-updates'] ) ) {
+ $views['scheduled-updates'] = sprintf(
+ '%3$s (%4$s)',
+ esc_url( add_query_arg( array( 'plugin_status' => 'scheduled-updates' ), 'plugins.php' ) ),
+ isset( $_REQUEST['plugin_status'] ) && 'scheduled-updates' === $_REQUEST['plugin_status'] ? 'current' : '', // phpcs:ignore WordPress.Security.NonceVerification
+ __( 'Scheduled Updates', 'jetpack-scheduled-updates' ),
+ number_format_i18n( $totals['scheduled-updates'] )
+ );
+ }
+
+ return $views;
+ }
+
+ /**
+ * Filters the HTML of the auto-updates setting for each plugin in the Plugins list table.
+ *
+ * @param string $html The HTML of the plugin's auto-update column content,
+ * including toggle auto-update action links and
+ * time to next update.
+ * @param string $plugin_file Path to the plugin file relative to the plugin directory.
+ */
+ public static function show_scheduled_updates( $html, $plugin_file ) {
+ if ( ! function_exists( 'wp_get_scheduled_events' ) ) {
+ require_once __DIR__ . '/pluggable.php';
+ }
+
+ $events = wp_get_scheduled_events( Scheduled_Updates::PLUGIN_CRON_HOOK );
+
+ $schedules = array();
+ foreach ( $events as $event ) {
+ if ( in_array( $plugin_file, $event->args, true ) ) {
+ $schedules[] = $event;
+ }
+ }
+
+ // Plugin is not part of an update schedule.
+ if ( empty( $schedules ) ) {
+ return $html;
+ }
+
+ $text = array_map( array( __CLASS__, 'get_scheduled_update_text' ), $schedules );
+
+ $html = '
' . implode( '
', $text ) . '
';
+ $html .= sprintf(
+ '%2$s',
+ esc_url( 'https://wordpress.com/plugins/scheduled-updates/' . ( new Status() )->get_site_suffix() ),
+ esc_html__( 'Edit', 'jetpack-scheduled-updates' )
+ );
+
+ return $html;
+ }
+
+ /**
+ * Get the text for a scheduled update.
+ *
+ * @param object $schedule The scheduled update.
+ * @return string
+ */
+ public static function get_scheduled_update_text( $schedule ) {
+ if ( DAY_IN_SECONDS === $schedule->interval ) {
+ $html = sprintf(
+ /* translators: %s is the time of day. Daily at 10 am. */
+ esc_html__( 'Daily at %s.', 'jetpack-scheduled-updates' ),
+ wp_date( get_option( 'time_format' ), $schedule->timestamp )
+ );
+ } else {
+ // Not getting smart about passing in weekdays makes it easier to translate.
+ $weekdays = array(
+ /* translators: %s is the time of day. Mondays at 10 am. */
+ 1 => __( 'Mondays at %s.', 'jetpack-scheduled-updates' ),
+ /* translators: %s is the time of day. Tuesdays at 10 am. */
+ 2 => __( 'Tuesdays at %s.', 'jetpack-scheduled-updates' ),
+ /* translators: %s is the time of day. Wednesdays at 10 am. */
+ 3 => __( 'Wednesdays at %s.', 'jetpack-scheduled-updates' ),
+ /* translators: %s is the time of day. Thursdays at 10 am. */
+ 4 => __( 'Thursdays at %s.', 'jetpack-scheduled-updates' ),
+ /* translators: %s is the time of day. Fridays at 10 am. */
+ 5 => __( 'Fridays at %s.', 'jetpack-scheduled-updates' ),
+ /* translators: %s is the time of day. Saturdays at 10 am. */
+ 6 => __( 'Saturdays at %s.', 'jetpack-scheduled-updates' ),
+ /* translators: %s is the time of day. Sundays at 10 am. */
+ 7 => __( 'Sundays at %s.', 'jetpack-scheduled-updates' ),
+ );
+
+ $html = sprintf(
+ $weekdays[ (int) wp_date( 'N', $schedule->timestamp ) ],
+ wp_date( get_option( 'time_format' ), $schedule->timestamp )
+ );
+ }
+
+ return $html;
+ }
+}
diff --git a/projects/packages/scheduled-updates/src/class-scheduled-updates.php b/projects/packages/scheduled-updates/src/class-scheduled-updates.php
index 4bdfce49e7ef8..cfa2c27a20a00 100644
--- a/projects/packages/scheduled-updates/src/class-scheduled-updates.php
+++ b/projects/packages/scheduled-updates/src/class-scheduled-updates.php
@@ -51,9 +51,12 @@ public static function init() {
add_action( self::PLUGIN_CRON_HOOK, array( __CLASS__, 'run_scheduled_update' ), 10, 10 );
add_action( 'rest_api_init', array( __CLASS__, 'add_is_managed_extension_field' ) );
add_filter( 'auto_update_plugin', array( __CLASS__, 'allowlist_scheduled_plugins' ), 10, 2 );
- add_filter( 'plugin_auto_update_setting_html', array( __CLASS__, 'show_scheduled_updates' ), 10, 2 );
add_action( 'deleted_plugin', array( __CLASS__, 'deleted_plugin' ), 10, 2 );
+ add_filter( 'plugins_list', array( Scheduled_Updates_Admin::class, 'add_scheduled_updates_context' ) );
+ add_filter( 'views_plugins', array( Scheduled_Updates_Admin::class, 'add_scheduled_updates_view' ) );
+ add_filter( 'plugin_auto_update_setting_html', array( Scheduled_Updates_Admin::class, 'show_scheduled_updates' ), 10, 2 );
+
// Update cron sync option after options update.
$callback = array( __CLASS__, 'update_option_cron' );
@@ -263,86 +266,6 @@ public static function allowlist_scheduled_plugins( $update, $item ) {
return $update;
}
- /**
- * Filters the HTML of the auto-updates setting for each plugin in the Plugins list table.
- *
- * @param string $html The HTML of the plugin's auto-update column content,
- * including toggle auto-update action links and
- * time to next update.
- * @param string $plugin_file Path to the plugin file relative to the plugin directory.
- */
- public static function show_scheduled_updates( $html, $plugin_file ) {
- if ( ! function_exists( 'wp_get_scheduled_events' ) ) {
- require_once __DIR__ . '/pluggable.php';
- }
-
- $events = wp_get_scheduled_events( self::PLUGIN_CRON_HOOK );
-
- $schedules = array();
- foreach ( $events as $event ) {
- if ( in_array( $plugin_file, $event->args, true ) ) {
- $schedules[] = $event;
- }
- }
-
- // Plugin is not part of an update schedule.
- if ( empty( $schedules ) ) {
- return $html;
- }
-
- $text = array_map( array( __CLASS__, 'get_scheduled_update_text' ), $schedules );
-
- $html = '' . implode( '
', $text ) . '
';
- $html .= sprintf(
- '%2$s',
- esc_url( 'https://wordpress.com/plugins/scheduled-updates/' . ( new Status() )->get_site_suffix() ),
- esc_html__( 'Edit', 'jetpack-scheduled-updates' )
- );
-
- return $html;
- }
-
- /**
- * Get the text for a scheduled update.
- *
- * @param object $schedule The scheduled update.
- * @return string
- */
- public static function get_scheduled_update_text( $schedule ) {
- if ( DAY_IN_SECONDS === $schedule->interval ) {
- $html = sprintf(
- /* translators: %s is the time of day. Daily at 10 am. */
- esc_html__( 'Daily at %s.', 'jetpack-scheduled-updates' ),
- wp_date( get_option( 'time_format' ), $schedule->timestamp )
- );
- } else {
- // Not getting smart about passing in weekdays makes it easier to translate.
- $weekdays = array(
- /* translators: %s is the time of day. Mondays at 10 am. */
- 1 => __( 'Mondays at %s.', 'jetpack-scheduled-updates' ),
- /* translators: %s is the time of day. Tuesdays at 10 am. */
- 2 => __( 'Tuesdays at %s.', 'jetpack-scheduled-updates' ),
- /* translators: %s is the time of day. Wednesdays at 10 am. */
- 3 => __( 'Wednesdays at %s.', 'jetpack-scheduled-updates' ),
- /* translators: %s is the time of day. Thursdays at 10 am. */
- 4 => __( 'Thursdays at %s.', 'jetpack-scheduled-updates' ),
- /* translators: %s is the time of day. Fridays at 10 am. */
- 5 => __( 'Fridays at %s.', 'jetpack-scheduled-updates' ),
- /* translators: %s is the time of day. Saturdays at 10 am. */
- 6 => __( 'Saturdays at %s.', 'jetpack-scheduled-updates' ),
- /* translators: %s is the time of day. Sundays at 10 am. */
- 7 => __( 'Sundays at %s.', 'jetpack-scheduled-updates' ),
- );
-
- $html = sprintf(
- $weekdays[ wp_date( 'N', $schedule->timestamp ) ],
- wp_date( get_option( 'time_format' ), $schedule->timestamp )
- );
- }
-
- return $html;
- }
-
/**
* Registers the is_managed field for the plugin REST API.
*/
diff --git a/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-admin-test.php b/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-admin-test.php
new file mode 100644
index 0000000000000..86c2051995217
--- /dev/null
+++ b/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-admin-test.php
@@ -0,0 +1,59 @@
+assertSame( $expected, Scheduled_Updates_Admin::get_scheduled_update_text( $schedule ) );
+ }
+
+ /**
+ * Data provider for test_get_scheduled_update_text.
+ *
+ * @return array[]
+ */
+ public function update_text_provider() {
+ return array(
+ array(
+ (object) array(
+ 'timestamp' => strtotime( 'next Monday 00:00' ),
+ 'interval' => WEEK_IN_SECONDS,
+ ),
+ sprintf( 'Mondays at %s.', gmdate( get_option( 'time_format' ), strtotime( 'next Monday 8:00' ) ) ),
+ ),
+ array(
+ (object) array(
+ 'timestamp' => strtotime( 'next Tuesday 00:00' ),
+ 'interval' => DAY_IN_SECONDS,
+ ),
+ sprintf( 'Daily at %s.', gmdate( get_option( 'time_format' ), strtotime( 'next Tuesday 8:00' ) ) ),
+ ),
+ array(
+ (object) array(
+ 'timestamp' => strtotime( 'next Sunday 00:00' ),
+ 'interval' => WEEK_IN_SECONDS,
+ ),
+ sprintf( 'Sundays at %s.', gmdate( get_option( 'time_format' ), strtotime( 'next Sunday 8:00' ) ) ),
+ ),
+ );
+ }
+}
diff --git a/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-test.php b/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-test.php
index 70f6a16e78801..6ebdead2d833a 100644
--- a/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-test.php
+++ b/projects/packages/scheduled-updates/tests/php/class-scheduled-updates-test.php
@@ -556,19 +556,6 @@ public function test_delete_plugin_new_events_inherit_statuses() {
}
}
- /**
- * Test get_scheduled_update_text.
- *
- * @dataProvider update_text_provider
- * @covers ::get_scheduled_update_text
- *
- * @param object $schedule The schedule object.
- * @param string $expected The expected text.
- */
- public function test_get_scheduled_update_text( $schedule, $expected ) {
- $this->assertSame( $expected, Scheduled_Updates::get_scheduled_update_text( $schedule ) );
- }
-
/**
* Test clear CRON cache.
*
@@ -645,37 +632,6 @@ public function test_clear_cron_cache() {
$this->assertArrayHasKey( $id_2, $data );
}
- /**
- * Data provider for test_get_scheduled_update_text.
- *
- * @return array[]
- */
- public function update_text_provider() {
- return array(
- array(
- (object) array(
- 'timestamp' => strtotime( 'next Monday 00:00' ),
- 'interval' => WEEK_IN_SECONDS,
- ),
- sprintf( 'Mondays at %s.', gmdate( get_option( 'time_format' ), strtotime( 'next Monday 8:00' ) ) ),
- ),
- array(
- (object) array(
- 'timestamp' => strtotime( 'next Tuesday 00:00' ),
- 'interval' => DAY_IN_SECONDS,
- ),
- sprintf( 'Daily at %s.', gmdate( get_option( 'time_format' ), strtotime( 'next Tuesday 8:00' ) ) ),
- ),
- array(
- (object) array(
- 'timestamp' => strtotime( 'next Sunday 00:00' ),
- 'interval' => WEEK_IN_SECONDS,
- ),
- sprintf( 'Sundays at %s.', gmdate( get_option( 'time_format' ), strtotime( 'next Sunday 8:00' ) ) ),
- ),
- );
- }
-
/**
* Create a list of plugins to be deleted.
*
diff --git a/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php b/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php
index a179dde6b8fd3..3076511b03f19 100644
--- a/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php
+++ b/projects/packages/scheduled-updates/tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php
@@ -59,6 +59,7 @@ public function set_up() {
);
wp_set_current_user( 0 );
+ Scheduled_Updates::init();
do_action( 'rest_api_init' );
}
@@ -73,6 +74,7 @@ public function tear_down() {
wp_clear_scheduled_hook( Scheduled_Updates::PLUGIN_CRON_HOOK );
delete_option( 'jetpack_scheduled_update_statuses' );
+ delete_option( Scheduled_Updates::PLUGIN_CRON_HOOK );
}
/**
@@ -865,11 +867,11 @@ public function test_admin_user_capabilities() {
}
/**
- * Test adding a log entry.
+ * Test adding a log entry for a non-existent schedule.
*
* @covers ::add_log
*/
- public function test_add_log() {
+ public function test_add_log_invalid_schedule() {
wp_set_current_user( $this->admin_id );
$request = new WP_REST_Request( 'PUT', '/wpcom/v2/update-schedules/' . Scheduled_Updates::generate_schedule_id( array() ) . '/logs' );
@@ -882,7 +884,7 @@ public function test_add_log() {
$result = rest_do_request( $request );
$this->assertSame( 404, $result->get_status() );
- $this->assertSame( false, get_option( Scheduled_Updates::PLUGIN_CRON_HOOK ) );
+ $this->assertEmpty( get_option( Scheduled_Updates::PLUGIN_CRON_HOOK ) );
}
/**
diff --git a/projects/plugins/mu-wpcom-plugin/changelog/add-plugin-list-filter b/projects/plugins/mu-wpcom-plugin/changelog/add-plugin-list-filter
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/mu-wpcom-plugin/changelog/add-plugin-list-filter
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+