PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.1
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.1
2.14.1 2.14.0 2.13.0 2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 All 56 releases
← All changes | includes/seo/class-schema-input-validator.php +58 -5 2.13.0 → 2.14.1 View file →
@@ -266,9 +266,9 @@
266 266 $result['errors'] = array_merge($result['errors'], $field_validation['errors']);
267 267 }
268 268
269 269 // 6. Validate data types and formats
270 - $format_validation = $this->validate_data_formats($sanitized_data, $schema_type);
270 + $format_validation = $this->validate_data_formats($sanitized_data, $schema_type, $schema_data);
271 271 if (!$format_validation['valid']) {
272 272 $result['errors'] = array_merge($result['errors'], $format_validation['errors']);
273 273 }
274 274 $result['warnings'] = array_merge($result['warnings'], $format_validation['warnings']);
@@ -582,11 +582,12 @@
582 582 * @since 1.0.0
583 583 *
584 584 * @param array $schema_data Schema data
585 585 * @param string $schema_type Schema type
586 + * @param array $raw_data Schema data as submitted, before sanitization.
586 587 * @return array Validation result
587 588 */
588 - private function validate_data_formats(array $schema_data, string $schema_type): array {
589 + private function validate_data_formats(array $schema_data, string $schema_type, array $raw_data = []): array {
589 590 $result = ['valid' => true, 'errors' => [], 'warnings' => []];
590 591
591 592 foreach ($schema_data as $field => $value) {
592 593 // sameAs is a list, so its members never reached the string branch
@@ -592,15 +593,27 @@
592 593 // sameAs is a list, so its members never reached the string branch
593 594 // below and free text entered in a social-profile field saved
594 595 // cleanly, then shipped as invalid structured data (#480).
595 596 if (is_array($value) && in_array($field, ['url', 'sameAs', 'logo', 'image'], true)) {
596 - foreach ($value as $item) {
597 - if (!is_string($item) || '' === trim($item)) {
597 + // Validated after sanitization, but reported as entered:
598 + // esc_url_raw() turns "not a url" into "http://not%20a%20url",
599 + // which the user never typed (#949 review).
600 + $items = $this->url_candidates($value);
601 + $raw_items = is_array($raw_data[$field] ?? null) ? $this->url_candidates($raw_data[$field]) : [];
602 + if (count($raw_items) !== count($items)) {
603 + // A non-string member sanitized into a string would shift
604 + // the positions; fall back rather than name the wrong one.
605 + $raw_items = [];
606 + }
607 +
608 + foreach ($items as $index => $item) {
609 + if ('' === trim($item)) {
598 610 continue;
599 611 }
600 612
601 613 if (!$this->is_valid_url($item)) {
602 - $result['errors'][] = "Invalid URL format for field: {$field} ({$item})";
614 + $shown = $raw_items[$index] ?? $item;
615 + $result['errors'][] = "Invalid URL format for field: {$field} ({$shown})";
603 616 $result['valid'] = false;
604 617 }
605 618 }
606 619
@@ -636,8 +649,47 @@
636 649 return $result;
637 650 }
638 651
639 652 /**
653 + * The URL strings inside an array-valued URL field.
654 + *
655 + * The field is either a list (`sameAs`, several `image` URLs) or a single
656 + * node such as the `ImageObject` Schema_Builder emits for a configured
657 + * logo. Checking every member of a node URL-checked its `@type` and
658 + * `width`, so "ImageObject" failed as an invalid URL and the deploy route
659 + * skipped — then retired — the site's Organization (#949). Only a node's
660 + * `url` / `contentUrl` are URLs; a list may hold strings or such nodes.
661 + *
662 + * @since 2.14.1
663 + *
664 + * @param array $value Array-valued URL field.
665 + * @return string[] URL strings to validate.
666 + */
667 + private function url_candidates(array $value): array {
668 + $node_urls = static function (array $node): array {
669 + return array_values(array_filter(
670 + [$node['url'] ?? null, $node['contentUrl'] ?? null],
671 + 'is_string'
672 + ));
673 + };
674 +
675 + if (array_values($value) !== $value) {
676 + return $node_urls($value);
677 + }
678 +
679 + $urls = [];
680 + foreach ($value as $item) {
681 + if (is_string($item)) {
682 + $urls[] = $item;
683 + } elseif (is_array($item)) {
684 + $urls = array_merge($urls, $node_urls($item));
685 + }
686 + }
687 +
688 + return $urls;
689 + }
690 +
691 + /**
640 692 * Validate content lengths
641 693 *
642 694 * @since 1.0.0
643 695 *
@@ -1014,8 +1066,9 @@
1014 1066 'enable_howto_schema' => 'boolean',
1015 1067 'enable_product_schema' => 'boolean',
1016 1068 'enable_local_business' => 'boolean',
1017 1069 'enable_breadcrumbs_schema' => 'boolean',
1070 + 'enable_accordion_faq_schema' => 'boolean',
1018 1071 ];
1019 1072
1020 1073 foreach ($options as $key => $value) {
1021 1074 $sanitized_key = sanitize_key($key);