PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.6.4
Jetpack – WP Security, Backup, Speed, & Growth v12.6.4
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.jetpack-post-images.php
jetpack Last commit date
3rd-party 2 years ago _inc 2 years ago css 2 years ago extensions 2 years ago images 3 years ago jetpack_vendor 21 hours ago json-endpoints 2 years ago modules 1 year ago sal 2 years ago src 3 years ago vendor 2 years ago views 3 years ago CHANGELOG.md 2 years ago LICENSE.txt 5 years ago SECURITY.md 5 years ago class-jetpack-connection-status.php 2 years ago class-jetpack-connection-widget.php 3 years ago class-jetpack-gallery-settings.php 3 years ago class-jetpack-pre-connection-jitms.php 4 years ago class-jetpack-recommendations-banner.php 2 years ago class-jetpack-stats-dashboard-widget.php 3 years ago class-jetpack-wizard-banner.php 5 years ago class-jetpack-xmlrpc-methods.php 3 years ago class.frame-nonce-preview.php 4 years ago class.jetpack-admin.php 2 years ago class.jetpack-affiliate.php 2 years ago class.jetpack-autoupdate.php 2 years ago class.jetpack-bbpress-json-api.compat.php 2 years ago class.jetpack-boost-modules.php 3 years ago class.jetpack-cli.php 3 years ago class.jetpack-client-server.php 4 years ago class.jetpack-connection-banner.php 3 years ago class.jetpack-data.php 2 years ago class.jetpack-gutenberg.php 2 years ago class.jetpack-heartbeat.php 2 years ago class.jetpack-idc.php 5 years ago class.jetpack-modules-list-table.php 2 years ago class.jetpack-network-sites-list-table.php 3 years ago class.jetpack-network.php 3 years ago class.jetpack-plan.php 3 years ago class.jetpack-post-images.php 2 years ago class.jetpack-twitter-cards.php 3 years ago class.jetpack-user-agent.php 2 years ago class.jetpack.php 2 years ago class.json-api-endpoints.php 2 years ago class.json-api.php 3 years ago class.photon.php 3 years ago composer.json 2 years ago enhanced-open-graph.php 3 years ago functions.compat.php 2 years ago functions.cookies.php 2 years ago functions.global.php 2 years ago functions.is-mobile.php 2 years ago functions.opengraph.php 2 years ago functions.photon.php 2 years ago jetpack.php 21 hours ago json-api-config.php 3 years ago json-endpoints.php 2 years ago load-jetpack.php 3 years ago locales.php 4 years ago readme.txt 21 hours ago uninstall.php 5 years ago wpml-config.xml 4 years ago
class.jetpack-post-images.php
1041 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Useful for finding an image to display alongside/in representation of a specific post.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
9
10 /**
11 * Useful for finding an image to display alongside/in representation of a specific post.
12 *
13 * Includes a few different methods, all of which return a similar-format array containing
14 * details of any images found. Everything can (should) be called statically, it's just a
15 * function-bucket. You can also call Jetpack_PostImages::get_image() to cycle through all of the methods until
16 * one of them finds something useful.
17 *
18 * This file is included verbatim in Jetpack
19 */
20 class Jetpack_PostImages {
21 /**
22 * If a slideshow is embedded within a post, then parse out the images involved and return them
23 *
24 * @param int $post_id Post ID.
25 * @param int $width Image width.
26 * @param int $height Image height.
27 * @return array Images.
28 */
29 public static function from_slideshow( $post_id, $width = 200, $height = 200 ) {
30 $images = array();
31
32 $post = get_post( $post_id );
33
34 if ( ! $post ) {
35 return $images;
36 }
37
38 if ( ! empty( $post->post_password ) ) {
39 return $images;
40 }
41
42 if ( false === has_shortcode( $post->post_content, 'slideshow' ) ) {
43 return $images; // no slideshow - bail.
44 }
45
46 $permalink = get_permalink( $post->ID );
47
48 // Mechanic: Somebody set us up the bomb.
49 $old_post = $GLOBALS['post'];
50 $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
51 $old_shortcodes = $GLOBALS['shortcode_tags'];
52 $GLOBALS['shortcode_tags'] = array( 'slideshow' => $old_shortcodes['slideshow'] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
53
54 // Find all the slideshows.
55 preg_match_all( '/' . get_shortcode_regex() . '/sx', $post->post_content, $slideshow_matches, PREG_SET_ORDER );
56
57 ob_start(); // The slideshow shortcode handler calls wp_print_scripts and wp_print_styles... not too happy about that.
58
59 foreach ( $slideshow_matches as $slideshow_match ) {
60 $slideshow = do_shortcode_tag( $slideshow_match );
61 $pos = stripos( $slideshow, 'jetpack-slideshow' );
62 if ( false === $pos ) { // must be something wrong - or we changed the output format in which case none of the following will work.
63 continue;
64 }
65 $start = strpos( $slideshow, '[', $pos );
66 $end = strpos( $slideshow, ']', $start );
67 $post_images = json_decode( wp_specialchars_decode( str_replace( "'", '"', substr( $slideshow, $start, $end - $start + 1 ) ), ENT_QUOTES ) ); // parse via JSON
68 // If the JSON didn't decode don't try and act on it.
69 if ( is_array( $post_images ) ) {
70 foreach ( $post_images as $post_image ) {
71 $post_image_id = absint( $post_image->id );
72 if ( ! $post_image_id ) {
73 continue;
74 }
75
76 $meta = wp_get_attachment_metadata( $post_image_id );
77
78 // Must be larger than 200x200 (or user-specified).
79 if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
80 continue;
81 }
82 if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
83 continue;
84 }
85
86 $url = wp_get_attachment_url( $post_image_id );
87
88 $images[] = array(
89 'type' => 'image',
90 'from' => 'slideshow',
91 'src' => $url,
92 'src_width' => $meta['width'],
93 'src_height' => $meta['height'],
94 'href' => $permalink,
95 );
96 }
97 }
98 }
99 ob_end_clean();
100
101 // Operator: Main screen turn on.
102 $GLOBALS['shortcode_tags'] = $old_shortcodes; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
103 $GLOBALS['post'] = $old_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
104
105 return $images;
106 }
107
108 /**
109 * Filtering out images with broken URL from galleries.
110 *
111 * @param array $galleries Galleries.
112 * @return array $filtered_galleries
113 */
114 public static function filter_gallery_urls( $galleries ) {
115 $filtered_galleries = array();
116 foreach ( $galleries as $this_gallery ) {
117 if ( ! isset( $this_gallery['src'] ) ) {
118 continue;
119 }
120 $ids = isset( $this_gallery['ids'] ) ? explode( ',', $this_gallery['ids'] ) : array();
121 // Make sure 'src' array isn't associative and has no holes.
122 $this_gallery['src'] = array_values( $this_gallery['src'] );
123 foreach ( $this_gallery['src'] as $idx => $src_url ) {
124 if ( filter_var( $src_url, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED ) === false ) {
125 unset( $this_gallery['src'][ $idx ] );
126 unset( $ids[ $idx ] );
127 }
128 }
129 if ( isset( $this_gallery['ids'] ) ) {
130 $this_gallery['ids'] = implode( ',', $ids );
131 }
132 // Remove any holes we introduced.
133 $this_gallery['src'] = array_values( $this_gallery['src'] );
134 $filtered_galleries[] = $this_gallery;
135 }
136
137 return $filtered_galleries;
138 }
139
140 /**
141 * If a gallery is detected, then get all the images from it.
142 *
143 * @param int $post_id Post ID.
144 * @return array Images.
145 */
146 public static function from_gallery( $post_id ) {
147 $images = array();
148
149 $post = get_post( $post_id );
150
151 if ( ! $post ) {
152 return $images;
153 }
154
155 if ( ! empty( $post->post_password ) ) {
156 return $images;
157 }
158 add_filter( 'get_post_galleries', array( __CLASS__, 'filter_gallery_urls' ), 999999 );
159
160 $permalink = get_permalink( $post->ID );
161
162 /**
163 * Juggle global post object because the gallery shortcode uses the
164 * global object.
165 *
166 * See core ticket:
167 * https://core.trac.wordpress.org/ticket/39304
168 */
169 // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
170 if ( isset( $GLOBALS['post'] ) ) {
171 $juggle_post = $GLOBALS['post'];
172 $GLOBALS['post'] = $post;
173 $galleries = get_post_galleries( $post->ID, false );
174 $GLOBALS['post'] = $juggle_post;
175 } else {
176 $GLOBALS['post'] = $post;
177 $galleries = get_post_galleries( $post->ID, false );
178 unset( $GLOBALS['post'] );
179 }
180 // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
181
182 foreach ( $galleries as $gallery ) {
183 if ( isset( $gallery['type'] ) && 'slideshow' === $gallery['type'] && ! empty( $gallery['ids'] ) ) {
184 $image_ids = explode( ',', $gallery['ids'] );
185 $image_size = isset( $gallery['size'] ) ? $gallery['size'] : 'thumbnail';
186 foreach ( $image_ids as $image_id ) {
187 $image = wp_get_attachment_image_src( $image_id, $image_size );
188 if ( ! empty( $image[0] ) ) {
189 list( $raw_src ) = explode( '?', $image[0] ); // pull off any Query string (?w=250).
190 $raw_src = wp_specialchars_decode( $raw_src ); // rawify it.
191 $raw_src = esc_url_raw( $raw_src ); // clean it.
192 $images[] = array(
193 'type' => 'image',
194 'from' => 'gallery',
195 'src' => $raw_src,
196 'href' => $permalink,
197 );
198 }
199 }
200 } elseif ( ! empty( $gallery['src'] ) ) {
201 foreach ( $gallery['src'] as $src ) {
202 list( $raw_src ) = explode( '?', $src ); // pull off any Query string (?w=250).
203 $raw_src = wp_specialchars_decode( $raw_src ); // rawify it.
204 $raw_src = esc_url_raw( $raw_src ); // clean it.
205 $images[] = array(
206 'type' => 'image',
207 'from' => 'gallery',
208 'src' => $raw_src,
209 'href' => $permalink,
210 );
211 }
212 }
213 }
214
215 return $images;
216 }
217
218 /**
219 * Get attachment images for a specified post and return them. Also make sure
220 * their dimensions are at or above a required minimum.
221 *
222 * @param int $post_id The post ID to check.
223 * @param int $width Image width.
224 * @param int $height Image height.
225 * @return array Containing details of the image, or empty array if none.
226 */
227 public static function from_attachment( $post_id, $width = 200, $height = 200 ) {
228 $images = array();
229
230 $post = get_post( $post_id );
231
232 if ( ! empty( $post->post_password ) ) {
233 return $images;
234 }
235
236 $post_images = get_posts(
237 array(
238 'post_parent' => $post_id, // Must be children of post.
239 'numberposts' => 5, // No more than 5.
240 'post_type' => 'attachment', // Must be attachments.
241 'post_mime_type' => 'image', // Must be images.
242 'suppress_filters' => false,
243 )
244 );
245
246 if ( ! $post_images ) {
247 return $images;
248 }
249
250 $permalink = get_permalink( $post_id );
251
252 foreach ( $post_images as $post_image ) {
253 $current_image = self::get_attachment_data( $post_image->ID, $permalink, $width, $height );
254 if ( false !== $current_image ) {
255 $images[] = $current_image;
256 }
257 }
258
259 /*
260 * We only want to pass back attached images that were actually inserted.
261 * We can load up all the images found in the HTML source and then
262 * compare URLs to see if an image is attached AND inserted.
263 */
264 $html_images = self::from_html( $post_id );
265 $inserted_images = array();
266
267 foreach ( $html_images as $html_image ) {
268 $src = wp_parse_url( $html_image['src'] );
269 // strip off any query strings from src.
270 if ( ! empty( $src['scheme'] ) && ! empty( $src['host'] ) ) {
271 $inserted_images[] = $src['scheme'] . '://' . $src['host'] . $src['path'];
272 } elseif ( ! empty( $src['host'] ) ) {
273 $inserted_images[] = set_url_scheme( 'http://' . $src['host'] . $src['path'] );
274 } else {
275 $inserted_images[] = site_url( '/' ) . $src['path'];
276 }
277 }
278 foreach ( $images as $i => $image ) {
279 if ( ! in_array( $image['src'], $inserted_images, true ) ) {
280 unset( $images[ $i ] );
281 }
282 }
283
284 return $images;
285 }
286
287 /**
288 * Check if a Featured Image is set for this post, and return it in a similar
289 * format to the other images?_from_*() methods.
290 *
291 * @param int $post_id The post ID to check.
292 * @param int $width Image width.
293 * @param int $height Image height.
294 * @return array containing details of the Featured Image, or empty array if none.
295 */
296 public static function from_thumbnail( $post_id, $width = 200, $height = 200 ) {
297 $images = array();
298
299 $post = get_post( $post_id );
300
301 if ( ! empty( $post->post_password ) ) {
302 return $images;
303 }
304
305 if ( 'attachment' === get_post_type( $post ) && wp_attachment_is_image( $post ) ) {
306 $thumb = $post_id;
307 } else {
308 $thumb = get_post_thumbnail_id( $post );
309 }
310
311 if ( $thumb ) {
312 $meta = wp_get_attachment_metadata( $thumb );
313 // Must be larger than requested minimums.
314 if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
315 return $images;
316 }
317 if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
318 return $images;
319 }
320
321 $too_big = ( ( ! empty( $meta['width'] ) && $meta['width'] > 1200 ) || ( ! empty( $meta['height'] ) && $meta['height'] > 1200 ) );
322
323 if (
324 $too_big &&
325 (
326 ( method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'photon' ) ) ||
327 ( defined( 'IS_WPCOM' ) && IS_WPCOM )
328 )
329 ) {
330 $img_src = wp_get_attachment_image_src( $thumb, array( 1200, 1200 ) );
331 } else {
332 $img_src = wp_get_attachment_image_src( $thumb, 'full' );
333 }
334 if ( ! is_array( $img_src ) ) {
335 // If wp_get_attachment_image_src returns false but we know that there should be an image that could be used.
336 // we try a bit harder and user the data that we have.
337 $thumb_post_data = get_post( $thumb );
338 $img_src = array( $thumb_post_data->guid, $meta['width'], $meta['height'] );
339 }
340
341 // Let's try to use the postmeta if we can, since it seems to be
342 // more reliable
343 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
344 $featured_image = get_post_meta( $post->ID, '_jetpack_featured_image' );
345 if ( $featured_image ) {
346 $url = $featured_image[0];
347 } else {
348 $url = $img_src[0];
349 }
350 } else {
351 $url = $img_src[0];
352 }
353 $images = array(
354 array( // Other methods below all return an array of arrays.
355 'type' => 'image',
356 'from' => 'thumbnail',
357 'src' => $url,
358 'src_width' => $img_src[1],
359 'src_height' => $img_src[2],
360 'href' => get_permalink( $thumb ),
361 'alt_text' => self::get_alt_text( $thumb ),
362 ),
363 );
364
365 }
366
367 if ( empty( $images ) && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
368 $meta_thumbnail = get_post_meta( $post_id, '_jetpack_post_thumbnail', true );
369 if ( ! empty( $meta_thumbnail ) ) {
370 if ( ! isset( $meta_thumbnail['width'] ) || $meta_thumbnail['width'] < $width ) {
371 return $images;
372 }
373
374 if ( ! isset( $meta_thumbnail['height'] ) || $meta_thumbnail['height'] < $height ) {
375 return $images;
376 }
377
378 $images = array(
379 array( // Other methods below all return an array of arrays.
380 'type' => 'image',
381 'from' => 'thumbnail',
382 'src' => $meta_thumbnail['URL'],
383 'src_width' => $meta_thumbnail['width'],
384 'src_height' => $meta_thumbnail['height'],
385 'href' => $meta_thumbnail['URL'],
386 'alt_text' => self::get_alt_text( $thumb ),
387 ),
388 );
389 }
390 }
391
392 return $images;
393 }
394
395 /**
396 * Get images from Gutenberg Image blocks.
397 *
398 * @since 6.9.0
399 *
400 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
401 * @param int $width Minimum Image width.
402 * @param int $height Minimum Image height.
403 */
404 public static function from_blocks( $html_or_id, $width = 200, $height = 200 ) {
405 $images = array();
406
407 $html_info = self::get_post_html( $html_or_id );
408
409 if ( empty( $html_info['html'] ) ) {
410 return $images;
411 }
412
413 // Look for block information in the HTML.
414 $blocks = parse_blocks( $html_info['html'] );
415 if ( empty( $blocks ) ) {
416 return $images;
417 }
418
419 /*
420 * Let's loop through our blocks.
421 * Some blocks may include some other blocks. Let's go 2 levels deep to look for blocks
422 * that we support and that may include images (see get_images_from_block)
423 *
424 * @to-do: instead of looping manually (that's a lot of if and loops), search recursively instead.
425 */
426 foreach ( $blocks as $block ) {
427 if ( ! self::is_nested_block( $block ) || 'core/media-text' === $block['blockName'] ) {
428 $images = self::get_images_from_block( $images, $block, $html_info, $width, $height );
429 } else {
430 foreach ( $block['innerBlocks'] as $inner_block ) {
431 if ( ! self::is_nested_block( $inner_block ) ) {
432 $images = self::get_images_from_block( $images, $inner_block, $html_info, $width, $height );
433 } else {
434 foreach ( $inner_block['innerBlocks'] as $inner_inner_block ) {
435 $images = self::get_images_from_block( $images, $inner_inner_block, $html_info, $width, $height );
436 }
437 }
438 }
439 }
440 }
441
442 /**
443 * Returning a filtered array because get_attachment_data returns false
444 * for unsuccessful attempts.
445 */
446 return array_filter( $images );
447 }
448
449 /**
450 * Very raw -- just parse the HTML and pull out any/all img tags and return their src
451 *
452 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
453 * @param int $width Minimum Image width.
454 * @param int $height Minimum Image height.
455 *
456 * @uses DOMDocument
457 *
458 * @return array containing images
459 */
460 public static function from_html( $html_or_id, $width = 200, $height = 200 ) {
461 $images = array();
462
463 $html_info = self::get_post_html( $html_or_id );
464
465 if ( empty( $html_info['html'] ) ) {
466 return $images;
467 }
468
469 // Do not go any further if DOMDocument is disabled on the server.
470 if ( ! class_exists( 'DOMDocument' ) ) {
471 return $images;
472 }
473
474 // Let's grab all image tags from the HTML.
475 $dom_doc = new DOMDocument();
476
477 // The @ is not enough to suppress errors when dealing with libxml,
478 // we have to tell it directly how we want to handle errors.
479 libxml_use_internal_errors( true );
480 @$dom_doc->loadHTML( $html_info['html'] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
481 libxml_use_internal_errors( false );
482
483 $image_tags = $dom_doc->getElementsByTagName( 'img' );
484
485 // For each image Tag, make sure it can be added to the $images array, and add it.
486 foreach ( $image_tags as $image_tag ) {
487 $img_src = $image_tag->getAttribute( 'src' );
488
489 if ( empty( $img_src ) ) {
490 continue;
491 }
492
493 // Do not grab smiley images that were automatically created by WP when entering text smilies.
494 if ( stripos( $img_src, '/smilies/' ) ) {
495 continue;
496 }
497
498 $meta = array(
499 'width' => (int) $image_tag->getAttribute( 'width' ),
500 'height' => (int) $image_tag->getAttribute( 'height' ),
501 'alt_text' => $image_tag->getAttribute( 'alt' ),
502 );
503
504 /**
505 * Filters the switch to ignore minimum image size requirements. Can be used
506 * to add custom logic to image dimensions, like only enforcing one of the dimensions,
507 * or disabling it entirely.
508 *
509 * @since 6.4.0
510 *
511 * @param bool $ignore Should the image dimensions be ignored?
512 * @param array $meta Array containing image dimensions parsed from the markup.
513 */
514 $ignore_dimensions = apply_filters( 'jetpack_postimages_ignore_minimum_dimensions', false, $meta );
515
516 // Must be larger than 200x200 (or user-specified).
517 if (
518 ! $ignore_dimensions
519 && (
520 empty( $meta['width'] )
521 || empty( $meta['height'] )
522 || $meta['width'] < $width
523 || $meta['height'] < $height
524 )
525 ) {
526 continue;
527 }
528
529 $images[] = array(
530 'type' => 'image',
531 'from' => 'html',
532 'src' => $img_src,
533 'src_width' => $meta['width'],
534 'src_height' => $meta['height'],
535 'href' => $html_info['post_url'],
536 'alt_text' => $meta['alt_text'],
537 );
538 }
539 return $images;
540 }
541
542 /**
543 * Data from blavatar.
544 *
545 * @param int $post_id The post ID to check.
546 * @param int $size Size.
547 * @return array containing details of the image, or empty array if none.
548 */
549 public static function from_blavatar( $post_id, $size = 96 ) {
550
551 $permalink = get_permalink( $post_id );
552
553 if ( function_exists( 'blavatar_domain' ) && function_exists( 'blavatar_exists' ) && function_exists( 'blavatar_url' ) ) {
554 $domain = blavatar_domain( $permalink );
555
556 if ( ! blavatar_exists( $domain ) ) {
557 return array();
558 }
559
560 $url = blavatar_url( $domain, 'img', $size );
561 } else {
562 $url = get_site_icon_url( $size );
563 if ( ! $url ) {
564 return array();
565 }
566 }
567
568 return array(
569 array(
570 'type' => 'image',
571 'from' => 'blavatar',
572 'src' => $url,
573 'src_width' => $size,
574 'src_height' => $size,
575 'href' => $permalink,
576 'alt_text' => '',
577 ),
578 );
579 }
580
581 /**
582 * Gets a post image from the author avatar.
583 *
584 * @param int $post_id The post ID to check.
585 * @param int $size The size of the avatar to get.
586 * @param string $default The default image to use.
587 * @return array containing details of the image, or empty array if none.
588 */
589 public static function from_gravatar( $post_id, $size = 96, $default = false ) {
590 $post = get_post( $post_id );
591 $permalink = get_permalink( $post_id );
592
593 if ( ! $post instanceof WP_Post ) {
594 return array();
595 }
596
597 if ( function_exists( 'wpcom_get_avatar_url' ) ) {
598 $url = wpcom_get_avatar_url( $post->post_author, $size, $default, true );
599 if ( $url && is_array( $url ) ) {
600 $url = $url[0];
601 }
602 } else {
603 $url = get_avatar_url(
604 $post->post_author,
605 array(
606 'size' => $size,
607 'default' => $default,
608 )
609 );
610 }
611
612 return array(
613 array(
614 'type' => 'image',
615 'from' => 'gravatar',
616 'src' => $url,
617 'src_width' => $size,
618 'src_height' => $size,
619 'href' => $permalink,
620 'alt_text' => '',
621 ),
622 );
623 }
624
625 /**
626 * Run through the different methods that we have available to try to find a single good
627 * display image for this post.
628 *
629 * @param int $post_id Post ID.
630 * @param array $args Other arguments (currently width and height required for images where possible to determine).
631 * @return array|null containing details of the best image to be used, or null if no image is found.
632 */
633 public static function get_image( $post_id, $args = array() ) {
634 $image = null;
635
636 /**
637 * Fires before we find a single good image for a specific post.
638 *
639 * @since 2.2.0
640 *
641 * @param int $post_id Post ID.
642 */
643 do_action( 'jetpack_postimages_pre_get_image', $post_id );
644 $media = self::get_images( $post_id, $args );
645
646 if ( is_array( $media ) ) {
647 foreach ( $media as $item ) {
648 if ( 'image' === $item['type'] ) {
649 $image = $item;
650 break;
651 }
652 }
653 }
654
655 /**
656 * Fires after we find a single good image for a specific post.
657 *
658 * @since 2.2.0
659 *
660 * @param int $post_id Post ID.
661 */
662 do_action( 'jetpack_postimages_post_get_image', $post_id );
663
664 return $image;
665 }
666
667 /**
668 * Get an array containing a collection of possible images for this post, stopping once we hit a method
669 * that returns something useful.
670 *
671 * @param int $post_id Post ID.
672 * @param array $args Optional args, see defaults list for details.
673 * @return array containing images that would be good for representing this post
674 */
675 public static function get_images( $post_id, $args = array() ) {
676 // Figure out which image to attach to this post.
677 $media = array();
678
679 /**
680 * Filters the array of images that would be good for a specific post.
681 * This filter is applied before options ($args) filter the original array.
682 *
683 * @since 2.0.0
684 *
685 * @param array $media Array of images that would be good for a specific post.
686 * @param int $post_id Post ID.
687 * @param array $args Array of options to get images.
688 */
689 $media = apply_filters( 'jetpack_images_pre_get_images', $media, $post_id, $args );
690 if ( $media ) {
691 return $media;
692 }
693
694 $defaults = array(
695 'width' => 200, // Required minimum width (if possible to determine).
696 'height' => 200, // Required minimum height (if possible to determine).
697
698 'fallback_to_avatars' => false, // Optionally include Blavatar and Gravatar (in that order) in the image stack.
699 'avatar_size' => 96, // Used for both Grav and Blav.
700 'gravatar_default' => false, // Default image to use if we end up with no Gravatar.
701
702 'from_thumbnail' => true, // Use these flags to specify which methods to use to find an image.
703 'from_slideshow' => true,
704 'from_gallery' => true,
705 'from_attachment' => true,
706 'from_blocks' => true,
707 'from_html' => true,
708
709 'html_content' => '', // HTML string to pass to from_html().
710 );
711 $args = wp_parse_args( $args, $defaults );
712
713 $media = array();
714 if ( $args['from_thumbnail'] ) {
715 $media = self::from_thumbnail( $post_id, $args['width'], $args['height'] );
716 }
717 if ( ! $media && $args['from_slideshow'] ) {
718 $media = self::from_slideshow( $post_id, $args['width'], $args['height'] );
719 }
720 if ( ! $media && $args['from_gallery'] ) {
721 $media = self::from_gallery( $post_id );
722 }
723 if ( ! $media && $args['from_attachment'] ) {
724 $media = self::from_attachment( $post_id, $args['width'], $args['height'] );
725 }
726 if ( ! $media && $args['from_blocks'] ) {
727 if ( empty( $args['html_content'] ) ) {
728 $media = self::from_blocks( $post_id, $args['width'], $args['height'] ); // Use the post_id, which will load the content.
729 } else {
730 $media = self::from_blocks( $args['html_content'], $args['width'], $args['height'] ); // If html_content is provided, use that.
731 }
732 }
733 if ( ! $media && $args['from_html'] ) {
734 if ( empty( $args['html_content'] ) ) {
735 $media = self::from_html( $post_id, $args['width'], $args['height'] ); // Use the post_id, which will load the content.
736 } else {
737 $media = self::from_html( $args['html_content'], $args['width'], $args['height'] ); // If html_content is provided, use that.
738 }
739 }
740
741 if ( ! $media && $args['fallback_to_avatars'] ) {
742 $media = self::from_blavatar( $post_id, $args['avatar_size'] );
743 if ( ! $media ) {
744 $media = self::from_gravatar( $post_id, $args['avatar_size'], $args['gravatar_default'] );
745 }
746 }
747
748 /**
749 * Filters the array of images that would be good for a specific post.
750 * This filter is applied after options ($args) filter the original array.
751 *
752 * @since 2.0.0
753 *
754 * @param array $media Array of images that would be good for a specific post.
755 * @param int $post_id Post ID.
756 * @param array $args Array of options to get images.
757 */
758 return apply_filters( 'jetpack_images_get_images', $media, $post_id, $args );
759 }
760
761 /**
762 * Takes an image and base pixel dimensions and returns a srcset for the
763 * resized and cropped images, based on a fixed set of multipliers.
764 *
765 * @param array $image Array containing details of the image.
766 * @param int $base_width Base image width (i.e., the width at 1x).
767 * @param int $base_height Base image height (i.e., the height at 1x).
768 * @param bool $use_widths Whether to generate the srcset with widths instead of multipliers.
769 * @return string The srcset for the image.
770 */
771 public static function generate_cropped_srcset( $image, $base_width, $base_height, $use_widths = false ) {
772 $srcset = '';
773
774 if ( ! is_array( $image ) || empty( $image['src'] ) || empty( $image['src_width'] ) ) {
775 return $srcset;
776 }
777
778 $multipliers = array( 1, 1.5, 2, 3, 4 );
779 $srcset_values = array();
780 foreach ( $multipliers as $multiplier ) {
781 $srcset_width = (int) ( $base_width * $multiplier );
782 $srcset_height = (int) ( $base_height * $multiplier );
783 if ( $srcset_width < 1 || $srcset_width > $image['src_width'] ) {
784 break;
785 }
786
787 $srcset_url = self::fit_image_url(
788 $image['src'],
789 $srcset_width,
790 $srcset_height
791 );
792
793 if ( $use_widths ) {
794 $srcset_values[] = "{$srcset_url} {$srcset_width}w";
795 } else {
796 $srcset_values[] = "{$srcset_url} {$multiplier}x";
797 }
798 }
799
800 if ( count( $srcset_values ) > 1 ) {
801 $srcset = implode( ', ', $srcset_values );
802 }
803
804 return $srcset;
805 }
806
807 /**
808 * Takes an image URL and pixel dimensions then returns a URL for the
809 * resized and cropped image.
810 *
811 * @param string $src Image URL.
812 * @param int $width Image width.
813 * @param int $height Image height.
814 * @return string Transformed image URL
815 */
816 public static function fit_image_url( $src, $width, $height ) {
817 $width = (int) $width;
818 $height = (int) $height;
819
820 if ( $width < 1 || $height < 1 ) {
821 return $src;
822 }
823
824 // See if we should bypass WordPress.com SaaS resizing.
825 if ( has_filter( 'jetpack_images_fit_image_url_override' ) ) {
826 /**
827 * Filters the image URL used after dimensions are set by Photon.
828 *
829 * @since 3.3.0
830 *
831 * @param string $src Image URL.
832 * @param int $width Image width.
833 * @param int $width Image height.
834 */
835 return apply_filters( 'jetpack_images_fit_image_url_override', $src, $width, $height );
836 }
837
838 // If WPCOM hosted image use native transformations.
839 $img_host = wp_parse_url( $src, PHP_URL_HOST );
840 if ( '.files.wordpress.com' === substr( $img_host, -20 ) ) {
841 return add_query_arg(
842 array(
843 'w' => $width,
844 'h' => $height,
845 'crop' => 1,
846 ),
847 set_url_scheme( $src )
848 );
849 }
850
851 // Use image cdn magic.
852 if ( class_exists( Image_CDN_Core::class ) && method_exists( Image_CDN_Core::class, 'cdn_url' ) ) {
853 return Image_CDN_Core::cdn_url( $src, array( 'resize' => "$width,$height" ) );
854 }
855
856 // Arg... no way to resize image using WordPress.com infrastructure!
857 return $src;
858 }
859
860 /**
861 * Get HTML from given post content.
862 *
863 * @since 6.9.0
864 *
865 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
866 *
867 * @return array $html_info {
868 * @type string $html Post content.
869 * @type string $post_url Post URL.
870 * }
871 */
872 public static function get_post_html( $html_or_id ) {
873 if ( is_numeric( $html_or_id ) ) {
874 $post = get_post( $html_or_id );
875
876 if ( empty( $post ) || ! empty( $post->post_password ) ) {
877 return '';
878 }
879
880 $html_info = array(
881 'html' => $post->post_content, // DO NOT apply the_content filters here, it will cause loops.
882 'post_url' => get_permalink( $post->ID ),
883 );
884 } else {
885 $html_info = array(
886 'html' => $html_or_id,
887 'post_url' => '',
888 );
889 }
890 return $html_info;
891 }
892
893 /**
894 * Get info about a WordPress attachment.
895 *
896 * @since 6.9.0
897 *
898 * @param int $attachment_id Attachment ID.
899 * @param string $post_url URL of the post, if we have one.
900 * @param int $width Minimum Image width.
901 * @param int $height Minimum Image height.
902 * @return array|bool Image data or false if unavailable.
903 */
904 public static function get_attachment_data( $attachment_id, $post_url, $width, $height ) {
905 if ( empty( $attachment_id ) ) {
906 return false;
907 }
908
909 $meta = wp_get_attachment_metadata( $attachment_id );
910
911 if ( empty( $meta ) ) {
912 return false;
913 }
914
915 if ( ! empty( $meta['videopress'] ) ) {
916 // Use poster image for VideoPress videos.
917 $url = $meta['videopress']['poster'];
918 $meta_width = $meta['videopress']['width'];
919 $meta_height = $meta['videopress']['height'];
920 } elseif ( ! empty( $meta['thumb'] ) ) {
921 // On WordPress.com, VideoPress videos have a 'thumb' property with the
922 // poster image filename instead.
923 $media_url = wp_get_attachment_url( $attachment_id );
924 $url = str_replace( wp_basename( $media_url ), $meta['thumb'], $media_url );
925 $meta_width = $meta['width'];
926 $meta_height = $meta['height'];
927 } elseif ( wp_attachment_is( 'video', $attachment_id ) ) {
928 // We don't have thumbnail images for non-VideoPress videos - skip them.
929 return false;
930 } else {
931 if ( ! isset( $meta['width'] ) || ! isset( $meta['height'] ) ) {
932 return false;
933 }
934 $url = wp_get_attachment_url( $attachment_id );
935 $meta_width = $meta['width'];
936 $meta_height = $meta['height'];
937 }
938
939 if ( $meta_width < $width || $meta_height < $height ) {
940 return false;
941 }
942
943 return array(
944 'type' => 'image',
945 'from' => 'attachment',
946 'src' => $url,
947 'src_width' => $meta_width,
948 'src_height' => $meta_height,
949 'href' => $post_url,
950 'alt_text' => self::get_alt_text( $attachment_id ),
951 );
952 }
953
954 /**
955 * Get the alt text for an image or other media from the Media Library.
956 *
957 * @since 7.1
958 *
959 * @param int $attachment_id The Post ID of the media.
960 * @return string The alt text value or an empty string.
961 */
962 public static function get_alt_text( $attachment_id ) {
963 return (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
964 }
965
966 /**
967 * Get an image from a block.
968 *
969 * @since 7.8.0
970 *
971 * @param array $images Images found.
972 * @param array $block Block and its attributes.
973 * @param array $html_info Info about the post where the block is found.
974 * @param int $width Desired image width.
975 * @param int $height Desired image height.
976 *
977 * @return array Array of images found.
978 */
979 private static function get_images_from_block( $images, $block, $html_info, $width, $height ) {
980 /**
981 * Parse content from Core Image blocks.
982 * If it is an image block for an image hosted on our site, it will have an ID.
983 * If it does not have an ID, let `from_html` parse that content later,
984 * and extract an image if it has size parameters.
985 */
986 if (
987 'core/image' === $block['blockName']
988 && ! empty( $block['attrs']['id'] )
989 ) {
990 $images[] = self::get_attachment_data( $block['attrs']['id'], $html_info['post_url'], $width, $height );
991 } elseif (
992 'core/media-text' === $block['blockName']
993 && ! empty( $block['attrs']['mediaId'] )
994 ) {
995 $images[] = self::get_attachment_data( $block['attrs']['mediaId'], $html_info['post_url'], $width, $height );
996 } elseif (
997 /**
998 * Parse content from Core Gallery blocks as well from Jetpack's Tiled Gallery and Slideshow blocks.
999 * Gallery blocks include the ID of each one of the images in the gallery.
1000 */
1001 in_array( $block['blockName'], array( 'core/gallery', 'jetpack/tiled-gallery', 'jetpack/slideshow' ), true )
1002 && ! empty( $block['attrs']['ids'] )
1003 ) {
1004 foreach ( $block['attrs']['ids'] as $img_id ) {
1005 $images[] = self::get_attachment_data( $img_id, $html_info['post_url'], $width, $height );
1006 }
1007 } elseif (
1008 /**
1009 * Parse content from Jetpack's Story block.
1010 */
1011 'jetpack/story' === $block['blockName']
1012 && ! empty( $block['attrs']['mediaFiles'] )
1013 ) {
1014 foreach ( $block['attrs']['mediaFiles'] as $media_file ) {
1015 if ( ! empty( $media_file['id'] ) ) {
1016 $images[] = self::get_attachment_data( $media_file['id'], $html_info['post_url'], $width, $height );
1017 }
1018 }
1019 }
1020
1021 return $images;
1022 }
1023
1024 /**
1025 * Check if a block has inner blocks.
1026 *
1027 * @since 7.8.0
1028 *
1029 * @param array $block Block and its attributes.
1030 *
1031 * @return bool
1032 */
1033 private static function is_nested_block( $block ) {
1034 if ( ! empty( $block['innerBlocks'] ) ) {
1035 return true;
1036 }
1037
1038 return false;
1039 }
1040 }
1041