PluginProbe
WPSSO Core – Complete Schema Markup and Meta Tags / trunk
WPSSO Core – Complete Schema Markup and Meta Tags vtrunk
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 / opengraph.php

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

1,730 lines 44.7 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( 'WpssoOpenGraph' ) ) {
19
20 class WpssoOpenGraph {
21
22 private $p; // Wpsso class object.
23 private $ns; // WpssoOpenGraphNS class object.
24
25 public function __construct( &$plugin ) {
26
27 $this->p =& $plugin;
28
29 if ( $this->p->debug->enabled ) {
30
31 $this->p->debug->mark();
32 }
33
34 /*
35 * Instantiate the WpssoOpenGraphNS class object.
36 */
37 if ( ! class_exists( 'WpssoOpenGraphNS' ) ) {
38
39 require_once WPSSO_PLUGINDIR . 'lib/opengraph-ns.php';
40 }
41
42 $this->ns = new WpssoOpenGraphNS( $plugin );
43
44 $this->p->util->add_plugin_filters( $this, array(
45 'plugin_image_sizes' => 1,
46 ) );
47
48 $this->p->util->add_plugin_filters( $this, array(
49 'get_post_options' => 3,
50 ), PHP_INT_MAX );
51 }
52
53 public function filter_plugin_image_sizes( array $sizes ) {
54
55 $sizes[ 'og' ] = array( // Option prefix.
56 'name' => 'opengraph',
57 'label_transl' => _x( 'Open Graph (Facebook and oEmbed)', 'option label', 'wpsso' ),
58 );
59
60 return $sizes;
61 }
62
63 public function filter_get_post_options( array $md_opts, $post_id, array $mod ) {
64
65 if ( $this->p->debug->enabled ) {
66
67 $this->p->debug->mark();
68 }
69
70 if ( is_admin() ) { // Keep processing on the front-end to a minimum.
71
72 /*
73 * If the Open Graph type isn't already hard-coded (ie. ':disabled'), then using the post type and
74 * the Schema type, check for a possible hard-coded Open Graph type.
75 */
76 $md_opts = $this->maybe_update_post_og_type( $md_opts, $post_id, $mod );
77 }
78
79 return $md_opts;
80 }
81
82 /*
83 * Returns the open graph type id.
84 */
85 public function get_mod_og_type_id( array $mod, $use_md_opts = true ) {
86
87 return $this->get_mod_og_type( $mod, $get_id = true, $use_md_opts );
88 }
89
90 /*
91 * Returns the open graph namespace.
92 */
93 public function get_mod_og_type_ns( array $mod, $use_md_opts = true ) {
94
95 return $this->get_mod_og_type( $mod, $get_id = false, $use_md_opts );
96 }
97
98 /*
99 * Returns the open graph type id by default.
100 *
101 * Example: article, product, place, etc.
102 *
103 * Use $get_id = false to return the open graph namespace instead of the ID.
104 */
105 public function get_mod_og_type( array $mod, $get_id = true, $use_md_opts = true ) {
106
107 if ( $this->p->debug->enabled ) {
108
109 $this->p->debug->mark();
110 }
111
112 static $local_cache = array();
113
114 $cache_salt = false;
115
116 /*
117 * Archive pages can call this method several times.
118 *
119 * Optimize and cache post/term/user og type values.
120 */
121 if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
122
123 /*
124 * Note that the sort order, page number, locale, amp and embed checks are provided by
125 * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
126 */
127 $cache_salt = SucomUtil::get_mod_salt( $mod ) . '_get:' . (string) $get_id . '_use:' . (string) $use_md_opts;
128
129 if ( isset( $local_cache[ $cache_salt ] ) ) {
130
131 return $local_cache[ $cache_salt ];
132 }
133 }
134
135 $type_id = null;
136 $og_type_ns = $this->p->cf[ 'head' ][ 'og_type_ns' ];
137
138 /*
139 * Maybe get a custom open graph type id from the post, term, or user meta.
140 */
141 if ( $use_md_opts ) {
142
143 if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
144
145 $type_id = $mod[ 'obj' ]->get_options( $mod[ 'id' ], 'og_type' ); // Returns null if index key not found.
146
147 if ( empty( $type_id ) || $type_id === 'none' || empty( $og_type_ns[ $type_id ] ) ) { // Check for an invalid type id.
148
149 $type_id = null;
150
151 } elseif ( $this->p->debug->enabled ) {
152
153 $this->p->debug->log( 'custom type id = ' . $type_id );
154 }
155 }
156 }
157
158 $is_custom = empty( $type_id ) ? false : true;
159
160 if ( ! $is_custom ) { // No custom open graph type id from the post, term, or user meta.
161
162 /*
163 * Similar module type logic can be found in the following methods:
164 *
165 * See WpssoOpenGraph->get_mod_og_type().
166 * See WpssoPage->get_description().
167 * See WpssoPage->get_the_title().
168 * See WpssoSchema->get_mod_schema_type().
169 * See WpssoUtil->get_canonical_url().
170 */
171 if ( $mod[ 'is_home' ] ) { // Home page (static or blog archive).
172
173 if ( $mod[ 'is_home_page' ] ) { // Static front page (singular post).
174
175 $type_id = $this->get_og_type_id( 'home_page' );
176
177 } else $type_id = $this->get_og_type_id( 'home_posts' );
178
179 } elseif ( $mod[ 'is_comment' ] ) {
180
181 if ( is_numeric( $mod[ 'comment_rating' ] ) ) {
182
183 $type_id = $this->get_og_type_id( 'comment_review' );
184
185 } elseif ( $mod[ 'comment_parent' ] ) {
186
187 $type_id = $this->get_og_type_id( 'comment_reply' );
188
189 } else $type_id = $this->get_og_type_id( 'comment' );
190
191 } elseif ( $mod[ 'is_post' ] ) {
192
193 if ( ! empty( $mod[ 'post_type' ] ) && is_string( $mod[ 'post_type' ] ) ) { // Not false or empty string.
194
195 if ( $mod[ 'is_post_type_archive' ] ) { // The post ID may be 0.
196
197 $type_id = $this->get_og_type_id( 'pta_' . $mod[ 'post_type' ] );
198
199 if ( empty( $type_id ) ) { // Just in case.
200
201 $type_id = $this->get_og_type_id( 'archive_page' );
202 }
203
204 } else {
205
206 $type_id = $this->get_og_type_id( $mod[ 'post_type' ] );
207
208 if ( empty( $type_id ) ) { // Just in case.
209
210 $type_id = $this->get_og_type_id( 'page' );
211 }
212 }
213
214 } elseif ( $this->p->debug->enabled ) {
215
216 $this->p->debug->log( 'no post type' );
217 }
218
219 } elseif ( $mod[ 'is_term' ] ) {
220
221 if ( ! empty( $mod[ 'tax_slug' ] ) ) { // Just in case.
222
223 $type_id = $this->get_og_type_id( 'tax_' . $mod[ 'tax_slug' ] );
224 }
225
226 if ( empty( $type_id ) ) { // Just in case.
227
228 $type_id = $this->get_og_type_id( 'archive_page' );
229 }
230
231 } elseif ( $mod[ 'is_user' ] ) {
232
233 $type_id = $this->get_og_type_id( 'user_page' );
234
235 } elseif ( $mod[ 'is_search' ] ) {
236
237 $type_id = $this->get_og_type_id( 'search_page' );
238
239 } elseif ( $mod[ 'is_archive' ] ) {
240
241 $type_id = $this->get_og_type_id( 'archive_page' );
242 }
243
244 if ( empty( $type_id ) ) { // Just in case.
245
246 if ( $this->p->debug->enabled ) {
247
248 $this->p->debug->log( 'unable to determine og type id (using default)' );
249 }
250
251 $type_id = 'website';
252 }
253 }
254
255 if ( $this->p->debug->enabled ) {
256
257 $this->p->debug->log( 'og type id before filter = ' . $type_id );
258 }
259
260 $type_id = apply_filters( 'wpsso_og_type', $type_id, $mod, $is_custom );
261
262 if ( $this->p->debug->enabled ) {
263
264 $this->p->debug->log( 'og type id after filter = ' . $type_id );
265 }
266
267 $get_value = false;
268
269 if ( empty( $type_id ) ) {
270
271 if ( $this->p->debug->enabled ) {
272
273 $this->p->debug->log( 'returning false: og type id is empty' );
274 }
275
276 } elseif ( 'none' === $type_id ) {
277
278 if ( $this->p->debug->enabled ) {
279
280 $this->p->debug->log( 'returning false: og type id is disabled' );
281 }
282
283 } elseif ( ! isset( $og_type_ns[ $type_id ] ) ) {
284
285 if ( $this->p->debug->enabled ) {
286
287 $this->p->debug->log( 'returning false: og type id ' . $type_id . ' is unknown' );
288 }
289
290 } elseif ( ! $get_id ) {
291
292 if ( $this->p->debug->enabled ) {
293
294 $this->p->debug->log( 'returning og type id namespace: ' . $og_type_ns[ $type_id ] );
295 }
296
297 $get_value = $og_type_ns[ $type_id ];
298
299 } else {
300
301 if ( $this->p->debug->enabled ) {
302
303 $this->p->debug->log( 'returning og type id: ' . $type_id );
304 }
305
306 $get_value = $type_id;
307 }
308
309 /*
310 * Optimize and cache post/term/user og type values.
311 */
312 if ( $cache_salt ) {
313
314 $local_cache[ $cache_salt ] = $get_value;
315 }
316
317 return $get_value;
318 }
319
320 /*
321 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
322 *
323 * $size_name is passed as-is to WpssoMedia->get_all_images().
324 */
325 public function get_array( array $mod, $size_names = 'opengraph', $md_pre = 'og' ) {
326
327 if ( $this->p->debug->enabled ) {
328
329 $this->p->debug->mark();
330 }
331
332 $max_nums = $this->p->util->get_max_nums( $mod );
333
334 if ( $this->p->debug->enabled ) {
335
336 $this->p->debug->mark( 'applying filters "wpsso_og_seed"' ); // Begin timer.
337 }
338
339 /*
340 * 'wpsso_og_seed' is hooked by e-commerce modules to provide product meta tags.
341 */
342 $mt_og = apply_filters( 'wpsso_og_seed', SucomUtil::get_mt_og_seed(), $mod );
343
344 if ( $this->p->debug->enabled ) {
345
346 $this->p->debug->log_arr( 'og_seed', $mt_og );
347
348 $this->p->debug->mark( 'applying filters "wpsso_og_seed"' ); // End timer.
349 }
350
351 /*
352 * Facebook app id meta tag.
353 */
354 if ( ! isset( $mt_og[ 'fb:app_id' ] ) ) {
355
356 $mt_og[ 'fb:app_id' ] = $this->p->options[ 'fb_app_id' ];
357 }
358
359 /*
360 * Current ISO 8601 date and time (non-standard / internal meta tag).
361 */
362 $mt_og[ 'og:time' ] = gmdate( 'c' );
363
364 /*
365 * Type id meta tag.
366 */
367 if ( ! isset( $mt_og[ 'og:type' ] ) ) {
368
369 $mt_og[ 'og:type' ] = $this->get_mod_og_type_id( $mod );
370
371 } elseif ( $this->p->debug->enabled ) {
372
373 $this->p->debug->log( 'og:type already defined = ' . $mt_og[ 'og:type' ] );
374 }
375
376 $type_id = $mt_og[ 'og:type' ];
377
378 /*
379 * URL meta tag.
380 */
381 if ( ! isset( $mt_og[ 'og:url' ] ) ) {
382
383 $mt_og[ 'og:url' ] = $this->p->util->get_canonical_url( $mod );
384
385 } elseif ( $this->p->debug->enabled ) {
386
387 $this->p->debug->log( 'og:url already defined = ' . $mt_og[ 'og:url' ] );
388 }
389
390 /*
391 * Redirect URL meta tag (non-standard / internal meta tag).
392 */
393 $mt_og[ 'og:redirect_url' ] = $this->p->util->get_redirect_url( $mod );
394
395 /*
396 * Locale meta tag.
397 */
398 if ( ! isset( $mt_og[ 'og:locale' ] ) ) {
399
400 if ( $this->p->debug->enabled ) {
401
402 $this->p->debug->log( 'calling get_fb_locale()' );
403 }
404
405 $mt_og[ 'og:locale' ] = $this->get_fb_locale( $mod );
406
407 } elseif ( $this->p->debug->enabled ) {
408
409 $this->p->debug->log( 'og:locale already defined = ' . $mt_og[ 'og:locale' ] );
410 }
411
412 /*
413 * Site name meta tag.
414 */
415 if ( ! isset( $mt_og[ 'og:site_name' ] ) ) {
416
417 if ( $this->p->debug->enabled ) {
418
419 $this->p->debug->log( 'getting site name for og:site_name meta tag' );
420 }
421
422 $mt_og[ 'og:site_name' ] = SucomUtilWP::get_site_name( $this->p->options, $mod ); // localized
423
424 } elseif ( $this->p->debug->enabled ) {
425
426 $this->p->debug->log( 'og:site_name already defined = ' . $mt_og[ 'og:site_name' ] );
427 }
428
429 /*
430 * Title meta tag.
431 */
432 if ( ! isset( $mt_og[ 'og:title' ] ) ) {
433
434 if ( $this->p->debug->enabled ) {
435
436 $this->p->debug->log( 'getting title for og:title meta tag' );
437 }
438
439 $mt_og[ 'og:title' ] = $this->p->page->get_title( $mod, $md_key = 'og_title', $max_len = 'og_title' );
440
441 if ( $this->p->debug->enabled ) {
442
443 $this->p->debug->log( 'og:title value = ' . $mt_og[ 'og:title' ] );
444 }
445
446 } elseif ( $this->p->debug->enabled ) {
447
448 $this->p->debug->log( 'og:title already defined = ' . $mt_og[ 'og:title' ] );
449 }
450
451 /*
452 * Description meta tag.
453 */
454 if ( ! isset( $mt_og[ 'og:description' ] ) ) {
455
456 if ( $this->p->debug->enabled ) {
457
458 $this->p->debug->log( 'getting description for og:description meta tag' );
459 }
460
461 $mt_og[ 'og:description' ] = $this->p->page->get_description( $mod, $md_key = 'og_desc', $max_len = 'og_desc', $num_hashtags = true );
462
463 if ( $this->p->debug->enabled ) {
464
465 $this->p->debug->log( 'og:description value = ' . $mt_og[ 'og:description' ] );
466 }
467
468 } elseif ( $this->p->debug->enabled ) {
469
470 $this->p->debug->log( 'og:description already defined = ' . $mt_og[ 'og:description' ] );
471 }
472
473 /*
474 * Updated date / time meta tag.
475 */
476 if ( ! isset( $mt_og[ 'og:updated_time' ] ) ) {
477
478 if ( $mod[ 'is_post' ] && $mod[ 'post_modified_time' ] ) { // ISO 8601 date or false.
479
480 $mt_og[ 'og:updated_time' ] = $mod[ 'post_modified_time' ];
481 }
482 }
483
484 /*
485 * Get all videos.
486 *
487 * Call before getting all images to find / use preview images.
488 */
489 if ( ! isset( $mt_og[ 'og:video' ] ) ) {
490
491 if ( $this->p->debug->enabled ) {
492
493 $this->p->debug->log( 'getting videos for og:video meta tag' );
494 }
495
496 if ( $max_nums[ 'og_vid_max' ] > 0 ) {
497
498 $mt_og[ 'og:video' ] = $this->p->media->get_all_videos( $max_nums[ 'og_vid_max' ], $mod, $md_pre );
499
500 if ( empty( $mt_og[ 'og:video' ] ) ) {
501
502 if ( $this->p->debug->enabled ) {
503
504 $this->p->debug->log( 'no videos returned' );
505 }
506
507 unset( $mt_og[ 'og:video' ] );
508
509 } else {
510
511 if ( $this->p->debug->enabled ) {
512
513 $this->p->debug->log( 'removing video images to avoid duplicates' );
514 }
515
516 foreach ( $mt_og[ 'og:video' ] as $key => $mt_single ) {
517
518 if ( ! is_array( $mt_single ) ) { // Just in case.
519
520 if ( $this->p->debug->enabled ) {
521
522 $this->p->debug->log( 'video ignored: $mt_single is not an array' );
523 }
524
525 continue;
526 }
527
528 $mt_og[ 'og:video' ][ $key ] = SucomUtil::preg_grep_keys( '/^og:image/', $mt_single, $invert = true );
529 }
530 }
531
532 } elseif ( $this->p->debug->enabled ) {
533
534 $this->p->debug->log( 'videos disabled: maximum videos is 0 or less' );
535 }
536 }
537
538 /*
539 * Get all images.
540 */
541 if ( ! isset( $mt_og[ 'og:image' ] ) ) {
542
543 if ( $this->p->debug->enabled ) {
544
545 $this->p->debug->log( 'getting images for og:image meta tag' );
546 }
547
548 if ( $max_nums[ 'og_img_max' ] > 0 ) {
549
550 $mt_og[ 'og:image' ] = $this->p->media->get_all_images( $max_nums[ 'og_img_max' ], $size_names, $mod, $md_pre );
551
552 if ( empty( $mt_og[ 'og:image' ] ) ) {
553
554 if ( $this->p->debug->enabled ) {
555
556 $this->p->debug->log( 'no images returned' );
557 }
558
559 unset( $mt_og[ 'og:image' ] );
560 }
561
562 } else {
563
564 if ( $this->p->debug->enabled ) {
565
566 $this->p->debug->log( 'skipped getting images: maximum images is 0 or less' );
567 }
568 }
569 }
570
571 /*
572 * Pre-define some basic open graph meta tags for this og:type. If the meta tag has an associated meta
573 * option name, then read it's value from the meta options.
574 */
575 if ( $this->p->debug->enabled ) {
576
577 $this->p->debug->log( 'checking og_type_mt array for known meta tags and md options' );
578 }
579
580 /*
581 * An array of Open Graph types, their meta tags, and their associated metadata keys.
582 */
583 if ( isset( $this->p->cf[ 'head' ][ 'og_type_mt' ][ $type_id ] ) ) { // Check if og:type is known.
584
585 /*
586 * Optimize and call get_options() only once. Returns an empty string if no meta found.
587 */
588 if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
589
590 $md_opts = $mod[ 'obj' ]->get_options( $mod[ 'id' ], $md_key = false, $filter_opts = true, $merge_defs = true );
591
592 } else $md_opts = array();
593
594 /*
595 * Add post/term/user meta data to the Open Graph meta tags.
596 */
597 $this->add_data_og_type_md( $mt_og, $type_id, $md_opts );
598 }
599
600 /*
601 * If the module is a post object, define the author, publishing date, etc. These values may still be used
602 * by other non-article filters, and if the og:type is not an article, the meta tags will be sanitized (ie.
603 * non-valid meta tags removed) at the end of WpssoHead->get_head_array().
604 */
605 if ( ! isset( $mt_og[ 'article:author' ] ) ) {
606
607 if ( $this->p->debug->enabled ) {
608
609 $this->p->debug->log( 'getting names / URLs for article:author meta tags' );
610 }
611
612 $mt_og[ 'article:author' ] = array();
613
614 if ( $mod[ 'is_post' ] ) {
615
616 if ( $mod[ 'post_author' ] ) {
617
618 $mt_og[ 'article:author:name' ] = $this->p->user->get_author_meta( $mod[ 'post_author' ], 'display_name' );
619 $mt_og[ 'article:author' ] = $this->p->user->get_authors_websites( $mod[ 'post_author' ], $meta_key = 'facebook' );
620 }
621
622 if ( $mod[ 'post_coauthors' ] ) {
623
624 $og_profile_urls = $this->p->user->get_authors_websites( $mod[ 'post_coauthors' ], $meta_key = 'facebook' );
625
626 $mt_og[ 'article:author' ] = array_merge( $mt_og[ 'article:author' ], $og_profile_urls );
627 }
628
629 } elseif ( $mod[ 'is_comment' ] ) {
630
631 if ( $mod[ 'comment_author' ] ) {
632
633 $mt_og[ 'article:author:name' ] = $this->p->user->get_author_meta( $mod[ 'comment_author' ], 'display_name' );
634 $mt_og[ 'article:author' ] = $this->p->user->get_authors_websites( $mod[ 'comment_author' ], $meta_key = 'facebook' );
635
636 } elseif ( $mod[ 'comment_author_name' ] ) {
637
638 $mt_og[ 'article:author:name' ] = $mod[ 'comment_author_name' ];
639 }
640 }
641 }
642
643 if ( ! isset( $mt_og[ 'article:publisher' ] ) ) {
644
645 $mt_og[ 'article:publisher' ] = SucomUtilOptions::get_key_value( 'fb_publisher_url', $this->p->options, $mod );
646 }
647
648 if ( ! isset( $mt_og[ 'article:tag' ] ) ) {
649
650 $mt_og[ 'article:tag' ] = $this->p->page->get_tag_names( $mod );
651 }
652
653 if ( ! isset( $mt_og[ 'article:published_time' ] ) ) {
654
655 if ( $mod[ 'is_post' ] && $mod[ 'post_time' ] ) { // ISO 8601 date or false.
656
657 switch ( $mod[ 'post_status' ] ) {
658
659 case 'auto-draft':
660 case 'draft':
661 case 'future':
662 case 'inherit': // Post revision.
663 case 'pending':
664 case 'trash':
665
666 if ( $this->p->debug->enabled ) {
667
668 $this->p->debug->log( 'skipping article published time for post status ' . $mod[ 'post_status' ] );
669 }
670
671 break;
672
673 case 'expired': // Previously published.
674 case 'private':
675 case 'publish':
676 default: // Any other post status.
677
678 $mt_og[ 'article:published_time' ] = $mod[ 'post_time' ];
679
680 break;
681 }
682
683 } elseif ( $mod[ 'is_comment' ] && $mod[ 'comment_time' ] ) { // ISO 8601 date or false.
684
685 $mt_og[ 'article:published_time' ] = $mod[ 'comment_time' ];
686 }
687 }
688
689 if ( ! isset( $mt_og[ 'article:modified_time' ] ) ) {
690
691 if ( $mod[ 'is_post' ] && $mod[ 'post_modified_time' ] ) { // ISO 8601 date or false.
692
693 $mt_og[ 'article:modified_time' ] = $mod[ 'post_modified_time' ];
694 }
695 }
696
697 if ( ! isset( $mt_og[ 'article:reading_mins' ] ) ) {
698
699 $mt_og[ 'article:reading_mins' ] = $this->p->page->get_reading_mins( $mod );
700 }
701
702 if ( ! empty( $this->p->cf[ 'head' ][ 'og_type_ns' ][ $type_id ] ) ) {
703
704 $og_ns = $this->p->cf[ 'head' ][ 'og_type_ns' ][ $type_id ]; // Example: https://ogp.me/ns/product#
705
706 $filter_name = 'wpsso_og_data_' . SucomUtil::sanitize_hookname( $og_ns );
707
708 $mt_og = apply_filters( $filter_name, $mt_og, $mod );
709 }
710
711 if ( $this->p->debug->enabled ) {
712
713 $this->p->debug->log( 'applying filters "wpsso_og"' );
714 }
715
716 $mt_og = apply_filters( 'wpsso_og', $mt_og, $mod );
717
718 /*
719 * Expand inline variables.
720 */
721 $mt_og = $this->p->util->inline->replace_variables( $mt_og, $mod );
722
723 if ( $this->p->debug->enabled ) {
724
725 $this->p->debug->log_arr( 'mt_og', $mt_og );
726 }
727
728 return $mt_og;
729 }
730
731 public function get_og_type_id( $opt_suffix, $default_id = false ) {
732
733 if ( $this->p->debug->enabled ) {
734
735 $this->p->debug->log_args( array(
736 'opt_suffix' => $opt_suffix,
737 'default_id' => $default_id,
738 ) );
739 }
740
741 if ( empty( $opt_suffix ) ) { // Just in case.
742
743 if ( $this->p->debug->enabled ) {
744
745 $this->p->debug->log( 'exiting early: opt_suffix is empty' );
746 }
747
748 return $default_id;
749
750 } elseif ( ! is_string( $opt_suffix ) ) { // Must be a string.
751
752 if ( $this->p->debug->enabled ) {
753
754 $this->p->debug->log( 'exiting early: opt_suffix is ' . gettype( $opt_suffix ) );
755 }
756
757 return $default_id;
758 }
759
760 $og_type_ns = $this->p->cf[ 'head' ][ 'og_type_ns' ];
761 $opt_key = 'og_type_for_' . $opt_suffix;
762 $type_id = isset( $this->p->options[ $opt_key ] ) ? $this->p->options[ $opt_key ] : $default_id;
763
764 if ( empty( $type_id ) || 'none' === $type_id || empty( $og_type_ns[ $type_id ] ) ) { // Invalid type id.
765
766 if ( empty( $default_id ) || 'none' === $default_id || empty( $og_type_ns[ $default_id ] ) ) { // Invalid default id.
767
768 return false;
769 }
770
771 return $default_id;
772 }
773
774 return $type_id;
775 }
776
777 public function get_og_types() {
778
779 if ( $this->p->debug->enabled ) {
780
781 $this->p->debug->mark();
782 }
783
784 return $this->p->cf[ 'head' ][ 'og_type_ns_compat' ];
785 }
786
787 public function get_og_types_select() {
788
789 if ( $this->p->debug->enabled ) {
790
791 $this->p->debug->mark();
792 }
793
794 $og_type_ns = $this->get_og_types();
795
796 $select = array();
797
798 foreach ( $og_type_ns as $type_id => $type_ns ) {
799
800 $type_url = preg_replace( '/(^.*\/\/|#$)/', '', $type_ns );
801 $type_name = preg_replace( '/(^.*\/ns\/|#$)/U', '', $type_url );
802
803 switch ( $this->p->options[ 'plugin_og_types_select_format' ] ) {
804
805 case 'name': // Options default.
806
807 $select[ $type_id ] = $type_name;
808
809 break;
810
811 case 'name_id':
812
813 $select[ $type_id ] = $type_name . ' [' . $type_id . ']';
814
815 break;
816
817 case 'id':
818
819 $select[ $type_id ] = $type_id;
820
821 break;
822
823 case 'id_url':
824
825 $select[ $type_id ] = $type_id . ' | ' . $type_url;
826
827 break;
828
829 case 'id_name':
830
831 $select[ $type_id ] = $type_id . ' | ' . $type_name;
832
833 break;
834
835 default:
836
837 $select[ $type_id ] = $type_name;
838
839 break;
840 }
841 }
842
843 if ( defined( 'SORT_STRING' ) ) { // Just in case.
844
845 asort( $select, SORT_STRING );
846
847 } else {
848
849 asort( $select );
850 }
851
852 return $select;
853 }
854
855 /*
856 * Returns an optional and customized locale value for the og:locale meta tag.
857 *
858 * $mixed = 'default' | 'current' | post ID | $mod array
859 *
860 * See WpssoOpenGraph->get_array().
861 * See WpssoOptions->get_defaults().
862 */
863 public function get_fb_locale( $mixed = 'current', $use_opts = true ) {
864
865 if ( $this->p->debug->enabled ) {
866
867 $this->p->debug->mark();
868 }
869
870 /*
871 * Maybe get an alternate Facebook locale from the plugin settings.
872 */
873 if ( $use_opts ) {
874
875 $key_locale = SucomUtilOptions::get_key_locale( 'fb_locale', $this->p->options, $mixed );
876
877 if ( ! empty( $this->p->options[ $key_locale ] ) ) { // Maybe use an alternate locale value.
878
879 $fb_locale = $this->p->options[ $key_locale ];
880
881 if ( $this->p->debug->enabled ) {
882
883 $this->p->debug->log( 'returning fb locale "' . $fb_locale . '" from ' . $key_locale . ' options key' );
884 }
885
886 return $fb_locale;
887 }
888 }
889
890 /*
891 * Maybe return the Facebook equivalent for this locale.
892 */
893 if ( $this->p->debug->enabled ) {
894
895 $this->p->debug->log( 'calling get_schema_lang()' );
896 }
897
898 $schema_lang = $this->p->schema->get_schema_lang( $mixed );
899
900 $fb_languages = SucomUtil::get_publisher_languages( 'facebook' );
901
902 /*
903 * Fix known exceptions.
904 */
905 switch ( $schema_lang ) {
906
907 case 'en_UK':
908
909 $schema_lang = 'en_GB';
910
911 break;
912
913 case 'de_DE_formal':
914
915 $schema_lang = 'de_DE';
916
917 break;
918 }
919
920 if ( ! empty( $fb_languages[ $schema_lang ] ) ) {
921
922 if ( $this->p->debug->enabled ) {
923
924 $this->p->debug->log( 'returning schema language "' . $schema_lang . '"' );
925 }
926
927 return $schema_lang;
928 }
929
930 /*
931 * Maybe fallback to the default locale.
932 */
933 $def_locale = $this->p->schema->get_schema_lang( 'default' );
934
935 if ( ! empty( $fb_languages[ $def_locale ] ) ) {
936
937 if ( $this->p->debug->enabled ) {
938
939 $this->p->debug->log( 'returning default locale "' . $def_locale . '"' );
940 }
941
942 return $def_locale;
943 }
944
945 /*
946 * Fallback to en_US.
947 */
948 if ( $this->p->debug->enabled ) {
949
950 $this->p->debug->log( 'returning fallback locale "en_US"' );
951 }
952
953 return 'en_US';
954 }
955
956 /*
957 * Since WPSSO Core v15.0.0.
958 *
959 * See WpssoCmcfActions->check_product_image_urls().
960 * See WpssoCmcfXml->add_product_images().
961 * See WpssoGmfActions->check_product_image_urls().
962 * See WpssoGmfXml->add_product_images().
963 */
964 public function get_product_retailer_item_image_urls( array $mt_single, $size_names = 'opengraph', $md_pre = 'og' ) {
965
966 $image_urls = array();
967 $mt_images = $this->get_product_retailer_item_images( $mt_single, $size_names, $md_pre );
968
969 if ( is_array( $mt_images ) ) { // Just in case.
970
971 foreach ( $mt_images as $mt_image ) {
972
973 if ( $image_url = SucomUtil::get_first_og_image_url( $mt_image ) ) {
974
975 $image_urls[] = $image_url;
976 }
977 }
978 }
979
980 return $image_urls;
981 }
982
983 /*
984 * Since WPSSO Core v15.0.0.
985 *
986 * WpssoOpenGraph->get_product_retailer_item_image_urls().
987 */
988 public function get_product_retailer_item_images( array $mt_single, $size_names = 'opengraph', $md_pre = 'og' ) {
989
990 if ( isset( $mt_single[ 'og:image' ] ) && is_array( $mt_single[ 'og:image' ] ) ) { // Nothing to do.
991
992 return $mt_single[ 'og:image' ];
993
994 } elseif ( ! $mod = $this->get_product_retailer_item_mod( $mt_single ) ) { // Returns false or array.
995
996 if ( $wpsso->debug->enabled ) {
997
998 $wpsso->debug->log( 'returning early: no post id for retailer item id' );
999 }
1000
1001 return array();
1002 }
1003
1004 $max_nums = $this->p->util->get_max_nums( $mod, 'og' ); // $mod must be an array.
1005
1006 return $this->p->media->get_all_images( $max_nums[ 'og_img_max' ], $size_names, $mod, $md_pre );
1007 }
1008
1009 /*
1010 * Since WPSSO Core v15.0.0.
1011 *
1012 * See WpssoCmcfActions->check_product_image_urls().
1013 * See WpssoGmfActions->check_product_image_urls().
1014 * See WpssoOpenGraph->get_product_retailer_item_images().
1015 * See WpssoSchema::add_offers_aggregate_data_mt().
1016 * See WpssoSchema::add_offers_data_mt().
1017 * See WpssoSchema::add_variants_data_mt().
1018 */
1019 public function get_product_retailer_item_mod( array $mt_single ) {
1020
1021 if ( ! empty( $mt_single[ 'product:retailer_item_id' ] ) && is_numeric( $mt_single[ 'product:retailer_item_id' ] ) ) {
1022
1023 $mod = $this->p->post->get_mod( $mt_single[ 'product:retailer_item_id' ] );
1024
1025 return empty( $mod[ 'id' ] ) ? false : $mod; // Just in case.
1026 }
1027
1028 return false;
1029 }
1030
1031 /*
1032 * Returns a string.
1033 *
1034 * Used for the 'product:retailer_category' meta tag value.
1035 */
1036 public function get_product_retailer_category( $mod ) {
1037
1038 $retailer_categories = $this->get_product_retailer_categories( $mod, $lists_max = 1 );
1039
1040 return isset( $retailer_categories[ 0 ] ) ? $retailer_categories[ 0 ] : '';
1041 }
1042
1043 /*
1044 * Returns an array of strings.
1045 */
1046 public function get_product_retailer_categories( $mod, $lists_max = 1 ) {
1047
1048 /*
1049 * Returns an associative array of term IDs and their names or objects.
1050 *
1051 * If the custom primary or default term ID exists in the post terms, it will be moved to the top.
1052 */
1053 $post_terms = $this->p->post->get_primary_terms( $mod, $mod[ 'post_primary_tax_slug' ], $output = 'objects' );
1054
1055 $category_names = array();
1056
1057 foreach ( $post_terms as $parent_term_obj ) {
1058
1059 $parent_term_id = $parent_term_obj->term_id;
1060
1061 $ancestor_ids = get_ancestors( $parent_term_id, $mod[ 'post_primary_tax_slug' ], $resource_type = 'taxonomy' );
1062
1063 if ( empty( $ancestor_ids ) || ! is_array( $ancestor_ids ) ) {
1064
1065 $ancestor_ids = array( $parent_term_id ); // Just do the parent term.
1066
1067 } else {
1068
1069 $ancestor_ids = array_reverse( $ancestor_ids ); // Add ancestors in reverse order.
1070
1071 $ancestor_ids[] = $parent_term_id; // Add parent term last.
1072 }
1073
1074 foreach ( $ancestor_ids as $term_id ) {
1075
1076 $term_mod = $this->p->term->get_mod( $term_id );
1077
1078 /*
1079 * Use $title_sep = false to avoid adding term parent names in the term title.
1080 *
1081 * $md_key = 'schema_title_bc' will use array( 'schema_title_bc', 'schema_title_alt', 'schema_title', 'seo_title' )
1082 */
1083 $category_names[ $parent_term_id ][ $term_id ] = $this->p->page->get_title( $term_mod,
1084 $md_key = 'schema_title_bc', $max_len = 'schema_title_bc', $title_sep = false );
1085 }
1086 }
1087
1088 if ( $lists_max ) {
1089
1090 $category_names = array_slice( $category_names, 0, $lists_max );
1091 }
1092
1093 $retailer_categories = array();
1094
1095 foreach ( $category_names as $parent_term_id => $term_ids ) {
1096
1097 $retailer_categories[] = implode( ' > ', $term_ids );
1098 }
1099
1100 return $retailer_categories;
1101 }
1102
1103 /*
1104 * Returns an array of strings.
1105 */
1106 public function get_product_awards( array $mod ) {
1107
1108 $awards = array();
1109
1110 if ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
1111
1112 $md_opts = $mod[ 'obj' ]->get_options( $mod[ 'id' ] );
1113
1114 if ( is_array( $md_opts ) ) { // Just in case.
1115
1116 $awards = SucomUtil::get_multi_values( $md_opts, 'product_award' );
1117 }
1118 }
1119
1120 $awards = apply_filters( 'wpsso_og_product_awards', $awards, $mod );
1121
1122 return $awards;
1123 }
1124
1125 /*
1126 * Called by WpssoHead->get_head_array() before merging all meta tag arrays.
1127 *
1128 * Unset mis-matched og_type meta tags using the 'og_type_mt' array as a reference. For example, remove all
1129 * 'article' meta tags if the og_type is 'website'. Removing only known meta tags (using the 'og_type_mt' array as
1130 * a reference) protects internal meta tags that may be used later by WpssoHead->extract_head_info().
1131 *
1132 * The 'og_content_map' array is also checked for Schema values that need to be swapped for simpler Open Graph meta
1133 * tag values.
1134 */
1135 public function sanitize_mt_array( array $mt_og ) {
1136
1137 /*
1138 * Array of meta tags to allow, reject, and map.
1139 */
1140 static $og_allow = array();
1141 static $og_reject = array();
1142 static $content_map = array();
1143 static $local_recursion = 0;
1144
1145 if ( $local_recursion > 32 ) { // Just in case.
1146
1147 return $mt_og;
1148 }
1149
1150 if ( 0 === $local_recursion ) { // Define the static variables once.
1151
1152 /*
1153 * The og:type is only needed when first run, to define the allow, reject, and map arrays.
1154 */
1155 if ( empty( $mt_og[ 'og:type' ] ) ) {
1156
1157 if ( $this->p->debug->enabled ) {
1158
1159 $this->p->debug->log( 'exiting early: og:type is empty and required' );
1160 }
1161
1162 return $mt_og;
1163 }
1164
1165 if ( $this->p->debug->enabled ) {
1166
1167 $this->p->debug->log( 'setting og allow, reject, and content map arrays' );
1168 }
1169
1170 $og_type = $mt_og[ 'og:type' ];
1171
1172 /*
1173 * An array of Open Graph types, their meta tags, and their associated metadata keys.
1174 */
1175 foreach ( $this->p->cf[ 'head' ][ 'og_type_mt' ] as $type_id => $og_type_mt_md ) {
1176
1177 /*
1178 * An array of meta tags and their associated metadata keys.
1179 */
1180 foreach ( $og_type_mt_md as $mt_name => $md_key ) {
1181
1182 if ( $og_type === $type_id ) {
1183
1184 $og_allow[ $mt_name ] = true; // Mark meta tag as allowed.
1185
1186 /*
1187 * If we have a content map for the meta tag, save the content mapping array.
1188 *
1189 * 'product:availability' => array(
1190 * 'https://schema.org/BackOrder' => 'available for order',
1191 * 'https://schema.org/Discontinued' => 'discontinued',
1192 * 'https://schema.org/InStock' => 'in stock',
1193 * 'https://schema.org/InStoreOnly' => 'in stock',
1194 * 'https://schema.org/LimitedAvailability' => 'in stock',
1195 * 'https://schema.org/OnlineOnly' => 'in stock',
1196 * 'https://schema.org/OutOfStock' => 'out of stock',
1197 * 'https://schema.org/PreOrder' => 'available for order',
1198 * 'https://schema.org/PreSale' => 'available for order',
1199 * 'https://schema.org/SoldOut' => 'out of stock',
1200 * ),
1201 */
1202 if ( ! empty( $this->p->cf[ 'head' ][ 'og_content_map' ][ $mt_name ] ) ) {
1203
1204 /*
1205 * Save the content mapping array.
1206 */
1207 $content_map[ $mt_name ] = $this->p->cf[ 'head' ][ 'og_content_map' ][ $mt_name ];
1208 }
1209
1210 } else $og_reject[ $mt_name ] = true; // Mark meta tag as disallowed.
1211 }
1212 }
1213
1214 foreach ( array( 'product:offers', 'product:variants' ) as $mt_name ) {
1215
1216 if ( ! empty( $mt_og[ $mt_name ] ) && is_array( $mt_og[ $mt_name ] ) ) {
1217
1218 foreach ( $mt_og[ $mt_name ] as $num => &$mt_single ) { // Allow changes to the variation array.
1219
1220 unset( $mt_single[ 'product:brand' ] ); // Allow only a single brand value (in the parent product).
1221
1222 foreach ( $mt_single as $mt_single_name => $mt_single_value ) {
1223
1224 /*
1225 * Avoid duplicate values (like prices) between the main product and its variations.
1226 */
1227 if ( isset( $mt_og[ $mt_single_name ] ) && $mt_og[ $mt_single_name ] === $mt_single_value ) {
1228
1229 /*
1230 * Duplicate prices and values are removed first. Check if
1231 * the currency or units meta tag needs to be removed as
1232 * well (ie. if the price or value does not exist).
1233 */
1234 if ( true === $this->have_currency_units_value( $mt_og, $mt_single_name ) ) {
1235
1236 if ( $this->p->debug->enabled ) {
1237
1238 $this->p->debug->log( 'main product duplicate ' . $mt_single_name . ' kept' );
1239 }
1240
1241 continue;
1242 }
1243
1244 if ( $this->p->debug->enabled ) {
1245
1246 $this->p->debug->log( 'main product duplicate ' . $mt_single_name . ' removed' );
1247 }
1248
1249 unset( $mt_og[ $mt_single_name ] );
1250 }
1251 }
1252 }
1253 }
1254 }
1255 }
1256
1257 /*
1258 * Check the meta tag names and their values.
1259 */
1260 foreach ( $mt_og as $key => $val ) {
1261
1262 if ( ! empty( $og_allow[ $key ] ) ) { // Meta tag is allowed - check it's value.
1263
1264 if ( $this->p->debug->enabled ) {
1265
1266 $this->p->debug->log( 'allowed meta tag ' . $key );
1267 }
1268
1269 /*
1270 * If we have a matching value in the content map, then assign the mapped value.
1271 *
1272 * 'https://schema.org/BackOrder' to 'available for order' for example.
1273 */
1274 if ( isset( $content_map[ $key ][ $val ] ) ) {
1275
1276 if ( $this->p->debug->enabled ) {
1277
1278 $this->p->debug->log( 'mapping meta tag ' . $key . ' = ' . $content_map[ $key ][ $val ] );
1279 }
1280
1281 $mt_og[ $key ] = $content_map[ $key ][ $val ];
1282
1283 /*
1284 * If this is a currency or units meta tag without a price or value, then set the meta tag to null.
1285 */
1286 } elseif ( false === $this->have_currency_units_value( $mt_og, $key ) ) {
1287
1288 if ( $this->p->debug->enabled ) {
1289
1290 $this->p->debug->log( 'setting meta tag ' . $key . ' = null' );
1291 }
1292
1293 $mt_og[ $key ] = null;
1294 }
1295
1296 } elseif ( ! empty( $og_reject[ $key ] ) ) {
1297
1298 if ( $this->p->debug->enabled ) {
1299
1300 $this->p->debug->log( 'rejected meta tag ' . $key );
1301 }
1302
1303 unset( $mt_og[ $key ] );
1304
1305 } elseif ( is_array( $val ) ) {
1306
1307 if ( $this->p->debug->enabled ) {
1308
1309 if ( is_string( $key ) ) {
1310
1311 $this->p->debug->log( 'sanitizing meta tag ' . $key . ' array' );
1312 }
1313 }
1314
1315 $local_recursion++;
1316
1317 $mt_og[ $key ] = $this->sanitize_mt_array( $val );
1318
1319 $local_recursion--;
1320 }
1321 }
1322
1323 return $mt_og;
1324 }
1325
1326 /*
1327 * Add post/term/user meta data to the Open Graph meta tags.
1328 */
1329 public function add_data_og_type_md( array &$mt_og, $type_id, array $md_opts ) { // Pass by reference is OK.
1330
1331 if ( $this->p->debug->enabled ) {
1332
1333 $this->p->debug->mark();
1334 }
1335
1336 /*
1337 * An array of Open Graph types, their meta tags, and their associated metadata keys.
1338 */
1339 if ( empty( $this->p->cf[ 'head' ][ 'og_type_mt' ][ $type_id ] ) ) { // Just in case.
1340
1341 return;
1342 }
1343
1344 $og_type_mt_md = $this->p->cf[ 'head' ][ 'og_type_mt' ][ $type_id ];
1345
1346 if ( $this->p->debug->enabled ) {
1347
1348 $this->p->debug->log( 'loading og_type_mt array for type id ' . $type_id );
1349 }
1350
1351 $md_keys_multi = WpssoConfig::get_md_keys_multi(); // Uses a local cache.
1352
1353 foreach ( $og_type_mt_md as $mt_name => $md_key_pre ) {
1354
1355 if ( empty( $md_key_pre ) ) { // Most of the metadata keys are empty.
1356
1357 continue;
1358
1359 } elseif ( false !== strpos( $mt_name, ':units' ) ) { // Define units when we define the value.
1360
1361 continue;
1362 }
1363
1364 $is_multi = empty( $md_keys_multi[ $md_key_pre ] ) ? false : true;
1365 $multi_keys = $is_multi ? array_keys( SucomUtil::preg_grep_keys( '/^' . $md_key_pre . '_[0-9]+$/', $md_opts ) ) : array( $md_key_pre );
1366
1367 foreach ( $multi_keys as $md_key ) {
1368
1369 $values = array();
1370
1371 /*
1372 * Use custom value(s) if available. Returns true or false.
1373 */
1374 if ( ! $this->have_og_type_md_values( $values, $type_id, $md_opts, $mt_name, $md_key ) ) {
1375
1376 $def_md_key = $this->get_def_md_key( $md_key );
1377
1378 if ( isset( $mt_og[ $mt_name ] ) ) { // Meta tag exists and is not null.
1379
1380 if ( $this->p->debug->enabled ) {
1381
1382 $this->p->debug->log( $mt_name . ' value kept = ' . $mt_og[ $mt_name ] );
1383 }
1384
1385 } elseif ( ! empty( $def_md_key ) ) { // Add default value from the plugin settings.
1386
1387 if ( $this->p->debug->enabled ) {
1388
1389 $this->p->debug->log( $mt_name . ' from options = ' . $this->p->options[ $def_md_key ] );
1390 }
1391
1392 $values[ $mt_name ] = $this->p->options[ $def_md_key ];
1393
1394 } else { // Add missing meta tag with null value.
1395
1396 if ( $this->p->debug->enabled ) {
1397
1398 $this->p->debug->log( $mt_name . ' = null' );
1399 }
1400
1401 $values[ $mt_name ] = null; // Use null so isset() returns false.
1402 }
1403 }
1404
1405 foreach ( $values as $mt_name => $val ) { // Can be an empty array.
1406
1407 if ( 'none' === $val ) {
1408
1409 if ( $this->p->debug->enabled ) {
1410
1411 $this->p->debug->log( 'setting ' . $mt_name . ' = null for value none' );
1412 }
1413
1414 $mt_og[ $mt_name ] = null;
1415
1416 } elseif ( $is_multi ) {
1417
1418 $mt_og[ $mt_name ][] = $val;
1419
1420 } else $mt_og[ $mt_name ] = $val;
1421 }
1422 }
1423 }
1424 }
1425
1426 /*
1427 * Check for a default value from the settings.
1428 *
1429 * Open Graph defaults have an 'og_def_' prefix, except for 'product_currency'.
1430 *
1431 * Schema defaults have a 'schema_def_' prefix.
1432 */
1433 public function get_def_md_key( $md_key ) {
1434
1435 $def_md_key = null;
1436
1437 if ( 'product_currency' === $md_key ) {
1438
1439 if ( isset( $this->p->options[ 'og_def_currency' ] ) ) { // Just in case.
1440
1441 $def_md_key = 'og_def_currency';
1442 }
1443
1444 } elseif ( isset( $this->p->options[ 'og_def_' . $md_key ] ) ) {
1445
1446 $def_md_key = 'og_def_' . $md_key;
1447
1448 } elseif ( isset( $this->p->options[ 'schema_def_' . $md_key ] ) ) {
1449
1450 $def_md_key = 'schema_def_' . $md_key;
1451 }
1452
1453 return $def_md_key;
1454 }
1455
1456 /*
1457 * If we have a GTIN number, try to improve the assigned property name.
1458 *
1459 * Pass $mt_og by reference to modify the array directly.
1460 *
1461 * A similar method exists as WpssoSchema::check_prop_value_gtin().
1462 */
1463 public static function check_mt_value_gtin( &$mt_og, $mt_pre = 'product' ) { // Pass by reference is OK.
1464
1465 $wpsso =& Wpsso::get_instance();
1466
1467 if ( $wpsso->debug->enabled ) {
1468
1469 $wpsso->debug->log( 'checking ' . $mt_pre . ' gtin value' );
1470 }
1471
1472 if ( isset( $mt_og[ $mt_pre . ':gtin' ] ) ) {
1473
1474 /*
1475 * The value may come from a custom field, so trim it, just in case.
1476 */
1477 $mt_og[ $mt_pre . ':gtin' ] = preg_replace( '/\s+/', '', $mt_og[ $mt_pre . ':gtin' ] );
1478
1479 $gtin_len = strlen( $mt_og[ $mt_pre . ':gtin' ] );
1480
1481 switch ( $gtin_len ) {
1482
1483 case 13:
1484
1485 if ( empty( $mt_og[ $mt_pre . ':ean' ] ) ) {
1486
1487 $mt_og[ $mt_pre . ':ean' ] = $mt_og[ $mt_pre . ':gtin' ];
1488 }
1489
1490 break;
1491
1492 case 12:
1493
1494 if ( empty( $mt_og[ $mt_pre . ':upc' ] ) ) {
1495
1496 $mt_og[ $mt_pre . ':upc' ] = $mt_og[ $mt_pre . ':gtin' ];
1497 }
1498
1499 break;
1500 }
1501 }
1502 }
1503
1504 public static function check_mt_value_price( &$mt_og, $mt_pre = 'product' ) { // Pass by reference is OK.
1505
1506 $wpsso =& Wpsso::get_instance();
1507
1508 if ( $wpsso->debug->enabled ) {
1509
1510 $wpsso->debug->log( 'checking ' . $mt_pre . ' price value' );
1511 }
1512
1513 $def_currency_key = $mt_pre . ':price:currency';
1514 $def_currency = empty( $mt_og[ $def_currency_key ] ) ? $wpsso->options[ 'og_def_currency' ] : $mt_og[ $def_currency_key ];
1515 $min_adv_price_key = $mt_pre . ':min_advert_price:amount';
1516 $min_adv_price = empty( $mt_og[ $min_adv_price_key ] ) ? 0 : $mt_og[ $min_adv_price_key ];
1517
1518 foreach ( array( 'min_advert_price', 'original_price', 'pretax_price', 'price', 'sale_price', 'shipping_cost' ) as $price_name ) {
1519
1520 $amount_key = $mt_pre . ':' . $price_name . ':amount';
1521 $currency_key = $mt_pre . ':' . $price_name . ':currency';
1522
1523 if ( isset( $mt_og[ $amount_key ] ) ) {
1524
1525 if ( is_numeric( $mt_og[ $amount_key ] ) ) { // Allow for price of 0.
1526
1527 if ( $min_adv_price && $mt_og[ $amount_key ] < $min_adv_price ) {
1528
1529 $mt_og[ $mt_pre . ':price:type' ] = 'https://schema.org/MinimumAdvertisedPrice';
1530
1531 $mt_og[ $amount_key ] = $min_adv_price;
1532 }
1533
1534 if ( empty( $mt_og[ $currency_key ] ) ) {
1535
1536 $mt_og[ $currency_key ] = $def_currency;
1537 }
1538
1539 } else {
1540
1541 if ( ! empty( $mt_og[ $amount_key ] ) ) { // Non-empty string, array, etc.
1542
1543 if ( $wpsso->debug->enabled ) {
1544
1545 $wpsso->debug->log( 'invalid ' . $amount_key . ' value = ' . print_r( $mt_og[ $amount_key ], true ) );
1546 }
1547 }
1548
1549 $mt_og[ $amount_key ] = null;
1550 $mt_og[ $currency_key ] = null;
1551 }
1552 }
1553 }
1554 }
1555
1556 public static function check_mt_value_energy_efficiency( &$mt_og, $mt_pre = 'product' ) { // Pass by reference is OK.
1557
1558 $wpsso =& Wpsso::get_instance();
1559
1560 if ( $wpsso->debug->enabled ) {
1561
1562 $wpsso->debug->log( 'checking ' . $mt_pre . ' energy efficiency value' );
1563 }
1564
1565 if ( empty( $mt_og[ $mt_pre . ':energy_efficiency:value' ] ) || 'none' === $mt_og[ $mt_pre . ':energy_efficiency:value' ] ) {
1566
1567 $mt_og[ $mt_pre . ':energy_efficiency:value' ] = null;
1568 $mt_og[ $mt_pre . ':energy_efficiency:min_value' ] = null;
1569 $mt_og[ $mt_pre . ':energy_efficiency:max_value' ] = null;
1570 }
1571 }
1572
1573 private function have_currency_units_value( $mt_og, $mt_key ) {
1574
1575 if ( preg_match( '/^(.*:)(currency|units)$/', $mt_key, $matches ) ) {
1576
1577 switch ( $matches[ 2 ] ) {
1578
1579 case 'currency': $mt_value_key = $matches[ 1 ] . 'amount'; break;
1580 case 'units': $mt_value_key = $matches[ 1 ] . 'value'; break;
1581 default: $mt_value_key = null; break;
1582 }
1583
1584 if ( $mt_value_key ) {
1585
1586 return isset( $mt_og[ $mt_value_key ] ) && '' !== $mt_og[ $mt_value_key ] ? true : false;
1587 }
1588 }
1589
1590 return null;
1591 }
1592
1593 /*
1594 * Use custom value(s) if available. Returns true or false.
1595 */
1596 private function have_og_type_md_values( array &$values, $type_id, array $md_opts, $mt_name, $md_key ) { // Pass by reference is OK.
1597
1598 if ( empty( $md_key ) || ! isset( $md_opts[ $md_key ] ) || '' === $md_opts[ $md_key ] ) {
1599
1600 return false;
1601 }
1602
1603 /*
1604 * Check for meta tags that require a unit value and may need to be converted.
1605 */
1606 if ( false !== strpos( $mt_name, ':value' ) && preg_match( '/^(.*):value$/', $mt_name, $mt_match ) ) {
1607
1608 if ( $this->p->debug->enabled ) {
1609
1610 $this->p->debug->log( $mt_name . ' from metadata = ' . $md_opts[ $md_key ] );
1611 }
1612
1613 $values[ $mt_name ] = $md_opts[ $md_key ];
1614
1615 /*
1616 * Check if the value meta tag needs a units meta tag.
1617 */
1618 $mt_units_name = $mt_match[ 1 ] . ':units';
1619
1620 /*
1621 * An array of Open Graph types, their meta tags, and their associated metadata keys.
1622 */
1623 $og_type_mt_md = $this->p->cf[ 'head' ][ 'og_type_mt' ][ $type_id ];
1624
1625 if ( isset( $og_type_mt_md[ $mt_units_name ] ) ) { // Value needs a units meta tag.
1626
1627 $md_units_key = $og_type_mt_md[ $mt_units_name ]; // An empty string or metadata options key.
1628
1629 $values[ $mt_units_name ] = $unit_text = WpssoSchema::get_unit_text( $md_key );
1630
1631 if ( ! empty( $md_opts[ $md_units_key ] ) ) { // Custom unit text found.
1632
1633 if ( $this->p->debug->enabled ) {
1634
1635 $this->p->debug->log( $mt_units_name . ' from metadata = ' . $md_opts[ $md_units_key ] );
1636 }
1637
1638 $values[ $mt_units_name ] = $md_opts[ $md_units_key ];
1639 }
1640
1641 /*
1642 * Since WPSSO Core v14.0.0.
1643 *
1644 * Check if we need to convert the value.
1645 */
1646 if ( $values[ $mt_units_name ] !== $unit_text ) {
1647
1648 $unit_value = WpssoUtilUnits::get_convert( $values[ $mt_name ], $unit_text, $values[ $mt_units_name ] );
1649
1650 if ( $this->p->debug->enabled ) {
1651
1652 $this->p->debug->log( 'converted ' . $values[ $mt_name ] . ' ' . $values[ $mt_units_name ] .
1653 ' to ' . $unit_value . ' ' . $unit_text );
1654 }
1655
1656 $values[ $mt_name ] = $unit_value;
1657 $values[ $mt_units_name ] = $unit_text;
1658 }
1659 }
1660
1661 return true;
1662 }
1663
1664 /*
1665 * If the value does not need to be converted, then add the custom metadata value as-is.
1666 */
1667 if ( $this->p->debug->enabled ) {
1668
1669 $this->p->debug->log( $mt_name . ' from metadata = ' . $md_opts[ $md_key ] );
1670 }
1671
1672 $values[ $mt_name ] = $md_opts[ $md_key ];
1673
1674 return true;
1675 }
1676
1677 /*
1678 * If the Open Graph type isn't already hard-coded (ie. ':disabled'), then using the post type and the Schema type,
1679 * check for a possible hard-coded Open Graph type.
1680 */
1681 private function maybe_update_post_og_type( array $md_opts, $post_id, array $mod ) {
1682
1683 if ( $this->p->debug->enabled ) {
1684
1685 $this->p->debug->mark();
1686 }
1687
1688 if ( empty( $md_opts[ 'og_type:disabled' ] ) ) { // Just in case.
1689
1690 /*
1691 * Check if the post type matches a pre-defined Open Graph type.
1692 *
1693 * For example, a post type of 'organization' would return 'website' for the Open Graph type.
1694 *
1695 * Returns false or an Open Graph type string.
1696 */
1697 if ( $og_type = $this->p->post->get_post_type_og_type( $mod ) ) {
1698
1699 $md_opts[ 'og_type' ] = $og_type;
1700
1701 $md_opts[ 'og_type:disabled' ] = true;
1702
1703 } else {
1704
1705 /*
1706 * Use the saved Schema type or get the default Schema type.
1707 */
1708 if ( isset( $md_opts[ 'schema_type' ] ) ) {
1709
1710 $type_id = $md_opts[ 'schema_type' ];
1711
1712 } else $type_id = $this->p->schema->get_mod_schema_type_id( $mod, $use_md_opts = false );
1713
1714 /*
1715 * Check if the Schema type matches a pre-defined Open Graph type.
1716 */
1717 if ( $og_type = $this->p->schema->get_schema_type_og_type( $type_id ) ) {
1718
1719 $md_opts[ 'og_type' ] = $og_type;
1720
1721 $md_opts[ 'og_type:disabled' ] = true;
1722 }
1723 }
1724 }
1725
1726 return $md_opts;
1727 }
1728 }
1729 }
1730