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 / abstract / wp-meta.php

wp-meta.php in WPSSO Core – Complete Schema Markup and Meta Tags 22.7.0, at lib/abstract/wp-meta.php

3,766 lines 123.3 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 /*
19 * Extended by the WpssoPost, WpssoTerm, and WpssoUser classes.
20 */
21 if ( ! class_exists( 'WpssoAbstractWpMeta' ) ) {
22
23 abstract class WpssoAbstractWpMeta {
24
25 protected $p; // Wpsso class object.
26 protected $a; // Wpsso add-on class object.
27 protected $upgrade; // WpssoUpgradeMeta class object.
28 protected $form; // SucomForm class object.
29
30 protected $md_cache_disabled = false; // Disable local caches.
31
32 protected static $head_tags = false; // Must be false by default.
33 protected static $head_info = array();
34
35 private static $mod_defaults = array(
36 'id' => 0, // Post, term, or user ID.
37 'name' => false, // Module name ('post', 'term', or 'user').
38 'name_transl' => false, // Module name translated.
39 'obj' => false, // Module object.
40 'wp_obj' => false, // WP object (WP_Post, WP_Term, etc.).
41 'query_vars' => array(), // Defined by WpssoPage->get_mod().
42 'posts_args' => array(),
43 'paged' => false, // False or numeric (aka $wp_query->query_vars[ 'paged' ]).
44 'paged_total' => false, // False or numberic (aka $wp_query->max_num_pages).
45 'is_404' => false,
46 'is_archive' => false,
47 'is_attachment' => false, // Post type is 'attachment'.
48 'is_comment' => false,
49 'is_date' => false,
50 'is_day' => false,
51 'is_feed' => false, // RSS feed.
52 'is_home' => false, // Home page (static or blog archive).
53 'is_home_page' => false, // Static front page (singular post).
54 'is_home_posts' => false, // Static posts page or blog archive page.
55 'is_month' => false,
56 'is_post' => false,
57 'is_post_type_archive' => false, // Post is an archive page.
58 'is_public' => true,
59 'is_search' => false,
60 'is_term' => false,
61 'is_user' => false,
62 'is_year' => false,
63 'comment_author' => false, // Comment author user ID.
64 'comment_author_name' => false, // Comment author name.
65 'comment_author_url' => false, // Comment author URL.
66 'comment_paged' => false, // False or numeric (aka $wp_query->query_vars[ 'cpage' ]).
67 'comment_parent' => false,
68 'comment_rating' => false,
69 'comment_time' => false,
70 'use_post' => false,
71 'post_slug' => false, // Post name (aka slug).
72 'post_type' => false, // Post type name.
73 'post_type_label_plural' => false, // Post type plural name.
74 'post_type_label_single' => false, // Post type singular name.
75 'post_mime_type' => false, // Post mime type (ie. image/jpg).
76 'post_mime_group' => false, // Post mime group (ie. image).
77 'post_mime_subgroup' => false, // Post mime subgroup (ie. jpg).
78 'post_status' => false, // Post status name.
79 'post_author' => false, // Post author id.
80 'post_coauthors' => array(),
81 'post_time' => false, // Post published time (ISO 8601 date or false).
82 'post_timestamp' => false, // Post published time (Unit timestamp or false).
83 'post_modified_time' => false, // Post modified time (ISO 8601 date or false).
84 'post_modified_timestamp' => false, // Post modified time (Unit timestamp or false).
85 'post_parent' => false, // Post parent id.
86 'post_primary_tax_slug' => 'category', // Primary taxonomy, filtered by 'wpsso_primary_tax_slug' in WpssoPost->get_mod().
87 'post_taxonomies' => array(), // Post taxonomy slugs.
88 'tax_slug' => '',
89 'tax_label_plural' => false, // Taxonomy plural name.
90 'tax_label_single' => false, // Taxonomy singular name.
91 'term_tax_id' => false,
92 'user_name' => '', // User display name.
93 'wpml_code' => '', // WPML language code.
94 );
95
96 public function __construct( &$plugin ) {
97
98 return self::must_be_extended();
99 }
100
101 /*
102 * Disable local cache for get_mod(), get_options(), and get_defaults().
103 */
104 public function md_cache_disable() {
105
106 $this->md_cache_disabled = true;
107 }
108
109 public function md_cache_enable() {
110
111 $this->md_cache_disabled = false;
112 }
113
114 /*
115 * Add WordPress action and filters callbacks.
116 */
117 public function add_wp_callbacks() {
118
119 return self::must_be_extended();
120 }
121
122 /*
123 * Since WPSSO Core v17.0.0.
124 *
125 * Register our comment, post, term, and user meta.
126 *
127 * Do not register a sanitation callback as the sanitation filter is executed too frequently by WordPress, and only
128 * provides $meta_value, $meta_key, and $object_type arguments.
129 *
130 * See sanitize_meta() in wordpress/wp-includes/meta.php.
131 * See WpssoComment->add_wp_callbacks().
132 * See WpssoPost->add_wp_callbacks().
133 * See WpssoTerm->add_wp_callbacks().
134 * See WpssoUser->add_wp_callbacks().
135 */
136 protected function register_meta( $object_type, $meta_key ) {
137
138 if ( $this->p->debug->enabled ) {
139
140 $this->p->debug->mark();
141 }
142
143 $meta_title = _x( $this->p->cf[ 'meta' ][ 'title' ], 'metabox title', 'wpsso' );
144
145 register_meta( $object_type, $meta_key, $args = array(
146 'type' => 'array',
147 'description' => sprintf( _x( '%s Metadata', 'metadata title', 'wpsso' ), $meta_title ),
148 'default' => array(),
149 'single' => true,
150 'sanitize_callback' => null, // Executed much too frequently by WordPress.
151 'auth_callback' => null,
152 'show_in_rest' => false,
153 'revisions_enabled' => 'post' === $object_type? true : false, // Can only be used when the object type is 'post'.
154 ) );
155
156 /*
157 * Although announced as a feature, WordPress v6.4 cannot display revisions with post meta arrays.
158 *
159 * Hooking the '_wp_post_revision_fields' filter to add the WPSSO_META_NAME array to the revision page will
160 * cause a PHP fatal error in PHP v8.2.
161 *
162 * See https://core.trac.wordpress.org/ticket/59827
163 *
164 * Enable this code and update the version check when WordPress is able to display revisions with post meta arrays.
165 *
166 * global $wp_version;
167 *
168 * if ( version_compare( $wp_version, '6.4.2', '<' ) ) {
169 *
170 * if ( $this->p->debug->enabled ) {
171 *
172 * $this->p->debug->log( 'exiting early: no revisions for post meta arrays in WordPress v' . $wp_version );
173 * }
174 *
175 * return; // Stop here.
176 * }
177 *
178 * add_filter( '_wp_post_revision_fields', array( $this, 'revision_fields_meta_title' ), 10, 2 );
179 *
180 * add_filter( '_wp_post_revision_field_' . WPSSO_META_NAME, array( $this, 'get_revision_fields_meta_options' ), 10, 3 );
181 */
182 }
183
184 /*
185 * Since WPSSO Core v17.3.0.
186 *
187 * Add WPSSO_META_NAME to the revision fields shown.
188 */
189 public function revision_fields_meta_title( $fields, $wp_obj ) {
190
191 $meta_title = _x( $this->p->cf[ 'meta' ][ 'title' ], 'metabox title', 'wpsso' );
192
193 $fields[ WPSSO_META_NAME ] = sprintf( _x( '%s Metadata', 'metadata title', 'wpsso' ), $meta_title );
194
195 return $fields;
196 }
197
198 /*
199 * Since WPSSO Core v17.3.0.
200 *
201 * Cleanup and convert the options array to a text string.
202 */
203 public function get_revision_fields_meta_options( $md_opts, $meta_key, $wp_obj ) {
204
205 if ( is_string( $md_opts ) ) { // Nothing to do.
206
207 return $md_opts;
208
209 } elseif ( is_array( $md_opts ) ) { // Convert array to string.
210
211 $this->p->opt->remove_versions_checksum( $md_opts );
212
213 return var_export( $md_opts, true );
214 }
215
216 return '';
217 }
218
219 /*
220 * Get the $mod object for a post, term, or user ID.
221 */
222 public function get_mod( $obj_id ) {
223
224 return self::must_be_extended( self::$mod_defaults );
225 }
226
227 public function get_mod_wp_object( array $mod ) {
228
229 return self::must_be_extended( $ret_val = false );
230 }
231
232 public static function get_mod_defaults() {
233
234 return self::$mod_defaults;
235 }
236
237 /*
238 * Get the $mod object for the home page.
239 */
240 public static function get_mod_home() {
241
242 $wpsso =& Wpsso::get_instance();
243
244 if ( $wpsso->debug->enabled ) {
245
246 $wpsso->debug->mark();
247 }
248
249 $mod = self::$mod_defaults;
250
251 $post_id = 0;
252
253 if ( 'page' === get_option( 'show_on_front' ) ) {
254
255 if ( ! $post_id = (int) get_option( 'page_on_front', $default = 0 ) ) {
256
257 $post_id = (int) get_option( 'page_for_posts', $default = 0 );
258 }
259
260 $mod = $wpsso->post->get_mod( $post_id );
261 }
262
263 /*
264 * Post elements.
265 */
266 $mod[ 'is_home' ] = true; // Home page (static or blog archive).
267
268 return $mod;
269 }
270
271 /*
272 * Option handling methods:
273 *
274 * get_defaults()
275 * get_options()
276 * save_options()
277 * delete_options()
278 */
279 public function get_defaults( $obj_id, $md_key = false ) {
280
281 if ( $this->p->debug->enabled ) {
282
283 $this->p->debug->log_args( array(
284 'obj_id' => $obj_id,
285 'md_key' => $md_key,
286 ) );
287 }
288
289 static $local_fifo = array();
290
291 /*
292 * Maybe retrieve the array from the local cache.
293 *
294 * Note that the local cache is unique to each child class, so we can simply index by the object ID.
295 */
296 $md_defs = array();
297
298 if ( WpssoOptions::is_cache_allowed() ) {
299
300 if ( ! $this->md_cache_disabled ) {
301
302 if ( isset( $local_fifo[ $obj_id ] ) ) {
303
304 if ( $this->p->debug->enabled ) {
305
306 $this->p->debug->log( 'using defaults from local cache for id ' . $obj_id );
307 }
308
309 } else {
310
311 /*
312 * Maybe limit the number of array elements.
313 */
314 $local_fifo = SucomUtil::array_slice_fifo( $local_fifo, WPSSO_CACHE_ARRAY_FIFO_MAX );
315
316 if ( $this->p->debug->enabled ) {
317
318 $this->p->debug->mark_diff( 'adding local cache element for id ' . $obj_id );
319 }
320
321 $local_fifo[ $obj_id ] = array();
322 }
323
324 $md_defs =& $local_fifo[ $obj_id ];
325
326 } elseif ( $this->p->debug->enabled ) {
327
328 $this->p->debug->log( 'metadata caching is disabled for id ' . $obj_id );
329 }
330
331 } elseif ( $this->p->debug->enabled ) {
332
333 $this->p->debug->log( 'options caching is not allowed for id ' . $obj_id );
334 }
335
336 if ( empty( $md_defs[ 'opt_filtered' ] ) ) {
337
338 /*
339 * Set before calling filters to prevent recursion.
340 */
341 if ( $this->p->debug->enabled ) {
342
343 $this->p->debug->log( 'setting opt_filtered to 1' );
344 }
345
346 $md_defs[ 'opt_filtered' ] = 1;
347
348 if ( $this->p->debug->enabled ) {
349
350 $this->p->debug->log( 'required call to ' . get_class( $this ) . '->get_mod() for id ' . $obj_id );
351 }
352
353 $mod = $this->get_mod( $obj_id );
354
355 $opts =& $this->p->options; // Shortcut variable name.
356
357 $def_lang = SucomUtilWP::get_locale( $mod, $read_cache = false ); // Get locale for post, term, or user object.
358 $def_dimension_units = WpssoUtilUnits::get_dimension_text();
359 $def_fluid_vol_units = WpssoUtilUnits::get_fluid_volume_text();
360 $def_weight_units = WpssoUtilUnits::get_weight_text();
361 $def_og_type = $this->p->og->get_mod_og_type_id( $mod, $use_md_opts = false );
362 $def_schema_type = $this->p->schema->get_mod_schema_type_id( $mod, $use_md_opts = false );
363 $def_primary_term_id = $this->p->post->get_default_term_id( $mod, $mod[ 'post_primary_tax_slug' ] ); // Returns term id or false.
364 $def_reading_mins = $this->p->page->get_reading_mins( $mod );
365 $def_currency = $this->p->get_options( 'og_def_currency', 'USD' );
366 $def_country = $this->p->get_options( 'og_def_country', 'none' );
367 $def_timezone = $this->p->get_options( 'og_def_timezone', 'UTC' );
368 $def_art_section = $this->p->get_options( 'schema_def_article_section', 'none' );
369 $def_adult_type = $this->p->get_options( 'schema_def_product_adult_type', 'none' );
370 $def_age_group = $this->p->get_options( 'schema_def_product_age_group', 'none' );
371 $def_product_cat = $this->p->get_options( 'schema_def_product_category', 'none' ); // Default Product Google Category.
372 $def_product_cond = $this->p->get_options( 'schema_def_product_condition', 'none' ); // Default Product Condition.
373 $def_product_mrp = $this->p->get_options( 'schema_def_product_mrp', 'none' ); // Default Product Return Policy.
374 $def_ener_eff_min = $this->p->get_options( 'schema_def_product_energy_efficiency_min', 'https://schema.org/EUEnergyEfficiencyCategoryD' );
375 $def_ener_eff_max = $this->p->get_options( 'schema_def_product_energy_efficiency_max', 'https://schema.org/EUEnergyEfficiencyCategoryA3Plus' );
376 $def_size_group_0 = $this->p->get_options( 'schema_def_product_size_group_0', 'none' );
377 $def_size_group_1 = $this->p->get_options( 'schema_def_product_size_group_1', 'none' );
378 $def_size_system = $this->p->get_options( 'schema_def_product_size_system', 'none' );
379 $def_target_gender = $this->p->get_options( 'schema_def_product_target_gender', 'none' );
380 $def_size_name = 'wpsso-opengraph';
381 $def_media_request = array( 'og_vid_upload' );
382 $def_media_info = $this->p->media->get_media_info( $def_size_name, $def_media_request, $mod, $md_pre = 'none' );
383
384 $md_defs = array(
385 'checksum' => '', // Checksum of plugin versions.
386 'opt_checksum' => '', // Checksum of option versions.
387 'opt_filtered' => 1, // Set before calling filters to prevent recursion.
388 'opt_versions' => array(),
389 'attach_img_crop_x' => 'none',
390 'attach_img_crop_y' => 'none',
391
392 /*
393 * Gravity View (Side Metabox).
394 */
395 'gv_id_title' => 0, // Title Field ID
396 'gv_id_desc' => 0, // Description Field ID
397 'gv_id_img' => 0, // Post Image Field ID
398
399 /*
400 * Edit General.
401 */
402 'primary_term_id' => $def_primary_term_id, // Primary Category.
403 'seo_title' => '', // SEO Title Tag.
404 'seo_desc' => '', // SEO Meta Description.
405 'og_type' => $def_og_type, // Open Graph Type.
406 'og_title' => '', // Social Title.
407 'og_desc' => '', // Social Description.
408 'pin_img_desc' => '', // Pinterest Description.
409 'tc_title' => '', // X (Twitter) Card Title.
410 'tc_desc' => '', // X (Twitter) Card Description.
411
412 /*
413 * Edit Media tab.
414 */
415 'og_img_max' => isset( $opts[ 'og_img_max' ] ) ? (int) $opts[ 'og_img_max' ] : 1,
416 'og_vid_max' => isset( $opts[ 'og_vid_max' ] ) ? (int) $opts[ 'og_vid_max' ] : 1,
417 'og_vid_prev_img' => empty( $opts[ 'og_vid_prev_img' ] ) ? 0 : 1,
418 'og_vid_autoplay' => empty( $opts[ 'og_vid_autoplay' ] ) ? 0 : 1,
419 'og_img_id' => '',
420 'og_img_id_lib' => 'wp',
421 'og_img_url' => '',
422 'og_vid_embed' => '',
423 'og_vid_url' => '',
424 'og_vid_title' => '',
425 'og_vid_desc' => '',
426 'og_vid_stream_url' => '',
427 'og_vid_width' => '',
428 'og_vid_height' => '',
429 'og_vid_upload_date' => $def_media_info[ 'og_vid_upload_date' ] ? $def_media_info[ 'og_vid_upload_date' ] : '',
430 'og_vid_upload_time' => $def_media_info[ 'og_vid_upload_time' ] ? $def_media_info[ 'og_vid_upload_time' ] : 'none',
431 'og_vid_upload_timezone' => $def_media_info[ 'og_vid_upload_timezone' ] ? $def_media_info[ 'og_vid_upload_timezone' ] : $def_timezone,
432 'pin_img_id' => '',
433 'pin_img_id_lib' => 'wp',
434 'pin_img_url' => '',
435 'tc_lrg_img_id' => '',
436 'tc_lrg_img_id_lib' => 'wp',
437 'tc_lrg_img_url' => '',
438 'tc_sum_img_id' => '',
439 'tc_sum_img_id_lib' => 'wp',
440 'tc_sum_img_url' => '',
441 'schema_img_id' => '',
442 'schema_img_id_lib' => 'wp',
443 'schema_img_url' => '',
444
445 /*
446 * Edit Visibility tab.
447 */
448 'canonical_url' => '', // Canonical URL.
449 'redirect_url' => '', // 301 Redirect URL.
450
451 /*
452 * Open Graph and Schema Product type.
453 *
454 * See WpssoOpengraph->add_data_og_type_md().
455 */
456 'product_category' => $def_product_cat, // Product Google Category.
457 'product_brand' => '',
458 'product_price' => '0.00', // Product Price.
459 'product_currency' => $def_currency,
460 'product_min_advert_price' => '0.00', // Product Min Advert Price.
461 'product_avail' => 'none', // Product Availability.
462 'product_condition' => $def_product_cond, // Product Condition.
463 'product_energy_efficiency' => 'none', // Product Energy Rating.
464 'product_energy_efficiency_min' => $def_ener_eff_min,
465 'product_energy_efficiency_max' => $def_ener_eff_max,
466 'product_material' => '',
467 'product_mrp' => $def_product_mrp, // Product Return Policy.
468 'product_pattern' => '',
469 'product_color' => '',
470 'product_target_gender' => $def_target_gender,
471 'product_size' => '',
472 'product_size_group_0' => $def_size_group_0,
473 'product_size_group_1' => $def_size_group_1,
474 'product_size_system' => $def_size_system,
475 'product_age_group' => $def_age_group,
476 'product_adult_type' => $def_adult_type,
477 'product_length_value' => '', // Product Net Len. / Depth.
478 'product_length_units' => $def_dimension_units,
479 'product_width_value' => '', // Product Net Width.
480 'product_width_units' => $def_dimension_units,
481 'product_height_value' => '', // Product Net Height.
482 'product_height_units' => $def_dimension_units,
483 'product_weight_value' => '', // Product Net Weight.
484 'product_weight_units' => $def_weight_units,
485 'product_fluid_volume_value' => '', // Product Fluid Volume.
486 'product_fluid_volume_units' => $def_fluid_vol_units,
487 'product_shipping_length_value' => '', // Product Shipping Length.
488 'product_shipping_length_units' => $def_dimension_units,
489 'product_shipping_width_value' => '', // Product Shipping Width.
490 'product_shipping_width_units' => $def_dimension_units,
491 'product_shipping_height_value' => '', // Product Shipping Height.
492 'product_shipping_height_units' => $def_dimension_units,
493 'product_shipping_weight_value' => '', // Product Shipping Weight.
494 'product_shipping_weight_units' => $def_weight_units,
495 'product_retailer_part_no' => '', // Product SKU.
496 'product_mfr_part_no' => '', // Product MPN.
497 'product_gtin14' => '', // Product GTIN-14.
498 'product_gtin13' => '', // Product GTIN-13 (EAN).
499 'product_gtin12' => '', // Product GTIN-12 (UPC).
500 'product_gtin8' => '', // Product GTIN-8.
501 'product_gtin' => '', // Product GTIN.
502 'product_isbn' => '', // Product ISBN.
503 'product_award_0' => '', // Product Award.
504 'product_award_1' => '', // Product Award.
505 'product_award_2' => '', // Product Award.
506 'product_award_3' => '', // Product Award.
507 'product_award_4' => '', // Product Award.
508
509 /*
510 * All Schema Types.
511 */
512 'schema_type' => $def_schema_type, // Schema Type.
513 'schema_title' => '', // Schema Name.
514 'schema_title_alt' => '', // Schema Alternate Name.
515 'schema_title_bc' => '', // Schema Breadcrumb Name.
516 'schema_desc' => '', // Schema Description.
517 'schema_addl_type_url_0' => '',
518 'schema_addl_type_url_1' => '',
519 'schema_addl_type_url_2' => '',
520 'schema_addl_type_url_3' => '',
521 'schema_addl_type_url_4' => '',
522 'schema_sameas_url_0' => '',
523 'schema_sameas_url_1' => '',
524 'schema_sameas_url_2' => '',
525 'schema_sameas_url_3' => '',
526 'schema_sameas_url_4' => '',
527
528 /*
529 * Schema Creative Work.
530 */
531 'schema_headline' => '', // Headline.
532 'schema_text' => '', // Full Text.
533 'schema_keywords_csv' => '', // Keywords.
534 'schema_lang' => $def_lang, // Language.
535 'schema_family_friendly' => $opts[ 'schema_def_family_friendly' ], // Family Friendly.
536 'schema_copyright_year' => '', // Copyright Year.
537 'schema_license_url' => '', // License URL.
538 'schema_pub_org_id' => $opts[ 'schema_def_pub_org_id' ], // Publisher Organization.
539 'schema_pub_person_id' => $opts[ 'schema_def_pub_person_id' ], // Publisher Person.
540 'schema_prov_org_id' => $opts[ 'schema_def_prov_org_id' ], // Provider Organization.
541 'schema_prov_person_id' => $opts[ 'schema_def_prov_person_id' ], // Provider Person.
542 'schema_fund_org_id' => $opts[ 'schema_def_fund_org_id' ], // Funder Organization.
543 'schema_fund_person_id' => $opts[ 'schema_def_fund_person_id' ], // Funder Person.
544 'schema_ispartof_url_0' => '', // Is Part of URLs.
545 'schema_ispartof_url_1' => '', // Is Part of URLs.
546 'schema_ispartof_url_2' => '', // Is Part of URLs.
547 'schema_award_0' => '', // Creative Work Awards.
548 'schema_award_1' => '', // Creative Work Awards.
549 'schema_award_2' => '', // Creative Work Awards.
550 'schema_award_3' => '', // Creative Work Awards.
551 'schema_award_4' => '', // Creative Work Awards.
552
553 /*
554 * See https://schema.org/citation.
555 *
556 * There is very little information available from Google about the expected JSON markup structure
557 * for citations - the only information available is from the the Google's Dataset type
558 * documentation.
559 *
560 * See https://developers.google.com/search/docs/appearance/structured-data/dataset.
561 */
562 'schema_citation_0' => '', // Reference Citations.
563 'schema_citation_1' => '', // Reference Citations.
564 'schema_citation_2' => '', // Reference Citations.
565 'schema_citation_3' => '', // Reference Citations.
566 'schema_citation_4' => '', // Reference Citations.
567
568 /*
569 * Schema Article.
570 */
571 'schema_article_section' => $def_art_section, // Article Section.
572 'schema_reading_mins' => $def_reading_mins, // Est. Reading Time.
573
574 /*
575 * Schema Book.
576 */
577 'schema_book_author_type' => $opts[ 'schema_def_book_author_type' ], // Book Author Type.
578 'schema_book_author_name' => '', // Book Author Name.
579 'schema_book_author_url' => '', // Book Author URL.
580 'schema_book_pub_date' => '', // Book Published Date (Y-m-d).
581 'schema_book_pub_time' => 'none', // Book Published Time (H:i).
582 'schema_book_pub_timezone' => $def_timezone, // Book Published Timezone.
583 'schema_book_created_date' => '', // Book Created Date (Y-m-d).
584 'schema_book_created_time' => 'none', // Book Created Time (H:i).
585 'schema_book_created_timezone' => $def_timezone, // Book Created Timezone.
586 'schema_book_edition' => '', // Book Edition.
587 'schema_book_format' => $opts[ 'schema_def_book_format' ], // Book Format.
588 'schema_book_pages' => '', // Book Pages.
589 'schema_book_isbn' => '', // Book ISBN.
590 'schema_book_offer_name_0' => '',
591 'schema_book_offer_name_1' => '',
592 'schema_book_offer_name_2' => '',
593 'schema_book_offer_name_3' => '',
594 'schema_book_offer_name_4' => '',
595 'schema_book_offer_price_0' => '',
596 'schema_book_offer_price_1' => '',
597 'schema_book_offer_price_2' => '',
598 'schema_book_offer_price_3' => '',
599 'schema_book_offer_price_4' => '',
600 'schema_book_offer_currency_0' => $def_currency,
601 'schema_book_offer_currency_1' => $def_currency,
602 'schema_book_offer_currency_2' => $def_currency,
603 'schema_book_offer_currency_3' => $def_currency,
604 'schema_book_offer_currency_4' => $def_currency,
605 'schema_book_offer_avail_0' => 'https://schema.org/InStock',
606 'schema_book_offer_avail_1' => 'https://schema.org/InStock',
607 'schema_book_offer_avail_2' => 'https://schema.org/InStock',
608 'schema_book_offer_avail_3' => 'https://schema.org/InStock',
609 'schema_book_offer_avail_4' => 'https://schema.org/InStock',
610
611 /*
612 * Schema Book > Audiobook.
613 */
614 'schema_book_audio_duration_days' => 0, // Audiobook Duration (Days).
615 'schema_book_audio_duration_hours' => 0, // Audiobook Duration (Hours).
616 'schema_book_audio_duration_mins' => 0, // Audiobook Duration (Mins).
617 'schema_book_audio_duration_secs' => 0, // Audiobook Duration (Secs).
618
619 /*
620 * Schema Event.
621 */
622 'schema_event_lang' => $def_lang, // Event Language.
623 'schema_event_attendance' => $opts[ 'schema_def_event_attendance' ], // Event Attendance.
624 'schema_event_online_url' => '', // Event Online URL.
625 'schema_event_location_id' => $opts[ 'schema_def_event_location_id' ], // Event Venue.
626 'schema_event_performer_org_id' => $opts[ 'schema_def_event_performer_org_id' ], // Performer Organization.
627 'schema_event_performer_person_id' => $opts[ 'schema_def_event_performer_person_id' ], // Performer Person.
628 'schema_event_organizer_org_id' => $opts[ 'schema_def_event_organizer_org_id' ], // Organizer Organization.
629 'schema_event_organizer_person_id' => $opts[ 'schema_def_event_organizer_person_id' ], // Organizer Person.
630 'schema_event_fund_org_id' => $opts[ 'schema_def_event_fund_org_id' ], // Funder Organization.
631 'schema_event_fund_person_id' => $opts[ 'schema_def_event_fund_person_id' ], // Funder Person.
632 'schema_event_status' => 'https://schema.org/EventScheduled', // Event Status.
633 'schema_event_start_date' => '', // Event Start Date (Y-m-d).
634 'schema_event_start_time' => 'none', // Event Start Time (H:i).
635 'schema_event_start_timezone' => $def_timezone, // Event Start Timezone.
636 'schema_event_end_date' => '', // Event End Date (Y-m-d).
637 'schema_event_end_time' => 'none', // Event End Time (H:i).
638 'schema_event_end_timezone' => $def_timezone, // Event End Timezone.
639 'schema_event_previous_date' => '', // Event Previous Start Date (Y-m-d).
640 'schema_event_previous_time' => 'none', // Event Previous Start Time (H:i).
641 'schema_event_previous_timezone' => $def_timezone, // Event Previous Start Timezone.
642 'schema_event_offer_name_0' => '',
643 'schema_event_offer_name_1' => '',
644 'schema_event_offer_name_2' => '',
645 'schema_event_offer_name_3' => '',
646 'schema_event_offer_name_4' => '',
647 'schema_event_offer_price_0' => '',
648 'schema_event_offer_price_1' => '',
649 'schema_event_offer_price_2' => '',
650 'schema_event_offer_price_3' => '',
651 'schema_event_offer_price_4' => '',
652 'schema_event_offer_currency_0' => $def_currency,
653 'schema_event_offer_currency_1' => $def_currency,
654 'schema_event_offer_currency_2' => $def_currency,
655 'schema_event_offer_currency_3' => $def_currency,
656 'schema_event_offer_currency_4' => $def_currency,
657 'schema_event_offer_avail_0' => 'https://schema.org/InStock',
658 'schema_event_offer_avail_1' => 'https://schema.org/InStock',
659 'schema_event_offer_avail_2' => 'https://schema.org/InStock',
660 'schema_event_offer_avail_3' => 'https://schema.org/InStock',
661 'schema_event_offer_avail_4' => 'https://schema.org/InStock',
662 'schema_event_offers_start_date' => '', // Event Offers Start Date (Y-m-d).
663 'schema_event_offers_start_time' => 'none', // Event Offers Start Time (H:i).
664 'schema_event_offers_start_timezone' => $def_timezone, // Event Offers Start Timezone.
665 'schema_event_offers_end_date' => '', // Event Offers End Date (Y-m-d).
666 'schema_event_offers_end_time' => 'none', // Event Offers End Time (H:i).
667 'schema_event_offers_end_timezone' => $def_timezone, // Event Offers End Timezone.
668
669 /*
670 * Schema How-To.
671 */
672 'schema_howto_yield' => '', // How-To Yield.
673 'schema_howto_prep_days' => 0, // How-To Preparation Time (Days).
674 'schema_howto_prep_hours' => 0, // How-To Preparation Time (Hours).
675 'schema_howto_prep_mins' => 0, // How-To Preparation Time (Mins).
676 'schema_howto_prep_secs' => 0, // How-To Preparation Time (Secs).
677 'schema_howto_total_days' => 0, // How-To Total Time (Days).
678 'schema_howto_total_hours' => 0, // How-To Total Time (Hours).
679 'schema_howto_total_mins' => 0, // How-To Total Time (Mins).
680 'schema_howto_total_secs' => 0, // How-To Total Time (Secs).
681 'schema_howto_supply_0' => '',
682 'schema_howto_supply_1' => '',
683 'schema_howto_supply_2' => '',
684 'schema_howto_step_section_0' => 0, // How-To Step or Section (0 or 1).
685 'schema_howto_step_section_1' => 0, // How-To Step or Section (0 or 1).
686 'schema_howto_step_section_2' => 0, // How-To Step or Section (0 or 1).
687 'schema_howto_step_0' => '',
688 'schema_howto_step_1' => '',
689 'schema_howto_step_2' => '',
690 'schema_howto_step_text_0' => '',
691 'schema_howto_step_text_1' => '',
692 'schema_howto_step_text_2' => '',
693 'schema_howto_step_text_3' => '',
694 'schema_howto_step_text_4' => '',
695 'schema_howto_step_image_id_0' => '',
696 'schema_howto_step_image_id_1' => '',
697 'schema_howto_step_image_id_2' => '',
698 'schema_howto_step_image_id_3' => '',
699 'schema_howto_step_image_id_4' => '',
700
701 /*
702 * Schema JobPosting.
703 */
704 'schema_job_title' => '', // Job Title.
705 'schema_job_hiring_org_id' => $opts[ 'schema_def_job_hiring_org_id' ], // Job Hiring Organization.
706 'schema_job_location_id' => $opts[ 'schema_def_job_location_id' ], // Job Location.
707 'schema_job_location_type' => $opts[ 'schema_def_job_location_type' ], // Job Location Type.
708 'schema_job_salary' => '', // Job Base Salary.
709 'schema_job_salary_currency' => $def_currency, // Job Base Salary Currency.
710 'schema_job_salary_period' => 'year', // Job Base Salary per Year, Month, Week, Hour.
711 'schema_job_empl_type_FULL_TIME' => 0, // Job Employment Type.
712 'schema_job_empl_type_PART_TIME' => 0,
713 'schema_job_empl_type_CONTRACTOR' => 0,
714 'schema_job_empl_type_TEMPORARY' => 0,
715 'schema_job_empl_type_INTERN' => 0,
716 'schema_job_empl_type_VOLUNTEER' => 0,
717 'schema_job_empl_type_PER_DIEM' => 0,
718 'schema_job_empl_type_OTHER' => 0,
719 'schema_job_expire_date' => '', // Job Posting Expires Date (Y-m-d).
720 'schema_job_expire_time' => 'none', // Job Posting Expires Time (H:i).
721 'schema_job_expire_timezone' => $def_timezone, // Job Posting Expires Timezone.
722
723 /*
724 * Schema CreativeWork > LearningResource.
725 */
726 'schema_learnres_educational_level' => 'none', // Educational Level.
727 'schema_learnres_resource_type' => '', // Resource Type.
728
729 /*
730 * Schema CreativeWork > Movie.
731 */
732 'schema_movie_actor_person_name_0' => '',
733 'schema_movie_actor_person_name_1' => '',
734 'schema_movie_actor_person_name_2' => '',
735 'schema_movie_actor_person_name_3' => '',
736 'schema_movie_actor_person_name_4' => '',
737 'schema_movie_director_person_name_0' => '',
738 'schema_movie_director_person_name_1' => '',
739 'schema_movie_director_person_name_2' => '',
740 'schema_movie_director_person_name_3' => '',
741 'schema_movie_director_person_name_4' => '',
742 'schema_movie_prodco_org_id' => 'none', // Movie Production Company.
743 'schema_movie_released_date' => '', // Movie Release Date (Y-m-d).
744 'schema_movie_released_time' => 'none', // Movie Release Time (H:i).
745 'schema_movie_released_timezone' => $def_timezone, // Movie Release Timezone.
746 'schema_movie_duration_days' => 0, // Movie Runtime (Days).
747 'schema_movie_duration_hours' => 0, // Movie Runtime (Hours).
748 'schema_movie_duration_mins' => 0, // Movie Runtime (Mins).
749 'schema_movie_duration_secs' => 0, // Movie Runtime (Secs).
750
751 /*
752 * Schema Organization.
753 */
754 'schema_organization_id' => 'none', // Organization.
755
756 /*
757 * Schema Person.
758 */
759 'schema_person_id' => 'none', // Person.
760
761 /*
762 * Schema Place.
763 */
764 'schema_place_id' => 'none', // Place.
765
766 /*
767 * Schema Profile Page.
768 */
769 'schema_profile_person_id' => 'none', // Person.
770
771 /*
772 * Schema QA Page.
773 */
774 'schema_qa_desc' => '', // QA Heading.
775
776 /*
777 * Schema Recipe.
778 */
779 'schema_recipe_cuisine' => '', // Recipe Cuisine.
780 'schema_recipe_course' => '', // Recipe Course.
781 'schema_recipe_yield' => '', // Recipe Yield.
782 'schema_recipe_cook_method' => '', // Recipe Cooking Method.
783 'schema_recipe_prep_days' => 0, // Recipe Preparation Time (Days).
784 'schema_recipe_prep_hours' => 0, // Recipe Preparation Time (Hours).
785 'schema_recipe_prep_mins' => 0, // Recipe Preparation Time (Mins).
786 'schema_recipe_prep_secs' => 0, // Recipe Preparation Time (Secs).
787 'schema_recipe_cook_days' => 0, // Recipe Cooking Time (Days).
788 'schema_recipe_cook_hours' => 0, // Recipe Cooking Time (Hours).
789 'schema_recipe_cook_mins' => 0, // Recipe Cooking Time (Mins).
790 'schema_recipe_cook_secs' => 0, // Recipe Cooking Time (Secs).
791 'schema_recipe_total_days' => 0, // Recipe Total Time (Days).
792 'schema_recipe_total_hours' => 0, // Recipe Total Time (Hours).
793 'schema_recipe_total_mins' => 0, // Recipe Total Time (Mins).
794 'schema_recipe_total_secs' => 0, // Recipe Total Time (Secs).
795 'schema_recipe_ingredient_0' => '',
796 'schema_recipe_ingredient_1' => '',
797 'schema_recipe_ingredient_2' => '',
798 'schema_recipe_ingredient_3' => '',
799 'schema_recipe_ingredient_4' => '',
800 'schema_recipe_instruction_section_0' => 0, // Recipe Instruction or Section (0 or 1).
801 'schema_recipe_instruction_section_1' => 0, // Recipe Instruction or Section (0 or 1).
802 'schema_recipe_instruction_section_2' => 0, // Recipe Instruction or Section (0 or 1).
803 'schema_recipe_instruction_section_3' => 0, // Recipe Instruction or Section (0 or 1).
804 'schema_recipe_instruction_section_4' => 0, // Recipe Instruction or Section (0 or 1).
805 'schema_recipe_instruction_0' => '',
806 'schema_recipe_instruction_1' => '',
807 'schema_recipe_instruction_2' => '',
808 'schema_recipe_instruction_3' => '',
809 'schema_recipe_instruction_4' => '',
810 'schema_recipe_instruction_text_0' => '',
811 'schema_recipe_instruction_text_1' => '',
812 'schema_recipe_instruction_text_2' => '',
813 'schema_recipe_instruction_text_3' => '',
814 'schema_recipe_instruction_text_4' => '',
815 'schema_recipe_instruction_img_id_0' => '',
816 'schema_recipe_instruction_img_id_1' => '',
817 'schema_recipe_instruction_img_id_2' => '',
818 'schema_recipe_instruction_img_id_3' => '',
819 'schema_recipe_instruction_img_id_4' => '',
820 'schema_recipe_instruction_img_lib_0' => '',
821 'schema_recipe_instruction_img_lib_1' => '',
822 'schema_recipe_instruction_img_lib_2' => '',
823 'schema_recipe_instruction_img_lib_3' => '',
824 'schema_recipe_instruction_img_lib_4' => '',
825
826 /*
827 * Schema Recipe - Nutrition Information.
828 */
829 'schema_recipe_nutri_serv' => '', // Serving Size.
830 'schema_recipe_nutri_cal' => '', // Calories.
831 'schema_recipe_nutri_prot' => '', // Protein.
832 'schema_recipe_nutri_fib' => '', // Fiber.
833 'schema_recipe_nutri_carb' => '', // Carbohydrates.
834 'schema_recipe_nutri_sugar' => '', // Sugar.
835 'schema_recipe_nutri_sod' => '', // Sodium.
836 'schema_recipe_nutri_fat' => '', // Fat.
837 'schema_recipe_nutri_trans_fat' => '', // Trans Fat.
838 'schema_recipe_nutri_sat_fat' => '', // Saturated Fat.
839 'schema_recipe_nutri_unsat_fat' => '', // Unsaturated Fat.
840 'schema_recipe_nutri_chol' => '', // Cholesterol.
841
842 /*
843 * Schema Review.
844 */
845 'schema_review_rating' => '0.0', // Review Rating.
846 'schema_review_rating_min' => $opts[ 'schema_def_review_rating_min' ], // Review Rating From.
847 'schema_review_rating_max' => $opts[ 'schema_def_review_rating_max' ], // Review Rating To.
848 'schema_review_rating_alt_name' => '', // Rating Alt Name.
849
850 /*
851 * Schema Review Subject (aka Item Reviewed).
852 */
853 'schema_review_item_name' => '', // Subject Name.
854 'schema_review_item_desc' => '', // Subject Description.
855 'schema_review_item_img_id' => '',
856 'schema_review_item_img_url' => '',
857 'schema_review_item_url' => '', // Subject Webpage URL.
858 'schema_review_item_type' => $opts[ 'schema_def_review_item_type' ], // Subject Schema Type.
859 'schema_review_item_sameas_url_0' => '',
860 'schema_review_item_sameas_url_1' => '',
861 'schema_review_item_sameas_url_2' => '',
862 'schema_review_item_sameas_url_3' => '',
863 'schema_review_item_sameas_url_4' => '',
864
865 /*
866 * Schema Review Subject: Creative Work.
867 */
868 'schema_review_item_cw_author_type' => 'none', // Subject Author Type.
869 'schema_review_item_cw_author_name' => '', // Subject Author Name.
870 'schema_review_item_cw_author_url' => '', // Subject Author URL.
871 'schema_review_item_cw_pub_date' => '', // Subject Published Date (Y-m-d).
872 'schema_review_item_cw_pub_time' => 'none', // Subject Published Time (H:i).
873 'schema_review_item_cw_pub_timezone' => $def_timezone, // Subject Published Timezone.
874 'schema_review_item_cw_created_date' => '', // Subject Created Date (Y-m-d).
875 'schema_review_item_cw_created_time' => 'none', // Subject Created Time (H:i).
876 'schema_review_item_cw_created_timezone' => $def_timezone, // Subject Created Timezone.
877
878 /*
879 * Schema Review Subject: Creative Work > Book.
880 */
881 'schema_review_item_cw_book_isbn' => '', // Subject Book ISBN.
882
883 /*
884 * Schema Review Subject: Creative Work > Movie.
885 */
886 'schema_review_item_cw_movie_actor_person_name_0' => '',
887 'schema_review_item_cw_movie_actor_person_name_1' => '',
888 'schema_review_item_cw_movie_actor_person_name_2' => '',
889 'schema_review_item_cw_movie_actor_person_name_3' => '',
890 'schema_review_item_cw_movie_actor_person_name_4' => '',
891 'schema_review_item_cw_movie_director_person_name_0' => '',
892 'schema_review_item_cw_movie_director_person_name_1' => '',
893 'schema_review_item_cw_movie_director_person_name_2' => '',
894 'schema_review_item_cw_movie_director_person_name_3' => '',
895 'schema_review_item_cw_movie_director_person_name_4' => '',
896
897 /*
898 * Schema Review Subject: Place.
899 */
900 'schema_review_item_place_street_address' => '',
901 'schema_review_item_place_po_box_number' => '',
902 'schema_review_item_place_city' => '',
903 'schema_review_item_place_region' => '',
904 'schema_review_item_place_postal_code' => '',
905 'schema_review_item_place_country' => $def_country,
906
907 /*
908 * Schema Review Subject: Place > Local Business.
909 */
910 'schema_review_item_place_price_range' => '',
911
912 /*
913 * Schema Review Subject: Place > Food Establishment.
914 */
915 'schema_review_item_place_cuisine' => '',
916
917 /*
918 * Schema Review Subject: Product.
919 */
920 'schema_review_item_product_brand' => '', // Product Brand.
921 'schema_review_item_product_offer_name_0' => '',
922 'schema_review_item_product_offer_name_1' => '',
923 'schema_review_item_product_offer_name_2' => '',
924 'schema_review_item_product_offer_name_3' => '',
925 'schema_review_item_product_offer_name_4' => '',
926 'schema_review_item_product_offer_price_0' => '',
927 'schema_review_item_product_offer_price_1' => '',
928 'schema_review_item_product_offer_price_2' => '',
929 'schema_review_item_product_offer_price_3' => '',
930 'schema_review_item_product_offer_price_4' => '',
931 'schema_review_item_product_offer_currency_0' => $def_currency,
932 'schema_review_item_product_offer_currency_1' => $def_currency,
933 'schema_review_item_product_offer_currency_2' => $def_currency,
934 'schema_review_item_product_offer_currency_3' => $def_currency,
935 'schema_review_item_product_offer_currency_4' => $def_currency,
936 'schema_review_item_product_offer_avail_0' => 'https://schema.org/InStock',
937 'schema_review_item_product_offer_avail_1' => 'https://schema.org/InStock',
938 'schema_review_item_product_offer_avail_2' => 'https://schema.org/InStock',
939 'schema_review_item_product_offer_avail_3' => 'https://schema.org/InStock',
940 'schema_review_item_product_offer_avail_4' => 'https://schema.org/InStock',
941 'schema_review_item_product_retailer_part_no' => '', // Product SKU.
942 'schema_review_item_product_mfr_part_no' => '', // Product MPN.
943
944 /*
945 * Schema Review Subject: Software Application.
946 */
947 'schema_review_item_software_app_cat' => '', // Application Category.
948 'schema_review_item_software_app_os' => '', // Operating System.
949 'schema_review_item_software_app_dl_url' => '', // Download URL.
950 'schema_review_item_software_app_offer_name_0' => '',
951 'schema_review_item_software_app_offer_name_1' => '',
952 'schema_review_item_software_app_offer_name_2' => '',
953 'schema_review_item_software_app_offer_name_3' => '',
954 'schema_review_item_software_app_offer_name_4' => '',
955 'schema_review_item_software_app_offer_price_0' => '',
956 'schema_review_item_software_app_offer_price_1' => '',
957 'schema_review_item_software_app_offer_price_2' => '',
958 'schema_review_item_software_app_offer_price_3' => '',
959 'schema_review_item_software_app_offer_price_4' => '',
960 'schema_review_item_software_app_offer_currency_0' => $def_currency,
961 'schema_review_item_software_app_offer_currency_1' => $def_currency,
962 'schema_review_item_software_app_offer_currency_2' => $def_currency,
963 'schema_review_item_software_app_offer_currency_3' => $def_currency,
964 'schema_review_item_software_app_offer_currency_4' => $def_currency,
965 'schema_review_item_software_app_offer_avail_0' => 'https://schema.org/InStock',
966 'schema_review_item_software_app_offer_avail_1' => 'https://schema.org/InStock',
967 'schema_review_item_software_app_offer_avail_2' => 'https://schema.org/InStock',
968 'schema_review_item_software_app_offer_avail_3' => 'https://schema.org/InStock',
969 'schema_review_item_software_app_offer_avail_4' => 'https://schema.org/InStock',
970
971 /*
972 * Schema Claim Review.
973 */
974 'schema_review_claim_reviewed' => '', // Short Summary of Claim.
975 'schema_review_claim_first_url' => '', // First Appearance URL.
976
977 /*
978 * Schema Service.
979 */
980 'schema_service_prov_org_id' => $opts[ 'schema_def_service_prov_org_id' ], // Provider Organization.
981 'schema_service_prov_person_id' => $opts[ 'schema_def_service_prov_person_id' ], // Provider Person.
982 'schema_service_award_0' => '', // Service Award.
983 'schema_service_award_1' => '', // Service Award.
984 'schema_service_award_2' => '', // Service Award.
985 'schema_service_award_3' => '', // Service Award.
986 'schema_service_award_4' => '', // Service Award.
987 'schema_service_offer_name_0' => '',
988 'schema_service_offer_name_1' => '',
989 'schema_service_offer_name_2' => '',
990 'schema_service_offer_name_3' => '',
991 'schema_service_offer_name_4' => '',
992 'schema_service_offer_price_0' => '',
993 'schema_service_offer_price_1' => '',
994 'schema_service_offer_price_2' => '',
995 'schema_service_offer_price_3' => '',
996 'schema_service_offer_price_4' => '',
997 'schema_service_offer_currency_0' => $def_currency,
998 'schema_service_offer_currency_1' => $def_currency,
999 'schema_service_offer_currency_2' => $def_currency,
1000 'schema_service_offer_currency_3' => $def_currency,
1001 'schema_service_offer_currency_4' => $def_currency,
1002 'schema_service_offer_avail_0' => 'https://schema.org/InStock',
1003 'schema_service_offer_avail_1' => 'https://schema.org/InStock',
1004 'schema_service_offer_avail_2' => 'https://schema.org/InStock',
1005 'schema_service_offer_avail_3' => 'https://schema.org/InStock',
1006 'schema_service_offer_avail_4' => 'https://schema.org/InStock',
1007 'schema_service_offer_catalog_0' => '', // Offer Catalog Name.
1008 'schema_service_offer_catalog_1' => '', // Offer Catalog Name.
1009 'schema_service_offer_catalog_2' => '', // Offer Catalog Name.
1010 'schema_service_offer_catalog_3' => '', // Offer Catalog Name.
1011 'schema_service_offer_catalog_4' => '', // Offer Catalog Name.
1012 'schema_service_offer_catalog_text_0' => '', // Offer Catalog Description.
1013 'schema_service_offer_catalog_text_1' => '', // Offer Catalog Description.
1014 'schema_service_offer_catalog_text_2' => '', // Offer Catalog Description.
1015 'schema_service_offer_catalog_text_3' => '', // Offer Catalog Description.
1016 'schema_service_offer_catalog_text_4' => '', // Offer Catalog Description.
1017 'schema_service_offer_catalog_url_0' => '', // Offer Catalog URL.
1018 'schema_service_offer_catalog_url_1' => '', // Offer Catalog URL.
1019 'schema_service_offer_catalog_url_2' => '', // Offer Catalog URL.
1020 'schema_service_offer_catalog_url_3' => '', // Offer Catalog URL.
1021 'schema_service_offer_catalog_url_4' => '', // Offer Catalog URL.
1022 'schema_service_latitude' => '', // Service Latitude.
1023 'schema_service_longitude' => '', // Service Longitude.
1024 'schema_service_radius' => '', // Service Radius.
1025 'schema_service_area_id_0' => 'none', // Service Areas.
1026 'schema_service_area_id_1' => 'none', // Service Areas.
1027 'schema_service_area_id_2' => 'none', // Service Areas.
1028 'schema_service_area_id_3' => 'none', // Service Areas.
1029 'schema_service_area_id_4' => 'none', // Service Areas.
1030
1031 /*
1032 * Schema Software Application.
1033 */
1034 'schema_software_app_cat' => '', // Application Category.
1035 'schema_software_app_os' => '', // Operating System.
1036 'schema_software_app_dl_url' => '', // Download URL.
1037
1038 /*
1039 * Schema WebPage.
1040 */
1041 'schema_webpage_reviewed_by_org_id_0' => '',
1042 'schema_webpage_reviewed_by_org_id_1' => '',
1043 'schema_webpage_reviewed_by_org_id_2' => '',
1044 'schema_webpage_reviewed_by_org_id_3' => '',
1045 'schema_webpage_reviewed_by_org_id_4' => '',
1046 'schema_webpage_reviewed_by_person_id_0' => '',
1047 'schema_webpage_reviewed_by_person_id_1' => '',
1048 'schema_webpage_reviewed_by_person_id_2' => '',
1049 'schema_webpage_reviewed_by_person_id_3' => '',
1050 'schema_webpage_reviewed_by_person_id_4' => '',
1051 'schema_webpage_reviewed_last_date' => '', // Reviewed Last Date (Y-m-d).
1052 'schema_webpage_reviewed_last_time' => 'none', // Reviewed Last Time (H:i).
1053 'schema_webpage_reviewed_last_timezone' => $def_timezone, // Reviewed Last Timezone.
1054 );
1055
1056 /*
1057 * Set the reviewed by options.
1058 */
1059 $reviewed_by_max = SucomUtil::get_const( 'WPSSO_SCHEMA_WEBPAGE_REVIEWED_BY_MAX' );
1060
1061 foreach ( range( 0, $reviewed_by_max - 1 ) as $num ) {
1062
1063 $md_defs[ 'schema_webpage_reviewed_by_org_id_' . $num ] = 'none';
1064 $md_defs[ 'schema_webpage_reviewed_by_person_id_' . $num ] = 'none';
1065 }
1066
1067 /*
1068 * See https://developers.google.com/search/reference/robots_meta_tag.
1069 */
1070 $directives = SucomUtilRobots::get_default_directives();
1071
1072 foreach ( $directives as $directive_key => $directive_value ) {
1073
1074 $opt_key = str_replace( '-', '_', 'robots_' . $directive_key ); // Convert dashes to underscores.
1075
1076 /*
1077 * Use a default value from the plugin settings, if one exists.
1078 */
1079 if ( isset( $this->p->options[ $opt_key ] ) ) {
1080
1081 $md_defs[ $opt_key ] = $this->p->options[ $opt_key ];
1082
1083 /*
1084 * Save as a checkbox:
1085 *
1086 * 'robots_noarchive'
1087 * 'robots_nofollow'
1088 * 'robots_noimageindex'
1089 * 'robots_noindex'
1090 * 'robots_nosnippet'
1091 * 'robots_notranslate'
1092 */
1093 } elseif ( 0 === strpos( $directive_key, 'no' ) && is_bool( $directive_value ) ) {
1094
1095 $md_defs[ $opt_key ] = $directive_value ? 1 : 0;
1096
1097 /*
1098 * Save the directive value:
1099 *
1100 * 'robots_max_snippet'
1101 * 'robots_max_image_preview'
1102 * 'robots_max_video_preview'
1103 */
1104 } elseif ( 0 === strpos( $directive_key, 'max' ) ) {
1105
1106 $md_defs[ $opt_key ] = $directive_value;
1107 }
1108 }
1109
1110 /*
1111 * Overwrite the default options with any custom options from the parent.
1112 */
1113 if ( $this->p->debug->enabled ) {
1114
1115 $this->p->debug->log( 'inheriting parent metadata options for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1116 }
1117
1118 $parent_opts = $this->get_inherited_md_opts( $mod );
1119
1120 if ( ! empty( $parent_opts ) ) {
1121
1122 $md_defs = SucomUtil::array_merge_recursive_distinct( $md_defs, $parent_opts );
1123 }
1124
1125 /*
1126 * The 'import_custom_fields' filter is executed before the 'wpsso_get_md_options' and
1127 * 'wpsso_get_post_options' filters, custom field values may get overwritten by these filters.
1128 *
1129 * The 'import_custom_fields' filter is also executed before the 'wpsso_get_md_defaults' and
1130 * 'wpsso_get_post_defaults' filters, so submitted form values that are identical to their defaults
1131 * can be removed before saving the options array.
1132 *
1133 * See WpssoPost->get_options().
1134 * See WpssoAbstractWpMeta->get_defaults().
1135 * See WpssoUtilCustomFields->filter_import_custom_fields().
1136 * See WpssoIntegEcomWooCommerce->add_mt_product().
1137 * See WpssoIntegEcomWooAddGtin->filter_wc_variation_alt_options().
1138 */
1139 if ( 'post' === $mod[ 'name' ] ) {
1140
1141 if ( $this->p->debug->enabled ) {
1142
1143 $this->p->debug->log( 'applying filters "wpsso_import_custom_fields" for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1144 }
1145
1146 $md_defs = apply_filters( 'wpsso_import_custom_fields', $md_defs, $mod, get_metadata( $mod[ 'name' ], $mod[ 'id' ] ) );
1147
1148 /*
1149 * Since WPSSO Core v14.2.0.
1150 *
1151 * See WpssoIntegEcomWooCommerce->add_mt_product().
1152 */
1153 if ( $this->p->debug->enabled ) {
1154
1155 $this->p->debug->log( 'applying filters "wpsso_import_product_attributes" for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1156 }
1157
1158 $md_defs = apply_filters( 'wpsso_import_product_attributes', $md_defs, $mod, $mod[ 'wp_obj' ] );
1159 }
1160
1161 if ( $this->p->debug->enabled ) {
1162
1163 $this->p->debug->log( 'applying filters "wpsso_get_md_defaults" for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1164 }
1165
1166 $md_defs = apply_filters( 'wpsso_get_md_defaults', $md_defs, $mod );
1167
1168 if ( ! empty( $mod[ 'name' ] ) ) {
1169
1170 /*
1171 * Example $filter_name = 'wpsso_get_post_defaults'.
1172 */
1173 $filter_name = SucomUtil::sanitize_hookname( 'wpsso_get_' . $mod[ 'name' ] . '_defaults' );
1174
1175 if ( $this->p->debug->enabled ) {
1176
1177 $this->p->debug->log( 'applying filters "' . $filter_name . '" for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1178 }
1179
1180 $md_defs = apply_filters( $filter_name, $md_defs, $mod[ 'id' ], $mod );
1181 }
1182
1183 if ( $this->p->debug->enabled ) {
1184
1185 $this->p->debug->log( 'applying filters "wpsso_sanitize_md_defaults" for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1186 }
1187
1188 $md_defs = apply_filters( 'wpsso_sanitize_md_defaults', $md_defs, $mod );
1189
1190 } elseif ( $this->p->debug->enabled ) {
1191
1192 $this->p->debug->log( 'skipped filters: opt_filtered is true' );
1193 }
1194
1195 if ( false !== $md_key ) {
1196
1197 if ( isset( $md_defs[ $md_key ] ) ) {
1198
1199 return $md_defs[ $md_key ];
1200 }
1201
1202 return null;
1203 }
1204
1205 return $md_defs;
1206 }
1207
1208 public function get_options( $obj_id, $md_key = false, $filter_opts = true, $merge_defs = false ) {
1209
1210 $ret_val = false === $md_key ? array() : null; // Allow for $md_key = 0.
1211
1212 return self::must_be_extended( $ret_val );
1213 }
1214
1215 /*
1216 * See WpssoComment->get_options().
1217 * See WpssoPost->get_options().
1218 * See WpssoTerm->get_options().
1219 * See WpssoUser->get_options().
1220 */
1221 protected function upgrade_options( $md_opts, $obj_id ) {
1222
1223 if ( $this->p->debug->enabled ) {
1224
1225 $this->p->debug->log( 'upgrading metadata options' );
1226 }
1227
1228 if ( ! is_object( $this->upgrade ) ) {
1229
1230 require_once WPSSO_PLUGINDIR . 'lib/upgrade-meta.php';
1231
1232 $this->upgrade = new WpssoUpgradeMeta( $this->p );
1233 }
1234
1235 $mod = $this->get_mod( $obj_id );
1236
1237 $md_opts = $this->upgrade->md_options( $md_opts, $mod );
1238
1239 return $md_opts;
1240 }
1241
1242 /*
1243 * Do not pass $md_opts by reference as the options array may get merged with default values.
1244 */
1245 protected function return_options( $obj_id, array $md_opts, $md_key = false, $merge_defs = false ) {
1246
1247 /*
1248 * If a multilingual plugin is available, trust the plugin and ignore previous / inherited custom language values.
1249 */
1250 if ( $this->p->avail[ 'lang' ][ 'any' ] ) {
1251
1252 unset( $md_opts[ 'schema_lang' ] );
1253 }
1254
1255 if ( $merge_defs ) {
1256
1257 $md_opts = $this->merge_defaults( $obj_id, $md_opts );
1258 }
1259
1260 if ( false !== $md_key ) {
1261
1262 if ( ! isset( $md_opts[ $md_key ] ) || '' === $md_opts[ $md_key ] ) {
1263
1264 if ( $this->p->debug->enabled ) {
1265
1266 $this->p->debug->log( 'returning null value: ' . $md_key . ' not set or empty string' );
1267 }
1268
1269 return null;
1270 }
1271
1272 if ( $this->p->debug->enabled ) {
1273
1274 $this->p->debug->log( 'returning meta value: ' . $md_key . ' = ' . $md_opts[ $md_key ] );
1275 }
1276
1277 return $md_opts[ $md_key ];
1278 }
1279
1280 return $md_opts;
1281 }
1282
1283 protected function merge_defaults( $obj_id, array $md_opts ) {
1284
1285 if ( empty( $md_opts[ 'options_merged' ] ) ) {
1286
1287 $md_defs = $this->get_defaults( $obj_id ); // Uses a local cache.
1288
1289 if ( is_array( $md_defs ) ) { // Just in case.
1290
1291 foreach ( $md_defs as $md_defs_key => $md_defs_val ) {
1292
1293 if ( ! isset( $md_opts[ $md_defs_key ] ) && '' !== $md_defs_val ) {
1294
1295 $md_opts[ $md_defs_key ] = $md_defs[ $md_defs_key ];
1296 }
1297 }
1298 }
1299
1300 $md_opts[ 'options_merged' ] = true;
1301 }
1302
1303 return $md_opts;
1304 }
1305
1306 /*
1307 * Extended by WpssoPost->save_options( $post_id, $rel = false );
1308 * Extended by WpssoTerm->save_options( $term_id, $term_tax_id = false );
1309 * Extended by WpssoUser->save_options( $user_id, $rel = false );
1310 */
1311 public function save_options( $obj_id, $rel = false ) {
1312
1313 return self::must_be_extended( $ret_val = false );
1314 }
1315
1316 /*
1317 * Extended by WpssoPost->delete_options( $post_id, $rel = false );
1318 * Extended by WpssoTerm->delete_options( $term_id, $term_tax_id = false );
1319 * Extended by WpssoUser->delete_options( $user_id, $rel = false );
1320 */
1321 public function delete_options( $obj_id, $rel = false ) {
1322
1323 return self::must_be_extended( $ret_val = false );
1324 }
1325
1326 /*
1327 * Get all publicly accessible post, term, or user IDs.
1328 */
1329 public static function get_public_ids( array $args = array() ) {
1330
1331 return self::must_be_extended( $ret_val = array() );
1332 }
1333
1334 /*
1335 * Return an array of post mods for a given $mod object.
1336 *
1337 * If 'posts_per_page' is not set for an archive page, it will be set to the WordPress settings value.
1338 *
1339 * See WpssoPage->get_posts_mods().
1340 */
1341 public function get_posts_mods( array $mod ) {
1342
1343 $posts_mods = array();
1344
1345 if ( $mod[ 'is_archive' ] ) {
1346
1347 if ( ! isset( $mod[ 'posts_args' ][ 'posts_per_page' ] ) ) {
1348
1349 $mod[ 'posts_args' ][ 'posts_per_page' ] = get_option( 'posts_per_page' );
1350 }
1351 }
1352
1353 if ( $this->p->debug->enabled ) {
1354
1355 if ( isset( $mod[ 'posts_args' ][ 'posts_per_page' ] ) ) {
1356
1357 $this->p->debug->log( 'using posts_per_page = ' . $mod[ 'posts_args' ][ 'posts_per_page' ] );
1358
1359 } else $this->p->debug->log( 'posts_per_page not defined' );
1360 }
1361
1362 $posts_ids = $this->get_posts_ids( $mod ); // Extended method.
1363
1364 if ( is_array( $posts_ids ) ) { // Just in case.
1365
1366 $have_num = 0;
1367
1368 foreach ( $posts_ids as $post_id ) {
1369
1370 $have_num++;
1371
1372 if ( $this->p->debug->enabled ) {
1373
1374 $this->p->debug->log( '#' . $have_num . ': getting mod for post id ' . $post_id );
1375 }
1376
1377 $posts_mods[] = $this->p->post->get_mod( $post_id );
1378 }
1379
1380 if ( $this->p->debug->enabled ) {
1381
1382 $this->p->debug->log( 'retrieved ' . $have_num . ' post mods' );
1383 }
1384 }
1385
1386 return $posts_mods;
1387 }
1388
1389 /*
1390 * Returns an array of post IDs for a given $mod object.
1391 *
1392 * See WpssoAbstractWpMeta->get_posts_mods().
1393 */
1394 public function get_posts_ids( array $mod ) {
1395
1396 return self::must_be_extended( $ret_val = array() ); // Return an empty array.
1397 }
1398
1399 public function ajax_get_metabox_sso() {
1400
1401 self::must_be_extended();
1402
1403 die( -1 ); // Nothing to do.
1404 }
1405
1406 /*
1407 * See WpssoPost->add_meta_boxes().
1408 * See WpssoProfileYourSSO->show_metabox_sso().
1409 * See WpssoTerm->add_meta_boxes().
1410 * See WpssoUser->add_meta_boxes().
1411 */
1412 public function show_metabox_sso( $wp_obj ) {
1413
1414 echo $this->get_metabox_sso( $wp_obj );
1415 }
1416
1417 public function get_metabox_sso( $wp_obj ) {
1418
1419 $mod = $this->p->page->get_mod( $use_post = false, $mod = false, $wp_obj );
1420 $metabox_id = $this->p->cf[ 'meta' ][ 'id' ];
1421 $tabs = $this->get_document_sso_tabs( $metabox_id, $mod );
1422 $md_opts = $this->get_options( $mod[ 'id' ] );
1423 $md_defs = $this->get_defaults( $mod[ 'id' ] );
1424
1425 $this->form = new SucomForm( $this->p, WPSSO_META_NAME, $md_opts, $md_defs, $this->p->id );
1426
1427 wp_nonce_field( WpssoAdmin::get_nonce_action(), WPSSO_NONCE_NAME );
1428
1429 $table_rows = array();
1430
1431 foreach ( $tabs as $tab_key => $title ) {
1432
1433 /*
1434 * See WpssoAmFiltersEdit->filter_mb_sso_edit_appmeta_rows().
1435 * See WpssoBcFiltersEdit->filter_mb_sso_edit_schema_rows().
1436 * See WpssoEditGeneral->filter_mb_sso_edit_general_rows().
1437 * See WpssoEditMedia->filter_mb_sso_edit_media_rows().
1438 * See WpssoEditSchema->filter_mb_sso_edit_schema_rows().
1439 * See WpssoEditVisibility->filter_mb_sso_edit_visibility_rows().
1440 */
1441 $filter_name = 'wpsso_mb_' . $metabox_id . '_' . $tab_key . '_rows';
1442
1443 if ( $this->p->debug->enabled ) {
1444
1445 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1446 }
1447
1448 $table_rows[ $tab_key ] = apply_filters( $filter_name, array(), $this->form, self::$head_info, $mod );
1449 }
1450
1451 $tabbed_args = array( 'layout' => 'vertical' ); // Force vertical layout.
1452
1453 $container_id = 'wpsso_mb_' . $metabox_id . '_inside';
1454
1455 $metabox_html = "\n" . '<div id="' . $container_id . '">';
1456 $metabox_html .= $this->p->util->metabox->get_tabbed( $metabox_id, $tabs, $table_rows, $tabbed_args );
1457 $metabox_html .= '<!-- ' . $container_id . '_footer begin -->' . "\n";
1458
1459 if ( $this->p->debug->enabled ) {
1460
1461 $this->p->debug->log( 'applying filters "' . $container_id . '"' );
1462 }
1463
1464 $metabox_html .= apply_filters( $container_id . '_footer', '', $mod );
1465 $metabox_html .= '<!-- ' . $container_id . '_footer end -->' . "\n";
1466 $metabox_html .= $this->get_metabox_javascript( $container_id );
1467 $metabox_html .= '</div><!-- #'. $container_id . ' -->' . "\n";
1468
1469 return $metabox_html;
1470 }
1471
1472 public function get_metabox_javascript( $container_id ) {
1473
1474 $doing_ajax = SucomUtilWP::doing_ajax();
1475 $container_id = empty( $container_id ) ? '' : '#' . $container_id;
1476 $metabox_html = '';
1477
1478 if ( $doing_ajax ) {
1479
1480 /*
1481 * Provide a parent container_id value to initialize a single metabox (when loading a single
1482 * metabox via ajax, for example).
1483 */
1484 $metabox_html .= '<!-- init metabox javascript for ajax call -->' . "\n" .
1485 '<script>wpssoInitMetabox( \'' . $container_id . '\', true );</script>' . "\n";
1486 }
1487
1488 return $metabox_html;
1489 }
1490
1491 public function load_meta_page( $screen = false ) {
1492
1493 return self::must_be_extended();
1494 }
1495
1496 /*
1497 * The post, term, or user has an ID, is public, and (in the case of a post) the post status is published.
1498 *
1499 * See WpssoPost->load_meta_page().
1500 * See WpssoTerm->load_meta_page().
1501 * See WpssoUser->load_meta_page().
1502 */
1503 protected function check_head_info( $mod ) {
1504
1505 if ( ! $mod[ 'id' ] || ! $mod[ 'is_public' ] ) {
1506
1507 return;
1508
1509 } elseif ( $mod[ 'is_post' ] && 'publish' !== $mod[ 'post_status' ] ) {
1510
1511 return;
1512 }
1513
1514 $canonical_url = $this->p->util->get_canonical_url( $mod );
1515
1516 $ref_url = $this->p->util->maybe_set_ref( $canonical_url, $mod, __( 'checking meta tags', 'wpsso' ) );
1517
1518 foreach ( array( 'image', 'description' ) as $mt_suffix ) {
1519
1520 if ( empty( self::$head_info[ 'og:' . $mt_suffix ] ) ) {
1521
1522 if ( $this->p->debug->enabled ) {
1523
1524 $this->p->debug->log( 'og:' . $mt_suffix . ' meta tag is value empty and required' );
1525 }
1526
1527 /*
1528 * An is_admin() test is required to make sure the WpssoMessages class is available.
1529 */
1530 if ( $this->p->notice->is_admin_pre_notices() ) {
1531
1532 $notice_msg = $this->p->msgs->get( 'notice-missing-og-' . $mt_suffix );
1533
1534 $notice_key = $mod[ 'name' ] . '-' . $mod[ 'id' ] . '-notice-missing-og-' . $mt_suffix;
1535
1536 if ( ! empty( $notice_msg ) ) { // Just in case.
1537
1538 $this->p->notice->err( $notice_msg, null, $notice_key );
1539 }
1540 }
1541 }
1542 }
1543
1544 $this->p->util->maybe_unset_ref( $ref_url );
1545
1546 do_action( 'wpsso_check_head_info', self::$head_info, $mod, $canonical_url );
1547 }
1548
1549 /*
1550 * Extended by WpssoPost->add_meta_boxes( $post_type, $post_obj = false );
1551 * Extended by WpssoTerm->add_meta_boxes( $term_obj, $tax_slug = false );
1552 * Extended by WpssoUser->add_meta_boxes( $user_obj, $rel = false );
1553 */
1554 public function add_meta_boxes( $mixed, $rel = false ) {
1555
1556 return self::must_be_extended();
1557 }
1558
1559 /*
1560 * Does this page have a Document SSO metabox?
1561 *
1562 * If this is a post/term/user editing page, then the self::$head_tags variable will be an array.
1563 */
1564 public static function is_meta_page() {
1565
1566 if ( is_array( self::$head_tags ) ) { // Array is allowed to be empty.
1567
1568 return true;
1569 }
1570
1571 return false;
1572 }
1573
1574 public static function get_head_tags() {
1575
1576 return self::$head_tags;
1577 }
1578
1579 protected function get_document_sso_tabs( $metabox_id, array $mod ) {
1580
1581 $tabs = array();
1582
1583 switch ( $metabox_id ) {
1584
1585 case $this->p->cf[ 'meta' ][ 'id' ]: // 'sso' metabox ID.
1586
1587 $tabs[ 'edit_general' ] = _x( 'Edit General', 'metabox tab', 'wpsso' );
1588
1589 if ( ! $this->p->util->is_schema_disabled() ) {
1590
1591 $tabs[ 'edit_schema' ] = _x( 'Edit Schema', 'metabox tab', 'wpsso' );
1592 }
1593
1594 $tabs[ 'edit_media' ] = _x( 'Edit Media', 'metabox tab', 'wpsso' );
1595
1596 if ( $mod[ 'is_public' ] ) {
1597
1598 $tabs[ 'edit_visibility' ] = _x( 'Edit Visibility', 'metabox tab', 'wpsso' );
1599 $tabs[ 'prev_social' ] = _x( 'Preview Social', 'metabox tab', 'wpsso' );
1600 $tabs[ 'prev_oembed' ] = _x( 'Preview oEmbed', 'metabox tab', 'wpsso' );
1601 $tabs[ 'validators' ] = _x( 'Validators', 'metabox tab', 'wpsso' );
1602 }
1603
1604 break;
1605 }
1606
1607 /*
1608 * Exclude the 'Edit Media' tab from attachment editing pages.
1609 */
1610 if ( $mod[ 'is_attachment' ] ) {
1611
1612 unset( $tabs[ 'edit_media' ] );
1613 }
1614
1615 /*
1616 * Exclude the 'oEmbed' tab from non-post editing pages.
1617 *
1618 * get_oembed_response_data() is available since WP v4.4.
1619 */
1620 if ( ! function_exists( 'get_oembed_response_data' ) || ! $mod[ 'is_post' ] || ! $mod[ 'id' ] ) {
1621
1622 unset( $tabs[ 'prev_oembed' ] );
1623 }
1624
1625 /*
1626 * See WpssoAmFiltersEdit->filter_mb_sso_tabs().
1627 */
1628 $filter_name = SucomUtil::sanitize_hookname( 'wpsso_mb_'. $metabox_id . '_tabs', $tabs, $mod );
1629
1630 if ( $this->p->debug->enabled ) {
1631
1632 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1633 }
1634
1635 return apply_filters( $filter_name, $tabs, $mod );
1636 }
1637
1638 /*
1639 * See WpssoAbstractWpMeta->check_sortable_meta().
1640 * See WpssoOembed->post_oembed_response_data().
1641 * See WpssoOembed->post_oembed_response_data_rich.
1642 * See WpssoOembed->the_embed_thumbnail_url().
1643 * See WpssoOembed->the_embed_thumbnail_url_image_shape().
1644 * See WpssoOembed->the_embed_thumbnail_id().
1645 * See WpssoOembed->the_embed_excerpt().
1646 */
1647 public function get_head_info( $mixed, $read_cache = true ) {
1648
1649 if ( $this->p->debug->enabled ) {
1650
1651 $this->p->debug->mark( 'getting head info' ); // Start timer.
1652 }
1653
1654 static $local_fifo = array();
1655
1656 $mod = is_array( $mixed ) ? $mixed : $this->get_mod( $mixed ); // Uses a local cache.
1657
1658 unset( $mixed );
1659
1660 if ( $this->p->debug->enabled ) {
1661
1662 $this->p->debug->log( 'getting head info for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
1663 }
1664
1665 /*
1666 * Maybe return the array from the local cache.
1667 */
1668 if ( $read_cache ) {
1669
1670 if ( isset( $local_fifo[ $mod[ 'id' ] ] ) ) {
1671
1672 if ( $this->p->debug->enabled ) {
1673
1674 $this->p->debug->log( 'exiting early: returning head info from cache' );
1675 }
1676
1677 return $local_fifo[ $mod[ 'id' ] ];
1678 }
1679 }
1680
1681 $local_fifo = SucomUtil::array_slice_fifo( $local_fifo, WPSSO_CACHE_ARRAY_FIFO_MAX ); // Maybe limit the number of array elements.
1682 $use_post = 'post' === $mod[ 'name' ] ? $mod[ 'id' ] : false;
1683
1684 if ( $this->p->debug->enabled ) {
1685
1686 $this->p->debug->log( 'calling WpssoHead->get_head_array()' );
1687 }
1688
1689 $head_tags = $this->p->head->get_head_array( $use_post, $mod, $read_cache );
1690
1691 if ( $this->p->debug->enabled ) {
1692
1693 $this->p->debug->log( 'calling WpssoHead->extract_head_info()' );
1694 }
1695
1696 $head_info = $this->p->head->extract_head_info( $head_tags, $mod );
1697
1698 if ( $this->p->debug->enabled ) {
1699
1700 $this->p->debug->mark( 'getting head info' ); // End timer.
1701 }
1702
1703 return $local_fifo[ $mod[ 'id' ] ] = $head_info;
1704 }
1705
1706 /*
1707 * Called by WpssoHead->extract_head_info().
1708 */
1709 public function get_head_info_thumb_bg_img( $head_info, $mod, $md_pre = 'og', $mt_pre = 'og' ) {
1710
1711 if ( $this->p->debug->enabled ) {
1712
1713 $this->p->debug->mark( 'getting thumbnail image' ); // Begin timer.
1714 }
1715
1716 $media_html = '';
1717
1718 if ( empty( $head_info[ $mt_pre . ':image:id' ] ) ) {
1719
1720 $media_html .= '<!-- checking head info array for image URL -->';
1721
1722 } else {
1723
1724 $pid = $head_info[ $mt_pre . ':image:id' ];
1725
1726 $size_name = 'thumbnail';
1727
1728 /*
1729 * get_mt_single_image_src() returns an og:image:url value, not an og:image:secure_url.
1730 *
1731 * Example:
1732 *
1733 * array(
1734 * $mt_pre . ':image:url' => '',
1735 * $mt_pre . ':image:width' => '',
1736 * $mt_pre . ':image:height' => '',
1737 * $mt_pre . ':image:cropped' => '', // Internal meta tag.
1738 * $mt_pre . ':image:id' => '', // Internal meta tag.
1739 * $mt_pre . ':image:alt' => '',
1740 * $mt_pre . ':image:size_name' => '', // Internal meta tag.
1741 * );
1742 */
1743 $mt_single_image = $this->p->media->get_mt_single_image_src( $pid, $size_name, $mt_pre );
1744
1745 $media_html .= '<!-- get ' . $size_name . ' for image id ' . $pid . ' = ';
1746
1747 if ( empty( $mt_single_image[ $mt_pre . ':image:url' ] ) ) { // Just in case.
1748
1749 $media_html .= 'failed';
1750
1751 } else {
1752
1753 $media_html .= 'success';
1754
1755 $head_info =& $mt_single_image; // Use the updated image information.
1756 }
1757
1758 $media_html .= ' -->';
1759 }
1760
1761 $image_url = SucomUtil::get_first_mt_media_url( $head_info );
1762
1763 if ( empty( $image_url ) ) {
1764
1765 if ( $this->p->debug->enabled ) {
1766
1767 $this->p->debug->log( 'no thumbnail image URL' );
1768 }
1769
1770 } else {
1771
1772 if ( $this->p->debug->enabled ) {
1773
1774 $this->p->debug->log( 'thumbnail image URL = ' . $image_url );
1775 }
1776
1777 $media_html .= '<div class="wp-thumb-bg-img" style="background-image:url(' . $image_url . ');"></div><!-- .wp-thumb-bg-img -->';
1778 }
1779
1780 if ( $this->p->debug->enabled ) {
1781
1782 $this->p->debug->mark( 'getting thumbnail image' ); // End timer.
1783 }
1784
1785 return $media_html;
1786 }
1787
1788 /*
1789 * Return a specific option from the custom social settings meta with fallback for multiple option keys. If $md_key
1790 * is an array, then get the first non-empty option from the options array. This is an easy way to provide a
1791 * fallback value for the first array key. Use 'none' as a key name to skip this fallback behavior.
1792 *
1793 * Example: get_options_multi( $obj_id, array( 'seo_desc', 'og_desc' ) );
1794 */
1795 public function get_options_multi( $obj_id, $md_key = false, $filter_opts = true ) {
1796
1797 if ( $this->p->debug->enabled ) {
1798
1799 $this->p->debug->log_args( array(
1800 'obj_id' => $obj_id,
1801 'md_key' => $md_key,
1802 'filter_opts' => $filter_opts,
1803 ) );
1804 }
1805
1806 if ( empty( $obj_id ) ) {
1807
1808 return null;
1809 }
1810
1811 if ( false === $md_key ) { // Return the whole options array.
1812
1813 $md_val = $this->get_options( $obj_id, $md_key, $filter_opts );
1814
1815 } elseif ( true === $md_key ) { // True is invalid for a custom meta key.
1816
1817 $md_val = null;
1818
1819 } else { // Return the first matching index value.
1820
1821 if ( is_array( $md_key ) ) {
1822
1823 $check_md_keys = array_unique( $md_key ); // Prevent duplicate key values.
1824
1825 } else {
1826
1827 $check_md_keys = array( $md_key ); // Convert a string to an array.
1828 }
1829
1830 foreach ( $check_md_keys as $md_key ) {
1831
1832 if ( 'none' === $md_key ) { // Special index keyword - stop here.
1833
1834 return null;
1835
1836 } elseif ( empty( $md_key ) ) { // Skip empty array keys.
1837
1838 continue;
1839
1840 } elseif ( is_array( $md_key ) ) { // An array of arrays is not valid.
1841
1842 continue;
1843
1844 } else {
1845
1846 if ( $this->p->debug->enabled ) {
1847
1848 $this->p->debug->log( 'getting id ' . $obj_id . ' option ' . $md_key . ' value' );
1849 }
1850
1851 if ( ( $md_val = $this->get_options( $obj_id, $md_key, $filter_opts ) ) !== null ) {
1852
1853 if ( $this->p->debug->enabled ) {
1854
1855 $this->p->debug->log( 'option ' . $md_key . ' value found (not null)' );
1856 }
1857
1858 break; // Stop after first match.
1859 }
1860 }
1861 }
1862 }
1863
1864 if ( null !== $md_val ) {
1865
1866 if ( $this->p->debug->enabled ) {
1867
1868 $mod = $this->get_mod( $obj_id );
1869
1870 $this->p->debug->log( 'custom ' . $mod[ 'name' ] . ' ' . ( false === $md_key ? 'options' :
1871 ( is_array( $md_key ) ? implode( $glue = ', ', $md_key ) : $md_key ) ) . ' = ' .
1872 ( is_array( $md_val ) ? print_r( $md_val, true ) : '"' . $md_val . '"' ) );
1873 }
1874 }
1875
1876 return $md_val;
1877 }
1878
1879 /*
1880 * Extended by WpssoPost->clear_cache( $post_id, $rel = false );
1881 * Extended by WpssoTerm->clear_cache( $term_id, $term_tax_id = false );
1882 * Extended by WpssoUser->clear_cache( $user_id, $rel = false );
1883 */
1884 public function clear_cache( $obj_id, $rel = false ) {
1885
1886 return self::must_be_extended();
1887 }
1888
1889 /*
1890 * Extended by WpssoPost->refresh_cache( $post_id, $rel = false );
1891 * Extended by WpssoTerm->refresh_cache( $term_id, $term_tax_id = false );
1892 * Extended by WpssoUser->refresh_cache( $user_id, $rel = false );
1893 */
1894 public function refresh_cache( $obj_id, $rel = false ) {
1895
1896 return self::must_be_extended();
1897 }
1898
1899 /*
1900 * Called by WpssoPost->clear_cache().
1901 * Called by WpssoTerm->clear_cache().
1902 * Called by WpssoUser->clear_cache().
1903 */
1904 protected function clear_mod_cache( array $mod ) {
1905
1906 /*
1907 * Clear the WpssoPage->get_the_content() cache.
1908 */
1909 $this->p->page->clear_the_content( $mod );
1910
1911 /*
1912 * Clear the WpssoHead->get_head_array() cache.
1913 */
1914 $this->p->head->clear_head_array( $mod );
1915
1916 /*
1917 * WordPress stores data using a post, term, or user ID, along with a group string.
1918 */
1919 wp_cache_delete( $mod[ 'id' ], $mod[ 'name' ] . '_meta' );
1920
1921 do_action( 'wpsso_clear_mod_cache', $mod );
1922 }
1923
1924 /*
1925 * Check user capability for the comment, post, term, or user id.
1926 *
1927 * Extended by WpssoComment->user_can_edit( $comment_id, $rel = false );
1928 * Extended by WpssoPost->user_can_edit( $post_id, $rel = false );
1929 * Extended by WpssoTerm->user_can_edit( $term_id, $term_tax_id = false );
1930 * Extended by WpssoUser->user_can_edit( $user_id, $rel = false );
1931 */
1932 public function user_can_edit( $obj_id, $rel = false ) {
1933
1934 return self::must_be_extended( $ret_val = false ); // Return false by default.
1935 }
1936
1937 /*
1938 * Called by WpssoComment->user_can_edit();
1939 * Called by WpssoPost->user_can_edit();
1940 * Called by WpssoTerm->user_can_edit();
1941 * Called by WpssoUser->user_can_edit();
1942 */
1943 protected function verify_submit_nonce() {
1944
1945 if ( empty( $_POST ) ) { // Nothing to save - nonce not required.
1946
1947 if ( $this->p->debug->enabled ) {
1948
1949 $this->p->debug->log( 'submit POST is empty' );
1950 }
1951
1952 return false;
1953 }
1954
1955 if ( empty( $_POST[ WPSSO_NONCE_NAME ] ) ) {
1956
1957 if ( $this->p->debug->enabled ) {
1958
1959 $this->p->debug->log( 'submit POST nonce token missing' );
1960 }
1961
1962 return false;
1963 }
1964
1965 if ( ! wp_verify_nonce( $_POST[ WPSSO_NONCE_NAME ], WpssoAdmin::get_nonce_action() ) ) {
1966
1967 if ( $this->p->debug->enabled ) {
1968
1969 $this->p->debug->log( 'submit nonce token validation failed' );
1970 }
1971
1972 if ( is_admin() ) {
1973
1974 $this->p->notice->err( __( 'Nonce token validation failed for the submitted form (update ignored).', 'wpsso' ) );
1975 }
1976
1977 return false;
1978
1979 }
1980
1981 return true;
1982 }
1983
1984 /*
1985 * Merge and check submitted post, term, and user metabox options.
1986 *
1987 * See WpssoComment->save_options().
1988 * See WpssoPost->save_options().
1989 * See WpssoTerm->save_options().
1990 * See WpssoUser->save_options().
1991 */
1992 protected function get_submit_opts( $mod ) {
1993
1994 /*
1995 * The $mod array argument is preferred but not required.
1996 */
1997 if ( ! is_array( $mod ) ) {
1998
1999 $mod = $this->get_mod( $mod );
2000 }
2001
2002 $md_defs = $this->get_defaults( $mod[ 'id' ] ); // Uses a local cache.
2003 $md_prev = $this->get_options( $mod[ 'id' ] ); // Uses a local cache.
2004
2005 /*
2006 * Merge and sanitize the new options.
2007 *
2008 * If no options were changed, then merge and re-sanitize the previous options.
2009 */
2010 if ( empty( $_POST[ WPSSO_META_NAME ] ) ) {
2011
2012 $md_opts = $md_prev;
2013
2014 } else {
2015
2016 $md_opts = $_POST[ WPSSO_META_NAME ];
2017 $md_opts = SucomUtil::restore_checkboxes( $md_opts );
2018 $md_opts = SucomUtil::array_merge_recursive_distinct( $md_prev, $md_opts ); // Complete the array with previous options.
2019 }
2020
2021 $md_opts = $this->p->opt->sanitize( $md_opts, $md_defs, $network = false, $mod );
2022
2023 /*
2024 * WpssoUtil->is_canonical_disabled() returns true if:
2025 *
2026 * - An SEO plugin is active.
2027 * - The 'add_link_rel_canonical' option is unchecked.
2028 * - The 'wpsso_add_link_rel_canonical' filter returns false.
2029 * - The 'wpsso_canonical_disabled' filter returns true.
2030 */
2031 if ( ! empty( $md_opts[ 'canonical_url' ] ) && $this->p->util->is_canonical_disabled() ) {
2032
2033 unset( $md_opts[ 'canonical_url' ] );
2034 }
2035
2036 /*
2037 * WpssoUtil->is_redirect_disabled() returns true if:
2038 *
2039 * - An SEO plugin is active.
2040 * - The 'wpsso_redirect_disabled' filter returns true.
2041 */
2042 if ( ! empty( $md_opts[ 'redirect_url' ] ) && $this->p->util->is_redirect_disabled() ) {
2043
2044 unset( $md_opts[ 'redirect_url' ] );
2045 }
2046
2047 /*
2048 * WpssoUtil->is_canonical_disabled() returns true if:
2049 *
2050 * - An SEO plugin is active.
2051 * - The theme does not support the 'title-tag' feature.
2052 * - The WPSSO_TITLE_TAG_DISABLE constant is true.
2053 * - The 'plugin_title_tag' option is not 'seo_title'.
2054 */
2055 if ( ! empty( $md_opts[ 'seo_title' ] ) && $this->p->util->is_seo_title_disabled() ) {
2056
2057 unset( $md_opts[ 'seo_title' ] );
2058 }
2059
2060 /*
2061 * WpssoUtil->is_seo_desc_disabled() returns true if:
2062 *
2063 * - An SEO plugin is active.
2064 * - The 'add_meta_name_description' option is unchecked.
2065 */
2066 if ( ! empty( $md_opts[ 'seo_desc' ] ) && $this->p->util->is_seo_desc_disabled() ) {
2067
2068 unset( $md_opts[ 'seo_desc' ] );
2069 }
2070
2071 /*
2072 * Check image size options (id, prefix, width, height, crop, etc.).
2073 */
2074 foreach ( array( 'og', 'pin', 'schema', 'tc_lrg', 'tc_sum' ) as $md_pre ) {
2075
2076 /*
2077 * If there's no image ID, then remove the image id library prefix.
2078 *
2079 * If an image id is being used, then remove the image URL (only one can be defined).
2080 */
2081 if ( empty( $md_opts[ $md_pre . '_img_id' ] ) ) {
2082
2083 unset( $md_opts[ $md_pre . '_img_id_lib' ] );
2084
2085 } else {
2086
2087 unset( $md_opts[ $md_pre . '_img_url' ] );
2088 }
2089
2090 if ( empty( $md_opts[ $md_pre . '_img_url' ] ) ) { // Just in case.
2091
2092 unset( $md_opts[ $md_pre . '_img_url:width' ] );
2093 unset( $md_opts[ $md_pre . '_img_url:height' ] );
2094 }
2095 }
2096
2097 /*
2098 * Remove empty or default values.
2099 *
2100 * Use strict comparison to manage conversion (don't allow string to integer conversion, for example).
2101 */
2102 foreach ( $md_opts as $md_key => $md_val ) {
2103
2104 if ( '' === $md_val ) {
2105
2106 unset( $md_opts[ $md_key ] );
2107
2108 } elseif ( is_array( $md_val ) ) { // ie. 'opt_versions' or other array.
2109
2110 if ( isset( $md_defs[ $md_key ] ) && $md_val === $md_defs[ $md_key ] ) {
2111
2112 unset( $md_opts[ $md_key ] );
2113 }
2114
2115 } elseif ( $md_val === WPSSO_UNDEF || $md_val === (string) WPSSO_UNDEF ) {
2116
2117 switch ( $md_key ) {
2118
2119 case 'robots_max_snippet':
2120 case 'robots_max_video_preview':
2121
2122 break;
2123
2124 default:
2125
2126 unset( $md_opts[ $md_key ] );
2127 }
2128
2129 } elseif ( isset( $md_defs[ $md_key ] ) ) {
2130
2131 if ( $md_val === $md_defs[ $md_key ] || $md_val === (string) $md_defs[ $md_key ] ) {
2132
2133 unset( $md_opts[ $md_key ] );
2134 }
2135 }
2136 }
2137
2138 /*
2139 * Re-number multi options (example: schema type url, recipe ingredient, recipe instruction, etc.).
2140 */
2141 $this->md_keys_multi_renum( $md_opts );
2142
2143 /*
2144 * Check for default recipe values.
2145 */
2146 foreach ( SucomUtil::preg_grep_keys( '/^schema_recipe_(prep|cook|total)_(days|hours|mins|secs)$/', $md_opts ) as $md_key => $value ) {
2147
2148 $md_opts[ $md_key ] = (int) $value;
2149
2150 if ( $md_opts[ $md_key ] === $md_defs[ $md_key ] ) {
2151
2152 unset( $md_opts[ $md_key ] );
2153 }
2154 }
2155
2156 /*
2157 * Check product energy efficiency rating and review rating.
2158 */
2159 foreach ( array( 'product_energy_efficiency', 'schema_review_rating' ) as $md_rating_key ) {
2160
2161 if ( ! empty( $md_opts[ $md_rating_key ] ) ) {
2162
2163 /*
2164 * Fallback to default values if the min/max is empty.
2165 */
2166 foreach ( array( $md_rating_key . '_min', $md_rating_key . '_max' ) as $md_key ) {
2167
2168 if ( empty( $md_opts[ $md_key ] ) && isset( $md_defs[ $md_key ] ) ) {
2169
2170 $md_opts[ $md_key ] = $md_defs[ $md_key ];
2171 }
2172 }
2173
2174 } else {
2175
2176 foreach ( array( $md_rating_key, $md_rating_key . '_min', $md_rating_key . '_max' ) as $md_key ) {
2177
2178 unset( $md_opts[ $md_key ] );
2179 }
2180 }
2181 }
2182
2183 /*
2184 * Check and maybe fix missing event date, time, and timezone values.
2185 */
2186 foreach ( array( 'schema_event_start', 'schema_event_end', 'schema_event_previous' ) as $md_pre ) {
2187
2188 /*
2189 * Unset date / time if same as the default value.
2190 */
2191 foreach ( array( 'date', 'time', 'timezone' ) as $md_ext ) {
2192
2193 if ( isset( $md_opts[ $md_pre . '_' . $md_ext ] ) &&
2194 ( $md_opts[ $md_pre . '_' . $md_ext ] === $md_defs[ $md_pre . '_' . $md_ext ] ||
2195 $md_opts[ $md_pre . '_' . $md_ext ] === 'none' ) ) {
2196
2197 unset( $md_opts[ $md_pre . '_' . $md_ext ] );
2198 }
2199 }
2200
2201 if ( empty( $md_opts[ $md_pre . '_date' ] ) && empty( $md_opts[ $md_pre . '_time' ] ) ) { // No date or time.
2202
2203 unset( $md_opts[ $md_pre . '_date' ] );
2204 unset( $md_opts[ $md_pre . '_time' ] );
2205 unset( $md_opts[ $md_pre . '_timezone' ] );
2206
2207 } elseif ( ! empty( $md_opts[ $md_pre . '_date' ] ) && empty( $md_opts[ $md_pre . '_time' ] ) ) { // Date with no time.
2208
2209 $md_opts[ $md_pre . '_time' ] = '00:00';
2210
2211 } elseif ( empty( $md_opts[ $md_pre . '_date' ] ) && ! empty( $md_opts[ $md_pre . '_time' ] ) ) { // Time with no date.
2212
2213 if ( 'schema_event_previous' === $md_pre ) {
2214
2215 unset( $md_opts[ $md_pre . '_date' ] );
2216 unset( $md_opts[ $md_pre . '_time' ] );
2217 unset( $md_opts[ $md_pre . '_timezone' ] );
2218
2219 } else $md_opts[ $md_pre . '_date' ] = gmdate( 'Y-m-d' );
2220 }
2221 }
2222
2223 /*
2224 * Events with a previous start date must have "rescheduled" as their status.
2225 *
2226 * A rescheduled event, without a previous start date, is also invalid.
2227 */
2228 if ( ! empty( $md_opts[ 'schema_event_previous_date' ] ) ) {
2229
2230 $md_opts[ 'schema_event_status' ] = 'https://schema.org/EventRescheduled';
2231 $md_opts[ 'schema_event_status:disabled' ] = true;
2232
2233 } elseif ( isset( $md_opts[ 'schema_event_status' ] ) && 'https://schema.org/EventRescheduled' === $md_opts[ 'schema_event_status' ] ) {
2234
2235 $md_opts[ 'schema_event_status' ] = 'https://schema.org/EventScheduled';
2236 }
2237
2238 /*
2239 * Check offer options to make sure they have at least a name or a price.
2240 */
2241 $metadata_offers_max = SucomUtil::get_const( 'WPSSO_SCHEMA_METADATA_OFFERS_MAX', 5 );
2242
2243 foreach( array( 'schema_event', 'schema_review_item_product', 'schema_review_item_software_app' ) as $md_pre ) {
2244
2245 foreach ( range( 0, $metadata_offers_max - 1, 1 ) as $key_num ) {
2246
2247 $is_valid_offer = false;
2248
2249 foreach ( array(
2250 $md_pre . '_offer_name',
2251 $md_pre . '_offer_price'
2252 ) as $md_offer_pre ) {
2253
2254 if ( isset( $md_opts[ $md_offer_pre . '_' . $key_num ] ) && '' !== $md_opts[ $md_offer_pre . '_' . $key_num ] ) {
2255
2256 $is_valid_offer = true;
2257 }
2258 }
2259
2260 if ( ! $is_valid_offer ) {
2261
2262 unset( $md_opts[ $md_pre . '_offer_currency_' . $key_num ] );
2263 unset( $md_opts[ $md_pre . '_offer_avail_' . $key_num ] );
2264 }
2265 }
2266 }
2267
2268 /*
2269 * Check for invalid Schema type combinations (ie. a claim review of a claim review).
2270 */
2271 if ( isset( $md_opts[ 'schema_type' ] ) ) { // Just in case.
2272
2273 if ( 'book.audio' === $md_opts[ 'schema_type' ] ) {
2274
2275 $md_opts[ 'schema_book_format' ] = 'https://schema.org/AudiobookFormat';
2276
2277 } elseif ( 'review' === $md_opts[ 'schema_type' ] ) {
2278
2279 if ( isset( $md_opts[ 'schema_review_item_type' ] ) && 'review' === $md_opts[ 'schema_review_item_type' ] ) {
2280
2281 $md_opts[ 'schema_review_item_type' ] = $this->p->options[ 'schema_def_review_item_type' ];
2282
2283 $notice_msg = __( 'Another review cannot be the subject of a review.', 'wpsso' ) . ' ';
2284
2285 $notice_msg .= __( 'Please select a Schema type for the review subject that describes the subject being reviewed.', 'wpsso' );
2286
2287 $this->p->notice->err( $notice_msg );
2288 }
2289
2290 } elseif ( 'review.claim' === $md_opts[ 'schema_type' ] ) {
2291
2292 if ( isset( $md_opts[ 'schema_review_item_type' ] ) && 'review.claim' === $md_opts[ 'schema_review_item_type' ] ) {
2293
2294 $md_opts[ 'schema_review_item_type' ] = $this->p->options[ 'schema_def_review_item_type' ];
2295
2296 $notice_msg = __( 'Another claim review cannot be the subject of a claim review.', 'wpsso' ) . ' ';
2297
2298 $notice_msg .= __( 'Please select a Schema type for the review subject that describes the subject being reviewed.', 'wpsso' );
2299
2300 $this->p->notice->err( $notice_msg );
2301 }
2302 }
2303 }
2304
2305 /*
2306 * Add plugin and add-on versions (ie. 'checksum', 'opt_checksum', and 'opt_versions').
2307 */
2308 $this->p->opt->add_versions_checksum( $md_opts ); // $md_opts must be an array.
2309
2310 return $md_opts;
2311 }
2312
2313 /*
2314 * Re-number multi options (example: schema type url, recipe ingredient, recipe instruction, etc.).
2315 */
2316 public function md_keys_multi_renum( array &$md_opts ) {
2317
2318 $md_keys_multi = WpssoConfig::get_md_keys_multi();
2319
2320 foreach ( $md_keys_multi as $multi_key => $is_multi ) {
2321
2322 /*
2323 * $is_multi = true | false | array.
2324 */
2325 if ( empty( $is_multi ) ) { // False.
2326
2327 continue;
2328 }
2329
2330 /*
2331 * Get multi option values indexed by their number.
2332 */
2333 $md_multi_opts = SucomUtil::preg_grep_keys( '/^' . $multi_key . '_([0-9]+)$/', $md_opts, $invert = false, $replace = '$1' );
2334 $md_renum_opts = array(); // Start with a fresh array.
2335 $renum = 0; // Start a new index at 0.
2336
2337 foreach ( $md_multi_opts as $md_num => $md_val ) {
2338
2339 if ( '' !== $md_val ) { // Only save non-empty values.
2340
2341 $md_renum_opts[ $multi_key . '_' . $renum ] = $md_val;
2342 }
2343
2344 /*
2345 * Check if there are linked options, and if so, re-number those options as well.
2346 */
2347 if ( is_array( $is_multi ) ) {
2348
2349 foreach ( $is_multi as $md_linked_key ) {
2350
2351 if ( isset( $md_opts[ $md_linked_key . '_' . $md_num ] ) ) { // Just in case.
2352
2353 $md_renum_opts[ $md_linked_key . '_' . $renum ] = $md_opts[ $md_linked_key . '_' . $md_num ];
2354 }
2355 }
2356 }
2357
2358 $renum++; // Increment the new index number.
2359 }
2360
2361 /*
2362 * Remove any existing multi options, including any linked options.
2363 */
2364 $md_opts = SucomUtil::preg_grep_keys( '/^' . $multi_key . '_([0-9]+)$/', $md_opts, $invert = true );
2365
2366 if ( is_array( $is_multi ) ) {
2367
2368 foreach ( $is_multi as $md_linked_key ) {
2369
2370 $md_opts = SucomUtil::preg_grep_keys( '/^' . $md_linked_key . '_([0-9]+)$/', $md_opts, $invert = true );
2371 }
2372 }
2373
2374 /*
2375 * Save the re-numbered options.
2376 */
2377 foreach ( $md_renum_opts as $md_key => $md_val ) {
2378
2379 $md_opts[ $md_key ] = $md_val;
2380 }
2381
2382 unset( $md_renum_opts );
2383 }
2384 }
2385
2386 /*
2387 * Return column keys and their column info.
2388 */
2389 public static function get_sortable_columns( $col_key = false ) {
2390
2391 static $local_cache = null;
2392
2393 if ( null === $local_cache ) {
2394
2395 $cf = WpssoConfig::get_config();
2396
2397 $columns = $cf[ 'edit' ][ 'columns' ];
2398
2399 $local_cache = apply_filters( 'wpsso_get_sortable_columns', $columns );
2400 }
2401
2402 if ( false !== $col_key ) {
2403
2404 if ( isset( $local_cache[ $col_key ] ) ) {
2405
2406 return $local_cache[ $col_key ];
2407 }
2408
2409 return null; // Column key not found.
2410 }
2411
2412 return $local_cache; // Always an array.
2413 }
2414
2415 public static function get_column_headers() {
2416
2417 $headers = array();
2418
2419 $sortable_cols = self::get_sortable_columns(); // Uses a local cache.
2420
2421 foreach ( $sortable_cols as $col_key => $col_info ) {
2422
2423 if ( ! empty( $col_info[ 'header' ] ) ) {
2424
2425 $headers[ $col_key ] = _x( $col_info[ 'header' ], 'column header', 'wpsso' );
2426 }
2427 }
2428
2429 return $headers;
2430 }
2431
2432 /*
2433 * Example Array (
2434 * [schema_type_name] => _wpsso_head_info_schema_type_name
2435 * [schema_type] => _wpsso_head_info_schema_type
2436 * [og_type] => _wpsso_head_info_og_type
2437 * [og_img] => _wpsso_head_info_og_img_thumb
2438 * [og_desc] => _wpsso_head_info_og_desc
2439 * [is_noindex] => _wpsso_head_info_is_noindex
2440 * [is_redirect] => _wpsso_head_info_is_redirect
2441 * )
2442 */
2443 public static function get_column_meta_keys( $col_key = false ) {
2444
2445 static $local_cache = null;
2446
2447 if ( null === $local_cache ) {
2448
2449 $local_cache = array();
2450
2451 $sortable_cols = self::get_sortable_columns(); // Uses a local cache.
2452
2453 foreach ( $sortable_cols as $cache_key => $cache_info ) { // Avoid variable name conflict with args.
2454
2455 if ( ! empty( $cache_info[ 'meta_key' ] ) ) {
2456
2457 $local_cache[ $cache_key ] = $cache_info[ 'meta_key' ];
2458 }
2459 }
2460 }
2461
2462 if ( false !== $col_key ) {
2463
2464 if ( isset( $local_cache[ $col_key ] ) ) {
2465
2466 return $local_cache[ $col_key ];
2467 }
2468
2469 return null; // Column key not found.
2470 }
2471
2472 return $local_cache;
2473 }
2474
2475 /*
2476 * See WpssoCmcfXml::get().
2477 * See WpssoGmfXml::get().
2478 * See https://developer.wordpress.org/reference/classes/wp_meta_query/.
2479 */
2480 static public function get_column_meta_query_og_type( $og_type = 'product', $schema_lang = null ) {
2481
2482 $wpsso =& Wpsso::get_instance();
2483
2484 if ( $wpsso->debug->enabled ) {
2485
2486 $wpsso->debug->mark();
2487 }
2488
2489 static $local_cache = array();
2490
2491 if ( null === $schema_lang ) {
2492
2493 $schema_lang = SucomUtilWP::get_locale();
2494 }
2495
2496 if ( ! isset( $local_cache[ $og_type ] ) ) {
2497
2498 $local_cache[ $og_type ] = array();
2499 }
2500
2501 if ( ! isset( $local_cache[ $og_type ][ $schema_lang ] ) ) {
2502
2503 $og_type_key = self::get_column_meta_keys( 'og_type' ); // Example: '_wpsso_head_info_og_type'.
2504 $noindex_key = self::get_column_meta_keys( 'is_noindex' ); // Example: '_wpsso_head_info_is_noindex'.
2505 $redirect_key = self::get_column_meta_keys( 'is_redirect' ); // Example: '_wpsso_head_info_is_redirect'.
2506 $schema_lang_key = self::get_column_meta_keys( 'schema_lang' ); // Example: '_wpsso_head_info_schema_lang'.
2507
2508 $local_cache[ $og_type ][ $schema_lang ] = array(
2509 'relation' => 'AND',
2510 array(
2511 'key' => $og_type_key,
2512 'value' => $og_type,
2513 'compare' => '=',
2514 'type' => 'CHAR',
2515 ),
2516 array(
2517 'key' => $noindex_key,
2518 'value' => '1',
2519 'compare' => '!=',
2520 'type' => 'CHAR',
2521 ),
2522 array(
2523 'key' => $redirect_key,
2524 'value' => '1',
2525 'compare' => '!=',
2526 'type' => 'CHAR',
2527 ),
2528 );
2529
2530 /*
2531 * The WPML integration module returns true so WPML can filter posts for the current language.
2532 *
2533 * See WpssoIntegLangWpml->__construct().
2534 */
2535 if ( $wpsso->debug->enabled ) {
2536
2537 $wpsso->debug->log( 'applying filters "wpsso_post_public_ids_suppress_filters"' );
2538 }
2539
2540 $suppress_filters = apply_filters( 'wpsso_post_public_ids_suppress_filters', true );
2541
2542 if ( $suppress_filters ) {
2543
2544 $local_cache[ $og_type ][ $schema_lang ][] = array(
2545 'key' => $schema_lang_key,
2546 'value' => $schema_lang,
2547 'compare' => '=',
2548 'type' => 'CHAR',
2549 );
2550 }
2551
2552 if ( $wpsso->debug->enabled ) {
2553
2554 $wpsso->debug->log( 'applying filters "wpsso_column_meta_query_og_type"' );
2555 }
2556
2557 $local_cache[ $og_type ][ $schema_lang ] = apply_filters( 'wpsso_column_meta_query_og_type',
2558 $local_cache[ $og_type ][ $schema_lang ], $og_type, $schema_lang );
2559 }
2560
2561 return $local_cache[ $og_type ][ $schema_lang ];
2562 }
2563
2564 /*
2565 * See https://developer.wordpress.org/reference/classes/wp_meta_query/.
2566 */
2567 static public function get_column_meta_query_exclude() {
2568
2569 $wpsso =& Wpsso::get_instance();
2570
2571 if ( $wpsso->debug->enabled ) {
2572
2573 $wpsso->debug->mark();
2574 }
2575
2576 static $local_cache = null;
2577
2578 if ( null === $local_cache ) {
2579
2580 $noindex_key = self::get_column_meta_keys( 'is_noindex' ); // Example: '_wpsso_head_info_is_noindex'.
2581 $redirect_key = self::get_column_meta_keys( 'is_redirect' ); // Example: '_wpsso_head_info_is_redirect'.
2582
2583 $local_cache = array(
2584 'relation' => 'OR',
2585 array(
2586 'key' => $noindex_key,
2587 'value' => '1',
2588 'compare' => '=',
2589 'type' => 'CHAR',
2590 ),
2591 array(
2592 'key' => $redirect_key,
2593 'value' => '1',
2594 'compare' => '=',
2595 'type' => 'CHAR',
2596 ),
2597 );
2598 }
2599
2600 return $local_cache; // Return an empty string or array.
2601 }
2602
2603 /*
2604 * See WpssoAbstractWpMeta->check_sortable_meta().
2605 */
2606 public static function get_column_info_by_meta_key( $meta_key ) {
2607
2608 $sortable_cols = self::get_sortable_columns(); // Uses a local cache.
2609
2610 foreach ( $sortable_cols as $col_key => $col_info ) {
2611
2612 if ( ! empty( $col_info[ 'meta_key' ] ) ) {
2613
2614 if ( $col_info[ 'meta_key' ] === $meta_key ) {
2615
2616 return $col_info;
2617 }
2618 }
2619 }
2620
2621 return array();
2622 }
2623
2624 public function get_column_content( $value, $column_name, $obj_id ) {
2625
2626 if ( empty( $obj_id ) || 0 !== strpos( $column_name, 'wpsso_' ) ) { // Just in case.
2627
2628 return $value;
2629 }
2630
2631 $col_key = str_replace( 'wpsso_', '', $column_name );
2632
2633 if ( $col_info = self::get_sortable_columns( $col_key ) ) { // Uses a local cache.
2634
2635 $mod = $this->get_mod( $obj_id );
2636
2637 $value = $this->get_column_wp_cache( $mod, $col_info ); // Can return 'none' or an empty string.
2638
2639 /*
2640 * Callback added by WpssoRarAdmin->filter_get_sortable_columns().
2641 */
2642 $callbacks_key = $mod[ 'name' ] . '_callbacks';
2643
2644 if ( isset( $col_info[ $callbacks_key ] ) && is_array( $col_info[ $callbacks_key ] ) ) {
2645
2646 foreach( $col_info[ $callbacks_key ] as $input_callback ) {
2647
2648 $value = call_user_func( $input_callback, $value, $obj_id );
2649 }
2650 }
2651
2652 if ( 'none' === $value ) {
2653
2654 $value = '';
2655 }
2656 }
2657
2658 return $value;
2659 }
2660
2661 /*
2662 * Can return 'none' or an empty string.
2663 *
2664 * Called by WpssoPost->get_column_content().
2665 * Called by WpssoTerm->get_column_content().
2666 * Called by WpssoUser->get_column_content().
2667 */
2668 public function get_column_wp_cache( array $mod, array $col_info ) {
2669
2670 if ( $this->p->debug->enabled ) {
2671
2672 $this->p->debug->mark();
2673 }
2674
2675 $value = '';
2676 $locale = empty( $col_info[ 'localized' ] ) ? '' : SucomUtilWP::get_locale();
2677
2678 if ( empty( $col_info[ 'meta_key' ] ) ) { // Just in case.
2679
2680 return $value;
2681 }
2682
2683 /*
2684 * Retrieves the cache contents from the cache by key and group.
2685 */
2686 $metadata = $mod[ 'obj' ]->get_update_meta_cache( $mod[ 'id' ] );
2687
2688 if ( ! empty( $metadata[ $col_info[ 'meta_key' ] ][ 0 ] ) ) {
2689
2690 $value = maybe_unserialize( $metadata[ $col_info[ 'meta_key' ] ][ 0 ] ); // String or localized array.
2691 }
2692
2693 /*
2694 * $value is an empty string by default. If get_update_meta_cache() did not return a value for this meta
2695 * key, then $value will still be an empty string. If this meta key is localized, and the locale key does
2696 * not exist in the array, then fetch a new / updated array value.
2697 */
2698 if ( '' === $value || ( $locale && ! isset( $value[ $locale ] ) ) ) {
2699
2700 $value = static::get_meta( $mod[ 'id' ], $col_info[ 'meta_key' ], $single = true ); // Use static method from child.
2701 }
2702
2703 if ( $locale ) { // Values stored by locale.
2704
2705 $value = isset( $value[ $locale ] ) ? $value[ $locale ] : '';
2706 }
2707
2708 if ( is_array( $value ) ) { // Just in case.
2709
2710 $value = '';
2711 }
2712
2713 return $value;
2714 }
2715
2716 /*
2717 * Adds metadata keys to an existing array to ignore when copying a post object.
2718 *
2719 * See WpssoIntegEcomWooCommerce->duplicate_product_exclude_meta().
2720 */
2721 public static function add_duplicate_exclude_meta_keys( array $exclude_keys = array() ) {
2722
2723 return array_merge( $exclude_keys, self::get_duplicate_exclude_meta_keys() );
2724 }
2725
2726 /*
2727 * Returns an array of metadata keys to ignore when copying a post object.
2728 */
2729 public static function get_duplicate_exclude_meta_keys() {
2730
2731 $meta_keys = array_values( self::get_column_meta_keys() );
2732
2733 foreach ( array(
2734 'WPSSORAR_META_AVERAGE_RATING' => '_wpsso_average_rating',
2735 'WPSSORAR_META_RATING_COUNTS' => '_wpsso_rating_counts',
2736 'WPSSORAR_META_REVIEW_COUNT' => '_wpsso_review_count',
2737 ) as $name => $default ) {
2738
2739 $meta_keys[] = SucomUtil::get_const( $name, $default );
2740 }
2741
2742 return $meta_keys;
2743 }
2744
2745 /*
2746 * Check the post, term, or user '_wpsso_head_info_' meta key value is not an empty string.
2747 *
2748 * Hooked to the WordPress 'get_post_metadata', 'get_term_metadata', 'get_user_metadata' actions.
2749 */
2750 public function check_sortable_meta( $value, $obj_id, $meta_key, $single ) {
2751
2752 /*
2753 * Only check the '_wpsso_head_info_' meta key values.
2754 */
2755 if ( empty( $obj_id ) || 0 !== strpos( $meta_key, '_wpsso_head_info_' ) ) {
2756
2757 return $value;
2758 }
2759
2760 if ( $this->p->debug->enabled ) {
2761
2762 $this->p->debug->mark();
2763 }
2764
2765 $mod = $this->get_mod( $obj_id ); // Get the post, term, or user $mod array.
2766
2767 if ( empty( $mod[ 'name' ] ) || empty( $mod[ 'id' ] ) ) {
2768
2769 if ( $this->p->debug->enabled ) {
2770
2771 $this->p->debug->log( 'exiting early: mod name or id is empty' );
2772 }
2773
2774 return $value;
2775
2776 } elseif ( isset( $mod[ 'post_status' ] ) && 'auto-draft' === $mod[ 'post_status' ] ) { // The post object may not yet exist.
2777
2778 if ( $this->p->debug->enabled ) {
2779
2780 $this->p->debug->log( 'exiting early: post status is ' . $mod[ 'post_status' ] );
2781 }
2782
2783 return $value;
2784 }
2785
2786 /*
2787 * Note that the sort order, page number, locale, amp and embed checks are provided by
2788 * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
2789 *
2790 * Example cache salts:
2791 *
2792 * 'post:123_type:page'
2793 * 'post:123_type:product_is_pta' // WooCommerce shop page.
2794 * 'term:123_tax:product_cat' // WooCommerce category page.
2795 */
2796 $cache_salt = SucomUtil::get_mod_salt( $mod );
2797
2798 static $local_cache = array(); // Only check once per page load.
2799
2800 if ( ! empty( $local_cache[ $cache_salt ][ $meta_key ] ) ) { // Meta key already checked.
2801
2802 if ( $this->p->debug->enabled ) {
2803
2804 $this->p->debug->log( 'exiting early: ' . $meta_key . ' meta key already checked' );
2805 }
2806
2807 return $value; // Return null.
2808 }
2809
2810 $local_cache[ $cache_salt ][ $meta_key ] = true; // Check once and also prevents recursion.
2811
2812 $col_info = self::get_column_info_by_meta_key( $meta_key );
2813
2814 if ( ! empty( $col_info ) ) {
2815
2816 if ( $this->p->debug->enabled ) {
2817
2818 $this->p->debug->log( 'checking ' . $meta_key . ' meta key value' );
2819 }
2820
2821 $metadata = static::get_meta( $obj_id, $meta_key, $single = true ); // Use static method from child.
2822
2823 $do_head_info = false;
2824
2825 if ( '' === $metadata ) {
2826
2827 if ( $this->p->debug->enabled ) {
2828
2829 $this->p->debug->log( $meta_key . ' meta key value is an empty string' );
2830 }
2831
2832 $do_head_info = true;
2833
2834 } elseif ( ! empty( $col_info[ 'localized' ] ) ) { // Value stored by locale.
2835
2836 $locale = SucomUtilWP::get_locale();
2837
2838 if ( empty( $metadata[ $locale ] ) ) {
2839
2840 if ( $this->p->debug->enabled ) {
2841
2842 $this->p->debug->log( $meta_key . ' meta key value for locale ' . $locale . ' is empty' );
2843 }
2844
2845 $do_head_info = true;
2846 }
2847 }
2848
2849 if ( $do_head_info ) {
2850
2851 if ( $this->p->debug->enabled ) {
2852
2853 $this->p->debug->log( 'calling ' . get_class( $this ) . '->get_head_info()' );
2854 }
2855
2856 $this->get_head_info( $mod, $read_cache = true ); // Uses a local cache.
2857 }
2858 }
2859
2860 return $value;
2861 }
2862
2863 public function update_sortable_meta( $obj_id, $col_key, $content ) {
2864
2865 if ( $this->p->debug->enabled ) {
2866
2867 $this->p->debug->mark();
2868 }
2869
2870 if ( empty( $obj_id ) ) { // Just in case.
2871
2872 if ( $this->p->debug->enabled ) {
2873
2874 $this->p->debug->log( 'exiting early: obj_id is empty' );
2875 }
2876
2877 return;
2878 }
2879
2880 if ( $col_info = self::get_sortable_columns( $col_key ) ) { // Uses a local cache.
2881
2882 if ( ! empty( $col_info[ 'meta_key' ] ) ) { // Just in case.
2883
2884 if ( ! empty( $col_info[ 'localized' ] ) ) { // Value stored by locale.
2885
2886 $locale = SucomUtilWP::get_locale();
2887 $content = array( $locale => $content );
2888 $metadata = static::get_meta( $obj_id, $col_info[ 'meta_key' ], $single = true ); // Use static method from child.
2889
2890 if ( is_array( $metadata ) ) {
2891
2892 $content = array_merge( $metadata, $content );
2893 }
2894 }
2895
2896 /*
2897 * The child methods call update_metadata().
2898 *
2899 * See WpssoAbstractWpMeta::update_meta() which must be extended.
2900 * See WpssoComment::update_meta()
2901 * See WpssoPost::update_meta()
2902 * See WpssoTerm::update_meta()
2903 * See WpssoUser::update_meta()
2904 */
2905 if ( $this->p->debug->enabled ) {
2906
2907 $this->p->debug->log( 'calling ' . get_class( $this ) . '::update_meta()' );
2908 }
2909
2910 static::update_meta( $obj_id, $col_info[ 'meta_key' ], $content ); // Use static method from child.
2911 }
2912 }
2913 }
2914
2915 public function add_sortable_columns( $columns ) {
2916
2917 $sortable_cols = self::get_sortable_columns(); // Uses a local cache.
2918
2919 foreach ( $sortable_cols as $col_key => $col_info ) {
2920
2921 if ( ! empty( $col_info[ 'orderby' ] ) ) {
2922
2923 $columns[ 'wpsso_' . $col_key ] = 'wpsso_' . $col_key;
2924 }
2925 }
2926
2927 return $columns;
2928 }
2929
2930 public function set_column_orderby( $query ) {
2931
2932 $col_name = $query->get( 'orderby' );
2933
2934 if ( is_string( $col_name ) && strpos( $col_name, 'wpsso_' ) === 0 ) {
2935
2936 $col_key = str_replace( 'wpsso_', '', $col_name );
2937
2938 if ( $col_info = self::get_sortable_columns( $col_key ) ) { // Uses a local cache.
2939
2940 foreach ( array( 'meta_key', 'orderby' ) as $set_name ) {
2941
2942 if ( ! empty( $col_info[ $set_name ] ) ) {
2943
2944 $query->set( $set_name, $col_info[ $set_name ] );
2945 }
2946 }
2947 }
2948 }
2949 }
2950
2951 /*
2952 * Called by WpssoPost->add_post_column_headings().
2953 * Called by WpssoPost->add_media_column_headings().
2954 * Called by WpssoTerm->add_term_column_headings().
2955 * Called by WpssoUser->add_user_column_headings().
2956 */
2957 protected function add_column_headings( $columns, $opt_suffix ) {
2958
2959 foreach ( self::get_column_headers() as $col_key => $col_header ) {
2960
2961 /*
2962 * Check if the column is enabled globally for the post, media, term, or user edit list.
2963 */
2964 if ( ! empty( $this->p->options[ 'plugin_' . $col_key . '_col_' . $opt_suffix ] ) ) {
2965
2966 if ( $this->p->debug->enabled ) {
2967
2968 $this->p->debug->log( 'adding wpsso_' . $col_key . ' column' );
2969 }
2970
2971 $columns[ 'wpsso_' . $col_key ] = $col_header;
2972 }
2973 }
2974
2975 return $columns;
2976 }
2977
2978 /*
2979 * Retrieves or updates the metadata cache by key and group.
2980 */
2981 public function get_update_meta_cache( $obj_id ) {
2982
2983 return self::must_be_extended( $ret_val = array() ); // Return an empty array.
2984 }
2985
2986 /*
2987 * Return merged custom options from the post or term parents.
2988 *
2989 * Called by WpssoAbstractWpMeta->get_defaults(), WpssoPost->get_options(), and WpssoTerm->get_options().
2990 */
2991 public function get_inherited_md_opts( array $mod ) {
2992
2993 if ( $this->p->debug->enabled ) {
2994
2995 $this->p->debug->mark();
2996 }
2997
2998 $md_opts = array();
2999 $ancestor_ids = array();
3000
3001 if ( $mod[ 'is_attachment' ] ) { // Attachments do not inherit metadata.
3002
3003 if ( $this->p->debug->enabled ) {
3004
3005 $this->p->debug->log( 'exiting early: attachments do not inherit metadata' );
3006 }
3007
3008 return $md_opts;
3009 }
3010
3011 /*
3012 * See https://www.php.net/manual/en/function.array-intersect-key.php.
3013 */
3014 $inherit_opts = $this->p->cf[ 'form' ][ 'inherit_md_opts' ];
3015 $inherit_opts = apply_filters( 'wpsso_inherit_md_opts', $inherit_opts, $mod );
3016
3017 /*
3018 * Since WPSSO Core v9.10.0.
3019 *
3020 * Note that only public children can inherit custom images from their parents.
3021 *
3022 * Use add_filter( 'wpsso_inherit_custom_images', '__return_true' ) to inherit images for private children.
3023 */
3024 $inherit_images = empty( $this->p->options[ 'plugin_inherit_images' ] ) ? false : $mod[ 'is_public' ];
3025 $inherit_images = (bool) apply_filters( 'wpsso_inherit_custom_images', $inherit_images, $mod );
3026
3027 if ( $inherit_images ) {
3028
3029 if ( $this->p->debug->enabled ) {
3030
3031 $this->p->debug->log( 'inherit custom images is enabled' );
3032 }
3033
3034 } else {
3035
3036 if ( $this->p->debug->enabled ) {
3037
3038 $this->p->debug->log( 'inherit custom images is disabled' );
3039 }
3040
3041 $inherit_opts = SucomUtil::preg_grep_keys( '/_img_/', $inherit_opts, $invert = true );
3042 }
3043
3044 if ( $mod[ 'is_post' ] ) {
3045
3046 if ( ! empty( $mod[ 'post_type' ] ) && is_string( $mod[ 'post_type' ] ) ) { // Not false or empty string.
3047
3048 if ( $this->p->debug->enabled ) {
3049
3050 $this->p->debug->log( 'getting ancestors for post type = ' . $mod[ 'post_type' ] );
3051 }
3052
3053 $ancestor_ids = get_ancestors( $mod[ 'id' ], $object_type = $mod[ 'post_type' ], $resource_type = 'post_type' );
3054
3055 } elseif ( $this->p->debug->enabled ) {
3056
3057 $this->p->debug->log( 'skipped getting ancestors: invalid post type' );
3058 }
3059
3060 } elseif ( $mod[ 'is_term' ] ) {
3061
3062 if ( $this->p->debug->enabled ) {
3063
3064 $this->p->debug->log( 'getting ancestors for taxonomy = ' . $mod[ 'tax_slug' ] );
3065 }
3066
3067 $ancestor_ids = get_ancestors( $mod[ 'id' ], $object_type = $mod[ 'tax_slug' ], $resource_type = 'taxonomy' );
3068 }
3069
3070 if ( empty( $ancestor_ids ) ) {
3071
3072 if ( $this->p->debug->enabled ) {
3073
3074 $this->p->debug->log( 'no ancestors for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
3075 }
3076
3077 } elseif ( empty( $inherit_opts ) ) {
3078
3079 if ( $this->p->debug->enabled ) {
3080
3081 $this->p->debug->log( 'no inherit opts for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
3082 }
3083
3084 } else {
3085
3086 /*
3087 * Merge the custom options array top-down, so we merge the closest parent last.
3088 */
3089 $ancestor_ids = array_reverse( $ancestor_ids );
3090
3091 foreach ( $ancestor_ids as $parent_id ) {
3092
3093 if ( $this->p->debug->enabled ) {
3094
3095 $this->p->debug->log( 'getting parent id ' . $parent_id . ' metadata for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] );
3096 }
3097
3098 $metadata = $mod[ 'obj' ]->get_update_meta_cache( $parent_id );
3099
3100 if ( empty( $metadata[ WPSSO_META_NAME ][ 0 ] ) ) {
3101
3102 if ( $this->p->debug->enabled ) {
3103
3104 $this->p->debug->log( 'no metadata for parent id ' . $parent_id );
3105 }
3106
3107 } else {
3108
3109 $parent_opts = maybe_unserialize( $metadata[ WPSSO_META_NAME ][ 0 ] );
3110
3111 if ( $this->p->debug->enabled ) {
3112
3113 $this->p->debug->log_arr( 'parent_opts', $parent_opts );
3114 }
3115
3116 $parent_opts = array_intersect_key( $parent_opts, $inherit_opts );
3117 $md_opts = SucomUtil::array_merge_recursive_distinct( $md_opts, $parent_opts );
3118 }
3119 }
3120
3121 if ( $this->p->debug->enabled ) {
3122
3123 $this->p->debug->log_arr( 'inherit_opts', $inherit_opts );
3124 $this->p->debug->log_arr( 'md_opts', $md_opts );
3125 }
3126 }
3127
3128 return $md_opts;
3129 }
3130
3131 /*
3132 * Returns an array of single image associative arrays.
3133 *
3134 * $size_names can be a keyword (ie. 'opengraph' or 'schema'), a registered size name, or an array of size names.
3135 *
3136 * $md_pre can be a text string or array of prefixes.
3137 */
3138 public function get_md_images( $num, $size_names, array $mod, $md_pre = 'og', $mt_pre = 'og' ) {
3139
3140 if ( $this->p->debug->enabled ) {
3141
3142 $this->p->debug->log_args( array(
3143 'num' => $num,
3144 'size_names' => $size_names,
3145 'mod' => $mod,
3146 'md_pre' => $md_pre,
3147 'mt_pre' => $mt_pre,
3148 ), get_class( $this ) );
3149 }
3150
3151 if ( empty( $mod[ 'id' ] ) ) { // Just in case.
3152
3153 return array();
3154
3155 } elseif ( $num < 1 ) { // Just in case.
3156
3157 return array();
3158 }
3159
3160 $size_names = $this->p->util->get_image_size_names( $size_names ); // Always returns an array.
3161 $md_pre = is_array( $md_pre ) ? array_merge( $md_pre, array( 'og' ) ) : array( $md_pre, 'og' );
3162 $mt_images = array();
3163
3164 foreach( array_unique( $md_pre ) as $opt_pre ) {
3165
3166 if ( $opt_pre === 'none' ) { // Special index keyword.
3167
3168 break;
3169
3170 } elseif ( empty( $opt_pre ) ) { // Skip empty md_pre values.
3171
3172 continue;
3173 }
3174
3175 $pid = $this->get_options( $mod[ 'id' ], $opt_pre . '_img_id' );
3176 $lib = $this->get_options( $mod[ 'id' ], $opt_pre . '_img_id_lib' );
3177 $url = $this->get_options( $mod[ 'id' ], $opt_pre . '_img_url' );
3178
3179 if ( $pid > 0 ) {
3180
3181 if ( $this->p->debug->enabled ) {
3182
3183 $this->p->debug->log( 'using custom ' . $opt_pre . ' image id = ' . $pid, get_class( $this ) );
3184 }
3185
3186 $mt_images = $this->p->media->get_mt_pid_images( $pid, $size_names, $mt_pre );
3187 }
3188
3189 if ( empty( $mt_images ) && $url ) {
3190
3191 if ( $this->p->debug->enabled ) {
3192
3193 $this->p->debug->log( 'using custom ' . $opt_pre . ' image url = ' . $url, get_class( $this ) );
3194 }
3195
3196 $img_width = $this->get_options( $mod[ 'id' ], $opt_pre . '_img_url:width' );
3197 $img_height = $this->get_options( $mod[ 'id' ], $opt_pre . '_img_url:height' );
3198
3199 $mt_single_image = SucomUtil::get_mt_image_seed( $mt_pre );
3200
3201 $mt_single_image[ $mt_pre . ':image:url' ] = $url;
3202 $mt_single_image[ $mt_pre . ':image:width' ] = $img_width > 0 ? $img_width : WPSSO_UNDEF;
3203 $mt_single_image[ $mt_pre . ':image:height' ] = $img_height > 0 ? $img_height : WPSSO_UNDEF;
3204
3205 if ( $this->p->util->push_max( $mt_images, $mt_single_image, $num ) ) {
3206
3207 return $mt_images;
3208 }
3209 }
3210
3211 if ( $pid || $url ) { // Stop after first $md_pre image found.
3212
3213 break;
3214 }
3215 }
3216
3217 if ( $this->p->util->is_maxed( $mt_images, $num ) ) {
3218
3219 return $mt_images;
3220 }
3221
3222 /*
3223 * Example filter names:
3224 *
3225 * 'wpsso_post_image_ids'
3226 * 'wpsso_term_image_ids'
3227 * 'wpsso_user_image_ids'
3228 */
3229 $filter_name = 'wpsso_' . $mod[ 'name' ] . '_image_ids';
3230
3231 $image_ids = apply_filters( $filter_name, array(), $size_names, $mod[ 'id' ], $mod );
3232
3233 foreach ( $image_ids as $pid ) {
3234
3235 if ( $pid > 0 ) { // Quick sanity check.
3236
3237 if ( $this->p->debug->enabled ) {
3238
3239 $this->p->debug->log( 'adding image pid: ' . $pid );
3240 }
3241
3242 $mt_pid_images = $this->p->media->get_mt_pid_images( $pid, $size_names, $mt_pre );
3243
3244 if ( $this->p->util->merge_max( $mt_images, $mt_pid_images, $num ) ) {
3245
3246 return $mt_images;
3247 }
3248 }
3249 }
3250
3251 /*
3252 * Example filter names:
3253 *
3254 * 'wpsso_post_image_urls'
3255 * 'wpsso_term_image_urls'
3256 * 'wpsso_user_image_urls'
3257 */
3258 $filter_name = 'wpsso_' . $mod[ 'name' ] . '_image_urls';
3259
3260 if ( $this->p->debug->enabled ) {
3261
3262 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
3263 }
3264
3265 $image_urls = apply_filters( $filter_name, array(), $size_names, $mod[ 'id' ], $mod );
3266
3267 foreach ( array_unique( $image_urls ) as $num => $url ) {
3268
3269 if ( false === filter_var( $url, FILTER_VALIDATE_URL ) ) { // Just in case.
3270
3271 if ( $this->p->debug->enabled ) {
3272
3273 $this->p->debug->log( 'skipping image #' . $num . ': url "' . $url . '" is invalid' );
3274 }
3275
3276 } else {
3277
3278 if ( $this->p->util->is_uniq_url( $url, 'image_urls', $mod ) ) {
3279
3280 if ( $this->p->debug->enabled ) {
3281
3282 $this->p->debug->log( 'adding image url: ' . $url );
3283 }
3284
3285 $mt_single_image = SucomUtil::get_mt_image_seed( $mt_pre );
3286
3287 $mt_single_image[ $mt_pre . ':image:url' ] = $url;
3288
3289 $this->p->util->add_image_url_size( $mt_single_image, $mt_pre . ':image' );
3290
3291 if ( $this->p->util->push_max( $mt_images, $mt_single_image, $num ) ) {
3292
3293 return $mt_images;
3294 }
3295 }
3296 }
3297 }
3298
3299 return $mt_images;
3300 }
3301
3302 /*
3303 * Extended by the WpssoUser class to support non-WordPress user images.
3304 *
3305 * Returns an array of single image associative arrays.
3306 *
3307 * $md_pre can be a text string or array of prefixes.
3308 */
3309 public function get_og_images( $num, $size_names, $obj_id, $md_pre = 'og', $mt_pre = 'og' ) {
3310
3311 if ( $this->p->debug->enabled ) {
3312
3313 $this->p->debug->mark();
3314 }
3315
3316 $mod = $this->get_mod( $obj_id );
3317
3318 return $this->get_md_images( $num, $size_names, $mod, $md_pre, $mt_pre );
3319 }
3320
3321 /*
3322 * Returns an array of single video associative arrays.
3323 *
3324 * $md_pre can be a text string or array of prefixes.
3325 */
3326 public function get_og_videos( $num, $obj_id, $md_pre = 'og', $mt_pre = 'og' ) {
3327
3328 if ( $this->p->debug->enabled ) {
3329
3330 $this->p->debug->log_args( array(
3331 'num' => $num,
3332 'obj_id' => $obj_id,
3333 'md_pre' => $md_pre,
3334 'mt_pre' => $mt_pre,
3335 ), get_class( $this ) );
3336 }
3337
3338 if ( empty( $obj_id ) ) { // Just in case.
3339
3340 return array();
3341
3342 } elseif ( $num < 1 ) { // Just in case.
3343
3344 return array();
3345 }
3346
3347 $mod = $this->get_mod( $obj_id ); // Required for get_content_videos().
3348
3349 $md_pre = is_array( $md_pre ) ? array_merge( $md_pre, array( 'og' ) ) : array( $md_pre, 'og' );
3350
3351 $mt_videos = array();
3352
3353 foreach( array_unique( $md_pre ) as $opt_pre ) {
3354
3355 if ( $opt_pre === 'none' ) { // Special index keyword.
3356
3357 break;
3358
3359 } elseif ( empty( $opt_pre ) ) { // Skip empty md_pre values.
3360
3361 continue;
3362 }
3363
3364 $embed_html = $this->get_options( $obj_id, $opt_pre . '_vid_embed' );
3365 $embed_url = $this->get_options( $obj_id, $opt_pre . '_vid_url' );
3366
3367 if ( $embed_html ) {
3368
3369 if ( $this->p->debug->enabled ) {
3370
3371 $this->p->debug->log( 'fetching video(s) from custom ' . $opt_pre . ' embed code', get_class( $this ) );
3372 }
3373
3374 /*
3375 * Returns an array of single video associative arrays.
3376 */
3377 $mt_videos = $this->p->media->get_content_videos( $num, $mod, $embed_html );
3378 }
3379
3380 if ( empty( $mt_videos ) && $embed_url ) {
3381
3382 if ( $this->p->util->is_uniq_url( $embed_url, $uniq_context = 'video', $mod ) ) {
3383
3384 if ( $this->p->debug->enabled ) {
3385
3386 $this->p->debug->log( 'fetching video from custom ' . $opt_pre . ' url ' . $embed_url, get_class( $this ) );
3387 }
3388
3389 $args = array(
3390 'url' => $embed_url,
3391 'stream_url' => '',
3392 'width' => null,
3393 'height' => null,
3394 'type' => '',
3395 'prev_url' => '',
3396 'api' => '',
3397 );
3398
3399 /*
3400 * Returns a single video associative array.
3401 */
3402 $mt_single_video = $this->p->media->get_video_details( $args, $mod, $fallback = true );
3403
3404 if ( ! empty( $mt_single_video ) ) {
3405
3406 if ( $this->p->util->push_max( $mt_videos, $mt_single_video, $num ) ) {
3407
3408 if ( $this->p->debug->enabled ) {
3409
3410 $this->p->debug->log( 'returning ' . count( $mt_videos ) . ' videos' );
3411 }
3412
3413 return $mt_videos;
3414 }
3415 }
3416 }
3417 }
3418
3419 if ( $embed_html || $embed_url ) { // Stop after first $md_pre video found.
3420
3421 break;
3422 }
3423 }
3424
3425 return $mt_videos;
3426 }
3427
3428 public function get_mt_reviews( $obj_id, $rating_meta = 'rating', $worst_rating = 1, $best_rating = 5 ) {
3429
3430 return self::must_be_extended( $ret_val = array() ); // Return an empty array.
3431 }
3432
3433 public function get_mt_comment_review( $comment_obj, $rating_meta = 'rating', $worst_rating = 1, $best_rating = 5 ) {
3434
3435 $mt_ret = array();
3436
3437 if ( empty( $comment_obj->comment_ID ) ) { // Just in case.
3438
3439 if ( $this->p->debug->enabled ) {
3440
3441 $this->p->debug->log( 'exiting early: comment object id is empty' );
3442 }
3443
3444 return $mt_ret;
3445 }
3446
3447 $comment_mod = $this->p->comment->get_mod( $comment_obj->comment_ID );
3448
3449 $mt_comment = $this->p->og->get_array( $comment_mod, $size_names = 'schema', $md_pre = array( 'schema', 'og' ) );
3450
3451 $mt_ret[ 'review:id' ] = $comment_mod[ 'id' ];
3452 $mt_ret[ 'review:url' ] = isset( $mt_comment[ 'og:url' ] ) ? $mt_comment[ 'og:url' ] : '';
3453 $mt_ret[ 'review:title' ] = isset( $mt_comment[ 'og:title' ] ) ? $mt_comment[ 'og:title' ] : '';
3454 $mt_ret[ 'review:description' ] = isset( $mt_comment[ 'og:description' ] ) ? $mt_comment[ 'og:description' ] : '';
3455 $mt_ret[ 'review:text' ] = $this->p->page->get_text( $comment_mod, $md_key = 'schema_text', $max_len = 'schema_text' );
3456 $mt_ret[ 'review:created_time' ] = $comment_mod[ 'comment_time' ];
3457 $mt_ret[ 'review:author:id' ] = $comment_mod[ 'comment_author' ];
3458 $mt_ret[ 'review:author:name' ] = $comment_mod[ 'comment_author_name' ];
3459 $mt_ret[ 'review:author:url' ] = $comment_mod[ 'comment_author_url' ];
3460 $mt_ret[ 'review:image' ] = isset( $mt_comment[ 'og:image' ] ) ? $mt_comment[ 'og:image' ] : array();
3461 $mt_ret[ 'review:video' ] = isset( $mt_comment[ 'og:video' ] ) ? $mt_comment[ 'og:video' ] : array();
3462
3463 /* foreach ( $mt_ret as $mt_key => $mt_val ) {
3464
3465 $filter_name = SucomUtil::sanitize_hookname( 'wpsso_mt_comment_review_' . $mt_key );
3466
3467 if ( $this->p->debug->enabled ) {
3468
3469 $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
3470 }
3471
3472 $mt_ret[ $mt_key ] = apply_filters( $filter_name, $mt_val, $comment_mod );
3473 } */
3474
3475 /*
3476 * Review rating.
3477 *
3478 * Rating values must be larger than 0 to include rating info.
3479 */
3480 $rating_value = (float) get_metadata( 'comment', $comment_mod[ 'id' ], $rating_meta, $single = true );
3481
3482 if ( $rating_value > 0 ) {
3483
3484 $mt_ret[ 'review:rating:value' ] = $rating_value;
3485 $mt_ret[ 'review:rating:worst' ] = $worst_rating;
3486 $mt_ret[ 'review:rating:best' ] = $best_rating;
3487 }
3488
3489 return $mt_ret;
3490 }
3491
3492 /*
3493 * WpssoPost class specific methods.
3494 */
3495 public function get_canonical_shortlink( $shortlink = false, $obj_id = 0, $context = 'post', $allow_slugs = true ) {
3496
3497 return self::must_be_extended( $ret_val = '' );
3498 }
3499
3500 public function maybe_restore_shortlink( $shortlink = false, $obj_id = 0, $context = 'post', $allow_slugs = true ) {
3501
3502 return self::must_be_extended( $ret_val = '' );
3503 }
3504
3505 /*
3506 * Since WPSSO Core v7.6.0.
3507 */
3508 public static function get_attached_ids( $obj_id, $attach_type ) {
3509
3510 $md_opts = static::get_meta( $obj_id, WPSSO_META_ATTACHED_NAME, $single = true ); // Use static method from child.
3511
3512 if ( isset( $md_opts[ $attach_type ] ) ) {
3513
3514 if ( is_array( $md_opts[ $attach_type ] ) ) { // Just in case.
3515
3516 return $md_opts[ $attach_type ];
3517 }
3518 }
3519
3520 return array(); // No values.
3521 }
3522
3523 /*
3524 * Since WPSSO Core v7.6.0.
3525 *
3526 * Used by WpssoFaqShortcodeQuestion->do_shortcode().
3527 */
3528 public static function add_attached( $obj_id, $attach_type, $attachment_id ) {
3529
3530 $md_opts = static::get_meta( $obj_id, WPSSO_META_ATTACHED_NAME, $single = true ); // Use static method from child.
3531
3532 if ( ! isset( $md_opts[ $attach_type ][ $attachment_id ] ) ) {
3533
3534 if ( ! is_array( $md_opts ) ) {
3535
3536 $md_opts = array();
3537 }
3538
3539 $md_opts[ $attach_type ][ $attachment_id ] = true;
3540
3541 return static::update_meta( $obj_id, WPSSO_META_ATTACHED_NAME, $md_opts ); // Use static method from child.
3542 }
3543
3544 return false; // No addition.
3545 }
3546
3547 /*
3548 * Since WPSSO Core v7.6.0.
3549 */
3550 public static function delete_attached( $obj_id, $attach_type, $attachment_id ) {
3551
3552 $md_opts = static::get_meta( $obj_id, WPSSO_META_ATTACHED_NAME, $single = true ); // Use static method from child.
3553
3554 if ( isset( $md_opts[ $attach_type ][ $attachment_id ] ) ) {
3555
3556 unset( $md_opts[ $attach_type ][ $attachment_id ] );
3557
3558 if ( empty( $md_opts ) ) { // Cleanup.
3559
3560 return static::delete_meta( $obj_id, WPSSO_META_ATTACHED_NAME ); // Use static method from child.
3561 }
3562
3563 return static::update_meta( $obj_id, WPSSO_META_ATTACHED_NAME, $md_opts ); // Use static method from child.
3564 }
3565
3566 return false; // No delete.
3567 }
3568
3569 /*
3570 * Returns a custom or default term ID, or false if a term for the $tax_slug is not found.
3571 */
3572 public function get_primary_term_id( array $mod, $tax_slug = 'category' ) {
3573
3574 return self::must_be_extended( $ret_val = false ); // Return false by default.
3575 }
3576
3577 /*
3578 * Returns the first taxonomy term ID, , or false if a term for the $tax_slug is not found.
3579 */
3580 public function get_default_term_id( array $mod, $tax_slug = 'category' ) {
3581
3582 return self::must_be_extended( $ret_val = false ); // Return false by default.
3583 }
3584
3585 /*
3586 * Returns an associative array of term IDs and their names or objects.
3587 *
3588 * The primary or default term id will be included as the first array element.
3589 */
3590 public function get_primary_terms( array $mod, $tax_slug = 'category', $output = 'objects' ) {
3591
3592 return self::must_be_extended( $ret_val = array() );
3593 }
3594
3595 /*
3596 * Since WPSSO Core v16.4.0.
3597 *
3598 * Add an element to the metadata options array, if the array key does not already exist.
3599 *
3600 * After changing the metadata options element, do not forget to refresh the cache for that object ID.
3601 */
3602 public static function add_meta_opts_key( $obj_id, $meta_key, $opts_key, $value ) {
3603
3604 return self::update_meta_opts_key( $obj_id, $meta_key, $opts_key, $value, $protect = true );
3605 }
3606
3607 /*
3608 * Always call this method as static::get_meta(), and not self::get_meta(), to execute the method via the child
3609 * class instead of the parent class. This method can also be called via $mod[ 'obj' ]::get_meta().
3610 *
3611 * If $meta_key is en empty string, retrieves all metadata for the specified object ID.
3612 *
3613 * See https://developer.wordpress.org/reference/functions/get_metadata/.
3614 */
3615 public static function get_meta( $obj_id, $meta_key = '', $single = false ) {
3616
3617 $ret_val = $single ? '' : array();
3618
3619 return self::must_be_extended( $ret_val );
3620 }
3621
3622 /*
3623 * Since WPSSO Core v16.4.0.
3624 *
3625 * Get an element from the metadata options array. Returns null if the array key does not exist.
3626 */
3627 public static function get_meta_opts_key( $obj_key, $meta_key, $opts_key ) {
3628
3629 $md_opts = static::get_meta( $obj_id, $meta_key, $single = true ); // Use static method from child.
3630
3631 if ( array_key_exists( $opts_key, $md_opts ) ) {
3632
3633 return $md_opts[ $opts_key ];
3634 }
3635
3636 return null; // No value.
3637 }
3638
3639 /*
3640 * The child methods call update_metadata().
3641 *
3642 * Always call this method as static::update_meta(), and not self::update_meta(), to execute the method via the
3643 * child class instead of the parent class. This method can also be called via $mod[ 'obj' ]::update_meta().
3644 *
3645 * See WpssoAbstractWpMeta::update_meta() which must be extended.
3646 * See WpssoComment::update_meta()
3647 * See WpssoPost::update_meta()
3648 * See WpssoTerm::update_meta()
3649 * See WpssoUser::update_meta()
3650 */
3651 public static function update_meta( $obj_id, $meta_key, $value ) {
3652
3653 return self::must_be_extended( $ret_val = false ); // No update.
3654 }
3655
3656 /*
3657 * Update a metadata options array element, if the array key does not exit, or its value is different.
3658 *
3659 * This method reads the $meta_key options array from the WordPress metadata table, updates the array element value
3660 * (if different), then saves the array back to the WordPress metadata table (if the array has changed). Because of
3661 * the overhead required to read and save the array from/to the WordPress metadata table, this method should be
3662 * used only for metadata maintenance purposes. Calling this method on every page load is not recommended.
3663 *
3664 * After changing the metadata options element, do not forget to refresh the cache for that object ID.
3665 *
3666 * Use $protect = true to prevent overwriting an existing value.
3667 */
3668 public static function update_meta_opts_key( $obj_id, $meta_key, $opts_key, $value, $protect = false ) {
3669
3670 $md_opts = static::get_meta( $obj_id, $meta_key, $single = true ); // Use static method from child.
3671
3672 if ( ! is_array( $md_opts ) ) {
3673
3674 $md_opts = array(); // $meta_key not found.
3675 }
3676
3677 if ( array_key_exists( $opts_key, $md_opts ) ) {
3678
3679 if ( $protect ) { // Prevent overwriting an existing value.
3680
3681 return false; // No update.
3682
3683 } elseif ( $value === $md_opts[ $opts_key ] ) { // Nothing to do.
3684
3685 return false; // No update.
3686 }
3687 }
3688
3689 $md_opts[ $opts_key ] = $value;
3690
3691 return static::update_meta( $obj_id, $meta_key, $md_opts ); // Use static method from child.
3692 }
3693
3694 /*
3695 * Always call this method as staticdelete_meta(), and not selfdelete_meta(), to execute the method via the child
3696 * class instead of the parent class. This method can also be called via $mod[ 'obj' ]delete_meta().
3697 */
3698 public static function delete_meta( $obj_id, $meta_key ) {
3699
3700 return self::must_be_extended( $ret_val = false ); // No delete.
3701 }
3702
3703 /*
3704 * Since WPSSO Core v16.4.0.
3705 *
3706 * Delete an element from a metadata options array, if the array key exists.
3707 *
3708 * After changing the metadata options element, do not forget to refresh the cache for that object ID.
3709 */
3710 public static function delete_meta_opts_key( $obj_id, $meta_key, $opts_key ) {
3711
3712 $md_opts = static::get_meta( $obj_id, $meta_key, $single = true ); // Use static method from child.
3713
3714 if ( ! is_array( $md_opts ) ) { // Nothing to do.
3715
3716 return false; // No update.
3717 }
3718
3719 if ( ! array_key_exists( $opts_key, $md_opts ) ) { // Nothing to do.
3720
3721 return false; // No update.
3722 }
3723
3724 unset( $md_opts[ $opts_key ] );
3725
3726 if ( empty( $md_opts ) ) { // Just in case.
3727
3728 return static::delete_meta( $obj_id, $meta_key ); // Use static method from child.
3729 }
3730
3731 return static::update_meta( $obj_id, $meta_key, $md_opts ); // Use static method from child.
3732 }
3733
3734 /*
3735 * See WpssoIntegSeoRankmath->filter_title_seed().
3736 * See WpssoIntegSeoRankmath->filter_description_seed().
3737 * See WpssoIntegSeoSeopress->filter_title_seed().
3738 * See WpssoIntegSeoSeopress->filter_description_seed().
3739 * See WpssoIntegSeoSeopress->filter_get_md_options().
3740 * See WpssoIntegSeoWpMetaSeo->filter_title_seed().
3741 * See WpssoIntegSeoWpMetaSeo->filter_description_seed().
3742 */
3743 public static function get_mod_meta( array $mod, $meta_key = '', $single = false ) {
3744
3745 if ( ! empty( $mod[ 'name' ] ) && ! empty( $mod[ 'id' ] ) ) { // Just in case.
3746
3747 return get_metadata( $mod[ 'name' ], $mod[ 'id' ], $meta_key, $single );
3748 }
3749
3750 return $single ? '' : array();
3751 }
3752
3753 protected static function must_be_extended( $method, $ret_val = null ) {
3754
3755 $wpsso =& Wpsso::get_instance();
3756
3757 if ( $wpsso->debug->enabled ) {
3758
3759 $wpsso->debug->log( 'method must be extended', $class_seq = 2, $func_seq = 2 );
3760 }
3761
3762 return $ret_val;
3763 }
3764 }
3765 }
3766