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-block-converter.php +57 -9 2.10.0 → 2.13.0 View file →
@@ -388,9 +388,9 @@
388 388 if ('' === $content || !class_exists('WP_Block_Parser')) {
389 389 return $unchanged;
390 390 }
391 391
392 - $spans = self::find_source_blocks($content);
392 + $spans = self::find_blocks($content, [Rank_Math_Blocks::class, 'is_source_block']);
393 393
394 394 if (is_string($spans)) {
395 395 return ['content' => $content, 'converted' => 0, 'error' => $spans];
396 396 }
@@ -425,15 +425,63 @@
425 425 ];
426 426 }
427 427
428 428 /**
429 - * Byte spans of every convertible block, in document order.
429 + * Byte spans of every `thinkrank/faq` block in a post, in document order.
430 430 *
431 + * Exposed so the FAQ abilities can edit FAQ blocks the same way this class
432 + * edits Rank Math ones: by replacing exact byte ranges. A
433 + * parse_blocks()/serialize_blocks() round trip would rewrite every other
434 + * block in the post as a side effect, which is the property the class
435 + * docblock above opens with (#767).
436 + *
437 + * @since 2.10.1
431 438 * @param string $content Post content.
432 439 * @return array<int,array{start:int,end:int,name:string,attrs:array<string,mixed>}>|string
440 + * The spans, or a message describing why the content is unsafe to edit.
441 + */
442 + public static function find_faq_blocks(string $content) {
443 + if (!class_exists('WP_Block_Parser')) {
444 + return [];
445 + }
446 +
447 + return self::find_blocks(
448 + $content,
449 + static fn(string $name): bool => \ThinkRank\SEO\FAQ_Content::FAQ_BLOCK === $name
450 + );
451 + }
452 +
453 + /**
454 + * A `thinkrank/faq` block, serialized exactly as the editor would save it.
455 + *
456 + * Only `faqs` is written: Gutenberg omits attributes that equal their
457 + * registered default, so a block carrying anything else would not match
458 + * what the editor regenerates on the next save.
459 + *
460 + * @since 2.10.1
461 + * @param array<int,array<string,mixed>> $faqs Repeater rows.
462 + * @return string Block markup, or '' when no row carries anything.
463 + */
464 + public static function serialize_faq_block(array $faqs): string {
465 + $attrs = ['faqs' => array_values($faqs)];
466 + $html = self::render_faq_html($attrs);
467 +
468 + if ('' === $html) {
469 + return '';
470 + }
471 +
472 + return self::serialize_block('thinkrank/faq', $attrs, $html);
473 + }
474 +
475 + /**
476 + * Byte spans of every matching block, in document order.
477 + *
478 + * @param string $content Post content.
479 + * @param callable $matches Receives a block name, returns whether to collect it.
480 + * @return array<int,array{start:int,end:int,name:string,attrs:array<string,mixed>}>|string
433 481 * The spans, or a message describing why the content is unsafe to convert.
434 482 */
435 - private static function find_source_blocks(string $content) {
483 + private static function find_blocks(string $content, callable $matches) {
436 484 $parser = new \WP_Block_Parser();
437 485 $parser->document = $content;
438 486 $parser->offset = 0;
439 487
@@ -454,17 +502,17 @@
454 502 }
455 503
456 504 $parser->offset = $start + $length;
457 505
458 - // A stray Rank Math closer with no opener has nothing to convert,
459 - // and removing it is not ours to decide.
460 - if ('block-closer' === $type || !Rank_Math_Blocks::is_source_block((string) $name)) {
506 + // A stray closer with no opener has nothing to convert, and
507 + // removing it is not ours to decide.
508 + if ('block-closer' === $type || !$matches((string) $name)) {
461 509 continue;
462 510 }
463 511
464 - // Attribute text that is not valid JSON decodes to null. Mapping
465 - // that as "no attributes" would convert a block with questions in
466 - // it into an empty one and delete them.
512 + // Attribute text that is not valid JSON decodes to null. Treating
513 + // that as "no attributes" would turn a block with questions in it
514 + // into an empty one and delete them.
467 515 if (!is_array($attrs)) {
468 516 return sprintf('%s at byte %d has attributes that are not valid JSON.', $name, $start);
469 517 }
470 518