| @@ -4,8 +4,10 @@ | ||
| 4 | 4 | namespace Imagify\Media; |
| 5 | 5 | |
| 6 | 6 | use Imagify\EventManagement\SubscriberInterface; |
| 7 | 7 | use Imagify\Media\Upload\Upload; |
| 8 | +use Imagify\Optimization\NextGenMarker; | |
| 9 | +use WP_Query; | |
| 8 | 10 | |
| 9 | 11 | /** |
| 10 | 12 | * Media Subscriber |
| 11 | 13 | */ |
| @@ -34,8 +36,10 @@ | ||
| 34 | 36 | public static function get_subscribed_events(): array { |
| 35 | 37 | return [ |
| 36 | 38 | // @action |
| 37 | 39 | 'restrict_manage_posts' => 'imagify_attachments_filter_dropdown', |
| 40 | + // @action | |
| 41 | + 'pre_get_posts' => 'filter_missing_nextgen_query', | |
| 38 | 42 | ]; |
| 39 | 43 | } |
| 40 | 44 | |
| 41 | 45 | /** |
| @@ -47,6 +51,139 @@ | ||
| 47 | 51 | if ( ! \Imagify_Views::get_instance()->is_wp_library_page() ) { |
| 48 | 52 | return; |
| 49 | 53 | } |
| 50 | 54 | $this->upload->add_imagify_filter_to_attachments_dropdown(); |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * Narrows the Media Library list table (list mode only, `WP_Query`-based) to attachments that | |
| 59 | + * are optimized but have no successful next-gen size recorded, when | |
| 60 | + * `?imagify-status=missing-nextgen` is requested. | |
| 61 | + * | |
| 62 | + * The predicate mirrors `Imagify\Bulk\WP::get_optimized_media_ids_without_format()` on purpose: | |
| 63 | + * the list and the "missing next-gen" count it feeds must never disagree. | |
| 64 | + * | |
| 65 | + * @param WP_Query $query The current query, by reference. | |
| 66 | + * | |
| 67 | + * @return void | |
| 68 | + */ | |
| 69 | + public function filter_missing_nextgen_query( WP_Query $query ) { | |
| 70 | + if ( ! is_admin() || ! $query->is_main_query() ) { | |
| 71 | + return; | |
| 72 | + } | |
| 73 | + | |
| 74 | + if ( ! \Imagify_Views::get_instance()->is_wp_library_page() ) { | |
| 75 | + return; | |
| 76 | + } | |
| 77 | + | |
| 78 | + $status = isset( $_GET['imagify-status'] ) ? sanitize_text_field( wp_unslash( $_GET['imagify-status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 79 | + | |
| 80 | + if ( 'missing-nextgen' !== $status ) { | |
| 81 | + return; | |
| 82 | + } | |
| 83 | + | |
| 84 | + $formats = imagify_nextgen_images_formats(); | |
| 85 | + | |
| 86 | + if ( empty( $formats ) ) { | |
| 87 | + // No next-gen format is enabled: nothing can be "missing" a format that isn't | |
| 88 | + // generated in the first place. Show no results rather than the whole library. | |
| 89 | + $query->set( 'post__in', [ 0 ] ); | |
| 90 | + return; | |
| 91 | + } | |
| 92 | + | |
| 93 | + $format = $this->get_enabled_format( $formats ); | |
| 94 | + $nextgen_suffix = $this->get_nextgen_suffix( $format ); | |
| 95 | + | |
| 96 | + if ( '' === $nextgen_suffix ) { | |
| 97 | + // The enabled format is not one we know how to detect: showing the whole library | |
| 98 | + // would be worse than showing nothing. | |
| 99 | + $query->set( 'post__in', [ 0 ] ); | |
| 100 | + return; | |
| 101 | + } | |
| 102 | + | |
| 103 | + $query->set( | |
| 104 | + 'meta_query', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query | |
| 105 | + [ | |
| 106 | + 'relation' => 'AND', | |
| 107 | + [ | |
| 108 | + 'key' => '_imagify_status', | |
| 109 | + 'value' => [ 'success', 'already_optimized' ], | |
| 110 | + 'compare' => 'IN', | |
| 111 | + ], | |
| 112 | + [ | |
| 113 | + 'key' => '_imagify_data', | |
| 114 | + 'value' => NextGenMarker::success( $nextgen_suffix ), | |
| 115 | + 'compare' => 'NOT LIKE', | |
| 116 | + ], | |
| 117 | + [ | |
| 118 | + 'key' => '_imagify_data', | |
| 119 | + 'value' => NextGenMarker::permanent_error( $nextgen_suffix ), | |
| 120 | + 'compare' => 'NOT LIKE', | |
| 121 | + ], | |
| 122 | + ] | |
| 123 | + ); | |
| 124 | + | |
| 125 | + if ( ! $query->get( 'post_mime_type' ) ) { | |
| 126 | + $query->set( 'post_mime_type', $this->get_convertible_mime_types( $format ) ); | |
| 127 | + } | |
| 128 | + } | |
| 129 | + | |
| 130 | + /** | |
| 131 | + * Reads the first enabled next-gen format. | |
| 132 | + * | |
| 133 | + * `imagify_nextgen_images_formats()` is filterable by third parties, so the array may come | |
| 134 | + * back as a list (`[ 'webp' ]`) rather than the keyed shape core builds (`[ 'webp' => 'webp' ]`). | |
| 135 | + * Reading the key would then yield the integer `0` — which is what made `strtoupper()` fatal | |
| 136 | + * on PHP 8 — so the value is read instead, and anything that is not a string is discarded. | |
| 137 | + * | |
| 138 | + * @param array $formats The enabled next-gen formats. | |
| 139 | + * | |
| 140 | + * @return string The format in lowercase, or an empty string if it is unusable. | |
| 141 | + */ | |
| 142 | + private function get_enabled_format( array $formats ): string { | |
| 143 | + $format = current( $formats ); | |
| 144 | + | |
| 145 | + return is_string( $format ) ? strtolower( $format ) : ''; | |
| 146 | + } | |
| 147 | + | |
| 148 | + /** | |
| 149 | + * Resolves the next-gen suffix (e.g. `@imagify-webp`) for the given format. | |
| 150 | + * | |
| 151 | + * An unrecognised format resolves to an empty string rather than raising an error, since the | |
| 152 | + * format ultimately comes from a public filter. | |
| 153 | + * | |
| 154 | + * @param string $format The next-gen format, lowercase. | |
| 155 | + * | |
| 156 | + * @return string The suffix, or an empty string if the format is not recognised. | |
| 157 | + */ | |
| 158 | + private function get_nextgen_suffix( string $format ): string { | |
| 159 | + $process_class = imagify_get_optimization_process_class_name( 'wp' ); | |
| 160 | + $suffixes = [ | |
| 161 | + 'avif' => constant( $process_class . '::AVIF_SUFFIX' ), | |
| 162 | + 'webp' => constant( $process_class . '::WEBP_SUFFIX' ), | |
| 163 | + ]; | |
| 164 | + | |
| 165 | + return isset( $suffixes[ $format ] ) ? $suffixes[ $format ] : ''; | |
| 166 | + } | |
| 167 | + | |
| 168 | + /** | |
| 169 | + * Lists the mime types that can hold a next-gen version of the given format. | |
| 170 | + * | |
| 171 | + * Mirrors `Imagify\Bulk\WP::get_optimized_media_ids_without_format()`: images only — a PDF has | |
| 172 | + * no next-gen version, so it can never be "missing" one — minus the target format's own mime | |
| 173 | + * type, since an image that already *is* WebP is not missing a WebP version. | |
| 174 | + * | |
| 175 | + * @param string $format The next-gen format being looked for, e.g. `webp`. | |
| 176 | + * | |
| 177 | + * @return array The mime types, as extension => mime type pairs. | |
| 178 | + */ | |
| 179 | + private function get_convertible_mime_types( string $format ): array { | |
| 180 | + $mime_types = imagify_get_mime_types( 'image' ); | |
| 181 | + | |
| 182 | + return array_filter( | |
| 183 | + $mime_types, | |
| 184 | + function ( $mime ) use ( $format ) { | |
| 185 | + return 'image/' . strtolower( $format ) !== $mime; | |
| 186 | + } | |
| 187 | + ); | |
| 51 | 188 | } |
| 52 | 189 | } |