| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Desktop Mode — Media dimension filtering for REST. | |
| 3 | + * OpenStation — Media dimension filtering for REST. | |
| 4 | 4 | * |
| 5 | 5 | * Adds two opt-in query parameters to `/wp/v2/media`: |
| 6 | 6 | * |
| 7 | - * - `desktop_mode_min_width` — only return images at least this many pixels wide. | |
| 8 | - * - `desktop_mode_min_height` — only return images at least this many pixels tall. | |
| 7 | + * - `openstation_min_width` — only return images at least this many pixels wide. | |
| 8 | + * - `openstation_min_height` — only return images at least this many pixels tall. | |
| 9 | 9 | * |
| 10 | 10 | * The OS Settings wallpaper picker uses these to keep a site with thousands |
| 11 | 11 | * of small product images from burying the handful of desktop-worthy HD |
| 12 | 12 | * shots under an infinite-scroll slog. The client still applies the same |
| @@ -18,23 +18,45 @@ | ||
| 18 | 18 | * reliably. We stamp two flat numeric meta keys on every new attachment |
| 19 | 19 | * (and opportunistically backfill existing ones) so `WP_Meta_Query` can |
| 20 | 20 | * use a normal NUMERIC `>=` comparison. |
| 21 | 21 | * |
| 22 | - * @package WPDesktopMode | |
| 23 | - * @since 0.5.0 | |
| 22 | + * @package OpenStation | |
| 24 | 23 | */ |
| 25 | 24 | |
| 26 | 25 | defined( 'ABSPATH' ) || exit; |
| 27 | 26 | |
| 28 | -/** Numeric post-meta keys stamped on every image attachment. */ | |
| 29 | -const DESKTOP_MODE_META_WIDTH = '_desktop_mode_width'; | |
| 30 | -const DESKTOP_MODE_META_HEIGHT = '_desktop_mode_height'; | |
| 27 | +/** | |
| 28 | + * Numeric post-meta keys stamped on every image attachment. | |
| 29 | + * | |
| 30 | + * The VALUE keeps its pre-rebrand spelling on purpose: it is a | |
| 31 | + * persisted or externally-visible identifier, so renaming it would | |
| 32 | + * orphan data already written by live installs (or break a live | |
| 33 | + * URL). The mismatch between this constant's name and its value is | |
| 34 | + * deliberate — it is NOT a half-finished rename. | |
| 35 | + */ | |
| 36 | +const OPENSTATION_META_WIDTH = '_desktop_mode_width'; | |
| 37 | +/** | |
| 38 | + * The VALUE keeps its pre-rebrand spelling on purpose: it is a | |
| 39 | + * persisted or externally-visible identifier, so renaming it would | |
| 40 | + * orphan data already written by live installs (or break a live | |
| 41 | + * URL). The mismatch between this constant's name and its value is | |
| 42 | + * deliberate — it is NOT a half-finished rename. | |
| 43 | + */ | |
| 44 | +const OPENSTATION_META_HEIGHT = '_desktop_mode_height'; | |
| 31 | 45 | |
| 32 | -/** Option key flipped to `1` once every image has been backfilled. */ | |
| 33 | -const DESKTOP_MODE_BACKFILL_DONE_OPTION = 'desktop_mode_media_dims_backfilled'; | |
| 46 | +/** | |
| 47 | + * Option key flipped to `1` once every image has been backfilled. | |
| 48 | + * | |
| 49 | + * The VALUE keeps its pre-rebrand spelling on purpose: it is a | |
| 50 | + * persisted or externally-visible identifier, so renaming it would | |
| 51 | + * orphan data already written by live installs (or break a live | |
| 52 | + * URL). The mismatch between this constant's name and its value is | |
| 53 | + * deliberate — it is NOT a half-finished rename. | |
| 54 | + */ | |
| 55 | +const OPENSTATION_BACKFILL_DONE_OPTION = 'desktop_mode_media_dims_backfilled'; | |
| 34 | 56 | |
| 35 | 57 | /** How many legacy attachments to backfill per filtered REST request. */ |
| 36 | -const DESKTOP_MODE_MEDIA_BACKFILL_BATCH = 50; | |
| 58 | +const OPENSTATION_MEDIA_BACKFILL_BATCH = 50; | |
| 37 | 59 | |
| 38 | 60 | /** |
| 39 | 61 | * Stamp numeric dimension meta whenever an attachment's metadata is |
| 40 | 62 | * generated or updated. Hooked on both `wp_generate_attachment_metadata` |
| @@ -41,15 +63,13 @@ | ||
| 41 | 63 | * (new uploads) and `wp_update_attachment_metadata` (regenerated |
| 42 | 64 | * thumbnails, image editor saves) so the stamp stays in sync with |
| 43 | 65 | * whatever Core thinks the canonical dimensions are. |
| 44 | 66 | * |
| 45 | - * @since 0.5.0 | |
| 46 | - * | |
| 47 | 67 | * @param array $metadata Attachment metadata. |
| 48 | 68 | * @param int $attachment_id Attachment post ID. |
| 49 | 69 | * @return array The metadata, unchanged — we only read from it. |
| 50 | 70 | */ |
| 51 | -function desktop_mode_stamp_media_dimensions( $metadata, $attachment_id ) { | |
| 71 | +function openstation_stamp_media_dimensions( $metadata, $attachment_id ) { | |
| 52 | 72 | if ( ! is_array( $metadata ) ) { |
| 53 | 73 | return $metadata; |
| 54 | 74 | } |
| 55 | 75 | |
| @@ -58,34 +78,32 @@ | ||
| 58 | 78 | |
| 59 | 79 | // Stamp zero for images we can't measure (SVGs, broken files) so the |
| 60 | 80 | // backfill sweep knows we've already inspected this row and doesn't |
| 61 | 81 | // re-check it on every subsequent filtered request. |
| 62 | - update_post_meta( $attachment_id, DESKTOP_MODE_META_WIDTH, max( 0, $width ) ); | |
| 63 | - update_post_meta( $attachment_id, DESKTOP_MODE_META_HEIGHT, max( 0, $height ) ); | |
| 82 | + update_post_meta( $attachment_id, OPENSTATION_META_WIDTH, max( 0, $width ) ); | |
| 83 | + update_post_meta( $attachment_id, OPENSTATION_META_HEIGHT, max( 0, $height ) ); | |
| 64 | 84 | |
| 65 | 85 | return $metadata; |
| 66 | 86 | } |
| 67 | -add_filter( 'wp_generate_attachment_metadata', 'desktop_mode_stamp_media_dimensions', 10, 2 ); | |
| 68 | -add_filter( 'wp_update_attachment_metadata', 'desktop_mode_stamp_media_dimensions', 10, 2 ); | |
| 87 | +add_filter( 'wp_generate_attachment_metadata', 'openstation_stamp_media_dimensions', 10, 2 ); | |
| 88 | +add_filter( 'wp_update_attachment_metadata', 'openstation_stamp_media_dimensions', 10, 2 ); | |
| 69 | 89 | |
| 70 | 90 | /** |
| 71 | - * Register the `desktop_mode_min_width` / `desktop_mode_min_height` query parameters on | |
| 91 | + * Register the `openstation_min_width` / `openstation_min_height` query parameters on | |
| 72 | 92 | * the media collection so Core sanitizes them before they reach our |
| 73 | 93 | * query filter. Without this, the params would still work but would |
| 74 | 94 | * show up as unknown to any REST consumer introspecting the schema. |
| 75 | 95 | * |
| 76 | - * @since 0.5.0 | |
| 77 | - * | |
| 78 | 96 | * @param array $params Existing collection params. |
| 79 | 97 | * @return array |
| 80 | 98 | */ |
| 81 | -function desktop_mode_register_media_query_params( $params ) { | |
| 82 | - $params['desktop_mode_min_width'] = array( | |
| 99 | +function openstation_register_media_query_params( $params ) { | |
| 100 | + $params['openstation_min_width'] = array( | |
| 83 | 101 | 'description' => __( 'Only return images at least this many pixels wide.', 'desktop-mode' ), |
| 84 | 102 | 'type' => 'integer', |
| 85 | 103 | 'minimum' => 1, |
| 86 | 104 | ); |
| 87 | - $params['desktop_mode_min_height'] = array( | |
| 105 | + $params['openstation_min_height'] = array( | |
| 88 | 106 | 'description' => __( 'Only return images at least this many pixels tall.', 'desktop-mode' ), |
| 89 | 107 | 'type' => 'integer', |
| 90 | 108 | 'minimum' => 1, |
| 91 | 109 | ); |
| @@ -90,9 +108,9 @@ | ||
| 90 | 108 | 'minimum' => 1, |
| 91 | 109 | ); |
| 92 | 110 | return $params; |
| 93 | 111 | } |
| 94 | -add_filter( 'rest_attachment_collection_params', 'desktop_mode_register_media_query_params' ); | |
| 112 | +add_filter( 'rest_attachment_collection_params', 'openstation_register_media_query_params' ); | |
| 95 | 113 | |
| 96 | 114 | /** |
| 97 | 115 | * Inject meta_query clauses on the attachment REST query when either |
| 98 | 116 | * dimension filter is present. Triggers a bounded backfill first so |
| @@ -98,17 +116,15 @@ | ||
| 98 | 116 | * dimension filter is present. Triggers a bounded backfill first so |
| 99 | 117 | * legacy attachments (uploaded before this filter existed) start |
| 100 | 118 | * participating without a one-shot CLI command. |
| 101 | 119 | * |
| 102 | - * @since 0.5.0 | |
| 103 | - * | |
| 104 | 120 | * @param array $args WP_Query args built by the REST controller. |
| 105 | 121 | * @param WP_REST_Request $request The REST request. |
| 106 | 122 | * @return array The query args, possibly with extra meta_query entries. |
| 107 | 123 | */ |
| 108 | -function desktop_mode_filter_media_by_dimensions( $args, $request ) { | |
| 109 | - $min_width = absint( $request->get_param( 'desktop_mode_min_width' ) ); | |
| 110 | - $min_height = absint( $request->get_param( 'desktop_mode_min_height' ) ); | |
| 124 | +function openstation_filter_media_by_dimensions( $args, $request ) { | |
| 125 | + $min_width = absint( $request->get_param( 'openstation_min_width' ) ); | |
| 126 | + $min_height = absint( $request->get_param( 'openstation_min_height' ) ); | |
| 111 | 127 | |
| 112 | 128 | if ( ! $min_width && ! $min_height ) { |
| 113 | 129 | return $args; |
| 114 | 130 | } |
| @@ -115,9 +131,14 @@ | ||
| 115 | 131 | |
| 116 | 132 | // Chip away at any remaining unstamped attachments before the query |
| 117 | 133 | // runs, so a site upgrading into this feature gets useful results on |
| 118 | 134 | // roughly the first few picker opens rather than after a CLI run. |
| 119 | - desktop_mode_backfill_media_dimensions( DESKTOP_MODE_MEDIA_BACKFILL_BATCH ); | |
| 135 | + // Logged-in users only — anonymous REST readers can still use the | |
| 136 | + // dimension filters, but must never trigger database writes (or the | |
| 137 | + // NOT EXISTS sweep query) on a public, unauthenticated endpoint. | |
| 138 | + if ( is_user_logged_in() ) { | |
| 139 | + openstation_backfill_media_dimensions( OPENSTATION_MEDIA_BACKFILL_BATCH ); | |
| 140 | + } | |
| 120 | 141 | |
| 121 | 142 | $meta_query = isset( $args['meta_query'] ) && is_array( $args['meta_query'] ) |
| 122 | 143 | ? $args['meta_query'] |
| 123 | 144 | : array(); |
| @@ -124,9 +145,9 @@ | ||
| 124 | 145 | |
| 125 | 146 | $clauses = array(); |
| 126 | 147 | if ( $min_width ) { |
| 127 | 148 | $clauses[] = array( |
| 128 | - 'key' => DESKTOP_MODE_META_WIDTH, | |
| 149 | + 'key' => OPENSTATION_META_WIDTH, | |
| 129 | 150 | 'value' => $min_width, |
| 130 | 151 | 'compare' => '>=', |
| 131 | 152 | 'type' => 'NUMERIC', |
| 132 | 153 | ); |
| @@ -132,9 +153,9 @@ | ||
| 132 | 153 | ); |
| 133 | 154 | } |
| 134 | 155 | if ( $min_height ) { |
| 135 | 156 | $clauses[] = array( |
| 136 | - 'key' => DESKTOP_MODE_META_HEIGHT, | |
| 157 | + 'key' => OPENSTATION_META_HEIGHT, | |
| 137 | 158 | 'value' => $min_height, |
| 138 | 159 | 'compare' => '>=', |
| 139 | 160 | 'type' => 'NUMERIC', |
| 140 | 161 | ); |
| @@ -157,9 +178,9 @@ | ||
| 157 | 178 | } |
| 158 | 179 | |
| 159 | 180 | return $args; |
| 160 | 181 | } |
| 161 | -add_filter( 'rest_attachment_query', 'desktop_mode_filter_media_by_dimensions', 10, 2 ); | |
| 182 | +add_filter( 'rest_attachment_query', 'openstation_filter_media_by_dimensions', 10, 2 ); | |
| 162 | 183 | |
| 163 | 184 | /** |
| 164 | 185 | * Stamp dimension meta on up to $batch image attachments that don't |
| 165 | 186 | * have it yet. Returns early once a site has been fully backfilled so |
| @@ -168,15 +189,13 @@ | ||
| 168 | 189 | * Ordering by `ID DESC` so newest attachments get stamped first — the |
| 169 | 190 | * user is most likely to be looking for something they uploaded |
| 170 | 191 | * recently, so we prioritize the tail they're staring at. |
| 171 | 192 | * |
| 172 | - * @since 0.5.0 | |
| 173 | - * | |
| 174 | 193 | * @param int $batch Maximum attachments to stamp this pass. |
| 175 | 194 | * @return int Number of attachments stamped (0 when nothing remained). |
| 176 | 195 | */ |
| 177 | -function desktop_mode_backfill_media_dimensions( $batch ) { | |
| 178 | - if ( get_option( DESKTOP_MODE_BACKFILL_DONE_OPTION ) ) { | |
| 196 | +function openstation_backfill_media_dimensions( $batch ) { | |
| 197 | + if ( get_option( OPENSTATION_BACKFILL_DONE_OPTION ) ) { | |
| 179 | 198 | return 0; |
| 180 | 199 | } |
| 181 | 200 | |
| 182 | 201 | $ids = get_posts( |
| @@ -192,9 +211,9 @@ | ||
| 192 | 211 | 'update_post_term_cache' => false, |
| 193 | 212 | // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- one-shot backfill targeting the small subset of attachments that lack our dimension stamp; runs at most $batch rows per filtered request and stops once the site option flips. |
| 194 | 213 | 'meta_query' => array( |
| 195 | 214 | array( |
| 196 | - 'key' => DESKTOP_MODE_META_WIDTH, | |
| 215 | + 'key' => OPENSTATION_META_WIDTH, | |
| 197 | 216 | 'compare' => 'NOT EXISTS', |
| 198 | 217 | ), |
| 199 | 218 | ), |
| 200 | 219 | ) |
| @@ -204,9 +223,9 @@ | ||
| 204 | 223 | // Flip the flag so future filtered requests skip this query. |
| 205 | 224 | // If a plugin ever adds new images via a back door that bypasses |
| 206 | 225 | // `wp_generate_attachment_metadata`, those can be picked up by |
| 207 | 226 | // manually deleting this option. |
| 208 | - update_option( DESKTOP_MODE_BACKFILL_DONE_OPTION, 1, false ); | |
| 227 | + update_option( OPENSTATION_BACKFILL_DONE_OPTION, 1, false ); | |
| 209 | 228 | return 0; |
| 210 | 229 | } |
| 211 | 230 | |
| 212 | 231 | foreach ( $ids as $id ) { |
| @@ -220,10 +239,10 @@ | ||
| 220 | 239 | $height = is_array( $metadata ) && isset( $metadata['height'] ) ? (int) $metadata['height'] : 0; |
| 221 | 240 | |
| 222 | 241 | // Always stamp, even when zero — that's how the sweep query |
| 223 | 242 | // knows to skip this row on the next pass. |
| 224 | - update_post_meta( $id, DESKTOP_MODE_META_WIDTH, max( 0, $width ) ); | |
| 225 | - update_post_meta( $id, DESKTOP_MODE_META_HEIGHT, max( 0, $height ) ); | |
| 243 | + update_post_meta( $id, OPENSTATION_META_WIDTH, max( 0, $width ) ); | |
| 244 | + update_post_meta( $id, OPENSTATION_META_HEIGHT, max( 0, $height ) ); | |
| 226 | 245 | } |
| 227 | 246 | |
| 228 | 247 | return count( $ids ); |
| 229 | 248 | } |