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

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