PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.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.9.8, at includes/my-wordpress/attached-media.php

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