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
← All changes | lib/abstract/wp-meta.php +3765 -0 22.5.322.7.0 View file →
@@ -1,0 +1,3765 @@
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 +}