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/diagnostics/class-foreign-schema-detector.php +43 -3 2.9.0 → 2.14.1 View file →
@@ -58,11 +58,12 @@
58 58 *
59 59 * `class` matches a token in the script tag's class attribute, which is the
60 60 * strongest signal available — the big four each tag their own block. `comment`
61 61 * matches the HTML comment wrapper a plugin prints around its head output,
62 - * for the ones that carry no class.
62 + * for the ones that carry no class. `id`, where present, matches the script
63 + * tag's id attribute exactly, for a plugin that labels its block that way.
63 64 *
64 - * @var array<string, array{name:string, class:string[], comment:string[]}>
65 + * @var array<string, array{name:string, class:string[], comment:string[], id?:string[]}>
65 66 */
66 67 private const SIGNATURES = [
67 68 'yoast' => [
68 69 'name' => 'Yoast SEO',
@@ -98,8 +99,16 @@
98 99 'name' => 'WooCommerce',
99 100 'class' => [],
100 101 'comment' => ['woocommerce json-ld'],
101 102 ],
103 + // SureRank tags its block by id, not class, and prints it outside
104 + // its "SureRank Meta Data" comment pair (#916).
105 + 'surerank' => [
106 + 'name' => 'SureRank',
107 + 'class' => [],
108 + 'comment' => [],
109 + 'id' => ['surerank-schema'],
110 + ],
102 111 ];
103 112
104 113 /**
105 114 * Schema types worth warning about when both sides publish one.
@@ -356,11 +365,16 @@
356 365 * @return array{slug:string, name:string, guess:bool}
357 366 */
358 367 private function attribute(array $block): array {
359 368 $classes = $this->class_tokens($block['tag']);
369 + $tag_id = $this->tag_id($block['tag']);
360 370 $comment = strtolower($block['preceding']);
361 371
362 372 foreach (self::SIGNATURES as $slug => $signature) {
373 + if ($tag_id !== '' && in_array($tag_id, $signature['id'] ?? [], true)) {
374 + return ['slug' => $slug, 'name' => $signature['name'], 'guess' => false];
375 + }
376 +
363 377 foreach ($signature['class'] as $class) {
364 378 if (in_array($class, $classes, true)) {
365 379 return ['slug' => $slug, 'name' => $signature['name'], 'guess' => false];
366 380 }
@@ -402,8 +416,25 @@
402 416 return $names;
403 417 }
404 418
405 419 /**
420 + * The id attribute of a script tag, lowercased.
421 + *
422 + * @since 2.15.0
423 + *
424 + * @param string $attributes Attribute text from the opening script tag.
425 + * @return string Id, or an empty string when the tag has none.
426 + */
427 + private function tag_id(string $attributes): string {
428 + // Whitespace before the name, so `data-id` is not read as `id`.
429 + if (!preg_match('#(?:^|\s)id\s*=\s*["\']([^"\']*)["\']#i', $attributes, $match)) {
430 + return '';
431 + }
432 +
433 + return strtolower(trim($match[1]));
434 + }
435 +
436 + /**
406 437 * Class tokens on a script tag.
407 438 *
408 439 * @param string $attributes Attribute text from the opening script tag.
409 440 * @return string[]
@@ -483,9 +514,18 @@
483 514 * @param string[] $types Type list.
484 515 * @return string[]
485 516 */
486 517 private function page_level_only(array $types): array {
487 - return array_values(array_intersect($types, self::PAGE_LEVEL_TYPES));
518 + // Every LocalBusiness subtype is the same kind of entity for this
519 + // purpose. ThinkRank publishes the subtype the site chose, so a
520 + // "Dentist" of ours beside another plugin's "LocalBusiness" is still
521 + // two businesses on one URL, and must still be reported as one.
522 + $types = array_map(
523 + static fn($type) => \ThinkRank\Config\Local_Business_Types_Config::is_local_business($type) ? 'LocalBusiness' : $type,
524 + $types
525 + );
526 +
527 + return array_values(array_unique(array_intersect($types, self::PAGE_LEVEL_TYPES)));
488 528 }
489 529
490 530 /**
491 531 * The URL to scan.