PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.8
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.8
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / inc / sitemaps / class-sitemap-image-parser.php

class-sitemap-image-parser.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.8, at inc/sitemaps/class-sitemap-image-parser.php

491 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\XML_Sitemaps
6 */
7
8 /**
9 * Parses images from the given post.
10 */
11 class WPSEO_Sitemap_Image_Parser {
12
13 /**
14 * Holds the home_url() value to speed up loops.
15 *
16 * @var string
17 */
18 protected $home_url = '';
19
20 /**
21 * Holds site URL hostname.
22 *
23 * @var string
24 */
25 protected $host = '';
26
27 /**
28 * Holds site URL protocol.
29 *
30 * @var string
31 */
32 protected $scheme = 'http';
33
34 /**
35 * Cached set of attachments for multiple posts.
36 *
37 * @var array
38 */
39 protected $attachments = [];
40
41 /**
42 * Holds blog charset value for use in DOM parsing.
43 *
44 * @var string
45 */
46 protected $charset = 'UTF-8';
47
48 /**
49 * Set up URL properties for reuse.
50 */
51 public function __construct() {
52
53 $this->home_url = home_url();
54 $parsed_home = wp_parse_url( $this->home_url );
55
56 if ( ! empty( $parsed_home['host'] ) ) {
57 $this->host = str_replace( 'www.', '', $parsed_home['host'] );
58 }
59
60 if ( ! empty( $parsed_home['scheme'] ) ) {
61 $this->scheme = $parsed_home['scheme'];
62 }
63
64 $this->charset = esc_attr( get_bloginfo( 'charset' ) );
65 }
66
67 /**
68 * Get set of image data sets for the given post.
69 *
70 * @param object $post Post object to get images for.
71 *
72 * @return array
73 */
74 public function get_images( $post ) {
75
76 $images = [];
77
78 if ( ! is_object( $post ) ) {
79 return $images;
80 }
81
82 $thumbnail_id = get_post_thumbnail_id( $post->ID );
83
84 if ( $thumbnail_id ) {
85
86 $src = $this->get_absolute_url( $this->image_url( $thumbnail_id ) );
87 $images[] = $this->get_image_item( $post, $src );
88 }
89
90 /**
91 * Filter: 'wpseo_sitemap_content_before_parse_html_images' - Filters the post content
92 * before it is parsed for images.
93 *
94 * @param string $content The raw/unprocessed post content.
95 */
96 $content = apply_filters( 'wpseo_sitemap_content_before_parse_html_images', $post->post_content );
97
98 $unfiltered_images = $this->parse_html_images( $content );
99
100 foreach ( $unfiltered_images as $image ) {
101 $images[] = $this->get_image_item( $post, $image['src'] );
102 }
103
104 foreach ( $this->parse_galleries( $content, $post->ID ) as $attachment ) {
105 $src = $this->get_absolute_url( $this->image_url( $attachment->ID ) );
106 $images[] = $this->get_image_item( $post, $src );
107 }
108
109 if ( $post->post_type === 'attachment' && wp_attachment_is_image( $post ) ) {
110 $src = $this->get_absolute_url( $this->image_url( $post->ID ) );
111 $images[] = $this->get_image_item( $post, $src );
112 }
113
114 foreach ( $images as $key => $image ) {
115
116 if ( empty( $image['src'] ) ) {
117 unset( $images[ $key ] );
118 }
119 }
120
121 /**
122 * Filter images to be included for the post in XML sitemap.
123 *
124 * @param array $images Array of image items.
125 * @param int $post_id ID of the post.
126 */
127 $images = apply_filters( 'wpseo_sitemap_urlimages', $images, $post->ID );
128
129 return $images;
130 }
131
132 /**
133 * Get the images in the term description.
134 *
135 * @param object $term Term to get images from description for.
136 *
137 * @return array
138 */
139 public function get_term_images( $term ) {
140
141 $images = $this->parse_html_images( $term->description );
142
143 foreach ( $this->parse_galleries( $term->description ) as $attachment ) {
144
145 $images[] = [
146 'src' => $this->get_absolute_url( $this->image_url( $attachment->ID ) ),
147 ];
148 }
149
150 return $images;
151 }
152
153 /**
154 * Parse `<img />` tags in content.
155 *
156 * @param string $content Content string to parse.
157 *
158 * @return array
159 */
160 private function parse_html_images( $content ) {
161
162 $images = [];
163
164 if ( ! class_exists( 'DOMDocument' ) ) {
165 return $images;
166 }
167
168 if ( empty( $content ) ) {
169 return $images;
170 }
171
172 // Prevent DOMDocument from bubbling warnings about invalid HTML.
173 libxml_use_internal_errors( true );
174
175 $post_dom = new DOMDocument();
176 $post_dom->loadHTML( '<?xml encoding="' . $this->charset . '">' . $content );
177
178 // Clear the errors, so they don't get kept in memory.
179 libxml_clear_errors();
180
181 /**
182 * Image attribute.
183 *
184 * @var DOMElement $img
185 */
186 foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) {
187
188 $src = $img->getAttribute( 'src' );
189
190 if ( empty( $src ) ) {
191 continue;
192 }
193
194 $class = $img->getAttribute( 'class' );
195
196 if ( // This detects WP-inserted images, which we need to upsize. R.
197 ! empty( $class )
198 && ( strpos( $class, 'size-full' ) === false )
199 && preg_match( '|wp-image-(?P<id>\d+)|', $class, $matches )
200 && get_post_status( $matches['id'] )
201 ) {
202 $src = $this->image_url( $matches['id'] );
203 }
204
205 $src = $this->get_absolute_url( $src );
206
207 if ( strpos( $src, $this->host ) === false ) {
208 continue;
209 }
210
211 if ( $src !== esc_url( $src ) ) {
212 continue;
213 }
214
215 $images[] = [
216 'src' => $src,
217 ];
218 }
219
220 return $images;
221 }
222
223 /**
224 * Parse gallery shortcodes in a given content.
225 *
226 * @param string $content Content string.
227 * @param int $post_id Optional. ID of post being parsed.
228 *
229 * @return array Set of attachment objects.
230 */
231 protected function parse_galleries( $content, $post_id = 0 ) {
232
233 $attachments = [];
234 $galleries = $this->get_content_galleries( $content );
235
236 foreach ( $galleries as $gallery ) {
237
238 $id = $post_id;
239
240 if ( ! empty( $gallery['id'] ) ) {
241 $id = intval( $gallery['id'] );
242 }
243
244 // Forked from core gallery_shortcode() to have exact same logic. R.
245 if ( ! empty( $gallery['ids'] ) ) {
246 $gallery['include'] = $gallery['ids'];
247 }
248
249 $gallery_attachments = $this->get_gallery_attachments( $id, $gallery );
250
251 $attachments = array_merge( $attachments, $gallery_attachments );
252 }
253
254 return array_unique( $attachments, SORT_REGULAR );
255 }
256
257 /**
258 * Retrieves galleries from the passed content.
259 *
260 * Forked from core to skip executing shortcodes for performance.
261 *
262 * @param string $content Content to parse for shortcodes.
263 *
264 * @return array A list of arrays, each containing gallery data.
265 */
266 protected function get_content_galleries( $content ) {
267
268 $galleries = [];
269
270 if ( ! preg_match_all( '/' . get_shortcode_regex( [ 'gallery' ] ) . '/s', $content, $matches, PREG_SET_ORDER ) ) {
271 return $galleries;
272 }
273
274 foreach ( $matches as $shortcode ) {
275
276 $attributes = shortcode_parse_atts( $shortcode[3] );
277
278 if ( $attributes === '' ) { // Valid shortcode without any attributes. R.
279 $attributes = [];
280 }
281
282 $galleries[] = $attributes;
283 }
284
285 return $galleries;
286 }
287
288 /**
289 * Get image item array with filters applied.
290 *
291 * @param WP_Post $post Post object for the context.
292 * @param string $src Image URL.
293 *
294 * @return array
295 */
296 protected function get_image_item( $post, $src ) {
297
298 $image = [];
299
300 /**
301 * Filter image URL to be included in XML sitemap for the post.
302 *
303 * @param string $src Image URL.
304 * @param object $post Post object.
305 */
306 $image['src'] = apply_filters( 'wpseo_xml_sitemap_img_src', $src, $post );
307
308 /**
309 * Filter image data to be included in XML sitemap for the post.
310 *
311 * @param array $image {
312 * Array of image data.
313 *
314 * @type string $src Image URL.
315 * }
316 *
317 * @param object $post Post object.
318 */
319 return apply_filters( 'wpseo_xml_sitemap_img', $image, $post );
320 }
321
322 /**
323 * Get attached image URL with filters applied. Adapted from core for speed.
324 *
325 * @param int $post_id ID of the post.
326 *
327 * @return string
328 */
329 private function image_url( $post_id ) {
330
331 static $uploads;
332
333 if ( empty( $uploads ) ) {
334 $uploads = wp_upload_dir();
335 }
336
337 if ( $uploads['error'] !== false ) {
338 return '';
339 }
340
341 $file = get_post_meta( $post_id, '_wp_attached_file', true );
342
343 if ( empty( $file ) ) {
344 return '';
345 }
346
347 // Check that the upload base exists in the file location.
348 if ( strpos( $file, $uploads['basedir'] ) === 0 ) {
349 $src = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
350 }
351 elseif ( strpos( $file, 'wp-content/uploads' ) !== false ) {
352 $src = $uploads['baseurl'] . substr( $file, ( strpos( $file, 'wp-content/uploads' ) + 18 ) );
353 }
354 else {
355 // It's a newly uploaded file, therefore $file is relative to the baseurl.
356 $src = $uploads['baseurl'] . '/' . $file;
357 }
358
359 return apply_filters( 'wp_get_attachment_url', $src, $post_id );
360 }
361
362 /**
363 * Make absolute URL for domain or protocol-relative one.
364 *
365 * @param string $src URL to process.
366 *
367 * @return string
368 */
369 protected function get_absolute_url( $src ) {
370
371 if ( empty( $src ) || ! is_string( $src ) ) {
372 return $src;
373 }
374
375 if ( YoastSEO()->helpers->url->is_relative( $src ) === true ) {
376
377 if ( $src[0] !== '/' ) {
378 return $src;
379 }
380
381 // The URL is relative, we'll have to make it absolute.
382 return $this->home_url . $src;
383 }
384
385 if ( strpos( $src, 'http' ) !== 0 ) {
386 // Protocol relative URL, we add the scheme as the standard requires a protocol.
387 return $this->scheme . ':' . $src;
388 }
389
390 return $src;
391 }
392
393 /**
394 * Returns the attachments for a gallery.
395 *
396 * @param int $id The post ID.
397 * @param array $gallery The gallery config.
398 *
399 * @return array The selected attachments.
400 */
401 protected function get_gallery_attachments( $id, $gallery ) {
402
403 // When there are attachments to include.
404 if ( ! empty( $gallery['include'] ) ) {
405 return $this->get_gallery_attachments_for_included( $gallery['include'] );
406 }
407
408 // When $id is empty, just return empty array.
409 if ( empty( $id ) ) {
410 return [];
411 }
412
413 return $this->get_gallery_attachments_for_parent( $id, $gallery );
414 }
415
416 /**
417 * Returns the attachments for the given ID.
418 *
419 * @param int $id The post ID.
420 * @param array $gallery The gallery config.
421 *
422 * @return array The selected attachments.
423 */
424 protected function get_gallery_attachments_for_parent( $id, $gallery ) {
425 $query = [
426 'posts_per_page' => -1,
427 'post_parent' => $id,
428 ];
429
430 // When there are posts that should be excluded from result set.
431 if ( ! empty( $gallery['exclude'] ) ) {
432 $query['post__not_in'] = wp_parse_id_list( $gallery['exclude'] );
433 }
434
435 return $this->get_attachments( $query );
436 }
437
438 /**
439 * Returns an array with attachments for the post IDs that will be included.
440 *
441 * @param array $included_ids Array with IDs to include.
442 *
443 * @return array The found attachments.
444 */
445 protected function get_gallery_attachments_for_included( $included_ids ) {
446 $ids_to_include = wp_parse_id_list( $included_ids );
447 $attachments = $this->get_attachments(
448 [
449 'posts_per_page' => count( $ids_to_include ),
450 'post__in' => $ids_to_include,
451 ]
452 );
453
454 $gallery_attachments = [];
455 foreach ( $attachments as $key => $val ) {
456 $gallery_attachments[ $val->ID ] = $val;
457 }
458
459 return $gallery_attachments;
460 }
461
462 /**
463 * Returns the attachments.
464 *
465 * @param array $args Array with query args.
466 *
467 * @return array The found attachments.
468 */
469 protected function get_attachments( $args ) {
470 $default_args = [
471 'post_status' => 'inherit',
472 'post_type' => 'attachment',
473 'post_mime_type' => 'image',
474
475 // Defaults taken from function get_posts.
476 'orderby' => 'date',
477 'order' => 'DESC',
478 'meta_key' => '',
479 'meta_value' => '',
480 'suppress_filters' => true,
481 'ignore_sticky_posts' => true,
482 'no_found_rows' => true,
483 ];
484
485 $args = wp_parse_args( $args, $default_args );
486
487 $get_attachments = new WP_Query();
488 return $get_attachments->query( $args );
489 }
490 }
491