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/head.php +1326 -0 22.5.222.7.0 View file →
@@ -1,0 +1,1326 @@
1 +<?php
2 +/*
3 + * License: GPLv3
4 + * License URI: https://www.gnu.org/licenses/gpl.txt
5 + * Copyright 2012-2026 Jean-Sebastien Morisset (https://wpsso.com/)
6 + */
7 +
8 +if ( ! defined( 'ABSPATH' ) ) {
9 +
10 + die( 'These aren\'t the droids you\'re looking for.' );
11 +}
12 +
13 +if ( ! defined( 'WPSSO_PLUGINDIR' ) ) {
14 +
15 + die( 'Do. Or do not. There is no try.' );
16 +}
17 +
18 +if ( ! class_exists( 'WpssoHead' ) ) {
19 +
20 + class WpssoHead {
21 +
22 + private $p; // Wpsso class object.
23 +
24 + public function __construct( &$plugin ) {
25 +
26 + $this->p =& $plugin;
27 +
28 + if ( $this->p->debug->enabled ) {
29 +
30 + $this->p->debug->mark();
31 + }
32 +
33 + /*
34 + * If any custom modifications are required to the WP_Query 'query_vars', they should be done before the
35 + * 'wp_head' action is triggered. The WpssoHead->show_head() method calls WpssoPage->get_mod() to determine
36 + * the current WordPress object (comment, post, term, or user), if any, and saves the 'query_vars' value
37 + * for WordPress archive queries.
38 + */
39 + add_action( 'wp_head', array( $this, 'show_head' ), WPSSO_HEAD_PRIORITY );
40 +
41 + /*
42 + * If an AMP plugin is active, hook 'amp_post_template_head' to add head markup for AMP pages.
43 + */
44 + if ( ! empty( $this->p->avail[ 'amp' ][ 'any' ] ) ) {
45 +
46 + add_action( 'amp_post_template_head', array( $this, 'show_head' ), WPSSO_HEAD_PRIORITY );
47 +
48 + if ( $this->p->avail[ 'p' ][ 'schema' ] ) {
49 +
50 + if ( $this->p->debug->enabled ) {
51 +
52 + $this->p->debug->log( 'disabling amp_post_template_metadata' );
53 + }
54 +
55 + add_filter( 'amp_post_template_metadata', '__return_empty_array', PHP_INT_MAX );
56 + }
57 + }
58 +
59 + /*
60 + * Maybe do a 301 redirect.
61 + */
62 + add_action( 'template_redirect', array( $this, 'maybe_redirect_url' ), PHP_INT_MIN );
63 + }
64 +
65 + public function maybe_redirect_url() {
66 +
67 + if ( $this->p->debug->enabled ) {
68 +
69 + $this->p->debug->mark();
70 + }
71 +
72 + if ( is_admin() || is_preview() || is_customize_preview() ) {
73 +
74 + if ( $this->p->debug->enabled ) {
75 +
76 + $this->p->debug->log( 'exiting early: is admin, preview, or customize preview' );
77 + }
78 +
79 + return;
80 +
81 + /*
82 + * WpssoUtil->is_redirect_disabled() returns true if:
83 + *
84 + * - An SEO plugin is active.
85 + * - The 'wpsso_redirect_disabled' filter returns true.
86 + */
87 + } elseif ( $this->p->util->is_redirect_disabled() ) {
88 +
89 + if ( $this->p->debug->enabled ) {
90 +
91 + $this->p->debug->log( 'exiting early: redirect is disabled' );
92 + }
93 +
94 + return;
95 + }
96 +
97 + $use_post = apply_filters( 'wpsso_use_post', false );
98 +
99 + $url = $this->p->util->get_redirect_url( $use_post );
100 +
101 + if ( $url ) {
102 +
103 + do_action( 'wpsso_before_redirect', $url );
104 +
105 + $redirect_code = absint( apply_filters( 'wpsso_redirect_status_code', 301 ) );
106 +
107 + if ( wp_safe_redirect( $url, $redirect_code ) ) {
108 +
109 + exit;
110 + }
111 + }
112 + }
113 +
114 + public function add_vary_user_agent_header( $headers ) {
115 +
116 + $headers [ 'Vary' ] = 'User-Agent';
117 +
118 + return $headers;
119 + }
120 +
121 + /*
122 + * Called by 'wp_head' and 'amp_post_template_head' actions.
123 + *
124 + * If any custom modifications are required to the WP_Query 'query_vars', they should be done before the 'wp_head'
125 + * action is triggered. The WpssoHead->show_head() method calls WpssoPage->get_mod() to determine the current
126 + * WordPress object (comment, post, term, or user), if any, and saves the 'query_vars' value for WordPress archive
127 + * queries.
128 + */
129 + public function show_head() {
130 +
131 + if ( $this->p->debug->enabled ) {
132 +
133 + $this->p->debug->mark();
134 + }
135 +
136 + if ( is_admin() || is_preview() || is_customize_preview() ) {
137 +
138 + if ( $this->p->debug->enabled ) {
139 +
140 + $this->p->debug->log( 'exiting early: is admin, preview, or customize preview' );
141 + }
142 +
143 + return;
144 + }
145 +
146 + if ( $this->p->debug->enabled ) {
147 +
148 + $this->p->debug->log( 'required call to WpssoPage->get_mod()' );
149 + }
150 +
151 + $use_post = apply_filters( 'wpsso_use_post', false );
152 +
153 + $mod = $this->p->page->get_mod( $use_post );
154 +
155 + if ( $this->p->debug->enabled ) {
156 +
157 + $this->p->debug->log_arr( 'mod', $mod );
158 + $this->p->debug->log( 'home url = ' . get_option( 'home' ) );
159 + $this->p->debug->log( 'locale current = ' . SucomUtilWP::get_locale() );
160 + $this->p->debug->log( 'locale default = ' . SucomUtilWP::get_locale( 'default' ) );
161 + $this->p->debug->log( 'locale mod = ' . SucomUtilWP::get_locale( $mod ) );
162 + $this->p->util->log_is_functions();
163 + }
164 +
165 + echo $this->get_head_html( $use_post, $mod, $read_cache = true );
166 + }
167 +
168 + public function get_head_html( $use_post = false, $mod = false, $read_cache = true ) {
169 +
170 + if ( $this->p->debug->enabled ) {
171 +
172 + $this->p->debug->mark();
173 + }
174 +
175 + $mtime_start = microtime( $get_float = true );
176 + $indent_num = 0;
177 +
178 + /*
179 + * The $mod array argument is preferred but not required.
180 + */
181 + if ( ! is_array( $mod ) ) {
182 +
183 + if ( $this->p->debug->enabled ) {
184 +
185 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
186 + }
187 +
188 + $mod = $this->p->page->get_mod( $use_post );
189 + }
190 +
191 + $html = "\n\n";
192 +
193 + if ( apply_filters( 'wpsso_head_disable', false, $mod ) ) { // Not disabled by default.
194 +
195 + if ( $this->p->debug->enabled ) {
196 +
197 + $this->p->debug->log( 'head structured data disabled' );
198 + }
199 +
200 + $head_tags = $this->get_head_array_disabled( $use_post, $mod );
201 +
202 + } else $head_tags = $this->get_head_array( $use_post, $mod, $read_cache );
203 +
204 + if ( is_array( $head_tags ) ) { // Just in case.
205 +
206 + foreach ( $head_tags as $mt ) {
207 +
208 + if ( ! empty( $mt[ 0 ] ) ) {
209 +
210 + if ( $indent_num && 0 === strpos( $mt[ 0 ], '</noscript' ) ) {
211 +
212 + $indent_num = 0;
213 + }
214 +
215 + $html .= str_repeat( "\t", (int) $indent_num ) . $mt[0];
216 +
217 + if ( 0 === strpos( $mt[ 0 ], '<noscript' ) ) { // Indent meta tags within a noscript container.
218 +
219 + $indent_num = 1;
220 + }
221 + }
222 + }
223 + }
224 +
225 + unset( $head_tags, $mt, $indent_num, $use_post, $mod, $read_cache );
226 +
227 + $mtime_total = microtime( $get_float = true ) - $mtime_start;
228 +
229 + $html .= $this->get_mt_data( 'added', $mtime_total ) . "\n";
230 +
231 + return $html;
232 + }
233 +
234 + /*
235 + * The cache is cleared by WpssoAbstractWpMeta->clear_mod_cache().
236 + *
237 + * The clear_head_array() method is called with $mod = false and a $canonical_url value for post date archive pages.
238 + */
239 + public function clear_head_array( $mod = false, $canonical_url = null ) {
240 +
241 + if ( $this->p->debug->enabled ) {
242 +
243 + $this->p->debug->mark();
244 + }
245 +
246 + if ( empty( $mod ) && empty( $canonical_url ) ) {
247 +
248 + return 0;
249 +
250 + } elseif ( null === $canonical_url ) {
251 +
252 + /*
253 + * Canonical URL for SucomUtil::get_mod_salt().
254 + */
255 + $canonical_url = $this->p->util->get_canonical_url( $mod );
256 + }
257 +
258 + /*
259 + * Setup variables for transient cache.
260 + *
261 + * Note that the sort order, page number, locale, amp and embed checks are provided by
262 + * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
263 + */
264 + $pretty_salt = $this->p->util->is_json_pretty() ? '_is_pretty' : '';
265 + $cache_md5_pre = 'wpsso_h_'; // Transient prefix for head markup.
266 + $cache_salt = __CLASS__ . '::head_array(' . SucomUtil::get_mod_salt( $mod, $canonical_url ) . $pretty_salt . ')';
267 + $cache_id = $cache_md5_pre . md5( $cache_salt );
268 +
269 + if ( $this->p->debug->enabled ) {
270 +
271 + $this->p->debug->log( 'canonical url = ' . $canonical_url );
272 + $this->p->debug->log( 'cache salt = ' . $cache_salt );
273 + $this->p->debug->log( 'cache id = ' . $cache_id );
274 + }
275 +
276 + delete_transient( $cache_id );
277 +
278 + return;
279 + }
280 +
281 + /*
282 + * Called when the 'wpsso_head_disable' filter returns true.
283 + */
284 + public function get_head_array_disabled( $use_post = false, $mod = false ) {
285 +
286 + if ( $this->p->debug->enabled ) {
287 +
288 + $this->p->debug->mark();
289 + }
290 +
291 + /*
292 + * Generator meta tags.
293 + */
294 + $mt_gen = array( 'generator' => $this->p->check->get_ext_gen_list() );
295 +
296 + return array_merge(
297 + array( array( $this->get_mt_data( 'begin' ) ) ),
298 + array( array( $this->get_mt_data( 'disabled' ) ) ),
299 + $this->get_mt_array( $tag = 'meta', $type = 'name', $mt_gen, $mod ),
300 + array( array( $this->get_mt_data( 'end' ) ) ),
301 + );
302 + }
303 +
304 + /*
305 + * The cache is cleared by WpssoAbstractWpMeta->clear_mod_cache().
306 + *
307 + * $read_cache is false when called by the post/term/user load_meta_page() method.
308 + *
309 + * See WpssoAbstractWpMeta->get_head_info().
310 + * See WpssoHead->get_head_html().
311 + * See WpssoUtilCache->refresh_mod_head_meta().
312 + */
313 + public function get_head_array( $use_post = false, $mod = false, $read_cache = true ) {
314 +
315 + if ( $this->p->debug->enabled ) {
316 +
317 + $this->p->debug->mark( 'building head array' ); // Begin timer.
318 + }
319 +
320 + /*
321 + * The $mod array argument is preferred but not required.
322 + */
323 + if ( ! is_array( $mod ) ) {
324 +
325 + if ( $this->p->debug->enabled ) {
326 +
327 + $this->p->debug->log( 'optional call to WpssoPage->get_mod()' );
328 + }
329 +
330 + $mod = $this->p->page->get_mod( $use_post );
331 + }
332 +
333 + /*
334 + * Canonical URL for SucomUtil::get_mod_salt().
335 + */
336 + $canonical_url = $this->p->util->get_canonical_url( $mod );
337 +
338 + /*
339 + * Disable head and content cache if the request URL contains one or more unknown query arguments.
340 + */
341 + if ( SucomUtilWP::doing_frontend() ) {
342 +
343 + $request_url = SucomUtil::get_url( $remove_ignored_args = true ); // Uses a local cache.
344 +
345 + if ( $this->p->debug->enabled ) {
346 +
347 + $this->p->debug->log( 'request url = ' . $request_url );
348 + }
349 +
350 + if ( $request_url !== $canonical_url && false !== strpos( $request_url, '?' ) ) {
351 +
352 + if ( $this->p->debug->enabled ) {
353 +
354 + $this->p->debug->log( 'unknown query arguments detected in request url' );
355 + }
356 +
357 + /*
358 + * WooCommerce product attributes do not have their own webpages - product attribute query
359 + * strings are used to pre-fill product selections on the front-end. The
360 + * WpssoIntegEcomWooCommerce->filter_request_url_query_cache_disable() method removes all
361 + * product attributes from the request URL, and if the $request_url and $canonical_url
362 + * values match, the filter will return false (ie. do not disable the cache).
363 + */
364 + if ( apply_filters( 'wpsso_request_url_query_cache_disable', true, $request_url, $canonical_url, $mod ) ) {
365 +
366 + if ( $this->p->debug->enabled ) {
367 +
368 + $this->p->debug->log( 'head and content cache disabled for query arguments' );
369 + }
370 +
371 + $this->p->util->add_plugin_filters( $this, array(
372 + 'cache_expire_head_markup' => '__return_zero', // Used by WpssoHead->get_head_array().
373 + 'cache_expire_the_content' => '__return_zero', // Used by WpssoPage->get_the_content().
374 + ) );
375 +
376 + } else {
377 +
378 + if ( $this->p->debug->enabled ) {
379 +
380 + $this->p->debug->log( 'head and content cache allowed for query arguments' );
381 + }
382 + }
383 + }
384 + }
385 +
386 + /*
387 + * Setup variables for transient cache.
388 + *
389 + * Note that WpssoUtil->get_cache_exp_secs() will return 0 for some pre-defined conditions in the $mod
390 + * array (ie. 404, attachment, date, and search).
391 + *
392 + * Note that the sort order, page number, locale, amp and embed checks are provided by
393 + * WpssoHead->get_head_cache_index() and not SucomUtil::get_mod_salt().
394 + */
395 + $pretty_salt = $this->p->util->is_json_pretty() ? '_is_pretty' : '';
396 + $cache_md5_pre = 'wpsso_h_'; // Transient prefix for head markup.
397 + $cache_exp_secs = $this->p->util->get_cache_exp_secs( $cache_md5_pre, $cache_type = 'transient', $mod );
398 + $cache_salt = __CLASS__ . '::head_array(' . SucomUtil::get_mod_salt( $mod, $canonical_url ) . $pretty_salt . ')';
399 + $cache_id = $cache_md5_pre . md5( $cache_salt );
400 + $cache_index = $this->get_head_cache_index( $mod ); // Includes the page number.
401 +
402 + if ( $this->p->debug->enabled ) {
403 +
404 + $this->p->debug->log( 'canonical url = ' . $canonical_url );
405 + $this->p->debug->log( 'cache expire = ' . $cache_exp_secs );
406 + $this->p->debug->log( 'cache salt = ' . $cache_salt );
407 + $this->p->debug->log( 'cache id = ' . $cache_id );
408 + $this->p->debug->log( 'cache index = ' . $cache_index );
409 + $this->p->debug->log( 'read cache is ' . SucomUtil::get_bool_string( $read_cache ) );
410 + }
411 +
412 + $cache_array = get_transient( $cache_id );
413 +
414 + if ( is_array( $cache_array ) ) {
415 +
416 + if ( isset( $cache_array[ $cache_index ] ) ) {
417 +
418 + if ( is_array( $cache_array[ $cache_index ] ) ) {
419 +
420 + /*
421 + * $cache_exp_secs can be 0 if URL query arguments are present or caching has been
422 + * disabled for debugging.
423 + */
424 + if ( $cache_exp_secs > 0 && $read_cache ) {
425 +
426 + if ( $this->p->debug->enabled ) {
427 +
428 + $this->p->debug->log( 'cache index found in transient cache' );
429 +
430 + $this->p->debug->mark( 'building head array' ); // End timer.
431 + }
432 +
433 + return $cache_array[ $cache_index ]; // Stop here.
434 + }
435 +
436 + unset( $cache_array[ $cache_index ] );
437 + }
438 + }
439 +
440 + } else $cache_array = array(); // Initialize a new transient cache array.
441 +
442 + if ( $this->p->debug->enabled ) {
443 +
444 + $this->p->debug->log( 'cache index not in transient cache' );
445 + }
446 +
447 + /*
448 + * Set a general reference value for admin notices.
449 + */
450 + $this->p->util->maybe_set_ref( $canonical_url, $mod );
451 +
452 + /*
453 + * Define the author_id (if one is available).
454 + */
455 + $author_id = WpssoUser::get_author_id( $mod );
456 +
457 + if ( $this->p->debug->enabled ) {
458 +
459 + $this->p->debug->log( 'author_id = ' . ( false === $author_id ? 'false' : $author_id ) );
460 + }
461 +
462 + /*
463 + * Open Graph - define first to pass the mt_og array to other methods.
464 + */
465 + $this->p->util->maybe_set_ref( $canonical_url, $mod, __( 'adding open graph meta tags', 'wpsso' ) );
466 +
467 + $mt_og = $this->p->og->get_array( $mod );
468 +
469 + $this->p->util->maybe_unset_ref( $canonical_url );
470 +
471 + /*
472 + * X (Twitter) Cards.
473 + */
474 + $this->p->util->maybe_set_ref( $canonical_url, $mod, __( 'adding twitter card meta tags', 'wpsso' ) );
475 +
476 + $mt_tc = $this->p->tc->get_array( $mod, $mt_og, $author_id );
477 +
478 + $this->p->util->maybe_unset_ref( $canonical_url );
479 +
480 + /*
481 + * Name / SEO meta tags.
482 + */
483 + $this->p->util->maybe_set_ref( $canonical_url, $mod, __( 'adding meta name meta tags', 'wpsso' ) );
484 +
485 + $mt_name = $this->p->meta_name->get_array( $mod, $mt_og, $author_id );
486 +
487 + $this->p->util->maybe_unset_ref( $canonical_url );
488 +
489 + /*
490 + * Link relation tags.
491 + */
492 + $this->p->util->maybe_set_ref( $canonical_url, $mod, __( 'adding link relation tags', 'wpsso' ) );
493 +
494 + $link_rel = $this->p->link_rel->get_array( $mod, $mt_og, $author_id );
495 +
496 + $this->p->util->maybe_unset_ref( $canonical_url );
497 +
498 + /*
499 + * Schema json scripts.
500 + */
501 + if ( empty( $this->p->avail[ 'p' ][ 'schema' ] ) ) {
502 +
503 + if ( $this->p->debug->enabled ) {
504 +
505 + $this->p->debug->log( 'schema markup is disabled' );
506 + }
507 +
508 + $schema_scripts = array();
509 +
510 + } else {
511 +
512 + $this->p->util->maybe_set_ref( $canonical_url, $mod, __( 'adding schema json-ld markup', 'wpsso' ) );
513 +
514 + $schema_scripts = $this->p->schema->get_array( $mod, $mt_og );
515 +
516 + $this->p->util->maybe_unset_ref( $canonical_url );
517 + }
518 +
519 + /*
520 + * Unset mis-matched og_type meta tags.
521 + */
522 + $mt_og = $this->p->og->sanitize_mt_array( $mt_og );
523 +
524 + /*
525 + * Generator meta tags.
526 + */
527 + $mt_gen = array( 'generator' => $this->p->check->get_ext_gen_list() );
528 +
529 + /*
530 + * Combine and return all meta tags.
531 + */
532 + $cache_array[ $cache_index ] = array_merge(
533 + array( array( $this->get_mt_data( 'begin' ) ) ),
534 + $this->get_mt_array( $tag = 'meta', $type = 'name', $mt_gen, $mod ),
535 + $this->get_mt_array( $tag = 'link', $type = 'rel', $link_rel, $mod ),
536 + $this->get_mt_array( $tag = 'meta', $type = 'property', $mt_og, $mod ),
537 + $this->get_mt_array( $tag = 'meta', $type = 'name', $mt_tc, $mod ),
538 + $this->get_mt_array( $tag = 'meta', $type = 'name', $mt_name, $mod ), // SEO description is last.
539 + $schema_scripts,
540 + array( array( $this->get_mt_data( 'end' ) ), array( $this->get_mt_data( 'cached', $cache_exp_secs ) ) ),
541 + );
542 +
543 + /*
544 + * Unset variables that are no longer needed.
545 + */
546 + unset( $mt_gen, $link_rel, $mt_og, $mt_tc, $mt_name, $schema_scripts );
547 +
548 + /*
549 + * Update the cached array and maintain the existing transient expiration time.
550 + */
551 + if ( $cache_exp_secs > 0 ) {
552 +
553 + $expires_in_secs = SucomUtilWP::update_transient_array( $cache_id, $cache_array, $cache_exp_secs );
554 +
555 + if ( $this->p->debug->enabled ) {
556 +
557 + $this->p->debug->log( 'head array saved to transient cache (expires in ' . $expires_in_secs . ' secs)' );
558 + }
559 + }
560 +
561 + /*
562 + * Unset the general reference value for admin notices.
563 + */
564 + $this->p->util->maybe_unset_ref( $canonical_url );
565 +
566 + if ( $this->p->debug->enabled ) {
567 +
568 + $this->p->debug->mark( 'building head array' ); // End timer.
569 + }
570 +
571 + return $cache_array[ $cache_index ];
572 + }
573 +
574 + /*
575 + * A cache index string based on current locale or $mod array.
576 + *
577 + * $mixed = 'default' | 'current' | post ID | $mod array
578 + *
579 + * See WpssoHead->get_head_array().
580 + * See WpssoIntegEcomWooCommerce->filter_head_cache_index().
581 + */
582 + public function get_head_cache_index( $mixed = 'current', $sep = '_' ) {
583 +
584 + $cache_index = '';
585 +
586 + if ( is_array( $mixed ) ) {
587 +
588 + if ( ! empty( $mixed[ 'paged' ] ) && $mixed[ 'paged' ] > 1 ) { // Greater than 1.
589 +
590 + $cache_index .= $sep . 'paged:' . $mixed[ 'paged' ];
591 + }
592 +
593 + if ( ! empty( $mixed[ 'query_vars' ][ 'cpage' ] ) && $mixed[ 'query_vars' ][ 'cpage' ] > 1 ) { // Greater than 1.
594 +
595 + $cache_index .= $sep . 'cpage:' . $mixed[ 'query_vars' ][ 'cpage' ];
596 + }
597 +
598 + if ( ! empty( $mixed[ 'query_vars' ][ 'order' ] ) ) { // Sort order.
599 +
600 + $cache_index .= $sep . 'order:' . $mixed[ 'query_vars' ][ 'order' ];
601 + }
602 + }
603 +
604 + $cache_index .= $sep . 'locale:' . SucomUtilWP::get_locale( $mixed );
605 +
606 + if ( SucomUtilWP::is_amp() ) $cache_index .= $sep . 'is_amp'; // Returns null, true, or false.
607 +
608 + if ( function_exists( 'is_embed' ) && is_embed() ) $cache_index .= $sep . 'is_embed'; // Embedded pages are "noindex" by default.
609 +
610 + $cache_index .= $sep . 'status:' . $this->p->check->get_ext_status( 'wpsso' );
611 +
612 + $cache_index = apply_filters( 'wpsso_head_cache_index', ltrim( $cache_index, $sep ), $mixed );
613 +
614 + if ( $this->p->debug->enabled ) {
615 +
616 + $this->p->debug->log( 'head cache index = ' . $cache_index );
617 + }
618 +
619 + return $cache_index;
620 + }
621 +
622 + /*
623 + * Extract certain key fields for display and sanity checks.
624 + *
625 + * Save meta tag values for later sorting in list tables.
626 + *
627 + * Called by WpssoAbstractWpMeta->get_head_info().
628 + * Called by WpssoPost->load_meta_page().
629 + * Called by WpssoPost->ajax_get_metabox_sso().
630 + * Called by WpssoTerm->load_meta_page().
631 + * Called by WpssoUser->load_meta_page().
632 + * Called by WpssoUtilCache->refresh_mod_head_meta().
633 + */
634 + public function extract_head_info( array $head_tags, $mod = false ) {
635 +
636 + $head_info = array();
637 +
638 + foreach ( $head_tags as $mt ) {
639 +
640 + if ( ! isset( $mt[ 2 ] ) || ! isset( $mt[ 3 ] ) ) {
641 +
642 + continue;
643 + }
644 +
645 + $mt_match = $mt[ 2 ] . '-' . $mt[ 3 ];
646 +
647 + switch ( $mt_match ) {
648 +
649 + case 'property-og:description':
650 + case 'property-og:locale':
651 + case 'property-og:title':
652 + case 'property-og:type':
653 + case 'property-og:url':
654 + case 'property-article:author:name':
655 + case ( strpos( $mt_match, 'name-schema:' ) === 0 ? true : false ):
656 + case ( strpos( $mt_match, 'name-twitter:' ) === 0 ? true : false ):
657 +
658 + if ( ! isset( $head_info[ $mt[ 3 ] ] ) ) { // Only save the first meta tag value.
659 +
660 + $head_info[ $mt[ 3 ] ] = $mt[ 5 ];
661 + }
662 +
663 + break;
664 +
665 + case ( preg_match( '/^property-((og|p):(image|video))(:secure_url|:url)?$/', $mt_match, $m ) ? true : false ):
666 +
667 + if ( ! empty( $mt[ 5 ] ) ) { // Just in case.
668 +
669 + $has_media[ $m[ 1 ] ] = true; // Optimize media loop.
670 + }
671 +
672 + break;
673 +
674 + case 'property-og:redirect_url':
675 +
676 + if ( ! empty( $mt[ 5 ] ) ) { // Just in case.
677 +
678 + $head_info[ 'is_redirect' ] = '1';
679 + }
680 +
681 + break;
682 +
683 + case 'name-robots':
684 +
685 + if ( ! empty( $mt[ 5 ] ) ) { // Just in case.
686 +
687 + $directives = $this->p->util->robots->get_content_directives( $mt[ 5 ] );
688 +
689 + if ( isset( $directives[ 'noindex' ] ) ) { // Empty string.
690 +
691 + $head_info[ 'is_noindex' ] = '1';
692 + }
693 + }
694 +
695 + break;
696 + }
697 + }
698 +
699 + /*
700 + * Save the first image and video information found.
701 + *
702 + * Assumes array key order defined by SucomUtil::get_mt_image_seed() and SucomUtil::get_mt_video_seed().
703 + */
704 + foreach ( array( 'og:image', 'og:video', 'p:image' ) as $mt_pre ) {
705 +
706 + if ( empty( $has_media[ $mt_pre ] ) ) {
707 +
708 + continue;
709 + }
710 +
711 + $is_first = false;
712 +
713 + foreach ( $head_tags as $mt ) {
714 +
715 + if ( ! isset( $mt[ 2 ] ) || ! isset( $mt[ 3 ] ) ) {
716 +
717 + continue;
718 + }
719 +
720 + if ( strpos( $mt[ 3 ], $mt_pre ) !== 0 ) {
721 +
722 + $is_first = false;
723 +
724 + /*
725 + * If we already found media, then skip to the next media prefix.
726 + */
727 + if ( ! empty( $head_info[ $mt_pre ] ) ) {
728 +
729 + continue 2;
730 +
731 + } else {
732 +
733 + continue; // Skip meta tags without matching prefix.
734 + }
735 + }
736 +
737 + $mt_match = $mt[ 2 ] . '-' . $mt[ 3 ];
738 +
739 + switch ( $mt_match ) {
740 +
741 + case ( preg_match( '/^property-' . $mt_pre . '(:secure_url|:url)?$/', $mt_match, $m ) ? true : false ):
742 +
743 + if ( ! empty( $head_info[ $mt_pre ] ) ) { // Only save the media URL once.
744 +
745 + continue 2; // Get the next meta tag.
746 + }
747 +
748 + if ( ! empty( $mt[ 5 ] ) ) {
749 +
750 + $head_info[ $mt_pre ] = $mt[ 5 ]; // Save the media URL.
751 +
752 + $is_first = true;
753 + }
754 +
755 + break;
756 +
757 + case ( preg_match( '/^property-' . $mt_pre . ':(width|height|cropped|id|title|description)$/', $mt_match, $m ) ? true : false ):
758 +
759 + if ( true !== $is_first ) { // Only save for first media found.
760 +
761 + continue 2; // Get the next meta tag.
762 + }
763 +
764 + $head_info[ $mt[ 3 ] ] = $mt[ 5 ];
765 +
766 + break;
767 + }
768 + }
769 + }
770 +
771 + /*
772 + * Save meta tag values for later sorting in list tables.
773 + *
774 + * Not that the column 'meta_key' value must begin with '_wpsso_head_info_'.
775 + */
776 + if ( isset( $_GET[ 'replytocom' ] ) || is_embed() || is_404() || is_search() ) {
777 +
778 + // Nothing to do.
779 +
780 + } elseif ( ! empty( $mod[ 'obj' ] ) && $mod[ 'id' ] ) {
781 +
782 + $sortable_cols = WpssoAbstractWpMeta::get_sortable_columns(); // Uses a local cache.
783 +
784 + foreach ( $sortable_cols as $col_key => $col_info ) {
785 +
786 + if ( empty( $col_info[ 'meta_key' ] ) ) {
787 +
788 + continue;
789 +
790 + } elseif ( 0 !== strpos( $col_info[ 'meta_key' ], '_wpsso_head_info_' ) ) {
791 +
792 + continue;
793 + }
794 +
795 + $meta_value = $col_info[ 'def_val' ] ? $col_info[ 'def_val' ] : 'none'; // Default value.
796 +
797 + if ( isset( $col_info[ 'mt_name' ] ) ) {
798 +
799 + if ( 'og:image' === $col_info[ 'mt_name' ] ) { // Get the image thumbnail HTML.
800 +
801 + /*
802 + * Example $media_html:
803 + *
804 + * <div class="wp-thumb-bg-img" style="background-image:url(https://.../thumbnail.jpg);"></div>
805 + */
806 + if ( $media_html = $mod[ 'obj' ]->get_head_info_thumb_bg_img( $head_info, $mod ) ) {
807 +
808 + $meta_value = $media_html;
809 + }
810 +
811 + } elseif ( isset( $head_info[ $col_info[ 'mt_name' ] ] ) && '' !== $head_info[ $col_info[ 'mt_name' ] ] ) {
812 +
813 + $meta_value = $head_info[ $col_info[ 'mt_name' ] ];
814 + }
815 + }
816 +
817 + if ( $this->p->debug->enabled ) {
818 +
819 + $this->p->debug->log( 'updating meta for ' . $mod[ 'name' ] . ' id ' . $mod[ 'id' ] . ' ' . $col_key . ' = ' . $meta_value );
820 + }
821 +
822 + $mod[ 'obj' ]->update_sortable_meta( $mod[ 'id' ], $col_key, $meta_value );
823 + }
824 + }
825 +
826 + return $head_info;
827 + }
828 +
829 + private function get_mt_array( $tag, $type, array $mixed, array $mod ) {
830 +
831 + $mt_array = array();
832 +
833 + $this->add_mt_array( $mt_array, $tag, $type, $name = '', $mixed, $cmt = '', $mod, $use_image = true );
834 +
835 + return $mt_array;
836 + }
837 +
838 + /*
839 + * Called by WpssoHead->get_head_array() with $type = 'begin', 'cached', and 'end'.
840 + * Called by WpssoHead->get_head_html() with $type = 'added'.
841 + * Called by WpssoPost->check_post_head() with $type = 'preg'.
842 + * Called by WpssoSsmFilters->strip_schema_microdata() with $type = 'preg'.
843 + */
844 + public function get_mt_data( $type, $args = null ) {
845 +
846 + switch ( $type ) {
847 +
848 + case 'added':
849 +
850 + $total_secs = sprintf( '%f secs', $args );
851 + $human_mem = SucomUtil::format_human_bytes( memory_get_peak_usage(), $dec = 2 );
852 +
853 + return '<meta name="wpsso-' . $type . '" content="' . gmdate( 'c' ) .
854 + ' in ' . $total_secs . ' (' . $human_mem . ' peak)"/>' . "\n";
855 +
856 + case 'begin':
857 + case 'disabled':
858 + case 'end':
859 +
860 + return '<meta name="wpsso-' . $type . '" content="' . WPSSO_DATA_ID . ' ' . $type . '"/>' . "\n";
861 +
862 + case 'cached':
863 +
864 + $home_url = SucomUtilWP::raw_get_home_url();
865 + $home_path = preg_replace( '/^[a-z]+:\/\//i', '', $home_url ); // Remove the protocol prefix.
866 +
867 + /*
868 + * $args = $cache_exp_secs.
869 + */
870 + return '<meta name="wpsso-' . $type . '" content="' . ( $args ? gmdate( 'c' ) : 'no cache' ) . ' for ' . $home_path . '"/>' . "\n";
871 +
872 + /*
873 + * Used by WpssoPost->check_post_head() and WpssoSsmFilters->strip_schema_microdata().
874 + */
875 + case 'preg':
876 +
877 + /*
878 + * Some HTML optimization plugins or services may remove the double-quotes from the name
879 + * attribute, along with the trailing space and slash characters, so make these optional in
880 + * the regex.
881 + */
882 + $preg_prefix = '<(meta[\s\n\r]+name="?wpsso-(begin|end)"?[\s\n\r]+content=")';
883 + $preg_suffix = '("[\s\n\r]*\/?)>';
884 +
885 + /*
886 + * U = Invert greediness of quantifiers, so they are NOT greedy by default, but become greedy if followed by ?.
887 + * u = Modifier to handle UTF-8 in subject strings.
888 + * m = The "^" and "$" constructs match newlines and the complete subject string.
889 + * s = A dot metacharacter in the pattern matches all characters, including newlines.
890 + */
891 + return '/' . $preg_prefix . WPSSO_DATA_ID . ' begin' . $preg_suffix . '.*' .
892 + $preg_prefix . WPSSO_DATA_ID . ' end' . $preg_suffix . '/Uums';
893 +
894 + default:
895 +
896 + return '';
897 + }
898 + }
899 +
900 + /*
901 + * Loops through the arrays and calls self->add_mt_singles() for each.
902 + *
903 + * The $name argument is required for numeric arrays.
904 + *
905 + * $mixed can be a null, boolean, a string, numeric, a numeric array, or an associative array.
906 + */
907 + private function add_mt_array( array &$mt_array, $tag, $type, $name, $mixed, $cmt = '', $mod = false, $use_image = true ) {
908 +
909 + if ( $this->p->debug->enabled ) {
910 +
911 + $this->p->debug->log( $mixed );
912 + }
913 +
914 + if ( is_array( $mixed ) ) {
915 +
916 + if ( SucomUtil::is_assoc( $mixed ) ) {
917 +
918 + if ( isset( $mixed[ 'og:video:type' ] ) ) {
919 +
920 + if ( empty( $mixed[ 'og:video:has_image' ] ) ) {
921 +
922 + if ( $this->p->debug->enabled ) {
923 +
924 + $this->p->debug->log( 'ignoring video preview images' );
925 + }
926 +
927 + $use_image = false;
928 + }
929 + }
930 +
931 + foreach ( $mixed as $key => $value ) {
932 +
933 + $log_prefix = $tag . ' ' . $type . ' ' . $key;
934 +
935 + if ( ! $use_image && 0 === strpos( $key, 'og:image' ) ) {
936 +
937 + if ( $this->p->debug->enabled ) {
938 +
939 + $this->p->debug->log( $log_prefix . ' skipped: use image is false' );
940 + }
941 +
942 + } elseif ( is_array( $value ) ) {
943 +
944 + $opt_key = strtolower( 'add_' . $tag . '_' . $type . '_' . $key );
945 +
946 + if ( ! empty( $this->p->options[ $opt_key ] ) || 0 === strpos( $key, 'wpsso-' ) ) {
947 +
948 + $this->add_mt_array( $mt_array, $tag, $type, $key, $value, $cmt, $mod, $use_image );
949 +
950 + } elseif ( $this->p->debug->enabled ) {
951 +
952 + $this->p->debug->log( $log_prefix . ' array skipped: option is disabled' );
953 + }
954 +
955 + } else $this->add_mt_singles( $mt_array, $tag, $type, $key, $value, $cmt, $mod );
956 + }
957 +
958 + } else {
959 +
960 + foreach ( $mixed as $num => $value ) {
961 +
962 + $cmt_num = ltrim( $cmt . ':' . $name . ':' . ( $num + 1 ), ':' );
963 +
964 + if ( is_array( $value ) ) {
965 +
966 + $this->add_mt_array( $mt_array, $tag, $type, $name, $value, $cmt_num, $mod, $use_image );
967 +
968 + } else $this->add_mt_singles( $mt_array, $tag, $type, $name, $value, $cmt_num, $mod );
969 + }
970 + }
971 +
972 + } else $this->add_mt_singles( $mt_array, $tag, $type, $name, $mixed, $cmt, $mod );
973 +
974 + return $mt_array;
975 + }
976 +
977 + public function add_mt_singles( &$mt_array, $tag, $type, $name, $value, $cmt = '', $mod = false ) {
978 +
979 + static $last_secure_url = null;
980 + static $last_url = null;
981 + static $charset = null;
982 +
983 + if ( null === $charset ) {
984 +
985 + $charset = get_bloginfo( $show = 'charset', $filter = 'raw' );
986 + }
987 +
988 + /*
989 + * Check for known exceptions for the "property" $type.
990 + */
991 + if ( 'meta' === $tag ) {
992 +
993 + if ( 'property' === $type ) {
994 +
995 + /*
996 + * Double-check the name to make sure its an open graph meta tag.
997 + */
998 + switch ( $name ) {
999 +
1000 + /*
1001 + * These names are not open graph meta tag names.
1002 + */
1003 + case ( strpos( $name, 'twitter:' ) === 0 ? true : false ):
1004 + case ( strpos( $name, 'schema:' ) === 0 ? true : false ): // Internal meta tags.
1005 + case ( strpos( $name, ':' ) === false ? true : false ): // No colon in $name.
1006 +
1007 + $type = 'name';
1008 +
1009 + break;
1010 + }
1011 +
1012 + } elseif ( 'itemprop' === $type ) {
1013 +
1014 + /*
1015 + * If an "itemprop" contains a url, then make sure it's a "link".
1016 + */
1017 + if ( $tag !== 'link' && is_string( $value ) && false !== filter_var( $value, FILTER_VALIDATE_URL ) ) {
1018 +
1019 + $tag = 'link';
1020 + }
1021 + }
1022 + }
1023 +
1024 + /*
1025 + * Check for "link rel href" and "link itemprop href" - all other meta tags use a 'content' attribute name.
1026 + */
1027 + $attr = 'link' === $tag ? 'href' : 'content';
1028 +
1029 + $log_prefix = $tag . ' ' . $type . ' ' . $name;
1030 +
1031 + if ( null === $value ) {
1032 +
1033 + if ( $this->p->debug->enabled ) {
1034 +
1035 + $this->p->debug->log( $log_prefix . ' value is null (skipped)' );
1036 + }
1037 +
1038 + return $mt_array;
1039 +
1040 + } elseif ( is_array( $value ) ) {
1041 +
1042 + if ( $this->p->debug->enabled ) {
1043 +
1044 + $this->p->debug->log( $log_prefix . ' value is an array (skipped)' );
1045 + }
1046 +
1047 + return $mt_array;
1048 +
1049 + } elseif ( is_object( $value ) ) {
1050 +
1051 + if ( $this->p->debug->enabled ) {
1052 +
1053 + $this->p->debug->log( $log_prefix . ' value is an object (skipped)' );
1054 + }
1055 +
1056 + return $mt_array;
1057 + }
1058 +
1059 + $singles = array();
1060 +
1061 + switch ( $name ) {
1062 +
1063 + case 'og:image:secure_url':
1064 + case 'og:video:secure_url':
1065 +
1066 + /*
1067 + * The secure url meta tag should always come first, but just in case it doesn't, make sure
1068 + * the url value has not already been added.
1069 + */
1070 + if ( $last_url !== $value ) { // Just in case.
1071 +
1072 + if ( $value ) {
1073 +
1074 + $name_secure_url = $name;
1075 + $name_url = str_replace( ':secure_url', ':url', $name );
1076 + $name_no_url = str_replace( ':secure_url', '', $name );
1077 +
1078 + if ( SucomUtil::is_https( $value ) ) {
1079 +
1080 + $singles[] = array( '', $tag, $type, $name_secure_url, $attr, $value, $cmt );
1081 + }
1082 +
1083 + $singles[] = array( '', $tag, $type, $name_url, $attr, $value, $cmt );
1084 +
1085 + $singles[] = array( '', $tag, $type, $name_no_url, $attr, $value, $cmt );
1086 +
1087 + $last_secure_url = $value;
1088 +
1089 + if ( $this->p->debug->enabled ) {
1090 +
1091 + $this->p->debug->log_arr( 'singles', $singles );
1092 + }
1093 + }
1094 + }
1095 +
1096 + $last_url = null;
1097 +
1098 + break;
1099 +
1100 + case 'og:image:url':
1101 + case 'og:video:url':
1102 +
1103 + /*
1104 + * The previous switch block would have set all three meta tags already, so only proceed if
1105 + * the last secure url (empty or not) is not equal to the current url value (empty or not).
1106 + */
1107 + if ( $last_secure_url !== $value ) { // Just in case.
1108 +
1109 + if ( $value ) {
1110 +
1111 + $name_secure_url = str_replace( ':url', ':secure_url', $name );
1112 + $name_url = $name;
1113 + $name_no_url = str_replace( ':url', '', $name );
1114 +
1115 + if ( SucomUtil::is_https( $value ) ) {
1116 +
1117 + $singles[] = array( '', $tag, $type, $name_secure_url, $attr, $value, $cmt );
1118 + }
1119 +
1120 + $singles[] = array( '', $tag, $type, $name_url, $attr, $value, $cmt );
1121 +
1122 + $singles[] = array( '', $tag, $type, $name_no_url, $attr, $value, $cmt );
1123 +
1124 + $last_url = $value;
1125 +
1126 + if ( $this->p->debug->enabled ) {
1127 +
1128 + $this->p->debug->log_arr( 'singles', $singles );
1129 + }
1130 + }
1131 + }
1132 +
1133 + $last_secure_url = null;
1134 +
1135 + break;
1136 +
1137 + case 'og:image':
1138 + case 'og:video':
1139 +
1140 + /*
1141 + * The previous two switch blocks would have set all three meta tags aready, so only
1142 + * proceed if we have a value, and it does not match the last secure url (empty or not) or
1143 + * the last insecure url (empty or not).
1144 + */
1145 + if ( empty( $value ) || $value === $last_secure_url || $value === $last_url ) {
1146 +
1147 + break;
1148 + }
1149 +
1150 + // No break.
1151 +
1152 + default:
1153 +
1154 + $singles[] = array( '', $tag, $type, $name, $attr, $value, $cmt );
1155 +
1156 + break;
1157 + }
1158 +
1159 +
1160 + /*
1161 + * $parts = array( $html, $tag, $type, $name, $attr, $value, $cmt );
1162 + */
1163 + foreach ( $singles as $num => $parts ) {
1164 +
1165 + if ( ! array_key_exists( 6, $parts ) ) { // Just in case - check for missing $value element.
1166 +
1167 + if ( $this->p->debug->enabled ) {
1168 +
1169 + $this->p->debug->log( 'parts array is incomplete (skipped)' );
1170 +
1171 + $this->p->debug->log_arr( 'parts', $parts );
1172 + }
1173 +
1174 + continue;
1175 + }
1176 +
1177 + $log_prefix = $parts[ 1 ] . ' ' . $parts[ 2 ] . ' ' . $parts[ 3 ];
1178 +
1179 + if ( $this->p->debug->enabled ) {
1180 +
1181 + $this->p->debug->log( $log_prefix . ' = "' . $parts[ 5 ] . '"' );
1182 + }
1183 +
1184 + if ( '' === $parts[ 5 ] || null === $parts[ 5 ] ) { // Allow for 0.
1185 +
1186 + if ( $this->p->debug->enabled ) {
1187 +
1188 + $this->p->debug->log( $log_prefix . ' skipped: value is empty' );
1189 + }
1190 +
1191 + $singles[ $num ][ 5 ] = ''; // Avoid null values in REST API output.
1192 +
1193 + continue;
1194 +
1195 + } elseif ( $parts[ 5 ] === WPSSO_UNDEF || $parts[ 5 ] === (string) WPSSO_UNDEF ) {
1196 +
1197 + if ( $this->p->debug->enabled ) {
1198 +
1199 + $this->p->debug->log( $log_prefix . ' skipped: value is ' . WPSSO_UNDEF );
1200 + }
1201 +
1202 + continue;
1203 + }
1204 +
1205 + /*
1206 + * Encode and escape all values, regardless if the head tag is enabled or not. If the head tag is
1207 + * enabled, HTML will be created and saved in $parts[ 0 ].
1208 + */
1209 + if ( 'itemprop' === $parts[ 2 ] && 0 !== strpos( $parts[ 3 ], '.' ) ) {
1210 +
1211 + $match_name = preg_replace( '/^.*\./', '', $parts[ 3 ] );
1212 +
1213 + } else $match_name = $parts[ 3 ];
1214 +
1215 + /*
1216 + * Boolean values are converted to their string equivalent.
1217 + */
1218 + if ( is_bool( $parts[ 5 ] ) ) {
1219 +
1220 + $parts[ 5 ] = $parts[ 5 ] ? 'true' : 'false';
1221 + }
1222 +
1223 + switch ( $match_name ) {
1224 +
1225 + /*
1226 + * Description values that may include emoji.
1227 + */
1228 + case 'og:title':
1229 + case 'og:description':
1230 + case 'og:image:alt':
1231 + case 'twitter:title':
1232 + case 'twitter:description':
1233 + case 'twitter:image:alt':
1234 + case 'description':
1235 + case 'name':
1236 +
1237 + $parts[ 5 ] = SucomUtil::encode_html_emoji( $parts[ 5 ] ); // Does not double-encode.
1238 +
1239 + break;
1240 +
1241 + /*
1242 + * URL values that must be URL encoded.
1243 + */
1244 + case 'og:secure_url':
1245 + case 'og:url':
1246 + case 'og:image:secure_url':
1247 + case 'og:image:url':
1248 + case 'og:image':
1249 + case 'og:video:embed_url':
1250 + case 'og:video:secure_url':
1251 + case 'og:video:url':
1252 + case 'og:video':
1253 + case 'place:business:menu_url':
1254 + case 'place:business:order_url':
1255 + case 'twitter:image':
1256 + case 'twitter:player':
1257 + case 'canonical':
1258 + case 'shortlink':
1259 + case 'image':
1260 + case 'hasmenu': // Food establishment menu url.
1261 + case 'url':
1262 +
1263 + $parts[ 5 ] = SucomUtil::esc_url_encode( $parts[ 5 ] );
1264 +
1265 + if ( $parts[ 2 ] === 'itemprop' ) { // An itemprop URL must be a 'link'.
1266 +
1267 + $parts[ 1 ] = 'link';
1268 + $parts[ 4 ] = 'href';
1269 + }
1270 +
1271 + break;
1272 +
1273 + /*
1274 + * Allow for mobile app / non-standard protocols.
1275 + */
1276 + case 'al:android:url':
1277 + case 'al:ios:url':
1278 + case 'al:web:url':
1279 + case 'twitter:app:url:iphone':
1280 + case 'twitter:app:url:ipad':
1281 + case 'twitter:app:url:googleplay':
1282 +
1283 + $parts[ 5 ] = SucomUtil::esc_url_encode( $parts[ 5 ], $esc_url = false );
1284 +
1285 + break;
1286 +
1287 + /*
1288 + * Encode html entities for everything else.
1289 + */
1290 + default:
1291 +
1292 + $parts[ 5 ] = htmlentities( $parts[ 5 ], ENT_QUOTES, $charset, $double_encode = false );
1293 +
1294 + break;
1295 + }
1296 +
1297 + /*
1298 + * Convert mixed case itemprop names to lower case.
1299 + */
1300 + $opt_key = strtolower( 'add_' . $parts[ 1 ] . '_' . $parts[ 2 ] . '_' . $parts[ 3 ] );
1301 +
1302 + if ( ! empty( $this->p->options[ $opt_key ] ) || 0 === strpos( $match_name, 'wpsso-' ) ) {
1303 +
1304 + $parts_prefix = empty( $parts[ 6 ] ) ? '' : '<!-- ' . $parts[ 6 ] . ' -->';
1305 +
1306 + $parts[ 0 ] = $parts_prefix . '<' . $parts[ 1 ] . ' ' . $parts[ 2 ] . '="' . $match_name . '" ' .
1307 + $parts[ 4 ] . '="' . $parts[ 5 ] . '"/>' . "\n";
1308 +
1309 + } elseif ( $this->p->debug->enabled ) {
1310 +
1311 + if ( isset( $this->p->options[ $opt_key ] ) ) {
1312 +
1313 + $this->p->debug->log( $log_prefix . ' skipped: option is disabled' );
1314 +
1315 + } else $this->p->debug->log( $log_prefix . ' skipped: internal meta tag' );
1316 + }
1317 +
1318 + $mt_array[] = $parts; // Save the HTML and encoded value.
1319 + }
1320 +
1321 + unset( $singles, $num, $parts );
1322 +
1323 + return $mt_array;
1324 + }
1325 + }
1326 +}