PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.13.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.13.0
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 1.26.0 All 55 releases
← All changes | includes/admin/importers/class-abstract-plugin-exporter.php +147 -11 1.30.0 → 2.13.0 View file →
@@ -152,8 +152,26 @@
152 152 return [];
153 153 }
154 154
155 155 /**
156 + * Records for a data type outside the fixed set above.
157 + *
158 + * Nothing to hand over by default. It exists so an exporter can gain a type
159 + * (ThinkRank's own export lets Pro register its tables this way) without
160 + * every subclass having to reimplement export_chunk()'s chunk writing and
161 + * manifest bookkeeping.
162 + *
163 + * @since 2.2.0
164 + *
165 + * @param string $type Data type
166 + * @param int $page Page number (1-indexed)
167 + * @return array Records
168 + */
169 + protected function export_custom_type_page(string $type, int $page): array {
170 + return [];
171 + }
172 +
173 + /**
156 174 * Capture the source plugin's Role Manager assignments: role slug => the
157 175 * capabilities that role holds whose name starts with $prefix.
158 176 *
159 177 * Shared by every exporter because role capabilities live on the roles
@@ -209,9 +227,9 @@
209 227 * @param mixed $value Value potentially containing template variables
210 228 * @param int|null $post_id Post ID for context-specific variables
211 229 * @return string Converted string
212 230 */
213 - abstract protected function convert_template_variables(mixed $value, ?int $post_id = null): string;
231 + abstract protected function convert_template_variables($value, ?int $post_id = null): string;
214 232
215 233 /**
216 234 * Coerce a foreign settings/meta value into a template string.
217 235 *
@@ -223,9 +241,9 @@
223 241 *
224 242 * @param mixed $value Raw value from the source plugin's storage.
225 243 * @return string Usable template string, possibly ''.
226 244 */
227 - final protected function stringify_template_value(mixed $value): string {
245 + final protected function stringify_template_value($value): string {
228 246 if (is_string($value)) {
229 247 return $value;
230 248 }
231 249
@@ -236,8 +254,113 @@
236 254 return '';
237 255 }
238 256
239 257 /**
258 + * Placeholder a mapped token travels as while the rest of a template is
259 + * resolved literally. A control character no source vocabulary uses, so
260 + * no literal resolver matches, strips or splits on it.
261 + */
262 + private const TOKEN_MARK = "\x1A";
263 +
264 + /**
265 + * Convert a per-object (post or term) SEO value into ThinkRank variable
266 + * tags wherever ThinkRank resolves the same thing per request, and into
267 + * literal text everywhere else.
268 + *
269 + * A source title such as "%%title%% %%sep%% %%sitename%%" used to be
270 + * resolved to literal text at import, so the site name, separator and
271 + * post title were frozen as they stood that day: renaming the site or
272 + * switching the separator left every imported title behind, and every
273 + * exporter wrote " - " whatever separator the source used. Mapped tokens
274 + * now become `%title%`, `%sep%`, `%sitename%`… — the tags the metabox
275 + * inserts and Pattern_Resolver resolves on every output path — and only
276 + * tokens ThinkRank has no per-object equivalent for are resolved here.
277 + *
278 + * Mapped tokens are masked before `$resolve_rest` runs, so a literal
279 + * resolver that knows them (Rank Math's shares ThinkRank's syntax) never
280 + * sees them, and unmasked afterwards. A value with no mapped token is
281 + * returned exactly as `$resolve_rest` produces it.
282 + *
283 + * @param string $value Raw source value (already stringified)
284 + * @param array $token_map Regex matching a source token => ThinkRank tag
285 + * @param callable $resolve_rest fn(string): string — the exporter's literal resolver
286 + * @return string ThinkRank value
287 + */
288 + final protected function tokenize_object_template(string $value, array $token_map, callable $resolve_rest): string {
289 + $tags = [];
290 + $masked = $value;
291 +
292 + if ($value !== '') {
293 + foreach ($token_map as $pattern => $tag) {
294 + $masked = (string) preg_replace_callback(
295 + $pattern,
296 + static function () use (&$tags, $tag): string {
297 + $index = array_search($tag, $tags, true);
298 + if ($index === false) {
299 + $tags[] = $tag;
300 + $index = count($tags) - 1;
301 + }
302 +
303 + return self::TOKEN_MARK . $index . self::TOKEN_MARK;
304 + },
305 + $masked
306 + );
307 + }
308 + }
309 +
310 + if (empty($tags)) {
311 + return (string) $resolve_rest($value);
312 + }
313 +
314 + $resolved = (string) preg_replace_callback(
315 + '/' . self::TOKEN_MARK . '(\d+)' . self::TOKEN_MARK . '/',
316 + static fn(array $m): string => $tags[(int) $m[1]] ?? '',
317 + (string) $resolve_rest($masked)
318 + );
319 +
320 + // Tokens resolved to '' (e.g. %%page%%) leave separators with nothing
321 + // between or around them; Pattern_Resolver tidies those too, but the
322 + // stored value should read cleanly in the metabox.
323 + $resolved = trim((string) preg_replace('/\s+/', ' ', $resolved));
324 + $resolved = (string) preg_replace('/%sep%(?:\s*%sep%)+/', '%sep%', $resolved);
325 + $resolved = (string) preg_replace('/^(?:%sep%\s*)+|(?:\s*%sep%)+$/', '', $resolved);
326 +
327 + return trim($resolved);
328 + }
329 +
330 + /**
331 + * Token map for tokenize_object_template() from literal source tokens
332 + * (`%%title%%`, `%title%`), matched case-insensitively.
333 + *
334 + * @param array<string,string> $map Source token => ThinkRank tag
335 + * @return array<string,string> Regex => ThinkRank tag
336 + */
337 + final protected static function literal_token_patterns(array $map): array {
338 + $patterns = [];
339 + foreach ($map as $token => $tag) {
340 + $patterns['/' . preg_quote((string) $token, '/') . '/i'] = $tag;
341 + }
342 +
343 + return $patterns;
344 + }
345 +
346 + /**
347 + * Token map for tokenize_object_template() from brace-style variable
348 + * names (`title` matches `{{title}}` and `{{ title }}`).
349 + *
350 + * @param array<string,string> $map Variable name => ThinkRank tag
351 + * @return array<string,string> Regex => ThinkRank tag
352 + */
353 + final protected static function brace_token_patterns(array $map): array {
354 + $patterns = [];
355 + foreach ($map as $name => $tag) {
356 + $patterns['/\{\{\s*' . preg_quote((string) $name, '/') . '\s*\}\}/i'] = $tag;
357 + }
358 +
359 + return $patterns;
360 + }
361 +
362 + /**
240 363 * Export a chunk of data and write to snapshot
241 364 *
242 365 * This is the main orchestration method. It calls the appropriate
243 366 * export_*_page() method, writes the chunk via Snapshot_Store, and
@@ -251,17 +374,30 @@
251 374 // Reset before the page method runs; it (or its id helper) records the
252 375 // raw fetched-row count here.
253 376 $this->last_page_row_count = null;
254 377
255 - $records = match ($type) {
256 - 'postmeta' => $this->export_postmeta_page($page),
257 - 'termmeta' => $this->export_termmeta_page($page),
258 - 'usermeta' => $this->export_usermeta_page($page),
259 - 'settings' => $this->export_settings(),
260 - 'redirections' => $this->export_redirections_page($page),
261 - '404_logs' => $this->export_404_logs_page($page),
262 - default => [],
263 - };
378 + switch ($type) {
379 + case 'postmeta':
380 + $records = $this->export_postmeta_page($page);
381 + break;
382 + case 'termmeta':
383 + $records = $this->export_termmeta_page($page);
384 + break;
385 + case 'usermeta':
386 + $records = $this->export_usermeta_page($page);
387 + break;
388 + case 'settings':
389 + $records = $this->export_settings();
390 + break;
391 + case 'redirections':
392 + $records = $this->export_redirections_page($page);
393 + break;
394 + case '404_logs':
395 + $records = $this->export_404_logs_page($page);
396 + break;
397 + default:
398 + $records = $this->export_custom_type_page($type, $page);
399 + }
264 400
265 401 $exported_count = count($records);
266 402
267 403 // Write chunk to snapshot store