PluginProbe
WPSSO Core – Complete Schema Markup and Meta Tags / 22.7.0
WPSSO Core – Complete Schema Markup and Meta Tags v22.7.0
22.7.0 22.6.1 22.6.0 22.5.3 22.5.2 22.5.1 22.4.0 22.3.0 22.2.1 22.2.0 22.1.3 22.1.2 22.1.1 22.1.0 22.0.0 21.13.3 21.13.2 trunk 21.13.1
wpsso / lib / media.php

media.php in WPSSO Core – Complete Schema Markup and Meta Tags 22.7.0, at lib/media.php

4,141 lines 122.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * License: GPLv3
4 * License URI: https://www.gnu.org/licenses/gpl.txt
5 * Copyright 2012-2026 Jean-Sebastien Morisset (https://wpsso.com/)
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9
10 die( 'These aren\'t the droids you\'re looking for.' );
11 }
12
13 if ( ! defined( 'WPSSO_PLUGINDIR' ) ) {
14
15 die( 'Do. Or do not. There is no try.' );
16 }
17
18 if ( ! class_exists( 'WpssoMedia' ) ) {
19
20 class WpssoMedia {
21
22 private $p; // Wpsso class object.
23 private $filters; // WpssoMediaFilters class object.
24
25 private $default_content_img_preg = array(
26 'html_tag' => 'img',
27 'pid_attr' => 'data-[a-z]+-pid',
28 );
29
30 private static $image_src_args = null;
31
32 public function __construct( &$plugin ) {
33
34 $this->p =& $plugin;
35
36 if ( $this->p->debug->enabled ) {
37
38 $this->p->debug->mark();
39 }
40
41 require_once WPSSO_PLUGINDIR . 'lib/media-filters.php';
42
43 $this->filters = new WpssoMediaFilters( $plugin );
44
45 add_action( 'init', array( $this, 'allow_img_data_attributes' ) );
46 add_action( 'post-upload-ui', array( $this, 'show_post_upload_ui_message' ) );
47
48 add_filter( 'editor_max_image_size', array( $this, 'maybe_adjust_max_image_size' ), 10, 3 );
49 add_filter( 'image_make_intermediate_size', array( $this, 'maybe_update_intermediate_size_filepath' ), -5000, 1 );
50 add_filter( 'wp_image_resize_identical_dimensions', array( $this, 'maybe_resize_fuzzy_dimensions' ), PHP_INT_MAX, 1 );
51 add_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_attachment_image_attributes' ), 10, 2 );
52 add_filter( 'get_image_tag', array( $this, 'get_image_tag' ), 10, 6 );
53 }
54
55 public function allow_img_data_attributes() {
56
57 global $allowedposttags;
58
59 $allowedposttags[ 'img' ][ 'data-wp-pid' ] = true;
60 }
61
62 public function show_post_upload_ui_message() {
63
64 $msg_transl = '<strong>' . __( 'Minimum image dimension to satisfy all %1$d image sizes: %2$dpx width by %3$dpx height.', 'wpsso' ) . '</strong>';
65
66 list( $min_width, $min_height, $size_count ) = SucomUtilWP::get_minimum_image_wh();
67
68 echo '<p class="minimum-image-dimensions">';
69
70 echo sprintf( $msg_transl, $size_count, $min_width, $min_height );
71
72 echo '</p>' . "\n";
73
74 /*
75 * Hide the "suggested image dimensions" as they are less than the minimum we suggest here.
76 */
77 echo '<style type="text/css">p.suggested-dimensions{ display:none; }</style>' . "\n";
78 }
79
80 /*
81 * Note that $size can be a string or an array().
82 */
83 public function maybe_adjust_max_image_size( $max_image_size = array(), $size = '', $context = '' ) {
84
85 /*
86 * Allow our sizes to exceed the editor width.
87 */
88 if ( is_string( $size ) && 0 === strpos( $size, 'wpsso-' ) ) {
89
90 $max_image_size = array( 0, 0 );
91 }
92
93 return $max_image_size;
94 }
95
96 /*
97 * By default, WordPress adds only the resolution of the resized image to the file name. If the image size is ours,
98 * then add crop information to the file name as well. This allows for different cropped versions for the same
99 * image resolution.
100 *
101 * Example:
102 *
103 * unicorn-wallpaper-1200x628.jpg
104 * unicorn-wallpaper-1200x628-cropped.jpg
105 * unicorn-wallpaper-1200x628-cropped-center-top.jpg
106 */
107 public function maybe_update_intermediate_size_filepath( $filepath ) {
108
109 if ( $this->p->debug->enabled ) {
110
111 $this->p->debug->mark();
112 }
113
114 /*
115 * get_attachment_image_src() in the WpssoMedia class saves / sets the image information (pid, size_name,
116 * etc) before calling the image_make_intermediate_size() function (and others). Returns null if no image
117 * information was set (presumably because we arrived here without passing through our own method).
118 */
119 $img_src_args = self::get_image_src_args();
120
121 if ( empty( $img_src_args[ 'size_name' ] ) ) {
122
123 if ( $this->p->debug->enabled ) {
124
125 $this->p->debug->log( 'skipping ' . $filepath . ': size name is empty' );
126 }
127
128 return $filepath;
129
130 } elseif ( 0 !== strpos( $img_src_args[ 'size_name' ], 'wpsso-' ) ) {
131
132 if ( $this->p->debug->enabled ) {
133
134 $this->p->debug->log( 'skipping ' . $filepath . ': size name is ' . $img_src_args[ 'size_name' ] );
135 }
136
137 return $filepath;
138 }
139
140 /*
141 * If the resized image is not cropped, then leave the file name as-is.
142 */
143 $size_is_cropped = $this->p->util->is_size_cropped( $img_src_args[ 'size_name' ], $img_src_args[ 'pid' ] );
144
145 if ( ! $size_is_cropped ) {
146
147 return $filepath;
148 }
149
150 /*
151 * Example $size_info = Array (
152 * [size_name] => wpsso-opengraph,
153 * [attachment_id] => false,
154 * [width] => 1200,
155 * [height] => 630,
156 * [crop] => 1,
157 * [is_cropped] => 1,
158 * [dims_transl] => 1200x630 cropped,
159 * [label_transl] => Open Graph (Facebook and oEmbed),
160 * [opt_prefix] => og,
161 * );
162 */
163 $size_info = $this->p->util->get_size_info( $img_src_args[ 'size_name' ], $img_src_args[ 'pid' ] ); // Uses a local static cache.
164
165 $new_filepath = $this->maybe_update_cropped_image_filepath( $filepath, $size_info );
166
167 if ( $filepath !== $new_filepath ) { // Just in case
168
169 /*
170 * Determine if an image can be renamed or needs to be copied.
171 *
172 * Check for conflicting image sizes (ie. same dimensions uncropped, or same dimensions from other WordPress sizes).
173 */
174 $can_rename = $this->can_rename_image_filename( $img_src_args[ 'size_name' ], $img_src_args[ 'pid' ] );
175
176 if ( $can_rename ) {
177
178 if ( rename( $filepath, $new_filepath ) ) { // No other plugin/theme uses the same dimensions.
179
180 return $new_filepath; // Return the new file path on success.
181
182 } elseif ( copy( $filepath, $new_filepath ) ) { // If rename failed, then copy and delete.
183
184 if ( unlink( $filepath ) ) { // Remove the old file path.
185
186 return $new_filepath; // Return the new file path on success.
187
188 } else { // If deleting the original failed, then never mind. :)
189
190 unlink( $new_filepath );
191 }
192 }
193
194 } elseif ( copy( $filepath, $new_filepath ) ) {
195
196 return $new_filepath; // Return the new file path on successful copy.
197 }
198 }
199
200 return $filepath;
201 }
202
203 /*
204 * Hooked to the 'wp_image_resize_identical_dimensions' filter added in WP v5.3.
205 */
206 public function maybe_resize_fuzzy_dimensions( $resize ) {
207
208 $img_src_args = self::get_image_src_args();
209
210 if ( empty( $img_src_args[ 'size_name' ] ) || 0 !== strpos( $img_src_args[ 'size_name' ], 'wpsso-' ) ) {
211
212 return $resize;
213 }
214
215 return true;
216 }
217
218 /*
219 * $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment );
220 */
221 public function add_attachment_image_attributes( $attr, $attachment ) {
222
223 $attr[ 'data-wp-pid' ] = $attachment->ID;
224
225 return $attr;
226 }
227
228 /*
229 * $html = apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size );
230 */
231 public function get_image_tag( $html, $id, $alt, $title, $align, $size ) {
232
233 $html = SucomUtil::insert_html_tag_attributes( $html, array( 'data-wp-pid' => $id ) );
234
235 return $html;
236 }
237
238 public function get_all_previews( $num, array $mod, $md_pre = 'og', $force_prev = false ) {
239
240 /*
241 * The get_all_videos() method uses the 'og_vid_max' argument as part of its caching salt, so re-use the
242 * original number to get all possible videos (from its cache), then maybe limit the number of preview
243 * images if necessary.
244 */
245 $max_nums = $this->p->util->get_max_nums( $mod );
246
247 $mt_videos = $this->get_all_videos( $max_nums[ 'og_vid_max' ], $mod, $md_pre, $force_prev );
248
249 $this->p->util->clear_uniq_urls( $uniq_context = array( 'preview' ), $mod );
250
251 $mt_images = array();
252
253 foreach ( $mt_videos as $num => $mt_single_video ) {
254
255 $image_url = SucomUtil::get_first_mt_media_url( $mt_single_video );
256
257 /*
258 * Check preview images for duplicates since the same videos may be available in different formats
259 * (application/x-shockwave-flash and text/html for example).
260 */
261 if ( $image_url ) {
262
263 if ( $this->p->util->is_uniq_url( $image_url, $uniq_context = 'preview', $mod ) ) {
264
265 $mt_single_image = SucomUtil::preg_grep_keys( '/^og:image/', $mt_single_video );
266
267 if ( $this->p->util->push_max( $mt_images, $mt_single_image, $num ) ) {
268
269 return $mt_images;
270 }
271 }
272 }
273
274 unset( $mt_videos[ $num ] );
275 }
276
277 return $mt_images;
278 }
279
280 /*
281 * Returns an array of single video associative arrays.
282 */
283 public function get_all_videos( $num, array $mod, $md_pre = 'og', $force_prev = false ) {
284
285 $cache_salt = array(
286 'num' => $num,
287 'mod' => $mod,
288 'md_pre' => $md_pre,
289 'force_prev' => $force_prev,
290 );
291
292 if ( $this->p->debug->enabled ) {
293
294 $this->p->debug->mark( 'getting all videos' ); // Begin timer.
295
296 $this->p->debug->log_args( $cache_salt );
297 }
298
299 static $local_fifo = array();
300
301 $cache_id = md5( SucomUtil::get_array_pretty( $cache_salt, $flatten = true ) );
302
303 if ( $this->p->debug->enabled ) {
304
305 $this->p->debug->log( 'cache id = ' . $cache_id );
306 }
307
308 if ( isset( $local_fifo[ $cache_id ] ) ) {
309
310 if ( $this->p->debug->enabled ) {
311
312 $this->p->debug->log( 'returning video data from local cache' );
313
314 $this->p->debug->mark( 'getting all videos' ); // End timer.
315 }
316
317 return $local_fifo[ $cache_id ];
318 }
319
320 /*
321 * Maybe limit the number of array elements.
322 */
323 $local_fifo = SucomUtil::array_slice_fifo( $local_fifo, WPSSO_CACHE_ARRAY_FIFO_MAX );
324
325 $local_fifo[ $cache_id ] = array();
326
327 $mt_videos =& $local_fifo[ $cache_id ]; // Set reference variable.
328
329 $this->p->util->clear_uniq_urls( array( 'video', 'video_details' ), $mod );
330
331 $add_vid_prev = empty( $this->p->options[ 'og_vid_prev_img' ] ) ? false : true; // Change value from 0/1 to false/true.
332
333 if ( $this->p->debug->enabled ) {
334
335 $this->p->debug->log( '$add_vid_prev is ' . ( $add_vid_prev ? 'true' : 'false' ) );
336 }
337
338 $num_diff = SucomUtil::array_count_diff( $mt_videos, $num );
339
340 /*
341 * Get video information and preview enable/disable option from the post/term/user meta.
342 */
343 if ( is_object( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
344
345 /*
346 * Note that get_options() returns null if an index key is not found.
347 */
348 if ( ( $mod_vid_prev = $mod[ 'obj' ]->get_options( $mod[ 'id' ], 'og_vid_prev_img' ) ) !== null ) { // Returns null, 0, or 1.
349
350 if ( $this->p->debug->enabled ) {
351
352 $this->p->debug->log( '$mod_vid_prev is ' . ( $mod_vid_prev ? 'true' : 'false' ) );
353 }
354
355 $add_vid_prev = empty( $mod_vid_prev ) ? false : true; // Change value from 0/1 to false/true.
356 }
357
358 if ( $this->p->debug->enabled ) {
359
360 $this->p->debug->mark( 'checking for videos in ' . $mod[ 'name' ] . ' options' ); // Begin timer.
361 }
362
363 /*
364 * get_og_videos() converts the $md_pre value to an array and always checks for 'og' metadata as a fallback.
365 */
366 $mt_videos = array_merge( $mt_videos, $mod[ 'obj' ]->get_og_videos( $num_diff, $mod[ 'id' ], $md_pre ) );
367
368 if ( $this->p->debug->enabled ) {
369
370 $this->p->debug->mark( 'checking for videos in ' . $mod[ 'name' ] . ' options' ); // End timer.
371 }
372 }
373
374 $num_diff = SucomUtil::array_count_diff( $mt_videos, $num );
375
376 /*
377 * Maybe get more videos from the comment or post content.
378 */
379 if ( $mod[ 'is_comment' ] || $mod[ 'is_post' ] ) {
380
381 if ( ! $this->p->util->is_maxed( $mt_videos, $num ) ) {
382
383 if ( $this->p->debug->enabled ) {
384
385 $this->p->debug->mark( 'checking for videos in the ' . $mod[ 'name' ] . ' content' ); // Begin timer.
386 }
387
388 $mt_videos = array_merge( $mt_videos, $this->get_content_videos( $num_diff, $mod ) );
389
390 if ( $this->p->debug->enabled ) {
391
392 $this->p->debug->mark( 'checking for videos in the ' . $mod[ 'name' ] . ' content' ); // End timer.
393 }
394 }
395 }
396
397 $this->p->util->slice_max( $mt_videos, $num ); // Maybe trim the $mt_videos array.
398
399 /*
400 * Maybe remove the image meta tags (aka video preview).
401 */
402 if ( empty( $add_vid_prev ) && empty( $force_prev ) ) {
403
404 if ( $this->p->debug->enabled ) {
405
406 $this->p->debug->log( 'removing video preview images: $add_vid_prev and $force_prev are both false' );
407 }
408
409 foreach ( $mt_videos as &$mt_single_video ) { // Uses reference.
410
411 $mt_single_video = SucomUtil::preg_grep_keys( '/^og:image/', $mt_single_video, $invert = true );
412
413 $mt_single_video[ 'og:video:has_image' ] = false;
414 }
415 }
416
417 /*
418 * Get custom video information from post/term/user meta data for the FIRST video.
419 *
420 * If $md_pre is 'none' (special index keyword), then don't load any custom video information.
421 *
422 * The og:video:title and og:video:description meta tags are not standard and their values will only appear in Schema markup.
423 */
424 if ( is_object( $mod[ 'obj' ] ) && $mod[ 'id' ] && $md_pre !== 'none' ) {
425
426 foreach ( $mt_videos as &$mt_single_video ) { // Uses reference.
427
428 foreach ( array(
429 'og_vid_title' => 'og:video:title',
430 'og_vid_desc' => 'og:video:description',
431 'og_vid_stream_url' => 'og:video:stream_url', // VideoObject contentUrl.
432 'og_vid_width' => 'og:video:width',
433 'og_vid_height' => 'og:video:height',
434 'og_vid_upload' => 'og:video:upload_date',
435 ) as $md_key => $mt_name ) {
436
437 if ( 'og_vid_upload' === $md_key ) {
438
439 if ( ! empty( $mt_single_video[ 'og:video:upload_date' ] ) ) {
440
441 /*
442 * Use the existing upload date, time, and timezone as defaults.
443 */
444 $value = WpssoSchema::get_mod_date_iso( $mod, $md_key, $mt_single_video[ 'og:video:upload_date' ] );
445
446 } else $value = WpssoSchema::get_mod_date_iso( $mod, $md_key );
447
448 } else {
449
450 /*
451 * Note that get_options() returns null if an index key is not found.
452 */
453 $value = $mod[ 'obj' ]->get_options( $mod[ 'id' ], $md_key );
454 }
455
456 if ( ! empty( $value ) ) { // Must be a non-empty string.
457
458 $mt_single_video[ $mt_name ] = $value;
459 }
460 }
461
462 break; // Only do the first video.
463 }
464 }
465
466 if ( $this->p->debug->enabled ) {
467
468 $this->p->debug->log( 'creating $mt_extend array' );
469 }
470
471 $mt_extend = array();
472
473 foreach ( $mt_videos as &$mt_single_video ) { // Uses reference.
474
475 if ( ! is_array( $mt_single_video ) ) { // Just in case.
476
477 if ( $this->p->debug->enabled ) {
478
479 $this->p->debug->log( 'video ignored: $mt_single_video is not an array' );
480 }
481
482 continue;
483 }
484
485 if ( $this->p->debug->enabled ) {
486
487 $this->p->debug->log( '' );
488 }
489
490 if ( ! empty( $mt_single_video[ 'og:video:embed_url' ] ) && 'text/html' !== $mt_single_video[ 'og:video:type' ] ) {
491
492 if ( $this->p->debug->enabled ) {
493
494 $this->p->debug->log( 'adding og:video:type text/html for ' . $mt_single_video[ 'og:video:embed_url' ] );
495 }
496
497 /*
498 * Start with a fresh copy of all og meta tags.
499 */
500 $og_single_embed = SucomUtil::get_mt_video_seed( 'og', $mt_single_video, false );
501
502 /*
503 * Use only og meta tags, excluding the facebook applink meta tags.
504 */
505 $og_single_embed = SucomUtil::preg_grep_keys( '/^og:/', $og_single_embed );
506
507 unset( $og_single_embed[ 'og:video:secure_url' ] ); // Just in case.
508
509 $og_single_embed[ 'og:video:url' ] = $mt_single_video[ 'og:video:embed_url' ];
510 $og_single_embed[ 'og:video:has_video' ] = true; // Used by video API modules.
511 $og_single_embed[ 'og:video:type' ] = 'text/html';
512
513 /*
514 * Embedded videos may not have width / height information defined.
515 */
516 foreach ( array( 'og:video:width', 'og:video:height' ) as $mt_name ) {
517
518 if ( isset( $og_single_embed[ $mt_name ] ) && $og_single_embed[ $mt_name ] === '' ) {
519
520 unset( $og_single_embed[ $mt_name ] );
521 }
522 }
523
524 /*
525 * Add application/x-shockwave-flash video first and the text/html video second.
526 */
527 if ( SucomUtil::get_first_mt_media_url( $mt_single_video, $mt_media_pre = 'og:video',
528 $mt_suffixes = array( ':secure_url', ':url', '' ) ) ) {
529
530 $mt_extend[] = $mt_single_video;
531 }
532
533 $mt_extend[] = $og_single_embed;
534
535 } else $mt_extend[] = $mt_single_video;
536 }
537
538 if ( $this->p->debug->enabled ) {
539
540 $this->p->debug->log( 'returning ' . count( $mt_extend ) . ' videos' );
541
542 $this->p->debug->log_arr( 'mt_extend', $mt_extend );
543
544 $this->p->debug->mark( 'getting all videos' ); // End timer.
545 }
546
547 /*
548 * Update the local static cache and return the videos array.
549 */
550 return $mt_videos = $mt_extend;
551 }
552
553 /*
554 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
555 */
556 public function get_thumbnail_url( $size_names, array $mod, $md_pre = 'og' ) {
557
558 if ( $this->p->debug->enabled ) {
559
560 $this->p->debug->mark();
561 }
562
563 $mt_ret = $this->get_all_images( $num = 1, $size_names, $mod, $md_pre );
564
565 return SucomUtil::get_first_mt_media_url( $mt_ret );
566 }
567
568 /*
569 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
570 */
571 public function get_all_images( $num, $size_names, array $mod, $md_pre = 'og' ) {
572
573 if ( $this->p->debug->enabled ) {
574
575 $this->p->debug->mark( 'getting all images' ); // Begin timer.
576
577 $this->p->debug->log_args( array(
578 'num' => $num,
579 'size_names' => $size_names,
580 'mod' => $mod,
581 'md_pre' => $md_pre,
582 ) );
583 }
584
585 $mt_ret = array();
586
587 if ( $this->p->debug->enabled ) {
588
589 $this->p->debug->log( 'getting all video preview images' );
590 }
591
592 $preview_images = $this->get_all_previews( $num, $mod, $md_pre );
593
594 if ( empty( $preview_images ) ) {
595
596 if ( $this->p->debug->enabled ) {
597
598 $this->p->debug->log( 'no video preview images' );
599 }
600
601 } else {
602
603 if ( $this->p->debug->enabled ) {
604
605 $this->p->debug->log( 'merging video preview images' );
606 }
607
608 $mt_ret = array_merge( $mt_ret, $preview_images );
609 }
610
611 $num_diff = SucomUtil::array_count_diff( $mt_ret, $num );
612 $size_names = $this->p->util->get_image_size_names( $size_names ); // Always returns an array.
613
614 if ( $this->p->debug->enabled ) {
615
616 $this->p->debug->log_arr( 'size_names', $size_names );
617 }
618
619 if ( $num_diff >= 1 ) { // Just in case.
620
621 $this->p->util->clear_uniq_urls( $size_names );
622
623 foreach ( $size_names as $size_name ) {
624
625 /*
626 * $size_name must be a string.
627 */
628 $mt_images = $this->get_size_name_images( $num_diff, $size_name, $mod, $md_pre );
629
630 if ( empty( $mt_images ) ) {
631
632 if ( $this->p->debug->enabled ) {
633
634 $this->p->debug->log( 'no images for size name ' . $size_name );
635 }
636
637 } else {
638
639 if ( $this->p->debug->enabled ) {
640
641 $this->p->debug->log( 'merging ' . count( $mt_images ) . ' images for size name ' . $size_name );
642 }
643
644 $mt_ret = array_merge( $mt_ret, $mt_images );
645 }
646 }
647 }
648
649 if ( $this->p->debug->enabled ) {
650
651 $this->p->debug->mark( 'getting all images' ); // End timer.
652 }
653
654 return $mt_ret;
655 }
656
657 /*
658 * $size_name must be a string.
659 */
660 public function get_size_name_images( $num, $size_name, array $mod, $md_pre = 'og' ) {
661
662 if ( $this->p->debug->enabled ) {
663
664 $this->p->debug->mark();
665 }
666
667 if ( ! is_string( $size_name ) ) {
668
669 if ( $this->p->debug->enabled ) {
670
671 $this->p->debug->log( 'exiting early: size_name argument must be a string' );
672 }
673
674 return array();
675
676 } elseif ( $num < 1 ) {
677
678 if ( $this->p->debug->enabled ) {
679
680 $this->p->debug->log( 'exiting early: num argument must be 1 or more' );
681 }
682
683 return array();
684 }
685
686 /*
687 * Example $size_info = Array (
688 * [size_name] => wpsso-opengraph,
689 * [attachment_id] => false,
690 * [width] => 1200,
691 * [height] => 630,
692 * [crop] => 1,
693 * [is_cropped] => 1,
694 * [dims_transl] => 1200x630 cropped,
695 * [label_transl] => Open Graph (Facebook and oEmbed),
696 * [opt_prefix] => og,
697 * );
698 */
699 $size_info = $this->p->util->get_size_info( $size_name );
700
701 if ( empty( $size_info[ 'width' ] ) && empty( $size_info[ 'height' ] ) ) {
702
703 if ( $this->p->debug->enabled ) {
704
705 $this->p->debug->log( 'exiting early: missing size information for ' . $size_name );
706 }
707
708 return array();
709 }
710
711 if ( $this->p->debug->enabled ) {
712
713 $this->p->debug->log( 'getting ' . $num . ' images for size name ' . $size_name );
714 }
715
716 $mt_ret = array();
717
718 if ( is_object( $mod[ 'obj' ] ) && $mod[ 'id' ] ) { // Comment, post, term, or user.
719
720 if ( $this->p->debug->enabled ) {
721
722 $this->p->debug->log( 'getting ' . $mod[ 'name' ] . ' images' );
723 }
724
725 if ( $mod[ 'is_post' ] ) {
726
727 if ( $mod[ 'is_attachment' ] && wp_attachment_is( 'image', $mod[ 'id' ] ) ) {
728
729 /*
730 * $size_name must be a string.
731 */
732 $mt_single_image = $this->get_attachment_image( $num, $size_name, $mod[ 'id' ] );
733
734 if ( empty( $mt_single_image ) ) {
735
736 if ( $this->p->debug->enabled ) {
737
738 $this->p->debug->log( 'exiting early: no attachment image' );
739 }
740
741 return $mt_ret; // Stop here.
742 }
743
744 $mt_ret = array_merge( $mt_ret, $mt_single_image );
745
746 if ( $this->p->debug->enabled ) {
747
748 $this->p->debug->log( 'returning attachment images' );
749
750 $this->p->debug->log_arr( 'mt_ret', $mt_ret );
751 }
752
753 return $mt_ret; // Stop here.
754 }
755
756 /*
757 * Check for custom meta, featured, or attached image(s).
758 *
759 * Allow for empty post ID in order to execute featured / attached image filters for modules.
760 */
761 $post_images = $this->get_post_images( $num, $size_name, $mod[ 'id' ], $md_pre );
762
763 if ( ! empty( $post_images ) ) {
764
765 $mt_ret = array_merge( $mt_ret, $post_images );
766 }
767
768 } else {
769
770 /*
771 * get_og_images() provides filter hooks for additional image IDs and URLs.
772 *
773 * Unless $md_pre is 'none', get_og_images() will fallback to using the 'og' custom meta.
774 */
775 $mt_images = $mod[ 'obj' ]->get_og_images( $num, $size_name, $mod[ 'id' ], $md_pre );
776
777 if ( ! empty( $mt_images ) ) {
778
779 $mt_ret = array_merge( $mt_ret, $mt_images );
780 }
781 }
782
783 /*
784 * If we haven't reached the limit of images yet, keep going and check the content text.
785 */
786 if ( empty( $this->p->options[ 'plugin_content_images' ] ) ) {
787
788 if ( $this->p->debug->enabled ) {
789
790 $this->p->debug->log( 'skipping content images' );
791 }
792
793 } else {
794
795 if ( ! $this->p->util->is_maxed( $mt_ret, $num ) ) {
796
797 if ( $this->p->debug->enabled ) {
798
799 $this->p->debug->log( 'getting content images' );
800 }
801
802 $num_diff = SucomUtil::array_count_diff( $mt_ret, $num );
803
804 $content_images = $this->get_content_images( $num_diff, $size_name, $mod );
805
806 if ( ! empty( $content_images ) ) {
807
808 $mt_ret = array_merge( $mt_ret, $content_images );
809 }
810
811 unset( $content_images );
812 }
813 }
814 }
815
816 if ( empty( $mt_ret ) ) {
817
818 if ( $mod[ 'is_home' ] || $mod[ 'is_archive' ] || $mod[ 'is_post' ] ) {
819
820 if ( $mod[ 'is_attachment' ] ) {
821
822 if ( $this->p->debug->enabled ) {
823
824 $this->p->debug->log( 'skipping default image: post is attachment' );
825 }
826
827 } elseif ( ! $mod[ 'is_public' ] ) { // WooCommerce variations, for example.
828
829 if ( $this->p->debug->enabled ) {
830
831 $this->p->debug->log( 'skipping default image: not public' );
832
833 $this->p->debug->log_arr( 'mod', $mod );
834 }
835
836 } else {
837
838 if ( $this->p->debug->enabled ) {
839
840 $this->p->debug->log( 'using the default image' );
841 }
842
843 $mt_ret = $this->get_default_images( $size_name );
844
845 if ( $this->p->debug->enabled ) {
846
847 if ( empty( $mt_ret ) ) {
848
849 $this->p->debug->log( 'no default image' );
850 }
851 }
852 }
853 }
854 }
855
856 $this->p->util->slice_max( $mt_ret, $num );
857
858 if ( $this->p->debug->enabled ) {
859
860 $this->p->debug->log( 'returning ' . count( $mt_ret ) . ' images' );
861
862 $this->p->debug->log_arr( 'mt_ret', $mt_ret );
863 }
864
865 return $mt_ret;
866 }
867
868 /*
869 * $size_name must be a string.
870 */
871 public function get_post_images( $num, $size_name, $post_id, $md_pre = 'og' ) {
872
873 if ( $this->p->debug->enabled ) {
874
875 $this->p->debug->log_args( array(
876 'num' => $num,
877 'size_name' => $size_name,
878 'post_id' => $post_id,
879 'md_pre' => $md_pre,
880 ) );
881 }
882
883 $mt_ret = array();
884
885 if ( ! empty( $post_id ) ) {
886
887 if ( $this->p->debug->enabled ) {
888
889 $this->p->debug->log( 'getting og images' );
890 }
891
892 /*
893 * get_og_images() provides filter hooks for additional image IDs and URLs.
894 *
895 * Unless $md_pre is 'none', get_og_images() will fallback to using the 'og' custom meta.
896 */
897 $mt_ret = array_merge( $mt_ret, $this->p->post->get_og_images( $num, $size_name, $post_id, $md_pre ) );
898 }
899
900 /*
901 * Allow for empty post_id in order to execute featured / attached image filters for modules.
902 */
903 if ( ! $this->p->util->is_maxed( $mt_ret, $num ) ) {
904
905 if ( $this->p->debug->enabled ) {
906
907 $this->p->debug->log( 'getting featured images' );
908 }
909
910 $num_diff = SucomUtil::array_count_diff( $mt_ret, $num );
911
912 $mt_ret = array_merge( $mt_ret, $this->get_featured( $num_diff, $size_name, $post_id ) );
913 }
914
915 /*
916 * Maybe get attached images, including WooCommerce product gallery images.
917 */
918 if ( empty( $this->p->options[ 'plugin_attached_images' ] ) ) {
919
920 if ( $this->p->debug->enabled ) {
921
922 $this->p->debug->log( 'skipping attached images' );
923 }
924
925 } else {
926
927 if ( ! $this->p->util->is_maxed( $mt_ret, $num ) ) {
928
929 if ( $this->p->debug->enabled ) {
930
931 $this->p->debug->log( 'getting attached images' );
932 }
933
934 $num_diff = SucomUtil::array_count_diff( $mt_ret, $num );
935
936 $attached_images = $this->get_attached_images( $num_diff, $size_name, $post_id );
937
938 if ( ! empty( $attached_images ) ) {
939
940 $mt_ret = array_merge( $mt_ret, $attached_images );
941 }
942
943 unset( $attached_images );
944 }
945 }
946
947 if ( $this->p->debug->enabled ) {
948
949 $this->p->debug->log_arr( 'mt_ret', $mt_ret );
950 }
951
952 return $mt_ret;
953 }
954
955 /*
956 * $size_name must be a string.
957 */
958 public function get_featured( $num, $size_name, $post_id ) {
959
960 if ( $this->p->debug->enabled ) {
961
962 $this->p->debug->log_args( array(
963 'num' => $num,
964 'size_name' => $size_name,
965 'post_id' => $post_id,
966 ) );
967 }
968
969 $mt_ret = array();
970
971 if ( ! empty( $post_id ) ) { // Just in case.
972
973 /*
974 * If the post ID is an attachment page, then use the post ID as the image ID.
975 */
976 if ( ( is_attachment( $post_id ) || 'attachment' === get_post_type( $post_id ) ) && wp_attachment_is( 'image', $post_id ) ) {
977
978 $pid = $post_id;
979
980 } elseif ( has_post_thumbnail( $post_id ) ) {
981
982 $pid = get_post_thumbnail_id( $post_id );
983
984 } else $pid = false;
985
986 $filter_name = 'wpsso_featured_image_id';
987
988 if ( $this->p->debug->enabled ) {
989
990 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
991 }
992
993 $pid = apply_filters( $filter_name, $pid, $post_id );
994
995 if ( ! empty( $pid ) && is_numeric( $pid ) ) { // Just in case.
996
997 /*
998 * Returns an og:image:url value, not an og:image:secure_url.
999 */
1000 $mt_single_image = $this->get_mt_single_image_src( $pid, $size_name );
1001
1002 if ( ! empty( $mt_single_image[ 'og:image:url' ] ) ) {
1003
1004 $this->p->util->push_max( $mt_ret, $mt_single_image, $num );
1005
1006 // Continue and apply the 'wpsso_og_featured' filter.
1007 }
1008 }
1009 }
1010
1011 $filter_name = 'wpsso_og_featured';
1012
1013 if ( $this->p->debug->enabled ) {
1014
1015 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1016 }
1017
1018 return apply_filters( $filter_name, $mt_ret, $num, $size_name, $post_id );
1019 }
1020
1021 /*
1022 * $size_name must be a string.
1023 *
1024 * See WpssoMedia->get_post_images().
1025 */
1026 public function get_attached_images( $num, $size_name, $post_id ) {
1027
1028 if ( $this->p->debug->enabled ) {
1029
1030 $this->p->debug->log_args( array(
1031 'num' => $num,
1032 'size_name' => $size_name,
1033 'post_id' => $post_id,
1034 ) );
1035 }
1036
1037 $mt_ret = array();
1038
1039 if ( ! empty( $post_id ) ) { // Just in case.
1040
1041 static $local_fifo = array();
1042
1043 if ( ! isset( $local_fifo[ $post_id ] ) ) {
1044
1045 /*
1046 * Maybe limit the number of array elements.
1047 */
1048 $local_fifo = SucomUtil::array_slice_fifo( $local_fifo, WPSSO_CACHE_ARRAY_FIFO_MAX );
1049
1050 $local_fifo[ $post_id ] = array();
1051
1052 $images = get_children( array(
1053 'post_parent' => $post_id,
1054 'post_type' => 'attachment',
1055 'post_mime_type' => 'image' // Aka 'image/%' query.
1056 ), OBJECT ); // OBJECT, ARRAY_A, or ARRAY_N.
1057
1058 /*
1059 * Featured images are handled beforehand by WpssoMedia->get_featured().
1060 *
1061 * Avoid duplicates by excluding attached image IDs that are also featured image IDs.
1062 */
1063 $post_thumbnail_id = get_post_thumbnail_id( $post_id );
1064
1065 foreach ( $images as $attachment ) {
1066
1067 if ( ! empty( $attachment->ID ) && $post_thumbnail_id !== $attachment->ID ) {
1068
1069 $local_fifo[ $post_id ][] = $attachment->ID;
1070 }
1071 }
1072
1073 rsort( $local_fifo[ $post_id ], SORT_NUMERIC );
1074
1075 $filter_name = 'wpsso_attached_image_ids';
1076
1077 if ( $this->p->debug->enabled ) {
1078
1079 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1080 }
1081
1082 $local_fifo[ $post_id ] = apply_filters( $filter_name, $local_fifo[ $post_id ], $post_id );
1083 $local_fifo[ $post_id ] = array_unique( $local_fifo[ $post_id ] ); // Just in case.
1084 }
1085
1086 if ( $this->p->debug->enabled ) {
1087
1088 $this->p->debug->log( 'found ' . count( $local_fifo[ $post_id ] ) . ' attached images for post id ' . $post_id );
1089 }
1090
1091 foreach ( $local_fifo[ $post_id ] as $pid ) {
1092
1093 /*
1094 * get_mt_single_image_src() returns an og:image:url value, not an og:image:secure_url.
1095 */
1096 $mt_single_image = $this->get_mt_single_image_src( $pid, $size_name );
1097
1098 if ( ! empty( $mt_single_image[ 'og:image:url' ] ) ) {
1099
1100 if ( $this->p->util->push_max( $mt_ret, $mt_single_image, $num ) ) {
1101
1102 break; // Stop here and apply the 'wpsso_og_attached_images' filter.
1103 }
1104 }
1105 }
1106 }
1107
1108 $filter_name = 'wpsso_og_attached_images';
1109
1110 if ( $this->p->debug->enabled ) {
1111
1112 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1113 }
1114
1115 return apply_filters( $filter_name, $mt_ret, $num, $size_name, $post_id );
1116 }
1117
1118 /*
1119 * $size_name must be a string.
1120 *
1121 * See WpssoMedia->get_size_name_images().
1122 */
1123 public function get_content_images( $num = 0, $size_name = 'thumbnail', $mod = true, $content = '' ) {
1124
1125 if ( $this->p->debug->enabled ) {
1126
1127 $this->p->debug->mark();
1128 }
1129
1130 /*
1131 * The $mod array argument is preferred but not required.
1132 *
1133 * $mod = true | false | post_id | $mod array
1134 */
1135 if ( ! is_array( $mod ) ) {
1136
1137 if ( $this->p->debug->enabled ) {
1138
1139 $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
1140 }
1141
1142 $mod = $this->p->page->get_mod( $mod );
1143 }
1144
1145 $mt_images = array();
1146
1147 /*
1148 * Allow custom content to be passed as an argument in $content.
1149 */
1150 if ( empty( $content ) ) {
1151
1152 $content = $this->p->page->get_the_content( $mod );
1153
1154 $content_passed = false;
1155
1156 } else $content_passed = true;
1157
1158 if ( empty( $content ) ) {
1159
1160 if ( $this->p->debug->enabled ) {
1161
1162 $this->p->debug->log( 'exiting early: empty ' . $mod[ 'name' ] . ' content' );
1163 }
1164
1165 return $mt_images;
1166 }
1167
1168 $content_img_preg = $this->default_content_img_preg;
1169
1170 $mt_single_image = SucomUtil::get_mt_image_seed();
1171
1172 /*
1173 * Allow the html_tag and pid_attr regex to be modified.
1174 */
1175 foreach( array( 'html_tag', 'pid_attr' ) as $type ) {
1176
1177 $filter_name = 'wpsso_content_image_preg_' . $type; // No need to sanitize.
1178
1179 if ( $this->p->debug->enabled ) {
1180
1181 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1182 }
1183
1184 $content_img_preg[ $type ] = apply_filters( $filter_name, $this->default_content_img_preg[ $type ] );
1185 }
1186
1187 if ( $this->p->debug->enabled ) {
1188
1189 $this->p->debug->log_arr( 'content_img_preg', $content_img_preg );
1190 }
1191
1192 /*
1193 * <img/> attributes in order of preference.
1194 */
1195 if ( preg_match_all( '/<((' . $content_img_preg[ 'html_tag' ] . ')[^>]*? (' . $content_img_preg[ 'pid_attr' ] . ')=[\'"]([0-9]+)[\'"]|' .
1196 '(img)[^>]*? (data-lazy-src|data-share-src|data-src|src)=[\'"]([^\'"]+)[\'"])[^>]*>/s',
1197 $content, $all_matches, PREG_SET_ORDER ) ) {
1198
1199 $content_img_max = SucomUtil::get_const( 'WPSSO_CONTENT_IMAGES_MAX', 5 );
1200
1201 if ( count( $all_matches ) > $content_img_max ) {
1202
1203 if ( $this->p->debug->enabled ) {
1204
1205 $this->p->debug->log( 'limiting matches returned from ' . count( $all_matches ) . ' to ' . $content_img_max );
1206 }
1207
1208 $all_matches = array_splice( $all_matches, 0, $content_img_max );
1209 }
1210
1211 if ( $this->p->debug->enabled ) {
1212
1213 $this->p->debug->log( count( $all_matches ) . ' matching <' . $content_img_preg[ 'html_tag' ] . '/> html tag(s) found' );
1214 }
1215
1216 foreach ( $all_matches as $match_num => $media ) {
1217
1218 $tag_value = $media[ 0 ];
1219
1220 if ( empty( $media[ 5 ] ) ) {
1221
1222 $tag_name = $media[ 2 ]; // img
1223 $attr_name = $media[ 3 ]; // data-wp-pid
1224 $attr_value = $media[ 4 ]; // id
1225
1226 } else {
1227
1228 $tag_name = $media[ 5 ]; // img
1229 $attr_name = $media[ 6 ]; // data-share-src|data-lazy-src|data-src|src
1230 $attr_value = $media[ 7 ]; // url
1231 }
1232
1233 if ( $this->p->debug->enabled ) {
1234
1235 $this->p->debug->log( 'match ' . $match_num . ': ' . $tag_name . ' ' . $attr_name . '="' . $attr_value . '"' );
1236 }
1237
1238 switch ( $attr_name ) {
1239
1240 /*
1241 * WordPress media library image ID.
1242 */
1243 case 'data-wp-pid':
1244
1245 if ( $this->p->debug->enabled ) {
1246
1247 $this->p->debug->log( 'WP image attribute id found: ' . $attr_value );
1248 }
1249
1250 $this->add_mt_single_image_src( $mt_single_image, $attr_value, $size_name, false );
1251
1252 break;
1253
1254 /*
1255 * Check for other data attributes like 'data-ngg-pid'.
1256 */
1257 case ( preg_match( '/^' . $content_img_preg[ 'pid_attr' ] . '$/', $attr_name ) ? true : false ):
1258
1259 /*
1260 * Filter hook for third-party modules to return image information.
1261 */
1262 $filter_name = SucomUtil::sanitize_hookname( 'wpsso_get_content_' . $tag_name . '_' . $attr_name );
1263
1264 if ( false !== has_filter( $filter_name ) ) {
1265
1266 if ( $this->p->debug->enabled ) {
1267
1268 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1269 }
1270
1271 list(
1272 $mt_single_image[ 'og:image:url' ],
1273 $mt_single_image[ 'og:image:width' ],
1274 $mt_single_image[ 'og:image:height' ],
1275 $mt_single_image[ 'og:image:cropped' ],
1276 $mt_single_image[ 'og:image:id' ],
1277 $mt_single_image[ 'og:image:alt' ],
1278 $mt_single_image[ 'og:image:size_name' ],
1279 ) = apply_filters( $filter_name, self::reset_image_src_args(), $attr_value, $size_name, false );
1280 }
1281
1282 break;
1283
1284 /*
1285 * data-share-src | data-lazy-src | data-src | src
1286 */
1287 default:
1288
1289 /*
1290 * Recognize gravatar images in the content.
1291 */
1292 if ( preg_match( '/^https?:?\/\/[^\/]*gravatar\.com\/avatar\/([a-zA-Z0-9]+)/', $attr_value, $match ) ) {
1293
1294 $img_size = $this->get_gravatar_size();
1295 $img_url = 'https://secure.gravatar.com/avatar/' . $match[ 1 ] . '.jpg?s=' . $img_size . '&d=404&r=G';
1296
1297 $mt_single_image[ 'og:image:url' ] = $img_url;
1298 $mt_single_image[ 'og:image:width' ] = $img_size;
1299 $mt_single_image[ 'og:image:height' ] = $img_size;
1300
1301 if ( $this->p->debug->enabled ) {
1302
1303 $this->p->debug->log( 'gravatar image found: ' . $img_url );
1304 }
1305
1306 break; // Stop here.
1307 }
1308
1309 /*
1310 * Check for image ID in class for old content w/o the data-wp-pid attribute.
1311 */
1312 if ( preg_match( '/class="[^"]+ wp-image-([0-9]+)/', $tag_value, $match ) ) {
1313
1314 $this->add_mt_single_image_src( $mt_single_image, $match[ 1 ], $size_name, false );
1315
1316 break; // Stop here.
1317
1318 } else {
1319
1320 if ( $this->p->debug->enabled ) {
1321
1322 $this->p->debug->log( 'using attribute value for og:image = ' .
1323 $attr_value . ' (' . WPSSO_UNDEF . 'x' . WPSSO_UNDEF . ')' );
1324 }
1325
1326 $mt_single_image = array(
1327 'og:image:url' => $attr_value,
1328 'og:image:width' => WPSSO_UNDEF,
1329 'og:image:height' => WPSSO_UNDEF,
1330 'og:image:alt' => preg_match( '/ alt="([^"]+)"/', $tag_value, $match ) ? $match[ 1 ] : '',
1331 );
1332 }
1333
1334 if ( empty( $mt_single_image[ 'og:image:url' ] ) ) {
1335
1336 if ( $this->p->debug->enabled ) {
1337
1338 $this->p->debug->log( 'single image og:image value is empty' );
1339 }
1340
1341 break; // Stop here.
1342 }
1343
1344 $check_size_limits = true;
1345 $img_within_limits = true;
1346
1347 /*
1348 * Get the actual width and height of the image using http / https.
1349 */
1350 if ( empty( $mt_single_image[ 'og:image:width' ] ) || $mt_single_image[ 'og:image:width' ] < 0 ||
1351 empty( $mt_single_image[ 'og:image:height' ] ) || $mt_single_image[ 'og:image:height' ] < 0 ) {
1352
1353 /*
1354 * Add correct image sizes for the image URL using getimagesize().
1355 *
1356 * Note that PHP v7.1 or better is required to get the image size of WebP images.
1357 */
1358 $this->p->util->add_image_url_size( $mt_single_image );
1359
1360 if ( $this->p->debug->enabled ) {
1361
1362 $this->p->debug->log( 'image URL size: ' .
1363 $mt_single_image[ 'og:image:width' ] . 'x' . $mt_single_image[ 'og:image:height' ] );
1364 }
1365
1366 /*
1367 * No use checking / retrieving the image size twice.
1368 */
1369 if ( WPSSO_UNDEF === $mt_single_image[ 'og:image:width' ] &&
1370 WPSSO_UNDEF === $mt_single_image[ 'og:image:height' ] ) {
1371
1372 $check_size_limits = false;
1373 $img_within_limits = false;
1374 }
1375
1376 } elseif ( $this->p->debug->enabled ) {
1377
1378 $this->p->debug->log( 'image width / height values: ' .
1379 $mt_single_image[ 'og:image:width' ] . 'x' . $mt_single_image[ 'og:image:height' ] );
1380 }
1381
1382 if ( $check_size_limits ) {
1383
1384 if ( $this->p->debug->enabled ) {
1385
1386 $this->p->debug->log( 'checking image size limits for ' . $mt_single_image[ 'og:image:url' ] .
1387 ' (' . $mt_single_image[ 'og:image:width' ] . 'x' . $mt_single_image[ 'og:image:height' ] . ')' );
1388 }
1389
1390 /*
1391 * Check if image exceeds hard-coded limits (dimensions, ratio, etc.).
1392 */
1393 $img_within_limits = $this->is_image_within_config_limits( $mt_single_image[ 'og:image:url' ],
1394 $size_name, $mt_single_image[ 'og:image:width' ], $mt_single_image[ 'og:image:height' ],
1395 __( 'Content', 'wpsso' ) );
1396
1397 } elseif ( $this->p->debug->enabled ) {
1398
1399 $this->p->debug->log( 'skipped image size limits for ' . $mt_single_image[ 'og:image:url' ] .
1400 ' (' . $mt_single_image[ 'og:image:width' ] . 'x' . $mt_single_image[ 'og:image:height' ] . ')' );
1401 }
1402
1403 if ( ! apply_filters( 'wpsso_content_accept_img_dims', $img_within_limits,
1404 $mt_single_image, $size_name, $attr_name, $content_passed ) ) {
1405
1406 $mt_single_image = array();
1407 }
1408
1409 break;
1410 }
1411
1412 if ( ! empty( $mt_single_image[ 'og:image:url' ] ) ) {
1413
1414 $mt_single_image[ 'og:image:url' ] = $this->p->util->fix_relative_url( $mt_single_image[ 'og:image:url' ] );
1415
1416 $filter_name = 'wpsso_rewrite_image_url';
1417
1418 if ( $this->p->debug->enabled ) {
1419
1420 $this->p->debug->log( 'applying filters "' . $filter_name . '" for ' . $mt_single_image[ 'og:image:url' ] );
1421 }
1422
1423 $mt_single_image[ 'og:image:url' ] = apply_filters( $filter_name, $mt_single_image[ 'og:image:url' ] );
1424
1425 if ( $this->p->util->is_uniq_url( $mt_single_image[ 'og:image:url' ], $size_name, $mod ) ) {
1426
1427 if ( $this->p->util->push_max( $mt_images, $mt_single_image, $num ) ) {
1428
1429 return $mt_images;
1430 }
1431 }
1432 }
1433 }
1434
1435 return $mt_images;
1436 }
1437
1438 if ( $this->p->debug->enabled ) {
1439
1440 $this->p->debug->log( 'no matching <' . $content_img_preg[ 'html_tag' ] . '/> html tag(s) found' );
1441 }
1442
1443 return $mt_images;
1444 }
1445
1446 /*
1447 * $size_name must be a string.
1448 */
1449 public function get_attachment_image( $num, $size_name, $attachment_id ) {
1450
1451 if ( $this->p->debug->enabled ) {
1452
1453 $this->p->debug->log_args( array(
1454 'num' => $num,
1455 'size_name' => $size_name,
1456 'attachment_id' => $attachment_id,
1457 ) );
1458 }
1459
1460 $mt_ret = array();
1461
1462 if ( ! empty( $attachment_id ) ) {
1463
1464 if ( wp_attachment_is( 'image', $attachment_id ) ) {
1465
1466 /*
1467 * get_mt_single_image_src() returns an og:image:url value, not an og:image:secure_url.
1468 */
1469 $mt_single_image = $this->get_mt_single_image_src( $attachment_id, $size_name );
1470
1471 if ( ! empty( $mt_single_image[ 'og:image:url' ] ) ) {
1472
1473 if ( $this->p->util->push_max( $mt_ret, $mt_single_image, $num ) ) {
1474
1475 return $mt_ret;
1476 }
1477 }
1478
1479 } elseif ( $this->p->debug->enabled ) {
1480
1481 $this->p->debug->log( 'attachment id ' . $attachment_id . ' is not an image' );
1482 }
1483 }
1484
1485 return $mt_ret;
1486 }
1487
1488 /*
1489 * Return only the image URL, which will be the first array element returned by get_attachment_image_src().
1490 */
1491 public function get_attachment_image_url( $pid, $size_name = 'thumbnail' ) {
1492
1493 $image_src = $this->get_attachment_image_src( $pid, $size_name );
1494
1495 foreach ( $image_src as $num => $value ) {
1496
1497 return $value;
1498 }
1499
1500 return null; // Return null if array is empty.
1501 }
1502
1503 /*
1504 * Note that that every return in this method must call self::reset_image_src_args().
1505 */
1506 public function get_attachment_image_src( $pid, $size_name = 'thumbnail' ) {
1507
1508 /*
1509 * Save arguments for the 'image_make_intermediate_size' and 'image_resize_dimensions' filters.
1510 */
1511 self::set_image_src_args( $args = array(
1512 'pid' => $pid,
1513 'size_name' => $size_name,
1514 ) );
1515
1516 if ( $this->p->debug->enabled ) {
1517
1518 $this->p->debug->log_args( $args );
1519 }
1520
1521 /*
1522 * Example $size_info = Array (
1523 * [size_name] => wpsso-opengraph,
1524 * [attachment_id] => false,
1525 * [width] => 1200,
1526 * [height] => 630,
1527 * [crop] => 1,
1528 * [is_cropped] => 1,
1529 * [dims_transl] => 1200x630 cropped,
1530 * [label_transl] => Open Graph (Facebook and oEmbed),
1531 * [opt_prefix] => og,
1532 * );
1533 */
1534 $size_info = $this->p->util->get_size_info( $size_name, $pid );
1535
1536 if ( empty( $size_info[ 'width' ] ) && empty( $size_info[ 'height' ] ) ) {
1537
1538 if ( $this->p->debug->enabled ) {
1539
1540 $this->p->debug->log( 'exiting early: missing size information for ' . $size_name );
1541 }
1542
1543 return self::reset_image_src_args();
1544 }
1545
1546 if ( $this->p->debug->enabled ) {
1547
1548 $this->p->debug->log_arr( 'size info', $size_info );
1549 }
1550
1551 $img_url = '';
1552 $img_width = WPSSO_UNDEF;
1553 $img_height = WPSSO_UNDEF;
1554 $use_full_size = false;
1555
1556 if ( ! wp_attachment_is( 'image', $pid ) ) {
1557
1558 if ( $this->p->debug->enabled ) {
1559
1560 $this->p->debug->log( 'exiting early: attachment ' . $pid . ' is not an image' );
1561 }
1562
1563 return self::reset_image_src_args();
1564 }
1565
1566 if ( $this->p->debug->enabled ) {
1567
1568 $this->p->debug->log( 'calling wp_get_attachment_metadata() for pid ' . $pid );
1569 }
1570
1571 $img_meta = wp_get_attachment_metadata( $pid ); // Returns a WP_Error object on failure.
1572 $img_alt = get_metadata( 'post', $pid, '_wp_attachment_image_alt', $single = true );
1573
1574 /*
1575 * Check to see if the full size image width / height matches the resize width / height we require.
1576 */
1577 if ( is_array( $img_meta ) && isset( $img_meta[ 'file' ] ) && isset( $img_meta[ 'width' ] ) && isset( $img_meta[ 'height' ] ) ) {
1578
1579 if ( $this->p->debug->enabled ) {
1580
1581 $this->p->debug->log( 'full size image ' . $img_meta[ 'file' ] .
1582 ' (' . $img_meta[ 'width' ] . 'x' . $img_meta[ 'height' ] . ')' );
1583 }
1584
1585 if ( $img_meta[ 'width' ] === $size_info[ 'width' ] && $img_meta[ 'height' ] === $size_info[ 'height' ] ) {
1586
1587 if ( $this->p->debug->enabled ) {
1588
1589 $this->p->debug->log( 'using full size image - dimensions identical to ' . $size_name .
1590 ' (' . $size_info[ 'width' ] . 'x' . $size_info[ 'height' ] . ' ' .
1591 ( $size_info[ 'crop' ] ? 'cropped' : 'uncropped' ) . ')' );
1592
1593 }
1594
1595 $use_full_size = true;
1596
1597 } elseif ( ! $size_info[ 'is_cropped' ] ) {
1598
1599 if ( $img_meta[ 'width' ] === $size_info[ 'width' ] || $img_meta[ 'height' ] === $size_info[ 'height' ] ) {
1600
1601 if ( $this->p->debug->enabled ) {
1602
1603 $this->p->debug->log( 'using full size image - dimension matches ' . $size_name .
1604 ' (' . $size_info[ 'dims_transl' ] . ')' );
1605 }
1606
1607 $use_full_size = true;
1608 }
1609 }
1610
1611 } else {
1612
1613 // translators: Please ignore - translation uses a different text domain.
1614 $img_lib = __( 'Media Library' );
1615 $edit_url = get_edit_post_link( $pid );
1616 $img_title = get_the_title( $pid );
1617 $func_name = 'wp_get_attachment_metadata()';
1618 $func_url = __( 'https://developer.wordpress.org/reference/functions/wp_get_attachment_metadata/', 'wpsso' );
1619 $regen_url = 'https://wordpress.org/plugins/search/regenerate+thumbnails/';
1620 $regen_msg = sprintf( __( 'You may consider regenerating the sizes of all WordPress Media Library images using one of <a href="%s">several available plugins from WordPress.org</a>.', 'wpsso' ), $regen_url );
1621
1622 /*
1623 * wp_get_attachment_metadata() returned a WP_Error object.
1624 */
1625 if ( is_wp_error( $img_meta ) ) {
1626
1627 $error_msg = $img_meta->get_error_message();
1628
1629 if ( $this->p->debug->enabled ) {
1630
1631 $this->p->debug->log( $func_name . ' error for ' . $pid . ': ' . $error_msg );
1632 }
1633
1634 if ( $this->p->notice->is_admin_pre_notices() ) {
1635
1636 $notice_msg = sprintf( __( 'Possible %1$s corruption detected - the <a href="%2$s">WordPress %3$s function</a> returned an error for <a href="%4$s">image ID %5$s</a>: %6$s.', 'wpsso' ), $img_lib, $func_url, '<code>' . $func_name . '</code>', $edit_url, $pid, $error_msg ) . ' ' . $regen_msg;
1637
1638 $notice_key = 'wp-get-attachment-metadata-error-for-' . $pid;
1639
1640 $this->p->notice->err( $notice_msg, null, $notice_key );
1641 }
1642
1643 $img_meta = array(); // Avoid "cannot use object of type WP_Error as array" error.
1644
1645 /*
1646 * Image dimensions are missing, but full size image path is present.
1647 */
1648 } elseif ( isset( $img_meta[ 'file' ] ) ) {
1649
1650 if ( $this->p->debug->enabled ) {
1651
1652 $this->p->debug->log( 'full size image ' . $img_meta[ 'file' ] . ' dimensions missing from image metadata' );
1653 }
1654
1655 if ( $this->p->notice->is_admin_pre_notices() ) {
1656
1657 $notice_msg = sprintf( __( 'Possible %1$s corruption detected - the full size image dimensions for <a href="%2$s">image ID %3$s</a> are missing from the image metadata returned by the <a href="%4$s">WordPress %5$s function</a>.', 'wpsso' ), $img_lib, $edit_url, $pid, $func_url, '<code>' . $func_name . '</code>' ) . ' ' . $regen_msg;
1658
1659 $notice_key = 'full-size-image-' . $pid . '-dimensions-missing';
1660
1661 $this->p->notice->err( $notice_msg, null, $notice_key );
1662 }
1663
1664 /*
1665 * Both the image dimensions and full size image path are missing.
1666 */
1667 } else {
1668
1669 if ( $this->p->debug->enabled ) {
1670
1671 $this->p->debug->log( 'full size image file path for ' . $pid . ' missing from image metadata' );
1672 }
1673
1674 if ( $this->p->notice->is_admin_pre_notices() ) {
1675
1676 $notice_msg = sprintf( __( 'Possible %1$s corruption detected - the full size image file path for <a href="%2$s">image ID %3$s</a> is missing from the image metadata returned by the <a href="%4$s">WordPress %5$s function</a>.', 'wpsso' ), $img_lib, $edit_url, $pid, $func_url, '<code>' . $func_name . '</code>' ) . ' ' . $regen_msg;
1677
1678 $notice_key = 'full-size-image-' . $pid . '-file-path-missing';
1679
1680 $this->p->notice->err( $notice_msg, null, $notice_key );
1681 }
1682 }
1683 }
1684
1685 /*
1686 * Only resize our own custom image sizes.
1687 */
1688 if ( 0 === strpos( $size_name, 'wpsso-' ) ) {
1689
1690 if ( ! $use_full_size ) {
1691
1692 $is_accurate_filename = false;
1693 $is_accurate_width = false;
1694 $is_accurate_height = false;
1695
1696 /*
1697 * Make sure the metadata contains complete image information for the requested size.
1698 */
1699 if ( ! empty( $img_meta[ 'sizes' ][ $size_name ] ) &&
1700 ! empty( $img_meta[ 'sizes' ][ $size_name ][ 'file' ] ) &&
1701 ! empty( $img_meta[ 'sizes' ][ $size_name ][ 'width' ] ) &&
1702 ! empty( $img_meta[ 'sizes' ][ $size_name ][ 'height' ] ) ) {
1703
1704 /*
1705 * By default, WordPress adds only the resolution of the resized image to the file
1706 * name. If the image size is ours, then add crop information to the file name as
1707 * well. This allows for different cropped versions for the same image resolution.
1708 */
1709 $new_filepath = $this->maybe_update_cropped_image_filepath( $img_meta[ 'sizes' ][ $size_name ][ 'file' ], $size_info );
1710 $is_accurate_filename = $new_filepath === $img_meta[ 'sizes' ][ $size_name ][ 'file' ] ? true : false;
1711 $is_accurate_width = $size_info[ 'width' ] === $img_meta[ 'sizes' ][ $size_name ][ 'width' ] ? true : false;
1712 $is_accurate_height = $size_info[ 'height' ] === $img_meta[ 'sizes' ][ $size_name ][ 'height' ] ? true : false;
1713
1714 /*
1715 * If not cropped, make sure the resized image respects the original aspect ratio.
1716 */
1717 if ( ! $size_info[ 'is_cropped' ] ) {
1718
1719 if ( $is_accurate_width && $is_accurate_height &&
1720 isset( $img_meta[ 'width' ] ) && isset( $img_meta[ 'height' ] ) ) {
1721
1722 if ( $img_meta[ 'width' ] > $img_meta[ 'height' ] ) {
1723
1724 $ratio = $img_meta[ 'width' ] / $size_info[ 'width' ];
1725 $check = 'height';
1726
1727 } else {
1728
1729 $ratio = $img_meta[ 'height' ] / $size_info[ 'height' ];
1730 $check = 'width';
1731 }
1732
1733 $should_be = (int) round( $img_meta[ $check ] / $ratio );
1734
1735 /*
1736 * Allow for a +/- one pixel difference.
1737 */
1738 if ( $img_meta[ 'sizes' ][ $size_name ][ $check ] < ( $should_be - 1 ) ||
1739 $img_meta[ 'sizes' ][ $size_name ][ $check ] > ( $should_be + 1 ) ) {
1740
1741 if ( $this->p->debug->enabled ) {
1742
1743 $this->p->debug->log( $size_name . ' image metadata not accurate' );
1744 }
1745
1746 $is_accurate_width = false;
1747 $is_accurate_height = false;
1748 }
1749 }
1750 }
1751 }
1752
1753 /*
1754 * Depending on cropping, one or both sides of the image must be accurate. If the image is
1755 * not accurate, then attempt to create a resized image by calling
1756 * image_make_intermediate_size().
1757 */
1758 if ( ! $is_accurate_filename ||
1759 ( ! $size_info[ 'is_cropped' ] && ( ! $is_accurate_width && ! $is_accurate_height ) ) ||
1760 ( $size_info[ 'is_cropped' ] && ( ! $is_accurate_width || ! $is_accurate_height ) ) ) {
1761
1762 if ( $this->can_make_intermediate_size( $img_meta, $size_info ) ) {
1763
1764 // translators: Please ignore - translation uses a different text domain.
1765 $img_lib = __( 'Media Library' );
1766 $func_name = 'image_make_intermediate_size()';
1767 $func_url = __( 'https://developer.wordpress.org/reference/functions/image_make_intermediate_size/', 'wpsso' );
1768 $fullsizepath = get_attached_file( $pid );
1769
1770 if ( $this->p->debug->enabled ) {
1771
1772 $this->p->debug->log( 'full size path = ' . $fullsizepath );
1773 $this->p->debug->log( 'calling image_make_intermediate_size()' );
1774 }
1775
1776 /*
1777 * image_make_intermediate_size() resizes an image to make a thumbnail or
1778 * intermediate size. Returns (array|false) metadata array on success,
1779 * false if no image was created.
1780 */
1781 $mtime_start = microtime( $get_float = true );
1782 $resized_meta = image_make_intermediate_size( $fullsizepath,
1783 $size_info[ 'width' ], $size_info[ 'height' ], $size_info[ 'crop' ] );
1784 $mtime_total = microtime( $get_float = true ) - $mtime_start;
1785 $mtime_max = WPSSO_IMAGE_MAKE_SIZE_MAX_TIME;
1786
1787 if ( $this->p->debug->enabled ) {
1788
1789 $this->p->debug->log( 'mtime total = ' . $mtime_total );
1790
1791 if ( false === $resized_meta ) {
1792
1793 $this->p->debug->log( 'resized meta = ' . false );
1794
1795 } else {
1796
1797 $this->p->debug->log_arr( 'resized meta', $resized_meta );
1798 }
1799 }
1800
1801 if ( $mtime_total > $mtime_max ) {
1802
1803 $error_pre = sprintf( __( '%s warning:', 'wpsso' ), __METHOD__ );
1804 $rec_max_msg = sprintf( __( 'longer than recommended max of %1$.3f secs', 'wpsso' ), $mtime_max );
1805 $notice_msg = sprintf( __( 'Slow WordPress function detected - %1$s took %2$.3f secs to make image size "%3$s" from %4$s (%5$s).', 'wpsso' ), '<code>' . $func_name . '</code>', $mtime_total, $size_name, $fullsizepath, $rec_max_msg );
1806
1807 if ( $this->p->debug->enabled ) {
1808
1809 $this->p->debug->log( sprintf( 'slow WordPress function detected - %1$s took %2$.3f secs' .
1810 ' to make image size "%3$s" from %4$s', $func_name, $mtime_total, $size_name,
1811 $fullsizepath ) );
1812 }
1813
1814 if ( $this->p->notice->is_admin_pre_notices() ) {
1815
1816 $this->p->notice->err( $notice_msg );
1817 }
1818
1819 SucomUtil::safe_error_log( $error_pre . ' ' . $notice_msg, $strip_html = true );
1820 }
1821
1822 /*
1823 * Returns (array|false) metadata array on success, false if no image was created.
1824 */
1825 if ( false === $resized_meta ) {
1826
1827 /*
1828 * Add notice only if the admin notices have not already been shown.
1829 */
1830 if ( $this->p->notice->is_admin_pre_notices() ) {
1831
1832 $notice_msg = sprintf( __( 'Possible %1$s corruption detected - the <a href="%2$s">WordPress %3$s function</a> failed to create the "%4$s" image size (%5$s) from %6$s.', 'wpsso' ), $img_lib, $func_url, '<code>' . $func_name . '</code>', $size_name, $size_info[ 'dims_transl' ], $fullsizepath ) . ' ';
1833
1834 $notice_msg .= sprintf( __( 'You may consider regenerating the sizes of all WordPress Media Library images using one of <a href="%s">several available plugins from WordPress.org</a>.', 'wpsso' ), 'https://wordpress.org/plugins/search/regenerate+thumbnails/' );
1835
1836 $notice_key = 'image-make-intermediate-size-' . $fullsizepath . '-failure';
1837
1838 $this->p->notice->err( $notice_msg, null, $notice_key, $dismiss_time = WEEK_IN_SECONDS );
1839 }
1840
1841 $use_full_size = true;
1842
1843 } else {
1844
1845 $img_meta[ 'sizes' ][ $size_name ] = $resized_meta;
1846
1847 wp_update_attachment_metadata( $pid, $img_meta );
1848 }
1849
1850 } else {
1851
1852 if ( $this->p->debug->enabled ) {
1853
1854 $this->p->debug->log( 'skipped image_make_intermediate_size()' );
1855 }
1856
1857 if ( isset( $img_meta[ 'file' ] ) && isset( $img_meta[ 'width' ] ) && isset( $img_meta[ 'height' ] ) ) {
1858
1859 if ( $this->p->debug->enabled ) {
1860
1861 $this->p->debug->log( 'falling back to full size image ' . $img_meta[ 'file' ] .
1862 ' (' . $img_meta[ 'width' ] . 'x' . $img_meta[ 'height' ] . ')' );
1863 }
1864
1865 $use_full_size = true;
1866 }
1867 }
1868 }
1869 }
1870
1871 if ( $use_full_size ) {
1872
1873 if ( isset( $img_meta[ 'sizes' ][ $size_name ] ) ) {
1874
1875 if ( $this->p->debug->enabled ) {
1876
1877 $this->p->debug->log( 'using full size image - removing ' . $size_name . ' image metadata' );
1878 }
1879
1880 unset( $img_meta[ 'sizes' ][ $size_name ] );
1881
1882 wp_update_attachment_metadata( $pid, $img_meta );
1883 }
1884 }
1885 }
1886
1887 /*
1888 * image_downsize() returns an array of image data, or boolean false if no image is available.
1889 */
1890 $img_downsized = image_downsize( $pid, ( $use_full_size ? 'full' : $size_name ) ); // Returns array or false.
1891
1892 if ( ! is_array( $img_downsized ) ) {
1893
1894 if ( $this->p->debug->enabled ) {
1895
1896 $this->p->debug->log( 'exiting early: image downsize for ' . $size_name . ' returned false' );
1897 }
1898
1899 return self::reset_image_src_args();
1900 }
1901
1902 /*
1903 * Some image_downsize() filter hooks may return only 3 elements, so use array_pad() to sanitize the returned array.
1904 */
1905 $img_downsized = array_pad( $img_downsized, 4, null );
1906
1907 list(
1908 $img_url,
1909 $img_width,
1910 $img_height,
1911 $img_intermediate
1912 ) = apply_filters( 'wpsso_image_downsize', $img_downsized, $pid, $size_name );
1913
1914 if ( $this->p->debug->enabled ) {
1915
1916 $this->p->debug->log( 'image downsize for ' . $size_name . ' returned ' . $img_url . ' (' . $img_width . 'x' . $img_height . ')' );
1917 }
1918
1919 if ( empty( $img_url ) ) {
1920
1921 if ( $this->p->debug->enabled ) {
1922
1923 $this->p->debug->log( 'exiting early: image downsize for ' . $size_name . ' returned an empty url' );
1924 }
1925
1926 return self::reset_image_src_args();
1927 }
1928
1929 /*
1930 * Check if image exceeds hard-coded limits (dimensions, ratio, etc.).
1931 */
1932 $img_within_limits = $this->is_image_within_config_limits( $pid, $size_name, $img_width, $img_height );
1933
1934 if ( apply_filters( 'wpsso_attached_accept_img_dims', $img_within_limits, $img_url, $img_width, $img_height, $size_name, $pid ) ) {
1935
1936 $img_url = $this->p->util->fix_relative_url( $img_url );
1937
1938 $filter_name = 'wpsso_rewrite_image_url';
1939
1940 if ( $this->p->debug->enabled ) {
1941
1942 $this->p->debug->log( 'applying filters "' . $filter_name . '" for ' . $img_url );
1943 }
1944
1945 $img_url = apply_filters( 'wpsso_rewrite_image_url', $img_url );
1946
1947 return self::reset_image_src_args( array( $img_url, $img_width, $img_height, $size_info[ 'is_cropped' ], $pid, $img_alt, $size_name ) );
1948 }
1949
1950 return self::reset_image_src_args();
1951 }
1952
1953 /*
1954 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
1955 */
1956 public function get_default_images( $size_names = 'thumbnail' ) {
1957
1958 if ( $this->p->debug->enabled ) {
1959
1960 $this->p->debug->mark();
1961 }
1962
1963 return $this->get_mt_opts_images( $this->p->options, $size_names, $img_prefix = 'og_def_img', $key_num = null, $mt_pre = 'og' );
1964 }
1965
1966 /*
1967 * The returned array can include a varying number of elements, depending on the $media_request value.
1968 *
1969 * $md_pre may be 'none' when getting Open Graph option defaults (and not their custom values).
1970 *
1971 * $size_name should be a string, not an array.
1972 *
1973 * See WpssoCmcfFiltersEdit->filter_mb_sso_edit_media_og_rows() for 'pid'.
1974 * See WpssoGmfFiltersEdit->filter_mb_sso_edit_media_schema_rows() for 'pid'.
1975 * See WpssoEditMedia->filter_mb_sso_edit_media_prio_image_rows() for 'pid'.
1976 * See WpssoEditMedia->filter_mb_sso_edit_media_twitter_rows() for 'pid'.
1977 * See WpssoEditMedia->filter_mb_sso_edit_media_schema_rows() for 'pid'.
1978 * See WpssoEditMedia->filter_mb_sso_edit_media_pinterest_rows() for 'pid'.
1979 * See WpssoProAdminEdit->filter_mb_sso_edit_media_prio_video_rows() for 'og_vid_url', 'og_vid_title',
1980 * 'og_vid_desc', 'og_vid_stream_url', 'og_vid_width', 'og_vid_height', 'og_vid_upload'.
1981 * See WpssoRrssbSubmenuSharePinterest->get_html() for 'og_img_url'.
1982 */
1983 public function get_media_info( $size_name, array $media_request, array $mod, $md_pre = 'og', $mt_pre = 'og' ) {
1984
1985 if ( ! is_string( $size_name ) ) {
1986
1987 if ( $this->p->debug->enabled ) {
1988
1989 $this->p->debug->log( 'exiting early: $size_name must be a string' );
1990 }
1991
1992 return array();
1993
1994 } elseif ( $this->p->debug->enabled ) {
1995
1996 $this->p->debug->mark();
1997 }
1998
1999 $media_info = array();
2000 $mt_images = null;
2001 $mt_videos = null;
2002
2003 foreach ( $media_request as $key ) {
2004
2005 switch ( $key ) {
2006
2007 case 'pid':
2008 case ( preg_match( '/^(' . $mt_pre . '_img)/', $key ) ? true : false ):
2009
2010 /*
2011 * Get images only once.
2012 */
2013 if ( null === $mt_images ) {
2014
2015 $mt_images = $this->get_size_name_images( $num = 1, $size_name, $mod, $md_pre );
2016 }
2017
2018 break;
2019
2020 case ( preg_match( '/^(' . $mt_pre . '_vid)/', $key ) ? true : false ):
2021
2022 /*
2023 * Get videos only once.
2024 */
2025 if ( null === $mt_videos ) {
2026
2027 $mt_videos = $this->get_all_videos( $num = 1, $mod, $md_pre );
2028 }
2029
2030 break;
2031 }
2032 }
2033
2034 foreach ( $media_request as $key ) {
2035
2036 switch ( $key ) {
2037
2038 case 'pid':
2039
2040 if ( empty( $get_mt_name ) ) {
2041
2042 $get_mt_name = $mt_pre . ':image:id';
2043 }
2044
2045 // No break.
2046
2047 case $mt_pre . '_img_url':
2048
2049 if ( empty( $get_mt_name ) ) {
2050
2051 $get_mt_name = $mt_pre . ':image';
2052 }
2053
2054 // No break.
2055
2056 if ( null !== $mt_videos ) {
2057
2058 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $get_mt_name );
2059 }
2060
2061 if ( empty( $media_info[ $key ] ) ) {
2062
2063 $media_info[ $key ] = $this->get_mt_media_value( $mt_images, $get_mt_name );
2064 }
2065
2066 break;
2067
2068 case $mt_pre . '_img_alt':
2069
2070 $media_info[ $key ] = $this->get_mt_media_value( $mt_images, $mt_pre . ':image:alt' );
2071
2072 break;
2073
2074 case $mt_pre . '_vid_url':
2075
2076 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video' );
2077
2078 break;
2079
2080 case $mt_pre . '_vid_type':
2081
2082 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:type' );
2083
2084 break;
2085
2086 case $mt_pre . '_vid_title':
2087
2088 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:title' );
2089
2090 break;
2091
2092 case $mt_pre . '_vid_desc':
2093
2094 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:description' );
2095
2096 break;
2097
2098 case $mt_pre . '_vid_stream_url':
2099
2100 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:stream_url' );
2101
2102 break;
2103
2104 case $mt_pre . '_vid_width':
2105
2106 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:width' );
2107
2108 break;
2109
2110 case $mt_pre . '_vid_height':
2111
2112 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:height' );
2113
2114 break;
2115
2116 case $mt_pre . '_vid_prev':
2117
2118 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:thumbnail_url' );
2119
2120 break;
2121
2122 case $mt_pre . '_vid_upload':
2123
2124 $media_info[ $key ] = $this->get_mt_media_value( $mt_videos, $mt_pre . ':video:upload_date' );
2125
2126 WpssoSchema::add_date_time_timezone_opts( $media_info[ $key ], $media_info, $key );
2127
2128 break;
2129
2130 default:
2131
2132 $media_info[ $key ] = '';
2133
2134 break;
2135 }
2136
2137 unset( $get_mt_name );
2138 }
2139
2140 if ( $this->p->debug->enabled ) {
2141
2142 $this->p->debug->log( $media_info );
2143 }
2144
2145 return $media_info;
2146 }
2147
2148 /*
2149 * To get the first image ID or media URL, use the following methods instead:
2150 *
2151 * SucomUtil::get_first_og_image_id()
2152 * SucomUtil::get_first_og_image_url()
2153 * SucomUtil::get_first_mt_media_id()
2154 * SucomUtil::get_first_mt_media_url()
2155 */
2156 public function get_mt_media_value( $mt_og, $mt_media_pre ) {
2157
2158 if ( empty( $mt_og ) || ! is_array( $mt_og ) ) { // Nothing to do.
2159
2160 return '';
2161 }
2162
2163 if ( ! SucomUtil::is_assoc( $mt_og ) ) {
2164
2165 $og_media = reset( $mt_og ); // Only search the first media array.
2166 }
2167
2168 switch ( $mt_media_pre ) {
2169
2170 /*
2171 * If we're asking for an image or video url, then search all three values sequentially.
2172 */
2173 case ( preg_match( '/:(image|video)(:secure_url|:url)?$/', $mt_media_pre ) ? true : false ):
2174
2175 $mt_search = array(
2176 $mt_media_pre . ':secure_url', // og:image:secure_url
2177 $mt_media_pre . ':url', // og:image:url
2178 $mt_media_pre, // og:image
2179 );
2180
2181 break;
2182
2183 /*
2184 * Otherwise, only search for that specific meta tag name.
2185 */
2186 default:
2187
2188 $mt_search = array( $mt_media_pre );
2189
2190 break;
2191 }
2192
2193 foreach ( $mt_search as $key ) {
2194
2195 if ( ! isset( $og_media[ $key ] ) ) {
2196
2197 continue;
2198
2199 } elseif ( '' === $og_media[ $key ] || null === $og_media[ $key ] ) { // Allow for 0.
2200
2201 if ( $this->p->debug->enabled ) {
2202
2203 $this->p->debug->log( $og_media[ $key ] . ' value is empty (skipped)' );
2204 }
2205
2206 } elseif ( WPSSO_UNDEF === $og_media[ $key ] || (string) WPSSO_UNDEF === $og_media[ $key ] ) {
2207
2208 if ( $this->p->debug->enabled ) {
2209
2210 $this->p->debug->log( $og_media[ $key ] . ' value is ' . WPSSO_UNDEF . ' (skipped)' );
2211 }
2212
2213 } else {
2214
2215 return $og_media[ $key ];
2216 }
2217 }
2218
2219 return '';
2220 }
2221
2222 /*
2223 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
2224 */
2225 public function get_mt_pid_images( $pid, $size_names = 'thumbnail', $mt_pre = 'og' ) {
2226
2227 if ( $this->p->debug->enabled ) {
2228
2229 $this->p->debug->log_args( array(
2230 'pid' => $pid,
2231 'size_names' => $size_names,
2232 'mt_pre' => $mt_pre,
2233 ) );
2234 }
2235
2236 $mt_ret = array();
2237 $size_names = $this->p->util->get_image_size_names( $size_names ); // Always returns an array.
2238
2239 foreach ( $size_names as $size_name ) {
2240
2241 /*
2242 * get_mt_single_image_src() returns an og:image:url value, not an og:image:secure_url.
2243 */
2244 $mt_single_image = $this->get_mt_single_image_src( $pid, $size_name, $mt_pre );
2245
2246 if ( ! empty( $mt_single_image[ $mt_pre . ':image:url' ] ) ) {
2247
2248 $mt_ret[] = $mt_single_image;
2249 }
2250 }
2251
2252 return $mt_ret;
2253 }
2254
2255 public function get_mt_single_image_url( $url, $mt_pre = 'og' ) {
2256
2257 $mt_single_image = SucomUtil::get_mt_image_seed( $mt_pre );
2258
2259 $this->add_mt_single_image_url( $mt_single_image, $url, $mt_pre );
2260
2261 return $mt_single_image;
2262 }
2263
2264 /*
2265 * $size_name must be a string.
2266 */
2267 public function get_mt_single_image_src( $pid, $size_name = 'thumbnail', $mt_pre = 'og' ) {
2268
2269 $mt_single_image = SucomUtil::get_mt_image_seed( $mt_pre );
2270
2271 $this->add_mt_single_image_src( $mt_single_image, $pid, $size_name, $mt_pre );
2272
2273 return $mt_single_image;
2274 }
2275
2276 /*
2277 * $size_name must be a string.
2278 */
2279 public function add_mt_single_image_src( array &$mt_single_image, $pid, $size_name = 'thumbnail', $mt_pre = 'og' ) {
2280
2281 list(
2282 $mt_single_image[ $mt_pre . ':image:url' ],
2283 $mt_single_image[ $mt_pre . ':image:width' ],
2284 $mt_single_image[ $mt_pre . ':image:height' ],
2285 $mt_single_image[ $mt_pre . ':image:cropped' ],
2286 $mt_single_image[ $mt_pre . ':image:id' ],
2287 $mt_single_image[ $mt_pre . ':image:alt' ],
2288 $mt_single_image[ $mt_pre . ':image:size_name' ],
2289 ) = $this->get_attachment_image_src( $pid, $size_name );
2290 }
2291
2292 public function add_mt_single_image_url( array &$mt_single_image, $url, $mt_pre = 'og' ) {
2293
2294 /*
2295 * Example $image_info:
2296 *
2297 * Array (
2298 * [0] => 2048
2299 * [1] => 2048
2300 * [2] => 3
2301 * [3] => width="2048" height="2048"
2302 * [bits] => 8
2303 * [mime] => image/png
2304 * )
2305 */
2306 list( $img_width, $img_height, $img_type, $img_attr ) = $this->p->util->get_image_url_info( $url );
2307
2308 $mt_single_image[ $mt_pre . ':image:url' ] = $url;
2309 $mt_single_image[ $mt_pre . ':image:width' ] = $img_width;
2310 $mt_single_image[ $mt_pre . ':image:height' ] = $img_height;
2311 }
2312
2313 /*
2314 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
2315 *
2316 * $size_name can also be false to ignore image IDs and only use image URLs.
2317 */
2318 public function get_mt_opts_images( $opts, $size_names, $img_prefix = 'og_img', $key_num = null, $mt_pre = 'og' ) {
2319
2320 $img_opts = array();
2321
2322 foreach ( array( 'id', 'id_lib', 'url', 'url:width', 'url:height' ) as $key ) {
2323
2324 $key_suffix = null === $key_num ? $key : $key . '_' . $key_num; // Use a numbered multi-option key.
2325
2326 $opt_key = $img_prefix . '_' . $key_suffix;
2327
2328 $img_opts[ $key ] = SucomUtilOptions::get_key_value( $opt_key, $opts );
2329 }
2330
2331 if ( $this->p->debug->enabled ) {
2332
2333 $this->p->debug->log_arr( 'img_opts', $img_opts );
2334 }
2335
2336 $mt_ret = array();
2337
2338 if ( ! empty( $img_opts[ 'id' ] ) && ! empty( $size_names ) ) {
2339
2340 $mt_ret = $this->get_mt_pid_images( $img_opts[ 'id' ], $size_names, $mt_pre );
2341
2342 } elseif ( ! empty( $img_opts[ 'url' ] ) ) {
2343
2344 $mt_ret[] = array(
2345 $mt_pre . ':image:url' => $img_opts[ 'url' ],
2346 $mt_pre . ':image:width' => $img_opts[ 'url:width' ] > 0 ? $img_opts[ 'url:width' ] : WPSSO_UNDEF,
2347 $mt_pre . ':image:height' => $img_opts[ 'url:height' ] > 0 ? $img_opts[ 'url:height' ] : WPSSO_UNDEF,
2348 $mt_pre . ':image:cropped' => null,
2349 $mt_pre . ':image:id' => null,
2350 $mt_pre . ':image:alt' => null,
2351 $mt_pre . ':image:size_name' => null,
2352 );
2353 }
2354
2355 return $mt_ret;
2356 }
2357
2358 public function get_mt_img_pre_url( $opts, $img_prefix = 'og_img', $key_num = null, $mt_pre = 'og' ) {
2359
2360 /*
2361 * $size_name is false to ignore image IDs and only use image URLs.
2362 */
2363 $mt_ret = $this->get_mt_opts_images( $opts, $size_name = false, $img_prefix, $key_num, $mt_pre );
2364
2365 return isset( $mt_ret[ 0 ] ) ? $mt_ret[ 0 ] : array();
2366 }
2367
2368 /*
2369 * Returns an array of single video associative arrays.
2370 */
2371 public function get_content_videos( $num = 0, $mod = true, $content = '' ) {
2372
2373 if ( $this->p->debug->enabled ) {
2374
2375 $this->p->debug->log_args( array(
2376 'num' => $num,
2377 'mod' => $mod,
2378 'content' => strlen( $content ) . ' chars',
2379 ) );
2380 }
2381
2382 /*
2383 * The $mod array argument is preferred but not required.
2384 *
2385 * $mod = true | false | post_id | $mod array
2386 */
2387 if ( ! is_array( $mod ) ) {
2388
2389 if ( $this->p->debug->enabled ) {
2390
2391 $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
2392 }
2393
2394 $mod = $this->p->page->get_mod( $mod );
2395 }
2396
2397 $mt_videos = array();
2398
2399 /*
2400 * Allow custom content to be passed as an argument in $content.
2401 */
2402 if ( empty( $content ) ) {
2403
2404 $content = $this->p->page->get_the_content( $mod );
2405
2406 $content_passed = false;
2407
2408 } else $content_passed = true;
2409
2410 if ( empty( $content ) ) {
2411
2412 if ( $this->p->debug->enabled ) {
2413
2414 $this->p->debug->log( 'exiting early: empty ' . $mod[ 'name' ] . ' content' );
2415 }
2416
2417 return $mt_videos;
2418 }
2419
2420 /*
2421 * Detect standard video tags.
2422 *
2423 * $media[ 1 ] = The tag matched (ie. figure, iframe, or embed).
2424 * $media[ 2 ] = The attribute matched.
2425 * $media[ 3 ] = The video URL.
2426 */
2427 $all_matches = array();
2428
2429 /*
2430 * Matches raw video URLs when the "Use Filtered Content" option is disabled.
2431 */
2432 if ( preg_match_all( '/<(figure) class="(wp-block-embed[^ "]*) [^"]+"><div class="wp-block-embed__wrapper">' .
2433 ' *([^ \'"<>]+\/(embed\/|embed_code\/|player\/|swf\/|v\/|videos?\/|video\.php\?|watch\?)[^ \'"<>]+) *' . // Raw URL.
2434 '<\/div><\/figure>/i', $content, $html_tag_matches, PREG_SET_ORDER ) ) {
2435
2436 if ( $this->p->debug->enabled ) {
2437
2438 $this->p->debug->log( count( $html_tag_matches ) . ' <figure/> video html tag(s) found' );
2439 }
2440
2441 $all_matches = array_merge( $all_matches, $html_tag_matches );
2442
2443 } elseif ( $this->p->debug->enabled ) {
2444
2445 $this->p->debug->log( 'no <figure/> video html tag(s) found' );
2446 }
2447
2448 if ( preg_match_all( '/<(iframe|embed)[^<>]*? (data-lazy-src|data-share-src|data-src|src)=[\'"]' .
2449 '([^ \'"<>]+\/(embed\/|embed_code\/|player\/|swf\/|v\/|videos?\/|video\.php\?|watch\?)[^ \'"<>]+)[\'"][^<>]*>/i',
2450 $content, $html_tag_matches, PREG_SET_ORDER ) ) {
2451
2452 if ( $this->p->debug->enabled ) {
2453
2454 $this->p->debug->log( count( $html_tag_matches ) . ' <iframe|embed/> video html tag(s) found' );
2455 }
2456
2457 $all_matches = array_merge( $all_matches, $html_tag_matches );
2458
2459 } elseif ( $this->p->debug->enabled ) {
2460
2461 $this->p->debug->log( 'no <iframe|embed/> video html tag(s) found' );
2462 }
2463
2464 /*
2465 * Get video details for standard video tags.
2466 *
2467 * $media[ 1 ] = The tag matched (ie. figure, iframe, or embed).
2468 * $media[ 2 ] = The attribute matched.
2469 * $media[ 3 ] = The video URL.
2470 */
2471 if ( is_array( $all_matches ) ) { // Just in case.
2472
2473 foreach ( $all_matches as $match_num => $media ) {
2474
2475 if ( $this->p->debug->enabled ) {
2476
2477 $this->p->debug->log( '<' . $media[ 1 ] . '/> video html tag found ' . $media[ 2 ] . ' = ' . $media[ 3 ] );
2478 }
2479
2480 if ( ! empty( $media[ 3 ] ) ) {
2481
2482 if ( $this->p->util->is_uniq_url( $media[ 3 ], $uniq_context = 'video', $mod ) ) {
2483
2484 $args = array(
2485 'url' => $media[ 3 ],
2486 'width' => preg_match( '/ width=[\'"]?([0-9]+)[\'"]?/i', $media[ 0 ], $match ) ? $match[ 1 ] : null,
2487 'height' => preg_match( '/ height=[\'"]?([0-9]+)[\'"]?/i', $media[ 0 ], $match ) ? $match[ 1 ] : null,
2488 );
2489
2490 $mt_single_video = $this->get_video_details( $args, $mod ); // Returns a single video associative array.
2491
2492 if ( ! empty( $mt_single_video ) ) {
2493
2494 if ( $this->p->util->push_max( $mt_videos, $mt_single_video, $num ) ) {
2495
2496 return $mt_videos;
2497 }
2498 }
2499 }
2500 }
2501 }
2502 }
2503
2504 /*
2505 * Filters / modules may detect additional embedded video markup.
2506 *
2507 * See WpssoIntegUtilElementor->filter_content_videos().
2508 * See WpssoProMediaSlideshare->filter_content_videos().
2509 * See WpssoProMediaWistia->filter_content_videos().
2510 * See WpssoProMediaWpvideoblock->filter_content_videos().
2511 * See WpssoProMediaWpvideoshortcode->filter_content_videos().
2512 * See WpssoStdMediaSlideshare->filter_content_videos().
2513 * See WpssoStdMediaWistia->filter_content_videos().
2514 * See WpssoStdMediaWpvideoblock->filter_content_videos().
2515 * See WpssoStdMediaWpvideoshortcode->filter_content_videos().
2516 */
2517 $filter_name = 'wpsso_content_videos'; // No need to sanitize.
2518
2519 if ( $this->p->debug->enabled ) {
2520
2521 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
2522 }
2523
2524 $all_matches = apply_filters( $filter_name, array(), $content, $mod );
2525
2526 if ( is_array( $all_matches ) ) { // Just in case.
2527
2528 if ( $this->p->debug->enabled ) {
2529
2530 $this->p->debug->log( count( $all_matches ) . ' videos returned by ' . $filter_name . ' filters' );
2531 }
2532
2533 foreach ( $all_matches as $match_num => $args ) { // Check each match until WpssoUtil->push_max() is true.
2534
2535 if ( is_array( $args ) ) { // Just in case.
2536
2537 if ( ! empty( $args[ 'url' ] ) ) {
2538
2539 if ( $this->p->util->is_uniq_url( $args[ 'url' ], $uniq_context = 'video', $mod ) ) {
2540
2541 $mt_single_video = $this->get_video_details( $args, $mod ); // Returns a single video associative array.
2542
2543 if ( ! empty( $mt_single_video ) ) {
2544
2545 if ( $this->p->util->push_max( $mt_videos, $mt_single_video, $num ) ) {
2546
2547 break; // Stop here.
2548 }
2549 }
2550 }
2551
2552 } elseif ( $this->p->debug->enabled ) {
2553
2554 $this->p->debug->log( 'args url missing from videos array element #' . $match_num );
2555 }
2556
2557 } elseif ( $this->p->debug->enabled ) {
2558
2559 $this->p->debug->log( 'videos array element #' . $match_num . ' is not a media array' );
2560 }
2561 }
2562 }
2563
2564 if ( $this->p->debug->enabled ) {
2565
2566 $this->p->debug->log( 'returning ' . count( $mt_videos ) . ' videos' );
2567 }
2568
2569 return $mt_videos;
2570 }
2571
2572 /*
2573 * Returns a single video associative array.
2574 *
2575 * $fallback = true when called from WpssoAbstractWpMeta->get_og_videos().
2576 */
2577 public function get_video_details( array $args, array $mod, $fallback = false ) {
2578
2579 if ( $this->p->debug->enabled ) {
2580
2581 $this->p->debug->mark();
2582 }
2583
2584 if ( empty( $args[ 'url' ] ) ) { // Just in case.
2585
2586 if ( $this->p->debug->enabled ) {
2587
2588 $this->p->debug->log( 'exiting early: no video URL' );
2589 }
2590
2591 return array();
2592 }
2593
2594 /*
2595 * Make sure we have all array keys defined.
2596 */
2597 $args = array_merge( array(
2598 'url' => '',
2599 'type' => '',
2600 'stream_url' => '',
2601 'prev_url' => '',
2602 'width' => null,
2603 'height' => null,
2604 'attach_id' => null,
2605 ), $args );
2606
2607 /*
2608 * Create the array of video details.
2609 */
2610 $mt_single_video = SucomUtil::get_mt_video_seed();
2611
2612 if ( ! empty( $args[ 'url' ] ) ) {
2613
2614 foreach ( array( 'url', 'stream_url', 'width', 'height', 'type' ) as $key ) {
2615
2616 if ( ! empty( $args[ $key ] ) ) { // Just in case.
2617
2618 $mt_single_video[ 'og:video:' . $key ] = $args[ $key ];
2619 }
2620 }
2621
2622 $mt_single_video[ 'og:video:has_image' ] = false; // Default.
2623
2624 /*
2625 * Check for preview image.
2626 */
2627 $vid_img_url = '';
2628
2629 if ( ! empty( $args[ 'prev_url' ] ) ) {
2630
2631 $vid_img_url = $args[ 'prev_url' ];
2632
2633 } elseif ( $this->p->debug->enabled ) {
2634
2635 $this->p->debug->log( 'prev_url missing from args array' );
2636 }
2637
2638 $filter_name = 'wpsso_og_video_image_url';
2639
2640 if ( $this->p->debug->enabled ) {
2641
2642 $this->p->debug->log( 'applying filters "' . $filter_name . '" for "' . $vid_img_url . '"' );
2643 }
2644
2645 if ( $vid_img_url = apply_filters( $filter_name, $vid_img_url, $args[ 'url' ] ) ) {
2646
2647 if ( $this->p->debug->enabled ) {
2648
2649 $this->p->debug->log( 'adding video preview url = ' . $vid_img_url );
2650 }
2651
2652 if ( SucomUtil::is_https( $vid_img_url ) ) { // Just in case.
2653
2654 $mt_single_video[ 'og:image:secure_url' ] = $vid_img_url;
2655
2656 unset( $mt_single_video[ 'og:image:url' ] ); // Just in case.
2657
2658 } else $mt_single_video[ 'og:image:url' ] = $vid_img_url;
2659
2660 $mt_single_video[ 'og:video:thumbnail_url' ] = $vid_img_url;
2661 $mt_single_video[ 'og:video:has_image' ] = true;
2662
2663 /*
2664 * Add correct image sizes for the image URL using getimagesize().
2665 *
2666 * Note that PHP v7.1 or better is required to get the image size of WebP images.
2667 */
2668 $this->p->util->add_image_url_size( $mt_single_video );
2669 }
2670
2671 /*
2672 * If we have a WordPress attachment ID for the video URL (self-hosted videos), then add the Open
2673 * Graph video meta tags from the attachment's post.
2674 */
2675 if ( ! empty( $args[ 'attach_id' ] ) || $args[ 'attach_id' ] = attachment_url_to_postid( $args[ 'url' ] ) ) {
2676
2677 if ( $this->p->debug->enabled ) {
2678
2679 $this->p->debug->log( 'adding video meta tags from attachment ID = ' . $args[ 'attach_id' ] );
2680 }
2681
2682 $attach_mod = $this->p->post->get_mod( $args[ 'attach_id' ] );
2683
2684 $this->p->media->add_og_video_from_attachment( $mt_single_video, $attach_mod );
2685 }
2686 }
2687
2688 if ( $this->p->debug->enabled ) {
2689
2690 $this->p->debug->log_arr( 'args', $args );
2691 }
2692
2693 /*
2694 * Filter the array of video details.
2695 *
2696 * See WpssoProMediaFacebook->filter_video_details().
2697 * See WpssoProMediaSlideshare->filter_video_details().
2698 * See WpssoProMediaSoundcloud->filter_video_details().
2699 * See WpssoProMediaVimeo->filter_video_details().
2700 * See WpssoProMediaWistia->filter_video_details().
2701 * See WpssoProMediaYoutube->filter_video_details().
2702 * See WpssoStdMediaFacebook->filter_video_details().
2703 * See WpssostdMediaSlideshare->filter_video_details().
2704 * See WpssoStdMediaSoundcloud->filter_video_details().
2705 * See WpssoStdMediaVimeo->filter_video_details().
2706 * See WpssoStdMediaWistia->filter_video_details().
2707 * See WpssoStdMediaYoutube->filter_video_details().
2708 */
2709 $filter_name = 'wpsso_video_details'; // No need to sanitize.
2710
2711 if ( $this->p->debug->enabled ) {
2712
2713 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
2714 }
2715
2716 $mt_single_video = apply_filters( $filter_name, $mt_single_video, $args, $mod );
2717
2718 if ( $this->p->debug->enabled ) {
2719
2720 $this->p->debug->log_arr( 'mt_single_video', $mt_single_video );
2721 }
2722
2723 /*
2724 * Sanitize the array of video details.
2725 */
2726 if ( isset( $mt_single_video[ 'al:web:url' ] ) ) { // Just in case.
2727
2728 if ( '' === $mt_single_video[ 'al:web:url' ] ) {
2729
2730 $mt_single_video[ 'al:web:should_fallback' ] = ''; // False by default.
2731 }
2732 }
2733
2734 foreach ( array( 'og:video', 'og:image' ) as $mt_media_pre ) {
2735
2736 $media_url = SucomUtil::get_first_mt_media_url( $mt_single_video, $mt_media_pre );
2737
2738 if ( 'og:video' === $mt_media_pre ) {
2739
2740 /*
2741 * Fallback to the original video url.
2742 */
2743 if ( empty( $media_url ) && $fallback ) {
2744
2745 if ( $this->p->debug->enabled ) {
2746
2747 $this->p->debug->log( 'falling back to media url = ' . $args[ 'url' ] );
2748 }
2749
2750 $media_url = $mt_single_video[ 'og:video:url' ] = $args[ 'url' ];
2751 }
2752
2753 /*
2754 * Check for an empty mime_type.
2755 */
2756 if ( empty( $mt_single_video[ 'og:video:type' ] ) ) {
2757
2758 if ( $this->p->debug->enabled ) {
2759
2760 $this->p->debug->log( 'og:video:type is empty - using media URL to determine the mime-type' );
2761 }
2762
2763 /*
2764 * Check for filename extension, slash, or common words (in that order), followed
2765 * by an optional query string (which is ignored).
2766 */
2767 if ( preg_match( '/(\.[a-z0-9]+|\/|\/embed\/.*|\/iframe\/.*)(\?[^\?]*)?$/', $media_url, $match ) ) {
2768
2769 if ( $this->p->debug->enabled ) {
2770
2771 $this->p->debug->log( 'matched media URL substr "' . $match[ 1 ] . '"' );
2772 }
2773
2774 switch ( $match[ 1 ] ) {
2775
2776 case '/': // WebPage
2777 case '.htm':
2778 case '.html':
2779 case ( strpos( $match[ 1 ], '/embed/' ) === 0 ? true : false ):
2780 case ( strpos( $match[ 1 ], '/iframe/' ) === 0 ? true : false ):
2781
2782 $mt_single_video[ 'og:video:type' ] = 'text/html';
2783
2784 break;
2785
2786 case '.3gp': // 3GP Mobile
2787
2788 $mt_single_video[ 'og:video:type' ] = 'video/3gpp';
2789
2790 break;
2791
2792 case '.avi': // A/V Interleave
2793
2794 $mt_single_video[ 'og:video:type' ] = 'video/x-msvideo';
2795
2796 break;
2797
2798 case '.flv': // Flash
2799
2800 $mt_single_video[ 'og:video:type' ] = 'video/x-flv';
2801
2802 break;
2803
2804 case '.m3u8': // iPhone Index
2805
2806 $mt_single_video[ 'og:video:type' ] = 'application/x-mpegURL';
2807
2808 break;
2809
2810 case '.mov': // QuickTime
2811
2812 $mt_single_video[ 'og:video:type' ] = 'video/quicktime';
2813
2814 break;
2815
2816 case '.mp4': // MPEG-4
2817
2818 $mt_single_video[ 'og:video:type' ] = 'video/mp4';
2819
2820 break;
2821
2822 case '.swf': // Shockwave Flash
2823
2824 $mt_single_video[ 'og:video:type' ] = 'application/x-shockwave-flash';
2825
2826 break;
2827
2828 case '.ts': // iPhone Segment
2829
2830 $mt_single_video[ 'og:video:type' ] = 'video/MP2T';
2831
2832 break;
2833
2834 case '.wmv': // Windows Media
2835
2836 $mt_single_video[ 'og:video:type' ] = 'video/x-ms-wmv';
2837
2838 break;
2839
2840 default:
2841
2842 if ( $this->p->debug->enabled ) {
2843
2844 $this->p->debug->log( 'unknown video extension "' . $match[ 1 ] . '"' );
2845 }
2846
2847 $mt_single_video[ 'og:video:type' ] = '';
2848
2849 break;
2850 }
2851
2852 if ( $this->p->debug->enabled ) {
2853
2854 $this->p->debug->log( 'setting og:video:type = ' . $mt_single_video[ 'og:video:type' ] );
2855 }
2856 }
2857 }
2858 }
2859
2860 $have_media[ $mt_media_pre ] = empty( $media_url ) ? false : true;
2861
2862 /*
2863 * Remove all meta tags if there's no media URL or the media URL is a duplicate.
2864 */
2865 if ( empty( $have_media[ $mt_media_pre ] ) ) {
2866
2867 if ( $this->p->debug->enabled ) {
2868
2869 $this->p->debug->log( 'no media URL: removing ' . $mt_media_pre . ' meta tags' );
2870 }
2871
2872 $mt_single_video = SucomUtil::preg_grep_keys( '/^' . $mt_media_pre . '(:.*)?$/', $mt_single_video, $invert = true );
2873
2874 } elseif ( empty( $mt_single_video[ 'og:video:embed_url' ] ) &&
2875 isset( $mt_single_video[ 'og:video:type' ] ) && 'text/html' === $mt_single_video[ 'og:video:type' ] ) {
2876
2877 if ( $this->p->debug->enabled ) {
2878
2879 $this->p->debug->log( 'text/html media URL: removing ' . $mt_media_pre . ' meta tags' );
2880 }
2881
2882 $mt_single_video = SucomUtil::preg_grep_keys( '/^' . $mt_media_pre . '(:.*)?$/', $mt_single_video, $invert = true );
2883
2884 } elseif ( $this->p->util->is_dupe_url( $media_url, $uniq_context = 'video_details', $mod ) ) {
2885
2886 if ( $this->p->debug->enabled ) {
2887
2888 $this->p->debug->log( 'duplicate media URL: removing ' . $mt_media_pre . ' meta tags' );
2889 }
2890
2891 $mt_single_video = SucomUtil::preg_grep_keys( '/^' . $mt_media_pre . '(:.*)?$/', $mt_single_video, $invert = true );
2892
2893 } elseif ( 'og:image' === $mt_media_pre ) { // If the media is an image, then maybe add missing sizes.
2894
2895 if ( empty( $mt_single_video[ 'og:image:width' ] ) || $mt_single_video[ 'og:image:width' ] < 0 ||
2896 empty( $mt_single_video[ 'og:image:height' ] ) || $mt_single_video[ 'og:image:height' ] < 0 ) {
2897
2898 /*
2899 * Add correct image sizes for the image URL using getimagesize().
2900 *
2901 * Note that PHP v7.1 or better is required to get the image size of WebP images.
2902 */
2903 $this->p->util->add_image_url_size( $mt_single_video );
2904
2905 if ( $this->p->debug->enabled ) {
2906
2907 $this->p->debug->log( 'video image URL size: ' .
2908 $mt_single_video[ 'og:image:width' ] . 'x' . $mt_single_video[ 'og:image:height' ] );
2909 }
2910
2911 } elseif ( $this->p->debug->enabled ) {
2912
2913 $this->p->debug->log( 'video image width and height: ' .
2914 $mt_single_video[ 'og:image:width' ] . 'x' . $mt_single_video[ 'og:image:height' ] );
2915 }
2916
2917 } elseif ( $this->p->debug->enabled ) {
2918
2919 $this->p->debug->log( 'accepting ' . $mt_media_pre . ' media URL: ' . $media_url );
2920 }
2921 }
2922
2923 /*
2924 * If there's no video or preview image, then return an empty array.
2925 */
2926 if ( ! $have_media[ 'og:video' ] && ! $have_media[ 'og:image' ] ) {
2927
2928 if ( $this->p->debug->enabled ) {
2929
2930 $this->p->debug->log( 'exiting early: no media found' );
2931 }
2932
2933 return array();
2934
2935 }
2936
2937 if ( $this->p->debug->enabled ) {
2938
2939 $this->p->debug->log( 'returning single video array' );
2940 }
2941
2942 return $mt_single_video;
2943 }
2944
2945 public function add_og_video_from_attachment( array &$mt_single_video, array $attach_mod ) {
2946
2947 if ( $this->p->debug->enabled ) {
2948
2949 $this->p->debug->mark();
2950 }
2951
2952 if ( empty( $attach_mod[ 'id' ] ) ) { // Just in case.
2953
2954 if ( $this->p->debug->enabled ) {
2955
2956 $this->p->debug->log( 'exiting early: args attachment mod id is empty' );
2957 }
2958
2959 return;
2960
2961 } elseif ( ! wp_attachment_is( 'video', $attach_mod[ 'id' ] ) ) { // Just in case.
2962
2963 if ( $this->p->debug->enabled ) {
2964
2965 $this->p->debug->log( 'exiting early: args attachment mod id is not a video' );
2966 }
2967
2968 return;
2969 }
2970
2971 $post_obj = SucomUtilWP::get_post_object( $attach_mod[ 'id' ] );
2972
2973 $attach_metadata = wp_get_attachment_metadata( $attach_mod[ 'id' ] ); // Returns a WP_Error object on failure.
2974
2975 if ( $this->p->debug->enabled ) {
2976
2977 $this->p->debug->log_arr( 'attach_metadata', $attach_metadata );
2978 }
2979
2980 foreach( array(
2981 'og:video:url',
2982 'og:video:embed_url',
2983 'og:video:stream_url',
2984 'og:video:stream_size',
2985 'og:video:thumbnail_url',
2986 'og:video:title',
2987 'og:video:description',
2988 'og:video:upload_date',
2989 'og:video:has_video', // Used by video API modules.
2990 'og:video:type',
2991 'og:video:width',
2992 'og:video:height',
2993 'og:video:duration',
2994 ) as $mt_name ) {
2995
2996 if ( ! empty( $mt_single_video[ $mt_name ] ) ) {
2997
2998 if ( $this->p->debug->enabled ) {
2999
3000 $this->p->debug->log( 'skipping ' . $mt_name . ' = ' . $mt_single_video[ $mt_name ] );
3001 }
3002
3003 continue;
3004 }
3005
3006 $mt_single_video[ $mt_name ] = null; // Just in case.
3007
3008 switch ( $mt_name ) {
3009
3010 case 'og:video:url':
3011 case 'og:video:stream_url': // VideoObject contentUrl.
3012
3013 $mt_single_video[ $mt_name ] = wp_get_attachment_url( $attach_mod[ 'id' ] );
3014
3015 break;
3016
3017 case 'og:video:stream_size': // VideoObject contentSize.
3018
3019 if ( ! empty( $attach_metadata[ 'filesize' ] ) ) {
3020
3021 $mt_single_video[ $mt_name ] = $attach_metadata[ 'filesize' ];
3022 }
3023
3024 break;
3025
3026 case 'og:video:embed_url':
3027
3028 $mt_single_video[ $mt_name ] = get_post_embed_url( $attach_mod[ 'id' ] );
3029
3030 break;
3031
3032 case 'og:video:thumbnail_url':
3033
3034 if ( ! empty( $mt_single_video[ 'og:video:has_image' ] ) ) { // Just in case.
3035
3036 if ( $this->p->debug->enabled ) {
3037
3038 $this->p->debug->log( 'skipping ' . $mt_name . ' - og:video:has_image is true' );
3039 }
3040
3041 break;
3042 }
3043
3044 $featured_image_id = get_post_thumbnail_id( $attach_mod[ 'id' ] );
3045
3046 if ( ! empty( $featured_image_id ) ) { // Just in case.
3047
3048 $mt_single_video[ $mt_name ] = wp_get_attachment_image_url( $featured_image_id, 'full' );
3049
3050 if ( ! empty( $mt_single_video[ $mt_name ] ) ) { // Just in case.
3051
3052 $mt_single_video[ 'og:video:has_image' ] = true;
3053
3054 $mt_single_video[ 'og:image:url' ] = $mt_single_video[ $mt_name ];
3055
3056 /*
3057 * Add correct image sizes for the image URL using getimagesize().
3058 *
3059 * Note that PHP v7.1 or better is required to get the image size of WebP images.
3060 */
3061 $this->p->util->add_image_url_size( $mt_single_video );
3062 }
3063 }
3064
3065 break;
3066
3067 case 'og:video:title':
3068
3069 $mt_single_video[ $mt_name ] = $this->p->page->get_title( $attach_mod, $md_key = 'og_title', $max_len = 'og_title' );
3070
3071 break;
3072
3073 case 'og:video:description':
3074
3075 $mt_single_video[ $mt_name ] = $this->p->page->get_description( $attach_mod, $md_key = 'og_desc', $max_len = 'og_desc' );
3076
3077 break;
3078
3079 case 'og:video:upload_date':
3080
3081 if ( ! empty( $post_obj->post_date_gmt ) ) {
3082
3083 $mt_single_video[ $mt_name ] = date_format( date_create( (string) $post_obj->post_date_gmt ), 'c' );
3084
3085 } elseif ( ! empty( $post_obj->post_date ) ) {
3086
3087 $mt_single_video[ $mt_name ] = date_format( date_create( (string) $post_obj->post_date ), 'c' );
3088 }
3089
3090 break;
3091
3092 case 'og:video:has_video': // Used by video API modules.
3093
3094 $mt_single_video[ $mt_name ] = true;
3095
3096 break;
3097
3098 case 'og:video:type':
3099
3100 if ( ! empty( $attach_metadata[ 'mime_type' ] ) ) {
3101
3102 $mt_single_video[ $mt_name ] = $attach_metadata[ 'mime_type' ];
3103 }
3104
3105 break;
3106
3107 case 'og:video:width':
3108
3109 if ( ! empty( $attach_metadata[ 'width' ] ) ) {
3110
3111 $mt_single_video[ $mt_name ] = $attach_metadata[ 'width' ];
3112 }
3113
3114 break;
3115
3116 case 'og:video:height':
3117
3118 if ( ! empty( $attach_metadata[ 'height' ] ) ) {
3119
3120 $mt_single_video[ $mt_name ] = $attach_metadata[ 'height' ];
3121 }
3122
3123 break;
3124
3125 case 'og:video:duration':
3126
3127 if ( ! empty( $attach_metadata[ 'length' ] ) ) {
3128
3129 $mt_single_video[ $mt_name ] = 'PT' . $attach_metadata[ 'length' ] . 'S';
3130 }
3131
3132 break;
3133 }
3134
3135 if ( $this->p->debug->enabled ) {
3136
3137 $this->p->debug->log( 'added ' . $mt_name . ' = ' . $mt_single_video[ $mt_name ] );
3138 }
3139 }
3140 }
3141
3142 /*
3143 * Modifies a single video associative array.
3144 */
3145 public function add_og_video_from_url( array &$mt_single_video, $url, $cache_exp_secs = 0, $throttle_secs = 0 ) {
3146
3147 if ( $this->p->debug->enabled ) {
3148
3149 $this->p->debug->mark();
3150 }
3151
3152 /*
3153 * Use the Facebook user agent to get Open Graph meta tags.
3154 */
3155 $curl_opts = array(
3156 'CURLOPT_USERAGENT' => WPSSO_PHP_CURL_USERAGENT_FACEBOOK,
3157 'CURLOPT_COOKIELIST' => 'ALL', // Erases all cookies held in memory.
3158 'CURLOPT_CACHE_THROTTLE' => $throttle_secs, // Internal curl option.
3159 );
3160
3161 if ( $this->p->notice->is_admin_pre_notices() ) {
3162
3163 if ( $cache_url = $this->p->util->is_html_head_meta_url_cached( $url, $cache_exp_secs ) ) {
3164
3165 if ( $this->p->debug->enabled ) {
3166
3167 $this->p->debug->log( 'getting video details for ' . $url . ' from file cache ' . $cache_url );
3168 }
3169
3170 } else {
3171
3172 if ( $this->p->debug->enabled ) {
3173
3174 $this->p->debug->log( 'getting video details for ' . $url );
3175 }
3176
3177 $notice_msg = sprintf( __( 'Getting video details for %s.', 'wpsso' ), '<a href="' . $url . '">' . $url . '</a>' ) . ' ';
3178
3179 $notice_key = 'video-details-' . $url;
3180
3181 $this->p->notice->inf( $notice_msg, null, $notice_key );
3182 }
3183 }
3184
3185 $metas = $this->p->util->get_html_head_meta( $url, $query = '//meta', $libxml_errors = false, $cache_exp_secs, $curl_opts );
3186
3187 /*
3188 * Array
3189 * (
3190 * [meta] => Array
3191 * (
3192 * [0] => Array
3193 * (
3194 * [http-equiv] => X-UA-Compatible
3195 * [content] => IE=edge
3196 * [textContent] =>
3197 * )
3198 *
3199 * [1] => Array
3200 * (
3201 * [name] => theme-color
3202 * [content] => rgba(255, 255, 255, 0.98)
3203 * [textContent] =>
3204 * )
3205 *
3206 * [2] => Array
3207 * (
3208 * [name] => title
3209 * [content] => Entering Namibia's TRIBAL LANDS [S5 - Eps. 58]
3210 * [textContent] =>
3211 * )
3212 *
3213 * [3] => Array
3214 * (
3215 * [name] => description
3216 * [content] => In this episode I am riding to Epupa Falls, gorgeous waterfalls right at the border between Namibia and Angola. The border with Angola is closed, so I can't ...
3217 * [textContent] =>
3218 * )
3219 *
3220 * [4] => Array
3221 * (
3222 * [name] => keywords
3223 * [content] => honda, Honda CRF250L, crf250L review, dual purpose bike, RTW, round the world on motorbike, adv rider, motorbike traveller, female motorcycle traveler, solo female traveller, best dirt bike, things to see namibia, motorcycling namibia, best roads namibia, solo ride namibia, Namibia, Southern Africa, off-roading namibia, himba tribe, tribes namibia, opuwo, epupa, epupa falls
3224 * [textContent] =>
3225 * )
3226 *
3227 * [5] => Array
3228 * (
3229 * [property] => og:site_name
3230 * [content] => YouTube
3231 * [textContent] =>
3232 * )
3233 *
3234 * [6] => Array
3235 * (
3236 * [property] => og:url
3237 * [content] => https://www.youtube.com/watch?v=IQu7ox_UxpA
3238 * [textContent] =>
3239 * )
3240 *
3241 * [7] => Array
3242 * (
3243 * [property] => og:title
3244 * [content] => Entering Namibia's TRIBAL LANDS [S5 - Eps. 58]
3245 * [textContent] =>
3246 * )
3247 *
3248 * [8] => Array
3249 * (
3250 * [property] => og:image
3251 * [content] => https://i.ytimg.com/vi/IQu7ox_UxpA/maxresdefault.jpg
3252 * [textContent] =>
3253 * )
3254 *
3255 * [9] => Array
3256 * (
3257 * [property] => og:image:width
3258 * [content] => 1280
3259 * [textContent] =>
3260 * )
3261 *
3262 * [10] => Array
3263 * (
3264 * [property] => og:image:height
3265 * [content] => 720
3266 * [textContent] =>
3267 * )
3268 *
3269 * [11] => Array
3270 * (
3271 * [property] => og:description
3272 * [content] => In this episode I am riding to Epupa Falls, gorgeous waterfalls right at the border between Namibia and Angola. The border with Angola is closed, so I can't ...
3273 * [textContent] =>
3274 * )
3275 *
3276 * [12] => Array
3277 * (
3278 * [property] => al:ios:app_store_id
3279 * [content] => 544007664
3280 * [textContent] =>
3281 * )
3282 *
3283 * [13] => Array
3284 * (
3285 * [property] => al:ios:app_name
3286 * [content] => YouTube
3287 * [textContent] =>
3288 * )
3289 *
3290 * [14] => Array
3291 * (
3292 * [property] => al:ios:url
3293 * [content] => vnd.youtube://www.youtube.com/watch?v=IQu7ox_UxpA&feature=applinks
3294 * [textContent] =>
3295 * )
3296 *
3297 * [15] => Array
3298 * (
3299 * [property] => al:android:url
3300 * [content] => vnd.youtube://www.youtube.com/watch?v=IQu7ox_UxpA&feature=applinks
3301 * [textContent] =>
3302 * )
3303 *
3304 * [16] => Array
3305 * (
3306 * [property] => al:web:url
3307 * [content] => http://www.youtube.com/watch?v=IQu7ox_UxpA&feature=applinks
3308 * [textContent] =>
3309 * )
3310 *
3311 * [17] => Array
3312 * (
3313 * [property] => og:type
3314 * [content] => video.other
3315 * [textContent] =>
3316 * )
3317 *
3318 * [18] => Array
3319 * (
3320 * [property] => og:video:url
3321 * [content] => https://www.youtube.com/embed/IQu7ox_UxpA
3322 * [textContent] =>
3323 * )
3324 *
3325 * [19] => Array
3326 * (
3327 * [property] => og:video:secure_url
3328 * [content] => https://www.youtube.com/embed/IQu7ox_UxpA
3329 * [textContent] =>
3330 * )
3331 *
3332 * [20] => Array
3333 * (
3334 * [property] => og:video:type
3335 * [content] => text/html
3336 * [textContent] =>
3337 * )
3338 *
3339 * [21] => Array
3340 * (
3341 * [property] => og:video:width
3342 * [content] => 1280
3343 * [textContent] =>
3344 * )
3345 *
3346 * [22] => Array
3347 * (
3348 * [property] => og:video:height
3349 * [content] => 720
3350 * [textContent] =>
3351 * )
3352 *
3353 * [23] => Array
3354 * (
3355 * [property] => al:android:app_name
3356 * [content] => YouTube
3357 * [textContent] =>
3358 * )
3359 *
3360 * [24] => Array
3361 * (
3362 * [property] => al:android:package
3363 * [content] => com.google.android.youtube
3364 * [textContent] =>
3365 * )
3366 *
3367 * [25] => Array
3368 * (
3369 * [property] => og:video:tag
3370 * [content] => honda
3371 * [textContent] =>
3372 * )
3373 *
3374 * [48] => Array
3375 * (
3376 * [property] => fb:app_id
3377 * [content] => 87741124305
3378 * [textContent] =>
3379 * )
3380 *
3381 * [49] => Array
3382 * (
3383 * [name] => twitter:card
3384 * [content] => player
3385 * [textContent] =>
3386 * )
3387 *
3388 * [50] => Array
3389 * (
3390 * [name] => twitter:site
3391 * [content] => @youtube
3392 * [textContent] =>
3393 * )
3394 *
3395 * [51] => Array
3396 * (
3397 * [name] => twitter:url
3398 * [content] => https://www.youtube.com/watch?v=IQu7ox_UxpA
3399 * [textContent] =>
3400 * )
3401 *
3402 * [52] => Array
3403 * (
3404 * [name] => twitter:title
3405 * [content] => Entering Namibia's TRIBAL LANDS [S5 - Eps. 58]
3406 * [textContent] =>
3407 * )
3408 *
3409 * [53] => Array
3410 * (
3411 * [name] => twitter:description
3412 * [content] => In this episode I am riding to Epupa Falls, gorgeous waterfalls right at the border between Namibia and Angola. The border with Angola is closed, so I can't ...
3413 * [textContent] =>
3414 * )
3415 *
3416 * [54] => Array
3417 * (
3418 * [name] => twitter:image
3419 * [content] => https://i.ytimg.com/vi/IQu7ox_UxpA/maxresdefault.jpg
3420 * [textContent] =>
3421 * )
3422 *
3423 * [55] => Array
3424 * (
3425 * [name] => twitter:app:name:iphone
3426 * [content] => YouTube
3427 * [textContent] =>
3428 * )
3429 *
3430 * [56] => Array
3431 * (
3432 * [name] => twitter:app:id:iphone
3433 * [content] => 544007664
3434 * [textContent] =>
3435 * )
3436 *
3437 * [57] => Array
3438 * (
3439 * [name] => twitter:app:name:ipad
3440 * [content] => YouTube
3441 * [textContent] =>
3442 * )
3443 *
3444 * [58] => Array
3445 * (
3446 * [name] => twitter:app:id:ipad
3447 * [content] => 544007664
3448 * [textContent] =>
3449 * )
3450 *
3451 * [59] => Array
3452 * (
3453 * [name] => twitter:app:url:iphone
3454 * [content] => vnd.youtube://www.youtube.com/watch?v=IQu7ox_UxpA&feature=applinks
3455 * [textContent] =>
3456 * )
3457 *
3458 * [60] => Array
3459 * (
3460 * [name] => twitter:app:url:ipad
3461 * [content] => vnd.youtube://www.youtube.com/watch?v=IQu7ox_UxpA&feature=applinks
3462 * [textContent] =>
3463 * )
3464 *
3465 * [61] => Array
3466 * (
3467 * [name] => twitter:app:name:googleplay
3468 * [content] => YouTube
3469 * [textContent] =>
3470 * )
3471 *
3472 * [62] => Array
3473 * (
3474 * [name] => twitter:app:id:googleplay
3475 * [content] => com.google.android.youtube
3476 * [textContent] =>
3477 * )
3478 *
3479 * [63] => Array
3480 * (
3481 * [name] => twitter:app:url:googleplay
3482 * [content] => https://www.youtube.com/watch?v=IQu7ox_UxpA
3483 * [textContent] =>
3484 * )
3485 *
3486 * [64] => Array
3487 * (
3488 * [name] => twitter:player
3489 * [content] => https://www.youtube.com/embed/IQu7ox_UxpA
3490 * [textContent] =>
3491 * )
3492 *
3493 * [65] => Array
3494 * (
3495 * [name] => twitter:player:width
3496 * [content] => 1280
3497 * [textContent] =>
3498 * )
3499 *
3500 * [66] => Array
3501 * (
3502 * [name] => twitter:player:height
3503 * [content] => 720
3504 * [textContent] =>
3505 * )
3506 *
3507 * [67] => Array
3508 * (
3509 * [itemprop] => name
3510 * [content] => Entering Namibia's TRIBAL LANDS [S5 - Eps. 58]
3511 * [textContent] =>
3512 * )
3513 *
3514 * [68] => Array
3515 * (
3516 * [itemprop] => description
3517 * [content] => In this episode I am riding to Epupa Falls, gorgeous waterfalls right at the border between Namibia and Angola. The border with Angola is closed, so I can't ...
3518 * [textContent] =>
3519 * )
3520 *
3521 * [69] => Array
3522 * (
3523 * [itemprop] => paid
3524 * [content] => False
3525 * [textContent] =>
3526 * )
3527 *
3528 * [70] => Array
3529 * (
3530 * [itemprop] => channelId
3531 * [content] => UCEIs9nkveW9WmYtsOcJBwTg
3532 * [textContent] =>
3533 * )
3534 *
3535 * [71] => Array
3536 * (
3537 * [itemprop] => videoId
3538 * [content] => IQu7ox_UxpA
3539 * [textContent] =>
3540 * )
3541 *
3542 * [72] => Array
3543 * (
3544 * [itemprop] => duration
3545 * [content] => PT21M53S
3546 * [textContent] =>
3547 * )
3548 *
3549 * [73] => Array
3550 * (
3551 * [itemprop] => unlisted
3552 * [content] => False
3553 * [textContent] =>
3554 * )
3555 *
3556 * [74] => Array
3557 * (
3558 * [itemprop] => width
3559 * [content] => 1280
3560 * [textContent] =>
3561 * )
3562 *
3563 * [75] => Array
3564 * (
3565 * [itemprop] => height
3566 * [content] => 720
3567 * [textContent] =>
3568 * )
3569 *
3570 * [76] => Array
3571 * (
3572 * [itemprop] => playerType
3573 * [content] => HTML5 Flash
3574 * [textContent] =>
3575 * )
3576 *
3577 * [77] => Array
3578 * (
3579 * [itemprop] => width
3580 * [content] => 1280
3581 * [textContent] =>
3582 * )
3583 *
3584 * [78] => Array
3585 * (
3586 * [itemprop] => height
3587 * [content] => 720
3588 * [textContent] =>
3589 * )
3590 *
3591 * [79] => Array
3592 * (
3593 * [itemprop] => isFamilyFriendly
3594 * [content] => true
3595 * [textContent] =>
3596 * )
3597 *
3598 * [80] => Array
3599 * (
3600 * [itemprop] => regionsAllowed
3601 * [content] => AD,AE,AF,AG,AI,AL,AM,AO,AQ,AR,AS,AT,AU,AW,AX,AZ,BA,BB,BD,BE,BF,BG,BH,BI,BJ,BL,BM,BN,BO,BQ,BR,BS,BT,BV,BW,BY,BZ,CA,CC,CD,CF,CG,CH,CI,CK,CL,CM,CN,CO,CR,CU,CV,CW,CX,CY,CZ,DE,DJ,DK,DM,DO,DZ,EC,EE,EG,EH,ER,ES,ET,FI,FJ,FK,FM,FO,FR,GA,GB,GD,GE,GF,GG,GH,GI,GL,GM,GN,GP,GQ,GR,GS,GT,GU,GW,GY,HK,HM,HN,HR,HT,HU,ID,IE,IL,IM,IN,IO,IQ,IR,IS,IT,JE,JM,JO,JP,KE,KG,KH,KI,KM,KN,KP,KR,KW,KY,KZ,LA,LB,LC,LI,LK,LR,LS,LT,LU,LV,LY,MA,MC,MD,ME,MF,MG,MH,MK,ML,MM,MN,MO,MP,MQ,MR,MS,MT,MU,MV,MW,MX,MY,MZ,NA,NC,NE,NF,NG,NI,NL,NO,NP,NR,NU,NZ,OM,PA,PE,PF,PG,PH,PK,PL,PM,PN,PR,PS,PT,PW,PY,QA,RE,RO,RS,RU,RW,SA,SB,SC,SD,SE,SG,SH,SI,SJ,SK,SL,SM,SN,SO,SR,SS,ST,SV,SX,SY,SZ,TC,TD,TF,TG,TH,TJ,TK,TL,TM,TN,TO,TR,TT,TV,TW,TZ,UA,UG,UM,US,UY,UZ,VA,VC,VE,VG,VI,VN,VU,WF,WS,YE,YT,ZA,ZM,ZW
3602 * [textContent] =>
3603 * )
3604 *
3605 * [81] => Array
3606 * (
3607 * [itemprop] => interactionCount
3608 * [content] => 180432
3609 * [textContent] =>
3610 * )
3611 *
3612 * [82] => Array
3613 * (
3614 * [itemprop] => datePublished
3615 * [content] => 2021-07-30
3616 * [textContent] =>
3617 * )
3618 *
3619 * [83] => Array
3620 * (
3621 * [itemprop] => uploadDate
3622 * [content] => 2021-07-30
3623 * [textContent] =>
3624 * )
3625 *
3626 * [84] => Array
3627 * (
3628 * [itemprop] => genre
3629 * [content] => Education
3630 * [textContent] =>
3631 * )
3632 *
3633 * )
3634 *
3635 * )
3636 */
3637 if ( isset( $metas[ 'meta' ] ) ) {
3638
3639 foreach ( $metas as $m ) { // Loop through all meta tags.
3640
3641 foreach ( $m as $a ) { // Loop through all attributes for that meta tag.
3642
3643 $meta_type = key( $a ); // Example: itemprop
3644 $meta_name = reset( $a ); // Example: uploadDate
3645 $meta_match = $meta_type . '-' . $meta_name; // Example: itemprop-uploadDate
3646 $content = isset( $a[ 'content' ] ) ? $a[ 'content' ] : '';
3647
3648 switch ( $meta_match ) {
3649
3650 /*
3651 * Use the property meta tag content as-is.
3652 */
3653 case 'property-og:video:width':
3654 case 'property-og:video:height':
3655 case 'property-og:video:type':
3656 case ( strpos( $meta_match, 'property-al:' ) === 0 ? true : false ): // Facebook AppLink.
3657
3658 $mt_single_video[ $a[ 'property' ] ] = $content;
3659
3660 break;
3661
3662 /*
3663 * Add the property meta tag content as an array.
3664 */
3665 case 'property-og:video:tag':
3666
3667 $mt_single_video[ $a[ 'property' ] ][] = $content; // Array of tags.
3668
3669 break;
3670
3671 case 'property-og:image:secure_url':
3672 case 'property-og:image:url':
3673 case 'property-og:image':
3674
3675 /*
3676 * Add the meta name as a query string to know where the value came from.
3677 */
3678 $content = add_query_arg( 'meta', $meta_name, $content );
3679
3680 if ( SucomUtil::is_https( $content ) ) {
3681
3682 $mt_single_video[ 'og:image:secure_url' ] = $content;
3683 $mt_single_video[ 'og:video:thumbnail_url' ] = $content;
3684
3685 } else {
3686
3687 $mt_single_video[ 'og:image:url' ] = $content;
3688
3689 if ( empty( $mt_single_video[ 'og:video:thumbnail_url' ] ) ) {
3690
3691 $mt_single_video[ 'og:video:thumbnail_url' ] = $content;
3692 }
3693 }
3694
3695 $mt_single_video[ 'og:video:has_image' ] = true;
3696
3697 break;
3698
3699 /*
3700 * Add additional, non-standard properties, like og:video:title and og:video:description.
3701 */
3702 case 'property-og:title':
3703 case 'property-og:description':
3704
3705 $og_key = 'og:video:' . substr( $a[ 'property' ], 3 );
3706
3707 $mt_single_video[ $og_key ] = $this->p->util->cleanup_html_tags( $content );
3708
3709 if ( $this->p->debug->enabled ) {
3710
3711 $this->p->debug->log( 'adding ' . $og_key . ' = ' . $mt_single_video[ $og_key ] );
3712 }
3713
3714 if ( empty( $mt_single_video[ 'og:image:alt' ] ) ) {
3715
3716 $mt_single_video[ 'og:image:alt' ] = $mt_single_video[ $og_key ];
3717 }
3718
3719 break;
3720
3721 /*
3722 * twitter:app:name:iphone
3723 * twitter:app:id:iphone
3724 * twitter:app:url:iphone
3725 */
3726 case ( strpos( $meta_match, 'name-twitter:app:' ) === 0 ? true : false ): // X (Twitter) apps.
3727
3728 if ( preg_match( '/^twitter:app:([a-z]+):([a-z]+)$/', $meta_name, $match ) ) {
3729
3730 $mt_single_video[ 'og:video:' . $match[ 2 ] . '_' . $match[ 1 ] ] = SucomUtil::decode_html( $content );
3731 }
3732
3733 break;
3734
3735 case 'itemprop-datePublished':
3736
3737 $mt_single_video[ 'og:video:published_date' ] = gmdate( 'c', strtotime( $content ) );
3738
3739 if ( empty( $mt_single_video[ 'og:video:upload_date' ] ) ) {
3740
3741 $mt_single_video[ 'og:video:upload_date' ] = $mt_single_video[ 'og:video:published_date' ];
3742 }
3743
3744 break;
3745
3746 case 'itemprop-uploadDate':
3747
3748 $mt_single_video[ 'og:video:upload_date' ] = gmdate( 'c', strtotime( $content ) );
3749
3750 if ( empty( $mt_single_video[ 'og:video:published_date' ] ) ) {
3751
3752 $mt_single_video[ 'og:video:published_date' ] = $mt_single_video[ 'og:video:upload_date' ];
3753 }
3754
3755 break;
3756
3757 case 'itemprop-duration':
3758
3759 $mt_single_video[ 'og:video:duration' ] = $content; // Example: PT21M53S.
3760
3761 break;
3762
3763 case 'itemprop-embedUrl':
3764 case 'itemprop-embedURL':
3765
3766 $mt_single_video[ 'og:video:embed_url' ] = SucomUtil::decode_html( $content );
3767
3768 break;
3769
3770 case 'itemprop-isFamilyFriendly':
3771
3772 /*
3773 * See WpssoUtil->get_sitemaps_videos().
3774 */
3775 switch ( $content ) {
3776
3777 case 'yes':
3778 case 'true':
3779
3780 $mt_single_video[ 'og:video:family_friendly' ] = 'yes';
3781
3782 break;
3783
3784 case 'no':
3785 case 'false':
3786
3787 $mt_single_video[ 'og:video:family_friendly' ] = 'no';
3788
3789 break;
3790 }
3791
3792 break;
3793
3794 case 'itemprop-regionsAllowed':
3795
3796 $mt_single_video[ 'og:video:regions_allowed' ] = preg_split( '|,[\s]*?|', trim( $content ) ); // Example: AD,AE,AF,AG,AI,AL,AM,AO,AQ,AR,AS,AT,AU,AW,AX,AZ,BA,BB,BD,BE,BF,BG,BH,BI,BJ,BL,BM,BN,BO,BQ,BR,BS,BT,BV,BW,BY,BZ,CA,CC,CD,CF,CG,CH,CI,CK,CL,CM,CN,CO,CR,CU,CV,CW,CX,CY,CZ,DE,DJ,DK,DM,DO,DZ,EC,EE,EG,EH,ER,ES,ET,FI,FJ,FK,FM,FO,FR,GA,GB,GD,GE,GF,GG,GH,GI,GL,GM,GN,GP,GQ,GR,GS,GT,GU,GW,GY,HK,HM,HN,HR,HT,HU,ID,IE,IL,IM,IN,IO,IQ,IR,IS,IT,JE,JM,JO,JP,KE,KG,KH,KI,KM,KN,KP,KR,KW,KY,KZ,LA,LB,LC,LI,LK,LR,LS,LT,LU,LV,LY,MA,MC,MD,ME,MF,MG,MH,MK,ML,MM,MN,MO,MP,MQ,MR,MS,MT,MU,MV,MW,MX,MY,MZ,NA,NC,NE,NF,NG,NI,NL,NO,NP,NR,NU,NZ,OM,PA,PE,PF,PG,PH,PK,PL,PM,PN,PR,PS,PT,PW,PY,QA,RE,RO,RS,RU,RW,SA,SB,SC,SD,SE,SG,SH,SI,SJ,SK,SL,SM,SN,SO,SR,SS,ST,SV,SX,SY,SZ,TC,TD,TF,TG,TH,TJ,TK,TL,TM,TN,TO,TR,TT,TV,TW,TZ,UA,UG,UM,US,UY,UZ,VA,VC,VE,VG,VI,VN,VU,WF,WS,YE,YT,ZA,ZM,ZW.
3797
3798 break;
3799 }
3800 }
3801 }
3802
3803 if ( $this->p->debug->enabled ) {
3804
3805 $this->p->debug->log( $mt_single_video );
3806 }
3807
3808 } elseif ( $this->p->debug->enabled ) {
3809
3810 $this->p->debug->log( 'no head meta found in ' . $url );
3811 }
3812 }
3813
3814 /*
3815 * $img_mixed can be an image ID or URL.
3816 *
3817 * $img_lib can be 'Media Library', 'Content', etc.
3818 */
3819 public function is_image_within_config_limits( $img_mixed, $size_name, $img_width, $img_height, $img_lib = null ) {
3820
3821 if ( 0 !== strpos( $size_name, 'wpsso-' ) ) { // Only check our own sizes.
3822
3823 return true;
3824 }
3825
3826 if ( null === $img_lib ) { // Default to the WordPress Media Library.
3827
3828 // translators: Please ignore - translation uses a different text domain.
3829 $img_lib = __( 'Media Library' );
3830 }
3831
3832 $img_ratio = 0;
3833 $img_label = $img_mixed;
3834
3835 if ( is_numeric( $img_mixed ) ) {
3836
3837 $edit_url = get_edit_post_link( $img_mixed );
3838 $img_title = get_the_title( $img_mixed );
3839 $img_label = sprintf( __( 'image ID %1$s (%2$s)', 'wpsso' ), $img_mixed, $img_title );
3840 $img_label = empty( $edit_url ) ? $img_label : '<a href="' . $edit_url . '">' . $img_label . '</a>';
3841
3842 } elseif ( false !== strpos( $img_mixed, '://' ) ) {
3843
3844 if ( WPSSO_UNDEF === $img_width || WPSSO_UNDEF === $img_height ) {
3845
3846 list( $img_width, $img_height, $img_type, $img_attr ) = $this->p->util->get_image_url_info( $img_mixed );
3847 }
3848
3849 $img_label = '<a href="' . $img_mixed . '">' . $img_mixed . '</a>';
3850 $img_label = sprintf( __( 'image URL %s', 'wpsso' ), $img_mixed );
3851 }
3852
3853 /*
3854 * Exit silently if the image width and/or height is not valid.
3855 */
3856 if ( WPSSO_UNDEF === $img_width || WPSSO_UNDEF === $img_height ) {
3857
3858 if ( $this->p->debug->enabled ) {
3859
3860 $this->p->debug->log( 'exiting early: ' . strtolower( $img_lib ) . ' ' . $img_mixed . ' rejected - ' .
3861 'invalid width and/or height ' . $img_width . 'x' . $img_height );
3862 }
3863
3864 return false; // Image rejected.
3865 }
3866
3867 if ( $img_width > 0 && $img_height > 0 ) {
3868
3869 $img_ratio = $img_width >= $img_height ? $img_width / $img_height : $img_height / $img_width;
3870 $img_ratio = number_format( $img_ratio, 3, '.', '' );
3871 }
3872
3873 $cf_min = $this->p->cf[ 'head' ][ 'limit_min' ];
3874 $cf_max = $this->p->cf[ 'head' ][ 'limit_max' ];
3875 $opt_pre = $this->p->util->get_image_size_opt( $size_name );
3876 $min_width = isset( $cf_min[ $opt_pre . '_img_width' ] ) ? $cf_min[ $opt_pre . '_img_width' ] : 0;
3877 $min_height = isset( $cf_min[ $opt_pre . '_img_height' ] ) ? $cf_min[ $opt_pre . '_img_height' ] : 0;
3878 $max_ratio = isset( $cf_max[ $opt_pre . '_img_ratio' ] ) ? $cf_max[ $opt_pre . '_img_ratio' ] : 0;
3879
3880 /*
3881 * Check the maximum image aspect ratio.
3882 */
3883 if ( $max_ratio && $img_ratio > $max_ratio ) {
3884
3885 if ( $this->p->debug->enabled ) {
3886
3887 $this->p->debug->log( 'exiting early: ' . strtolower( $img_lib ) . ' ' . $img_mixed . ' rejected - ' .
3888 $img_width . 'x' . $img_height . ' aspect ratio is equal to/or greater than ' . $max_ratio . ':1' );
3889 }
3890
3891 /*
3892 * Add notice only if the admin notices have not already been shown.
3893 *
3894 * An is_admin() test is required to make sure the WpssoMessages class is available.
3895 */
3896 if ( $this->p->notice->is_admin_pre_notices() ) {
3897
3898 $size_label = $this->p->util->get_image_size_label( $size_name ); // Returns pre-translated labels.
3899
3900 /*
3901 * $img_lib can be 'Media Library', 'Content', etc.
3902 */
3903 $notice_msg = sprintf( __( '%1$s %2$s ignored - the resulting resized image of %3$s has an <strong>aspect ratio equal to/or greater than %4$d:1 allowed by the %5$s standard</strong>.', 'wpsso' ), $img_lib, $img_label, $img_width . 'x' . $img_height, $max_ratio, $size_label ). ' ';
3904
3905 $notice_msg .= $this->p->msgs->get( 'notice-image-rejected', array( 'show_adjust_img_opts' => false ) );
3906
3907 $notice_key = 'image_' . $img_mixed . '_' . $img_width . 'x' . $img_height . '_' . $size_name . '_ratio_greater_than_allowed';
3908
3909 $this->p->notice->warn( $notice_msg, null, $notice_key, $dismiss_time = true );
3910 }
3911
3912 return false; // Image rejected.
3913 }
3914
3915 /*
3916 * Check the minimum image width and/or height.
3917 */
3918 if ( ( $min_width > 0 || $min_height > 0 ) && ( $img_width < $min_width || $img_height < $min_height ) ) {
3919
3920 if ( $this->p->debug->enabled ) {
3921
3922 $this->p->debug->log( 'exiting early: ' . strtolower( $img_lib ) . ' ' . $img_mixed . ' rejected - ' .
3923 $img_width . 'x' . $img_height . ' smaller than minimum ' . $min_width . 'x' . $min_height . ' for ' . $size_name );
3924 }
3925
3926 /*
3927 * Add notice only if the admin notices have not already been shown.
3928 *
3929 * An is_admin() test is required to make sure the WpssoMessages class is available.
3930 */
3931 if ( $this->p->notice->is_admin_pre_notices() ) {
3932
3933 $size_label = $this->p->util->get_image_size_label( $size_name ); // Returns pre-translated labels.
3934
3935 /*
3936 * $img_lib can be 'Media Library', 'Content', etc.
3937 */
3938 $notice_msg = sprintf( __( '%1$s %2$s ignored - the resulting resized image of %3$s is <strong>smaller than the minimum of %4$s allowed by the %5$s standard</strong>.', 'wpsso' ), $img_lib, $img_label, $img_width . 'x' . $img_height, $min_width . 'x' . $min_height, $size_label ) . ' ';
3939
3940 $notice_msg .= $this->p->msgs->get( 'notice-image-rejected', array( 'show_adjust_img_size_opts' => false ) );
3941
3942 $notice_key = 'image_' . $img_mixed . '_' . $img_width . 'x' . $img_height . '_' . $size_name . '_smaller_than_minimum_allowed';
3943
3944 $this->p->notice->warn( $notice_msg, null, $notice_key, $dismiss_time = true );
3945 }
3946
3947 return false; // Image rejected.
3948 }
3949
3950 return true; // Image accepted.
3951 }
3952
3953 public function can_make_intermediate_size( $img_meta, $size_info ) {
3954
3955 if ( $this->p->debug->enabled ) {
3956
3957 $this->p->debug->mark();
3958 }
3959
3960 $full_width = empty( $img_meta[ 'width' ] ) ? 0 : $img_meta[ 'width' ];
3961 $full_height = empty( $img_meta[ 'height' ] ) ? 0 : $img_meta[ 'height' ];
3962 $is_sufficient_w = $full_width >= $size_info[ 'width' ] ? true : false;
3963 $is_sufficient_h = $full_height >= $size_info[ 'height' ] ? true : false;
3964
3965 $upscale_multiplier = 1;
3966
3967 if ( ! empty( $this->p->options[ 'plugin_upscale_images' ] ) ) {
3968
3969 $img_src_args = self::get_image_src_args();
3970 $upscale_pct_max = apply_filters( 'wpsso_image_upscale_max', $this->p->options[ 'plugin_upscale_pct_max' ], $img_src_args );
3971 $upscale_multiplier = 1 + ( $upscale_pct_max / 100 );
3972 $upscale_full_width = round( $full_width * $upscale_multiplier );
3973 $upscale_full_height = round( $full_height * $upscale_multiplier );
3974 $is_sufficient_w = $upscale_full_width >= $size_info[ 'width' ] ? true : false;
3975 $is_sufficient_h = $upscale_full_height >= $size_info[ 'height' ] ? true : false;
3976 }
3977
3978 if ( ( ! $size_info[ 'is_cropped' ] && ( ! $is_sufficient_w && ! $is_sufficient_h ) ) ||
3979 ( $size_info[ 'is_cropped' ] && ( ! $is_sufficient_w || ! $is_sufficient_h ) ) ) {
3980
3981 $ret = false;
3982
3983 } else $ret = true;
3984
3985 if ( $this->p->debug->enabled ) {
3986
3987 $upscaled_by = $upscale_multiplier !== 1 ? ' (' . $upscale_full_width . 'x' . $upscale_full_height .
3988 ' upscaled by ' . $upscale_multiplier . ')' : '';
3989
3990 $this->p->debug->log( 'full size image of ' . $full_width . 'x' . $full_height . $upscaled_by .
3991 ( $ret ? ' sufficient' : ' too small' ) . ' to create ' . $size_info[ 'dims_transl' ] );
3992 }
3993
3994 return $ret;
3995 }
3996
3997 /*
3998 * Determine if an image can be renamed or needs to be copied.
3999 *
4000 * Check for conflicting image sizes (ie. same dimensions uncropped, or same dimensions from other WordPress sizes).
4001 */
4002 public function can_rename_image_filename( $size_name, $pid ) {
4003
4004 $img_meta = wp_get_attachment_metadata( $pid ); // Returns a WP_Error object on failure.
4005
4006 if ( isset( $img_meta[ 'sizes' ] ) ) { // Just in case.
4007
4008 if ( isset( $img_meta[ 'sizes' ][ $size_name ] ) ) { // Just in case.
4009
4010 $size_dims = $img_meta[ 'sizes' ][ $size_name ];
4011 $size_is_cropped = $this->p->util->is_size_cropped( $size_name, $pid );
4012
4013 foreach ( $img_meta[ 'sizes' ] as $img_meta_size_name => $img_meta_size_dims ) {
4014
4015 if ( $img_meta_size_name === $size_name ) { // Ignore ourselves.
4016
4017 continue;
4018 }
4019
4020 if ( $img_meta_size_dims[ 'width' ] == $size_dims[ 'width' ] && $img_meta_size_dims[ 'height' ] == $size_dims[ 'height' ] ) {
4021
4022 if ( 0 === strpos( $img_meta_size_name, 'wpsso-' ) ) {
4023
4024 $img_meta_is_cropped = $this->p->util->is_size_cropped( $img_meta_size_name, $pid );
4025
4026 if ( $img_meta_is_cropped !== $size_is_cropped ) {
4027
4028 return false;
4029 }
4030
4031 } else {
4032
4033 return false;
4034 }
4035 }
4036 }
4037 }
4038 }
4039
4040 return true;
4041 }
4042
4043 public function maybe_update_cropped_image_filepath( $filepath, $size_info ) {
4044
4045 $dir = pathinfo( $filepath, PATHINFO_DIRNAME ); // Returns '.' for filenames without paths.
4046 $ext = pathinfo( $filepath, PATHINFO_EXTENSION );
4047 $base = wp_basename( $filepath, '.' . $ext );
4048 $new_dir = '.' === $dir ? '' : trailingslashit( $dir );
4049 $new_ext = '.' . $ext;
4050 $new_base = preg_replace( '/-cropped(-[a-z]+-[a-z]+)?$/', '', $base );
4051 $new_suffix = '';
4052 $new_filepath = $new_dir . $new_base . $new_suffix . $new_ext;
4053
4054 if ( ! empty( $size_info[ 'crop' ] ) ) {
4055
4056 if ( ! empty( $this->p->options[ 'plugin_prevent_thumb_conflicts' ] ) ) { // Since WPSSO Core v15.6.0.
4057
4058 $new_suffix .= '-cropped';
4059
4060 if ( is_array( $size_info[ 'crop' ] ) ) {
4061
4062 if ( $size_info[ 'crop' ] !== array( 'center', 'center' ) ) {
4063
4064 $new_suffix .= '-' . implode( '-', $size_info[ 'crop' ] );
4065 }
4066 }
4067
4068 $new_filepath = $new_dir . $new_base . $new_suffix . $new_ext;
4069 }
4070
4071 $new_filepath = apply_filters( 'wpsso_cropped_image_filepath', $new_filepath, $filepath, $size_info );
4072 }
4073
4074 return $new_filepath;
4075 }
4076
4077 public function get_gravatar_size() {
4078
4079 $def_size = 1200;
4080 $max_size = 2048;
4081 $img_size = empty( $this->p->options[ 'plugin_gravatar_size' ] ) ? 1200 : $this->p->options[ 'plugin_gravatar_size' ];
4082 $img_size = $img_size > 2048 ? 2048 : $img_size;
4083
4084 return $img_size;
4085 }
4086
4087 /*
4088 * Use these static methods set / reset information about the image being processed for down-stream filters /
4089 * methods lacking this information.
4090 */
4091 public static function set_image_src_args( $image_src_args = null ) {
4092
4093 self::$image_src_args = $image_src_args;
4094 }
4095
4096 public static function get_image_src_args( $key = false ) {
4097
4098 if ( false !== $key ) {
4099
4100 if ( isset( self::$image_src_args[ $key ] ) ) {
4101
4102 return self::$image_src_args[ $key ];
4103
4104 }
4105
4106 return null;
4107 }
4108
4109 return self::$image_src_args;
4110 }
4111
4112 public static function reset_image_src_args( array $args = array() ) {
4113
4114 self::$image_src_args = null;
4115
4116 $have_count = count( $args );
4117
4118 $args_count = count( array(
4119 'og:image:url' => null,
4120 'og:image:width' => null,
4121 'og:image:height' => null,
4122 'og:image:cropped' => null,
4123 'og:image:id' => null,
4124 'og:image:alt' => null,
4125 'og:image:size_name' => null,
4126 ) );
4127
4128 if ( $have_count < $args_count ) {
4129
4130 $args = array_pad( $args, $args_count, null );
4131
4132 } elseif ( $have_count > $args_count ) {
4133
4134 $args = array_slice( $args, 0, $args_count );
4135 }
4136
4137 return $args;
4138 }
4139 }
4140 }
4141