From a2cb960ee3d04e4af5faafe3c0084aae5dd98f48 Mon Sep 17 00:00:00 2001
From: Muneeb Ashraf <58017595+muneeb-ashraf@users.noreply.github.com>
Date: Thu, 3 Sep 2026 18:04:45 +0500
Subject: [PATCH 1/3] Filter media library by optimization status
---
includes/admin/class-media-library.php | 162 +++++++++++++++++++++++
phpunit/tests/MediaLibraryTest.php | 175 +++++++++++++++++++++++++
2 files changed, 337 insertions(+)
create mode 100644 phpunit/tests/MediaLibraryTest.php
diff --git a/includes/admin/class-media-library.php b/includes/admin/class-media-library.php
index 901c67f..3f0b706 100644
--- a/includes/admin/class-media-library.php
+++ b/includes/admin/class-media-library.php
@@ -9,6 +9,7 @@
use WebberZone\Image_Optimizer\Attachment_Meta;
use WebberZone\Image_Optimizer\Converter;
+use WebberZone\Image_Optimizer\Database;
use WebberZone\Image_Optimizer\Processor;
use WebberZone\Image_Optimizer\Queue;
use WebberZone\Image_Optimizer\Util\Helpers;
@@ -25,6 +26,21 @@
*/
class Media_Library {
+ /**
+ * Query-string key for the optimization status filter.
+ *
+ * @since 1.3.0
+ * @var string
+ */
+ private const STATUS_FILTER = 'wzio_optimization_status';
+
+ /**
+ * Internal query flag for the failed-attachment SQL clause.
+ *
+ * @since 1.3.0
+ * @var string
+ */
+ private const FAILED_QUERY_VAR = 'wzio_filter_failed';
/**
* Constructor.
@@ -37,6 +53,9 @@ public function __construct() {
Hook_Registry::add_filter( 'media_row_actions', array( $this, 'add_row_actions' ), 10, 2 );
Hook_Registry::add_filter( 'bulk_actions-upload', array( $this, 'add_bulk_actions' ) );
Hook_Registry::add_filter( 'handle_bulk_actions-upload', array( $this, 'handle_bulk_restore' ), 10, 3 );
+ Hook_Registry::add_action( 'restrict_manage_posts', array( $this, 'render_status_filter' ), 10, 2 );
+ Hook_Registry::add_action( 'pre_get_posts', array( $this, 'filter_by_status' ) );
+ Hook_Registry::add_filter( 'posts_where', array( $this, 'filter_failed_attachments' ), 10, 2 );
Hook_Registry::add_action( 'admin_post_wzio_optimize_attachment', array( $this, 'handle_optimize' ) );
Hook_Registry::add_action( 'admin_post_wzio_restore_attachment', array( $this, 'handle_restore' ) );
Hook_Registry::add_action( 'wp_ajax_wzio_optimize_attachment', array( $this, 'ajax_optimize' ) );
@@ -152,6 +171,149 @@ public function add_column( $columns ) {
return $columns;
}
+ /**
+ * Add the optimization status filter to the media list table.
+ *
+ * @since 1.3.0
+ *
+ * @param string $post_type Current post type.
+ * @param string $which Location of the filter controls.
+ * @return void
+ */
+ public function render_status_filter( $post_type, $which ): void {
+ if ( 'attachment' !== $post_type || 'bar' !== $which || ! self::can_view_status_filter() ) {
+ return;
+ }
+
+ $selected = self::get_requested_status();
+ $options = array(
+ '' => __( 'All optimization statuses', 'webberzone-image-optimizer' ),
+ 'optimized' => __( 'Optimized', 'webberzone-image-optimizer' ),
+ 'unoptimized' => __( 'Not yet optimized', 'webberzone-image-optimizer' ),
+ 'failed' => __( 'Failed', 'webberzone-image-optimizer' ),
+ );
+ ?>
+
+
+ is_main_query()
+ || 'attachment' !== $query->get( 'post_type' )
+ || ! self::can_view_status_filter()
+ ) {
+ return;
+ }
+
+ $status = self::get_requested_status();
+
+ if ( 'failed' === $status ) {
+ $query->set( self::FAILED_QUERY_VAR, true );
+ return;
+ }
+
+ if ( 'optimized' !== $status && 'unoptimized' !== $status ) {
+ return;
+ }
+
+ $meta_clause = array(
+ 'key' => Attachment_Meta::META_KEY,
+ 'compare' => 'optimized' === $status ? 'EXISTS' : 'NOT EXISTS',
+ );
+ $meta_query = $query->get( 'meta_query' );
+
+ if ( ! is_array( $meta_query ) || empty( $meta_query ) ) {
+ $query->set( 'meta_query', array( $meta_clause ) );
+ return;
+ }
+
+ $query->set(
+ 'meta_query',
+ array(
+ 'relation' => 'AND',
+ $meta_query,
+ $meta_clause,
+ )
+ );
+ }
+
+ /**
+ * Limit a flagged media query to attachments in the failed queue state.
+ *
+ * @since 1.3.0
+ *
+ * @param string $where Current WHERE clause.
+ * @param \WP_Query $query Current query.
+ * @return string Filtered WHERE clause.
+ */
+ public function filter_failed_attachments( $where, $query ) {
+ global $wpdb;
+
+ if ( true !== $query->get( self::FAILED_QUERY_VAR ) ) {
+ return $where;
+ }
+
+ return $where . $wpdb->prepare(
+ ' AND %i.ID IN (SELECT attachment_id FROM %i WHERE status = %s)',
+ $wpdb->posts,
+ Database::get_table(),
+ Queue::FAILED
+ );
+ }
+
+ /**
+ * Whether the current user can use the status filter on this screen.
+ *
+ * @since 1.3.0
+ *
+ * @return bool True when the optimization column is visible.
+ */
+ private static function can_view_status_filter(): bool {
+ $screen = get_current_screen();
+
+ return current_user_can( 'upload_files' )
+ && $screen
+ && 'upload' === $screen->id
+ && ! in_array( 'wzio', get_hidden_columns( $screen ), true );
+ }
+
+ /**
+ * Get a valid requested optimization status.
+ *
+ * @since 1.3.0
+ *
+ * @return string Status, or an empty string when none is selected.
+ */
+ private static function get_requested_status(): string {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
+ $status = isset( $_GET[ self::STATUS_FILTER ] ) && is_string( $_GET[ self::STATUS_FILTER ] )
+ ? sanitize_key( wp_unslash( $_GET[ self::STATUS_FILTER ] ) )
+ : '';
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
+
+ return in_array( $status, array( 'optimized', 'unoptimized', 'failed' ), true ) ? $status : '';
+ }
+
/**
* Render the status column.
*
diff --git a/phpunit/tests/MediaLibraryTest.php b/phpunit/tests/MediaLibraryTest.php
new file mode 100644
index 0000000..ecc6fca
--- /dev/null
+++ b/phpunit/tests/MediaLibraryTest.php
@@ -0,0 +1,175 @@
+media_library = $reflection->newInstanceWithoutConstructor();
+
+ $this->original_pagenow = $GLOBALS['pagenow'] ?? null;
+ $this->original_main_query = $GLOBALS['wp_the_query'] ?? null;
+
+ $GLOBALS['pagenow'] = 'upload.php';
+ wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
+ set_current_screen( 'upload' );
+ }
+
+ /**
+ * Restore globals changed by a test.
+ */
+ public function tear_down() {
+ unset( $_GET['wzio_optimization_status'] );
+ delete_user_option( get_current_user_id(), 'manageuploadcolumnshidden' );
+
+ if ( null === $this->original_pagenow ) {
+ unset( $GLOBALS['pagenow'] );
+ } else {
+ $GLOBALS['pagenow'] = $this->original_pagenow;
+ }
+
+ $GLOBALS['wp_the_query'] = $this->original_main_query;
+
+ set_current_screen( 'front' );
+ wp_set_current_user( 0 );
+
+ parent::tear_down();
+ }
+
+ /**
+ * The list view renders every status and preserves the selection.
+ */
+ public function test_status_dropdown_renders_on_the_media_list_view() {
+ $_GET['wzio_optimization_status'] = 'failed';
+
+ ob_start();
+ $this->media_library->render_status_filter( 'attachment', 'bar' );
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'name="wzio_optimization_status"', $output );
+ $this->assertStringContainsString( 'All optimization statuses', $output );
+ $this->assertStringContainsString( 'Not yet optimized', $output );
+ $this->assertStringContainsString( 'value="failed" selected', $output );
+ }
+
+ /**
+ * The dropdown stays hidden when its status column is hidden.
+ */
+ public function test_status_dropdown_requires_a_visible_status_column() {
+ update_user_option( get_current_user_id(), 'manageuploadcolumnshidden', array( 'wzio' ) );
+
+ ob_start();
+ $this->media_library->render_status_filter( 'attachment', 'bar' );
+ $output = ob_get_clean();
+
+ $this->assertSame( '', $output );
+ }
+
+ /**
+ * Optimized filtering is added without replacing another meta query.
+ */
+ public function test_optimized_filter_composes_with_existing_query_filters() {
+ $existing_meta_query = array(
+ array(
+ 'key' => '_example_key',
+ 'value' => 'example-value',
+ ),
+ );
+ $query = $this->get_filtered_query( 'optimized', $existing_meta_query );
+ $meta_query = $query->get( 'meta_query' );
+
+ $this->assertSame( 'AND', $meta_query['relation'] );
+ $this->assertSame( $existing_meta_query, $meta_query[0] );
+ $this->assertSame( Attachment_Meta::META_KEY, $meta_query[1]['key'] );
+ $this->assertSame( 'EXISTS', $meta_query[1]['compare'] );
+ $this->assertSame( 'image/jpeg', $query->get( 'post_mime_type' ) );
+ $this->assertSame( 202601, $query->get( 'm' ) );
+ }
+
+ /**
+ * Unoptimized filtering selects attachments without conversion metadata.
+ */
+ public function test_unoptimized_filter_uses_a_not_exists_meta_query() {
+ $query = $this->get_filtered_query( 'unoptimized' );
+ $meta_query = $query->get( 'meta_query' );
+
+ $this->assertSame( Attachment_Meta::META_KEY, $meta_query[0]['key'] );
+ $this->assertSame( 'NOT EXISTS', $meta_query[0]['compare'] );
+ }
+
+ /**
+ * Failed filtering adds a queue-table subquery to the existing WHERE clause.
+ */
+ public function test_failed_filter_uses_the_queue_table() {
+ $query = $this->get_filtered_query( 'failed' );
+ $where = $this->media_library->filter_failed_attachments( ' WHERE 1=1', $query );
+
+ $this->assertStringStartsWith( ' WHERE 1=1 AND ', $where );
+ $this->assertStringContainsString( Database::get_table(), $where );
+ $this->assertStringContainsString( 'attachment_id', $where );
+ $this->assertStringContainsString( "status = 'failed'", $where );
+ }
+
+ /**
+ * Build a main media query and apply a requested status.
+ *
+ * @param string $status Requested status.
+ * @param array $meta_query Existing meta query.
+ * @return WP_Query Filtered query.
+ */
+ private function get_filtered_query( $status, $meta_query = array() ) {
+ $_GET['wzio_optimization_status'] = $status;
+
+ $query = new WP_Query();
+ $query->set( 'post_type', 'attachment' );
+ $query->set( 'post_mime_type', 'image/jpeg' );
+ $query->set( 'm', 202601 );
+
+ if ( ! empty( $meta_query ) ) {
+ $query->set( 'meta_query', $meta_query );
+ }
+
+ $GLOBALS['wp_the_query'] = $query;
+
+ $this->media_library->filter_by_status( $query );
+
+ return $query;
+ }
+}
From 8a9215311d6afa173e082ea0ebbdf1178c5fe1df Mon Sep 17 00:00:00 2001
From: Muneeb Ashraf <58017595+muneeb-ashraf@users.noreply.github.com>
Date: Fri, 4 Sep 2026 00:59:12 +0500
Subject: [PATCH 2/3] Address media status filter review
---
includes/admin/class-media-library.php | 90 ++++++-------
phpunit/tests/MediaLibraryTest.php | 170 ++++++++++++++++++-------
readme.txt | 5 +
3 files changed, 179 insertions(+), 86 deletions(-)
diff --git a/includes/admin/class-media-library.php b/includes/admin/class-media-library.php
index 3f0b706..f10b27d 100644
--- a/includes/admin/class-media-library.php
+++ b/includes/admin/class-media-library.php
@@ -29,18 +29,18 @@ class Media_Library {
/**
* Query-string key for the optimization status filter.
*
- * @since 1.3.0
+ * @since 1.1.0
* @var string
*/
private const STATUS_FILTER = 'wzio_optimization_status';
/**
- * Internal query flag for the failed-attachment SQL clause.
+ * Internal query variable for the requested optimization status.
*
- * @since 1.3.0
+ * @since 1.1.0
* @var string
*/
- private const FAILED_QUERY_VAR = 'wzio_filter_failed';
+ private const STATUS_QUERY_VAR = 'wzio_filter_status';
/**
* Constructor.
@@ -55,7 +55,7 @@ public function __construct() {
Hook_Registry::add_filter( 'handle_bulk_actions-upload', array( $this, 'handle_bulk_restore' ), 10, 3 );
Hook_Registry::add_action( 'restrict_manage_posts', array( $this, 'render_status_filter' ), 10, 2 );
Hook_Registry::add_action( 'pre_get_posts', array( $this, 'filter_by_status' ) );
- Hook_Registry::add_filter( 'posts_where', array( $this, 'filter_failed_attachments' ), 10, 2 );
+ Hook_Registry::add_filter( 'posts_where', array( $this, 'filter_attachments_by_status' ), 10, 2 );
Hook_Registry::add_action( 'admin_post_wzio_optimize_attachment', array( $this, 'handle_optimize' ) );
Hook_Registry::add_action( 'admin_post_wzio_restore_attachment', array( $this, 'handle_restore' ) );
Hook_Registry::add_action( 'wp_ajax_wzio_optimize_attachment', array( $this, 'ajax_optimize' ) );
@@ -174,7 +174,7 @@ public function add_column( $columns ) {
/**
* Add the optimization status filter to the media list table.
*
- * @since 1.3.0
+ * @since 1.1.0
*
* @param string $post_type Current post type.
* @param string $which Location of the filter controls.
@@ -190,6 +190,7 @@ public function render_status_filter( $post_type, $which ): void {
'' => __( 'All optimization statuses', 'webberzone-image-optimizer' ),
'optimized' => __( 'Optimized', 'webberzone-image-optimizer' ),
'unoptimized' => __( 'Not yet optimized', 'webberzone-image-optimizer' ),
+ 'skipped' => __( 'Skipped', 'webberzone-image-optimizer' ),
'failed' => __( 'Failed', 'webberzone-image-optimizer' ),
);
?>
@@ -207,7 +208,7 @@ public function render_status_filter( $post_type, $which ): void {
/**
* Filter the media library query by optimization status.
*
- * @since 1.3.0
+ * @since 1.1.0
*
* @param \WP_Query $query Current query.
* @return void
@@ -220,71 +221,74 @@ public function filter_by_status( $query ): void {
|| 'upload.php' !== $pagenow
|| ! $query->is_main_query()
|| 'attachment' !== $query->get( 'post_type' )
- || ! self::can_view_status_filter()
+ || ! current_user_can( 'upload_files' )
) {
return;
}
$status = self::get_requested_status();
- if ( 'failed' === $status ) {
- $query->set( self::FAILED_QUERY_VAR, true );
- return;
- }
-
- if ( 'optimized' !== $status && 'unoptimized' !== $status ) {
- return;
+ if ( '' !== $status ) {
+ $query->set( self::STATUS_QUERY_VAR, $status );
}
-
- $meta_clause = array(
- 'key' => Attachment_Meta::META_KEY,
- 'compare' => 'optimized' === $status ? 'EXISTS' : 'NOT EXISTS',
- );
- $meta_query = $query->get( 'meta_query' );
-
- if ( ! is_array( $meta_query ) || empty( $meta_query ) ) {
- $query->set( 'meta_query', array( $meta_clause ) );
- return;
- }
-
- $query->set(
- 'meta_query',
- array(
- 'relation' => 'AND',
- $meta_query,
- $meta_clause,
- )
- );
}
/**
- * Limit a flagged media query to attachments in the failed queue state.
+ * Limit a flagged media query to eligible attachments in the requested queue state.
*
- * @since 1.3.0
+ * @since 1.1.0
*
* @param string $where Current WHERE clause.
* @param \WP_Query $query Current query.
* @return string Filtered WHERE clause.
*/
- public function filter_failed_attachments( $where, $query ) {
+ public function filter_attachments_by_status( $where, $query ) {
global $wpdb;
- if ( true !== $query->get( self::FAILED_QUERY_VAR ) ) {
+ $status = $query->get( self::STATUS_QUERY_VAR );
+
+ if ( ! is_string( $status ) || ! in_array( $status, array( 'optimized', 'unoptimized', 'skipped', 'failed' ), true ) ) {
return $where;
}
+ $mimes = Helpers::SOURCE_MIME_TYPES;
+ $mime_placeholders = implode( ',', array_fill( 0, count( $mimes ), '%s' ) );
+
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
+ $where .= $wpdb->prepare(
+ " AND %i.post_mime_type IN ({$mime_placeholders})",
+ array_merge( array( $wpdb->posts ), $mimes )
+ );
+
+ if ( 'unoptimized' === $status ) {
+ $terminal_statuses = array( Queue::DONE, Queue::SKIPPED, Queue::FAILED );
+ $placeholders = implode( ',', array_fill( 0, count( $terminal_statuses ), '%s' ) );
+
+ return $where . $wpdb->prepare(
+ " AND %i.ID NOT IN (SELECT attachment_id FROM %i WHERE status IN ({$placeholders}))",
+ array_merge( array( $wpdb->posts, Database::get_table() ), $terminal_statuses )
+ );
+ }
+
+ $queue_status = array(
+ 'optimized' => Queue::DONE,
+ 'skipped' => Queue::SKIPPED,
+ 'failed' => Queue::FAILED,
+ )[ $status ];
+
return $where . $wpdb->prepare(
' AND %i.ID IN (SELECT attachment_id FROM %i WHERE status = %s)',
$wpdb->posts,
Database::get_table(),
- Queue::FAILED
+ $queue_status
);
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
}
/**
* Whether the current user can use the status filter on this screen.
*
- * @since 1.3.0
+ * @since 1.1.0
*
* @return bool True when the optimization column is visible.
*/
@@ -300,7 +304,7 @@ private static function can_view_status_filter(): bool {
/**
* Get a valid requested optimization status.
*
- * @since 1.3.0
+ * @since 1.1.0
*
* @return string Status, or an empty string when none is selected.
*/
@@ -311,7 +315,7 @@ private static function get_requested_status(): string {
: '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
- return in_array( $status, array( 'optimized', 'unoptimized', 'failed' ), true ) ? $status : '';
+ return in_array( $status, array( 'optimized', 'unoptimized', 'skipped', 'failed' ), true ) ? $status : '';
}
/**
diff --git a/phpunit/tests/MediaLibraryTest.php b/phpunit/tests/MediaLibraryTest.php
index ecc6fca..bb5508c 100644
--- a/phpunit/tests/MediaLibraryTest.php
+++ b/phpunit/tests/MediaLibraryTest.php
@@ -6,8 +6,8 @@
*/
use WebberZone\Image_Optimizer\Admin\Media_Library;
-use WebberZone\Image_Optimizer\Attachment_Meta;
use WebberZone\Image_Optimizer\Database;
+use WebberZone\Image_Optimizer\Queue;
/**
* Media library filtering controls and query clauses.
@@ -36,11 +36,14 @@ class MediaLibraryTest extends WP_UnitTestCase {
private $original_main_query;
/**
- * Prepare an upload screen and a media library instance without hooks.
+ * Prepare an upload screen, queue table and media library instance without hooks.
*/
public function set_up() {
parent::set_up();
+ Database::install();
+ Queue::clear();
+
$reflection = new ReflectionClass( Media_Library::class );
$this->media_library = $reflection->newInstanceWithoutConstructor();
@@ -58,6 +61,7 @@ public function set_up() {
public function tear_down() {
unset( $_GET['wzio_optimization_status'] );
delete_user_option( get_current_user_id(), 'manageuploadcolumnshidden' );
+ Queue::clear();
if ( null === $this->original_pagenow ) {
unset( $GLOBALS['pagenow'] );
@@ -86,6 +90,7 @@ public function test_status_dropdown_renders_on_the_media_list_view() {
$this->assertStringContainsString( 'name="wzio_optimization_status"', $output );
$this->assertStringContainsString( 'All optimization statuses', $output );
$this->assertStringContainsString( 'Not yet optimized', $output );
+ $this->assertStringContainsString( 'value="skipped"', $output );
$this->assertStringContainsString( 'value="failed" selected', $output );
}
@@ -103,73 +108,152 @@ public function test_status_dropdown_requires_a_visible_status_column() {
}
/**
- * Optimized filtering is added without replacing another meta query.
+ * Every status returns the matching eligible attachments and preserves other filters.
*/
- public function test_optimized_filter_composes_with_existing_query_filters() {
- $existing_meta_query = array(
- array(
- 'key' => '_example_key',
- 'value' => 'example-value',
- ),
+ public function test_status_filters_return_exact_attachment_ids() {
+ $attachments = $this->seed_attachments();
+
+ $this->assertSame(
+ array( $attachments['optimized'] ),
+ $this->get_filtered_attachment_ids( 'optimized' )
+ );
+ $this->assertSame(
+ array( $attachments['unoptimized'], $attachments['pending'], $attachments['processing'] ),
+ $this->get_filtered_attachment_ids( 'unoptimized' )
+ );
+ $this->assertSame(
+ array( $attachments['skipped'] ),
+ $this->get_filtered_attachment_ids( 'skipped' )
+ );
+ $this->assertSame(
+ array( $attachments['failed'] ),
+ $this->get_filtered_attachment_ids( 'failed' )
);
- $query = $this->get_filtered_query( 'optimized', $existing_meta_query );
- $meta_query = $query->get( 'meta_query' );
-
- $this->assertSame( 'AND', $meta_query['relation'] );
- $this->assertSame( $existing_meta_query, $meta_query[0] );
- $this->assertSame( Attachment_Meta::META_KEY, $meta_query[1]['key'] );
- $this->assertSame( 'EXISTS', $meta_query[1]['compare'] );
- $this->assertSame( 'image/jpeg', $query->get( 'post_mime_type' ) );
- $this->assertSame( 202601, $query->get( 'm' ) );
}
/**
- * Unoptimized filtering selects attachments without conversion metadata.
+ * Hiding the status column does not disable an active URL filter.
*/
- public function test_unoptimized_filter_uses_a_not_exists_meta_query() {
- $query = $this->get_filtered_query( 'unoptimized' );
- $meta_query = $query->get( 'meta_query' );
+ public function test_hidden_status_column_does_not_disable_filtering() {
+ $attachments = $this->seed_attachments();
+
+ update_user_option( get_current_user_id(), 'manageuploadcolumnshidden', array( 'wzio' ) );
- $this->assertSame( Attachment_Meta::META_KEY, $meta_query[0]['key'] );
- $this->assertSame( 'NOT EXISTS', $meta_query[0]['compare'] );
+ $this->assertSame(
+ array( $attachments['failed'] ),
+ $this->get_filtered_attachment_ids( 'failed' )
+ );
}
/**
- * Failed filtering adds a queue-table subquery to the existing WHERE clause.
+ * Seed eligible and unsupported attachments across every queue state.
+ *
+ * @return array Attachment IDs keyed by scenario.
*/
- public function test_failed_filter_uses_the_queue_table() {
- $query = $this->get_filtered_query( 'failed' );
- $where = $this->media_library->filter_failed_attachments( ' WHERE 1=1', $query );
-
- $this->assertStringStartsWith( ' WHERE 1=1 AND ', $where );
- $this->assertStringContainsString( Database::get_table(), $where );
- $this->assertStringContainsString( 'attachment_id', $where );
- $this->assertStringContainsString( "status = 'failed'", $where );
+ private function seed_attachments(): array {
+ $attachments = array(
+ 'optimized' => $this->create_attachment( 'optimized.jpg', 'image/jpeg' ),
+ 'unoptimized' => $this->create_attachment( 'unoptimized.jpg', 'image/jpeg' ),
+ 'skipped' => $this->create_attachment( 'skipped.jpg', 'image/jpeg' ),
+ 'failed' => $this->create_attachment( 'failed.jpg', 'image/jpeg' ),
+ 'pending' => $this->create_attachment( 'pending.jpg', 'image/jpeg' ),
+ 'processing' => $this->create_attachment( 'processing.jpg', 'image/jpeg' ),
+ 'unsupported' => $this->create_attachment( 'unsupported.pdf', 'application/pdf' ),
+ 'optimized_png' => $this->create_attachment( 'optimized.png', 'image/png' ),
+ 'optimized_next_month' => $this->create_attachment( 'optimized-next-month.jpg', 'image/jpeg', '2026-02-15 12:00:00' ),
+ );
+
+ $this->set_queue_status( $attachments['optimized'], Queue::DONE );
+ $this->set_queue_status( $attachments['skipped'], Queue::SKIPPED );
+ $this->set_queue_status( $attachments['failed'], Queue::FAILED );
+ $this->set_queue_status( $attachments['pending'], Queue::PENDING );
+ $this->set_queue_status( $attachments['processing'], Queue::PROCESSING );
+ $this->set_queue_status( $attachments['optimized_png'], Queue::DONE );
+ $this->set_queue_status( $attachments['optimized_next_month'], Queue::DONE );
+
+ return $attachments;
}
/**
- * Build a main media query and apply a requested status.
+ * Create an attachment fixture.
*
- * @param string $status Requested status.
- * @param array $meta_query Existing meta query.
- * @return WP_Query Filtered query.
+ * @param string $file Fixture filename.
+ * @param string $mime_type Attachment MIME type.
+ * @param string $date Attachment date.
+ * @return int Attachment ID.
*/
- private function get_filtered_query( $status, $meta_query = array() ) {
+ private function create_attachment( string $file, string $mime_type, string $date = '2026-01-15 12:00:00' ): int {
+ return (int) self::factory()->attachment->create(
+ array(
+ 'file' => $file,
+ 'post_title' => $file,
+ 'post_mime_type' => $mime_type,
+ 'post_status' => 'inherit',
+ 'post_date' => $date,
+ 'post_date_gmt' => $date,
+ )
+ );
+ }
+
+ /**
+ * Put an attachment into a queue state using the public queue API.
+ *
+ * @param int $attachment_id Attachment ID.
+ * @param string $status Queue status.
+ * @return void
+ */
+ private function set_queue_status( int $attachment_id, string $status ): void {
+ Queue::add( array( $attachment_id ), true );
+
+ if ( Queue::PROCESSING === $status ) {
+ Queue::claim_attachment( $attachment_id );
+ } elseif ( Queue::PENDING !== $status ) {
+ $row_id = Queue::get_id( $attachment_id );
+
+ if ( Queue::FAILED === $status ) {
+ for ( $attempt = 0; $attempt < Queue::MAX_ATTEMPTS; ++$attempt ) {
+ Queue::complete( $row_id, Queue::FAILED );
+ }
+ } else {
+ Queue::complete( $row_id, $status );
+ }
+ }
+
+ $this->assertSame( $status, Queue::get_status( $attachment_id ) );
+ }
+
+ /**
+ * Run a main media query with the requested status and existing MIME/date filters.
+ *
+ * @param string $status Requested status.
+ * @return array Matching attachment IDs.
+ */
+ private function get_filtered_attachment_ids( string $status ): array {
$_GET['wzio_optimization_status'] = $status;
$query = new WP_Query();
$query->set( 'post_type', 'attachment' );
+ $query->set( 'post_status', 'inherit' );
$query->set( 'post_mime_type', 'image/jpeg' );
$query->set( 'm', 202601 );
-
- if ( ! empty( $meta_query ) ) {
- $query->set( 'meta_query', $meta_query );
- }
+ $query->set( 'fields', 'ids' );
+ $query->set( 'orderby', 'ID' );
+ $query->set( 'order', 'ASC' );
+ $query->set( 'posts_per_page', -1 );
+ $query->set( 'no_found_rows', true );
+ $query->set( 'suppress_filters', false );
$GLOBALS['wp_the_query'] = $query;
$this->media_library->filter_by_status( $query );
+ add_filter( 'posts_where', array( $this->media_library, 'filter_attachments_by_status' ), 10, 2 );
+
+ try {
+ $attachment_ids = $query->get_posts();
+ } finally {
+ remove_filter( 'posts_where', array( $this->media_library, 'filter_attachments_by_status' ), 10 );
+ }
- return $query;
+ return array_map( 'intval', $attachment_ids );
}
}
diff --git a/readme.txt b/readme.txt
index dd21ef0..44a359d 100644
--- a/readme.txt
+++ b/readme.txt
@@ -108,6 +108,11 @@ Yes. Because the format choice happens in the browser rather than on the server,
== Changelog ==
+= 1.1.0 =
+
+* Features:
+ * Filter Media Library images by optimized, not-yet-optimized, skipped and failed status.
+
= 1.0.2 =
Release date: 27 August 2026
From c571f51548cf588bd562c8203209e338cc950cfa Mon Sep 17 00:00:00 2001
From: Muneeb Ashraf <58017595+muneeb-ashraf@users.noreply.github.com>
Date: Fri, 4 Sep 2026 19:43:47 +0500
Subject: [PATCH 3/3] Fix media library test setup
---
phpunit/tests/MediaLibraryTest.php | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/phpunit/tests/MediaLibraryTest.php b/phpunit/tests/MediaLibraryTest.php
index bb5508c..d6432d2 100644
--- a/phpunit/tests/MediaLibraryTest.php
+++ b/phpunit/tests/MediaLibraryTest.php
@@ -14,6 +14,24 @@
*/
class MediaLibraryTest extends WP_UnitTestCase {
+ /**
+ * Install the queue table outside the per-test database transaction.
+ */
+ public static function set_up_before_class() {
+ parent::set_up_before_class();
+
+ Database::install();
+ }
+
+ /**
+ * Remove the queue table after all tests in this class have run.
+ */
+ public static function tear_down_after_class() {
+ Database::drop_table();
+
+ parent::tear_down_after_class();
+ }
+
/**
* Media library integration under test.
*
@@ -41,7 +59,6 @@ class MediaLibraryTest extends WP_UnitTestCase {
public function set_up() {
parent::set_up();
- Database::install();
Queue::clear();
$reflection = new ReflectionClass( Media_Library::class );
@@ -91,7 +108,7 @@ public function test_status_dropdown_renders_on_the_media_list_view() {
$this->assertStringContainsString( 'All optimization statuses', $output );
$this->assertStringContainsString( 'Not yet optimized', $output );
$this->assertStringContainsString( 'value="skipped"', $output );
- $this->assertStringContainsString( 'value="failed" selected', $output );
+ $this->assertStringContainsString( 'value="failed" selected=\'selected\'', $output );
}
/**