PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.3
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.3
2.14.3 2.14.2 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 All 58 releases
← All changes | includes/core/class-seo-text.php +59 -0 2.12.0 → 2.14.3 View file →
@@ -102,8 +102,38 @@
102 102 return html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
103 103 }
104 104
105 105 /**
106 + * Strip leading and trailing whitespace and title separators.
107 + *
108 + * The separator is removed as a whole string, never byte by byte.
109 + * `trim($title, " \t..." . $separator)` reads its second argument as a
110 + * list of single bytes, so with the Bullet separator (`•`, E2 80 A2) it
111 + * also stripped any E2, 80 or A2 byte at either end of the title: the
112 + * middle of €, ✅, curly quotes and many Cyrillic letters. The broken
113 + * UTF-8 left behind made `<title>` invalid and `esc_attr()` blank out
114 + * og:title and twitter:title (#972).
115 + *
116 + * @since 2.15.0
117 + *
118 + * @param string $value Title or description.
119 + * @param string $separator The active separator, possibly multibyte.
120 + * @return string
121 + */
122 + public static function trim_separator(string $value, string $separator): string {
123 + $alternatives = '[\s\x00]';
124 + if ('' !== $separator) {
125 + $alternatives .= '|' . preg_quote($separator, '/');
126 + }
127 +
128 + $trimmed = preg_replace('/^(?:' . $alternatives . ')+|(?:' . $alternatives . ')+$/u', '', $value);
129 +
130 + // Invalid UTF-8 makes a /u pattern fail. Fall back to whitespace only,
131 + // which cannot cut a character in half.
132 + return null === $trimmed ? trim($value) : $trimmed;
133 + }
134 +
135 + /**
106 136 * Trim a description to a character budget, multibyte-safe.
107 137 *
108 138 * Cuts on a word boundary when one is available inside the budget, so the
109 139 * result does not end mid-word; falls back to a hard character cut for
@@ -175,8 +205,37 @@
175 205 '/\s*(\[\s*(\x{2026}|\.\.\.)\s*\]|\x{2026})\s*$/u',
176 206 '',
177 207 $text
178 208 );
209 +
210 + return trim((string) preg_replace('/\s+/u', ' ', $text));
211 + }
212 +
213 + /**
214 + * Make a title or name fit to appear in JSON-LD.
215 + *
216 + * get_the_title() runs wptexturize and keeps any markup in the title, and
217 + * WordPress stores term names and the site name esc_html()'d, so a page
218 + * called "R&D at Tom's" reached the graph as `R&#038;D at Tom&#8217;s` and
219 + * a category "Cats & Dogs" as `Cats &amp; Dogs`. Nothing downstream
220 + * decodes JSON-LD, so those were published literally (#976).
221 + *
222 + * normalize_schema_text() without its excerpt-marker strip: that marker
223 + * is an artefact of a derived description, while a title that ends in an
224 + * ellipsis was written that way and keeps it. A decoded `<` stays safe
225 + * because the graph is encoded with JSON_HEX_TAG (#118).
226 + *
227 + * @since 2.15.0
228 + *
229 + * @param string $text Raw title or name.
230 + * @return string Plain text with tags removed and entities decoded.
231 + */
232 + public static function normalize_schema_name(string $text): string {
233 + if ('' === trim($text)) {
234 + return '';
235 + }
236 +
237 + $text = self::decode_schema_entities(wp_strip_all_tags($text));
179 238
180 239 return trim((string) preg_replace('/\s+/u', ' ', $text));
181 240 }
182 241