PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.8
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / includes / my-wordpress / attached-media.php

attached-media.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.8.8, at includes/my-wordpress/attached-media.php

245 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — My WordPress: per-post attached-media REST field.
4 *
5 * Registers `desktop_mode_attached_media` on every public post
6 * type. Returns the canonical set of attachment ids referenced by
7 * the post — featured image + everything in `post_content` —
8 * computed server-side where `attachment_url_to_postid()` is
9 * available.
10 *
11 * Why server-side: a client regex over `content.rendered` catches
12 * the common `class="wp-image-N"` Gutenberg case, but misses:
13 * - Raw `<img src="…">` inserted via cross-window drag-bridge,
14 * plugin importers, or hand-edits with no `wp-image-N` class.
15 * - Gallery items that store the id in `data-id` / `data-
16 * attachment-id`.
17 * - Classic-editor `[caption id="attachment_N"]` shortcodes
18 * that render through `the_content` without re-emitting an
19 * `wp-image-N` class.
20 *
21 * Server has the full post object + WP core's URL→id resolver, so
22 * one round-trip per detail-view fetches an authoritative list.
23 *
24 * Filterable via `desktop_mode_my_wordpress_attached_media` for
25 * plugins (page builders, ACF, custom field handlers) to append
26 * their own references.
27 *
28 * @package WPDesktopMode
29 * @since 0.21.0
30 */
31
32 defined( 'ABSPATH' ) || exit;
33
34 /**
35 * Register `desktop_mode_attached_media` on every public post type.
36 *
37 * @since 0.21.0
38 */
39 function desktop_mode_my_wordpress_register_attached_media_field() {
40 $types = get_post_types(
41 array(
42 'show_in_rest' => true,
43 'public' => true,
44 ),
45 'names'
46 );
47 foreach ( $types as $type ) {
48 if ( 'attachment' === $type ) {
49 continue;
50 }
51 register_rest_field(
52 $type,
53 'desktop_mode_attached_media',
54 array(
55 'get_callback' => static function ( $post ) {
56 $id = isset( $post['id'] ) ? (int) $post['id'] : 0;
57 return desktop_mode_my_wordpress_post_attached_media( $id );
58 },
59 'schema' => array(
60 'description' => __( 'Attachment ids referenced by this post — featured image plus every attachment found in post_content (block-class scan + raw `<img src>` URL resolution).', 'desktop-mode' ),
61 'type' => 'array',
62 'items' => array( 'type' => 'integer' ),
63 'context' => array( 'view', 'edit' ),
64 'readonly' => true,
65 ),
66 )
67 );
68 }
69 }
70 add_action( 'rest_api_init', 'desktop_mode_my_wordpress_register_attached_media_field' );
71
72 /**
73 * Resolve every attachment referenced by `$post_id`. Featured
74 * image + a multi-pass scan of `post_content`:
75 *
76 * 1. `wp-image-N` class on any element.
77 * 2. `id="attachment_N"` (classic-editor caption shortcode).
78 * 3. `data-id="N"` and `data-attachment-id="N"` (gallery
79 * formats, Jetpack tiled-gallery, several SEO plugins).
80 * 4. Raw `<img src="…">` URLs resolved via `attachment_url_to_postid()`.
81 * Resolver results are cached in-process so a post embedding
82 * the same image twice only takes one DB hit per URL.
83 *
84 * @since 0.21.0
85 *
86 * @param int $post_id Post id.
87 * @return int[] Attachment ids, deduped, no order guarantee.
88 */
89 function desktop_mode_my_wordpress_post_attached_media( $post_id ) {
90 $post_id = (int) $post_id;
91 if ( $post_id <= 0 ) {
92 return array();
93 }
94 $post = get_post( $post_id );
95 if ( ! $post ) {
96 return array();
97 }
98
99 $ids = array();
100
101 // Featured image.
102 $thumb = (int) get_post_thumbnail_id( $post_id );
103 if ( $thumb > 0 ) {
104 $ids[ $thumb ] = true;
105 }
106
107 $content = isset( $post->post_content ) ? (string) $post->post_content : '';
108 if ( '' !== $content ) {
109 // Pass 1: explicit class / id / data-attribute patterns.
110 $patterns = array(
111 '/wp-image-(\d+)/',
112 '/id="attachment_(\d+)"/',
113 "/data-id=['\"](\d+)['\"]/",
114 "/data-attachment-id=['\"](\d+)['\"]/",
115 );
116 foreach ( $patterns as $pattern ) {
117 if ( preg_match_all( $pattern, $content, $m ) ) {
118 foreach ( $m[1] as $raw ) {
119 $id = (int) $raw;
120 if ( $id > 0 ) {
121 $ids[ $id ] = true;
122 }
123 }
124 }
125 }
126
127 // Pass 2: resolve `<img src="…">` URLs. Catches images that
128 // none of the above patterns tagged — the cross-window
129 // drag-bridge insertion path being the canonical example.
130 if ( preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\']/', $content, $m ) ) {
131 $seen_urls = array();
132 foreach ( $m[1] as $url ) {
133 $url = (string) $url;
134 if ( '' === $url || isset( $seen_urls[ $url ] ) ) {
135 continue;
136 }
137 $seen_urls[ $url ] = true;
138 $resolved = desktop_mode_my_wordpress_resolve_attachment_url( $url );
139 if ( $resolved > 0 ) {
140 $ids[ $resolved ] = true;
141 }
142 }
143 }
144 }
145
146 $out = array_map( 'intval', array_keys( $ids ) );
147
148 /**
149 * Filter the per-post attached-media id list.
150 *
151 * Plugins that store attachment references outside `post_content`
152 * (ACF image fields, page-builder block storage, post-meta
153 * galleries) can append their ids here.
154 *
155 * @since 0.21.0
156 *
157 * @param int[] $out Attachment ids resolved by the core scan.
158 * @param int $post_id Subject post id.
159 */
160 $filtered = apply_filters( 'desktop_mode_my_wordpress_attached_media', $out, $post_id );
161
162 if ( ! is_array( $filtered ) ) {
163 return $out;
164 }
165 // Sanitize: positive integers, deduped.
166 $dedup = array();
167 foreach ( $filtered as $id ) {
168 $id = (int) $id;
169 if ( $id > 0 ) {
170 $dedup[ $id ] = true;
171 }
172 }
173 return array_map( 'intval', array_keys( $dedup ) );
174 }
175
176 /**
177 * In-request URL→attachment-id cache. `attachment_url_to_postid`
178 * runs a SQL query per call; a post that embeds the same image
179 * multiple times (gallery + cover + content) would otherwise pay
180 * for each occurrence.
181 *
182 * @since 0.21.0
183 *
184 * @param string $url Image URL pulled from `<img src>`.
185 * @return int Attachment id, or 0 when no match.
186 */
187 function desktop_mode_my_wordpress_resolve_attachment_url( $url ) {
188 static $cache = array();
189 $url = (string) $url;
190 if ( '' === $url ) {
191 return 0;
192 }
193 if ( isset( $cache[ $url ] ) ) {
194 return (int) $cache[ $url ];
195 }
196 // `attachment_url_to_postid()` does a literal `_wp_attached_file`
197 // meta lookup — it doesn't account for WP-generated variants:
198 //
199 // - Sized intermediates `image-300x200.jpg` — strip `-WxH`.
200 // - `-scaled.jpg` WP autogenerates this for uploads
201 // past the big-image threshold and
202 // stores it as `_wp_attached_file`,
203 // while editors emit the original
204 // URL in `<img src>`. Try BOTH the
205 // scaled→original strip AND the
206 // original→scaled append.
207 //
208 // Strip query strings from every candidate so cache-buster URLs
209 // (`?ver=…`) still resolve.
210 $strip_query = static function ( $u ) {
211 $pos = strpos( $u, '?' );
212 return false === $pos ? $u : substr( $u, 0, $pos );
213 };
214
215 $clean = $strip_query( $url );
216 $candidates = array( $clean );
217
218 // `image-300x200.jpg` → `image.jpg`
219 if ( preg_match( '/^(.*)-\d+x\d+(\.[a-zA-Z0-9]+)$/', $clean, $m ) ) {
220 $candidates[] = $m[1] . $m[2];
221 }
222 // `image-scaled.jpg` → `image.jpg`
223 if ( preg_match( '/^(.*)-scaled(\.[a-zA-Z0-9]+)$/', $clean, $m ) ) {
224 $candidates[] = $m[1] . $m[2];
225 }
226 // `image.jpg` → `image-scaled.jpg` (post emits original, meta
227 // stores scaled — the most common drag-bridge insertion path).
228 if ( preg_match( '/^(.*)(\.[a-zA-Z0-9]+)$/', $clean, $m )
229 && ! preg_match( '/-scaled$/', $m[1] )
230 ) {
231 $candidates[] = $m[1] . '-scaled' . $m[2];
232 }
233
234 $resolved = 0;
235 foreach ( $candidates as $candidate ) {
236 $id = (int) attachment_url_to_postid( $candidate );
237 if ( $id > 0 ) {
238 $resolved = $id;
239 break;
240 }
241 }
242 $cache[ $url ] = $resolved;
243 return $resolved;
244 }
245