PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.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 1.26.0 1.25.0 trunk 1.0.0 1.0.1 All 51 releases
← All changes | includes/seo/class-schema-management-system.php +57 -2 2.7.0 → 2.10.0 View file →
@@ -201,13 +201,43 @@
201 201 'WebPage' => [
202 202 'name' => 'WebPage',
203 203 'description' => 'Individual web pages',
204 204 'required_properties' => ['name', 'url'],
205 + // 'post' as well as 'page': the per-page selector reaches this for
206 + // any post type, and a registry limited to 'page' silently produced
207 + // nothing for the rest (#624).
205 208 'recommended_properties' => ['description', 'author', 'datePublished', 'breadcrumb'],
206 209 'rich_snippets' => ['webpage', 'breadcrumb'],
207 - 'context_types' => ['page'],
210 + 'context_types' => ['page', 'post'],
208 211 'priority' => 'medium'
209 212 ],
213 + 'AboutPage' => [
214 + 'name' => 'AboutPage',
215 + 'description' => 'A page describing the organisation or person behind the site',
216 + 'required_properties' => ['name', 'url'],
217 + 'recommended_properties' => ['description', 'author', 'datePublished', 'breadcrumb'],
218 + 'rich_snippets' => ['webpage', 'breadcrumb'],
219 + 'context_types' => ['page', 'post'],
220 + 'priority' => 'medium'
221 + ],
222 + 'ContactPage' => [
223 + 'name' => 'ContactPage',
224 + 'description' => 'A page giving contact details',
225 + 'required_properties' => ['name', 'url'],
226 + 'recommended_properties' => ['description', 'author', 'datePublished', 'breadcrumb'],
227 + 'rich_snippets' => ['webpage', 'breadcrumb'],
228 + 'context_types' => ['page', 'post'],
229 + 'priority' => 'medium'
230 + ],
231 + 'ProfilePage' => [
232 + 'name' => 'ProfilePage',
233 + 'description' => 'A page about a single person or organisation',
234 + 'required_properties' => ['name', 'url'],
235 + 'recommended_properties' => ['description', 'author', 'datePublished', 'breadcrumb'],
236 + 'rich_snippets' => ['webpage', 'breadcrumb'],
237 + 'context_types' => ['page', 'post'],
238 + 'priority' => 'medium'
239 + ],
210 240 'FAQPage' => [
211 241 'name' => 'FAQPage',
212 242 'description' => 'Frequently Asked Questions pages',
213 243 'required_properties' => ['mainEntity'],
@@ -2258,9 +2288,12 @@
2258 2288 }
2259 2289
2260 2290 $type = $schema['@type'] ?? '';
2261 2291 $type = is_array($type) ? reset($type) : $type;
2262 - $is_entity = in_array((string) $type, self::ENTITY_URL_TYPES, true);
2292 + // A LocalBusiness is deployed under the subtype the site chose, so the
2293 + // exemption has to cover every subtype, not only the literal root.
2294 + $is_entity = in_array((string) $type, self::ENTITY_URL_TYPES, true)
2295 + || \ThinkRank\Config\Local_Business_Types_Config::is_local_business($type);
2263 2296
2264 2297 if (isset($schema['url']) && !$is_entity) {
2265 2298 $schema['url'] = $permalink;
2266 2299 }
@@ -2288,9 +2321,15 @@
2288 2321 * Covers non-ISO-8601 dates (#465) and WP locales in inLanguage, which must
2289 2322 * be a BCP-47 tag — en-US, not en_US (#473). Walks nested nodes so values
2290 2323 * inside author/publisher/@graph entries are covered too.
2291 2324 *
2325 + * Also decodes HTML entities in plain-text properties. Schema_Builder
2326 + * stored the block editor's `&` as-is until 2.10.0, and nothing
2327 + * decodes JSON-LD downstream, so every deployed node built from post text
2328 + * published the entity literally.
2329 + *
2292 2330 * @since 1.16.0
2331 + * @since 2.10.0 Decodes entities in plain-text properties.
2293 2332 *
2294 2333 * @param array $schema Decoded schema data.
2295 2334 * @return array Normalised schema.
2296 2335 */
@@ -2299,8 +2338,16 @@
2299 2338 'datePublished', 'dateModified', 'dateCreated', 'uploadDate',
2300 2339 'startDate', 'endDate', 'validFrom', 'validThrough', 'expires',
2301 2340 ];
2302 2341
2342 + // Plain text in schema.org. Answer/HowToStep `text` is deliberately
2343 + // absent: Google reads Answer.text as HTML, where an entity is correct
2344 + // and decoding `<` would turn escaped text into live markup.
2345 + static $text_keys = [
2346 + 'name', 'headline', 'alternativeHeadline', 'description',
2347 + 'reviewBody', 'about', 'abstract', 'caption',
2348 + ];
2349 +
2303 2350 foreach ($schema as $key => $value) {
2304 2351 if (is_array($value)) {
2305 2352 $schema[$key] = $this->normalize_stored_schema($value);
2306 2353 continue;
@@ -2307,8 +2354,16 @@
2307 2354 }
2308 2355
2309 2356 if ('inLanguage' === $key && is_string($value) && '' !== $value) {
2310 2357 $schema[$key] = str_replace('_', '-', $value);
2358 + continue;
2359 + }
2360 +
2361 + // Decode only: a snapshot already truncated with an ellipsis must
2362 + // keep it, which the full Seo_Text::normalize_schema_text() would
2363 + // strip as an excerpt marker.
2364 + if (in_array($key, $text_keys, true) && is_string($value) && '' !== $value) {
2365 + $schema[$key] = \ThinkRank\Core\Seo_Text::decode_schema_entities($value);
2311 2366 continue;
2312 2367 }
2313 2368
2314 2369 if (!in_array($key, $date_keys, true) || !is_string($value) || '' === $value) {