PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.3.6
FrontBlocks for Gutenberg/GeneratePress v1.3.6
trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / includes / Frontend / FaqSchema.php
frontblocks / includes / Frontend Last commit date
Animations.php 1 month ago BackButton.php 7 months ago BeforeAfter.php 1 month ago BlockPatterns.php 4 months ago Carousel.php 1 month ago ContainerEdgeAlignment.php 1 month ago Counter.php 1 month ago Events.php 1 month ago FaqSchema.php 1 month ago FluidTypography.php 1 month ago Gallery.php 8 months ago GravityFormsInline.php 1 month ago Headline.php 1 month ago InsertPost.php 1 month ago ProductCategories.php 1 month ago ReadingProgress.php 7 months ago ReadingTime.php 8 months ago ShapeAnimations.php 1 month ago StackedImages.php 4 months ago StickyColumn.php 1 month ago SvgUpload.php 1 month ago Testimonials.php 8 months ago TextAnimation.php 1 month ago UserText.php 1 month ago
FaqSchema.php
122 lines
1 <?php
2 /**
3 * FAQ Schema (JSON-LD) module for FrontBlocks.
4 *
5 * @package FrontBlocks
6 * @author Closemarketing
7 * @copyright 2025 Closemarketing
8 * @version 1.0
9 */
10
11 namespace FrontBlocks\Frontend;
12
13 defined( 'ABSPATH' ) || exit;
14
15 /**
16 * FaqSchema class.
17 *
18 * Collects Q&A pairs from core/details and generateblocks/accordion blocks
19 * that have frblFaqSchema enabled, then outputs a FAQPage JSON-LD in the footer.
20 *
21 * @since 1.0.0
22 */
23 class FaqSchema {
24
25 /**
26 * Collected FAQ entries: array of { question: string, answer: string }.
27 *
28 * @var array
29 */
30 private array $faq_items = array();
31
32 /**
33 * Constructor.
34 */
35 public function __construct() {
36 add_filter( 'render_block_core/accordion', array( $this, 'collect_details_block' ), 10, 2 );
37 add_action( 'wp_footer', array( $this, 'output_json_ld' ), 99 );
38 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor_assets' ) );
39 }
40
41 /**
42 * Collect FAQ entry from a core/details block when frblFaqSchema is enabled.
43 *
44 * @param string $block_content Rendered HTML.
45 * @param array $block Block data.
46 * @return string Unchanged block HTML.
47 */
48 public function collect_details_block( string $block_content, array $block ): string {
49 if ( empty( $block['attrs']['frblFaqSchema'] ) ) {
50 return $block_content;
51 }
52
53 preg_match_all( '/<span[^>]+class="[^"]*wp-block-accordion-heading__toggle-title[^"]*"[^>]*>(.*?)<\/span>/is', $block_content, $questions );
54 preg_match_all( '/<div[^>]+class="[^"]*wp-block-accordion-panel[^"]*"[^>]*>(.*?)<\/div>\s*(?:<\/div>|$)/is', $block_content, $answers );
55
56 foreach ( $questions[1] as $i => $raw_question ) {
57 $question = trim( wp_strip_all_tags( $raw_question ) );
58 $answer = isset( $answers[1][ $i ] ) ? trim( wp_strip_all_tags( $answers[1][ $i ] ) ) : '';
59
60 if ( '' !== $question && '' !== $answer ) {
61 $this->faq_items[] = array(
62 'question' => $question,
63 'answer' => $answer,
64 );
65 }
66 }
67
68 return $block_content;
69 }
70
71 /**
72 * Output the FAQPage JSON-LD script in the footer.
73 *
74 * @return void
75 */
76 public function output_json_ld(): void {
77 if ( empty( $this->faq_items ) ) {
78 return;
79 }
80
81 $entities = array();
82 foreach ( $this->faq_items as $item ) {
83 $entities[] = array(
84 '@type' => 'Question',
85 'name' => $item['question'],
86 'acceptedAnswer' => array(
87 '@type' => 'Answer',
88 'text' => $item['answer'],
89 ),
90 );
91 }
92
93 $schema = array(
94 '@context' => 'https://schema.org',
95 '@type' => 'FAQPage',
96 'mainEntity' => $entities,
97 );
98
99 echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . '</script>' . "\n";
100 }
101
102 /**
103 * Enqueue editor assets for the FAQ Schema inspector toggle.
104 *
105 * @return void
106 */
107 public function enqueue_editor_assets(): void {
108 $asset_file = FRBL_PLUGIN_PATH . 'assets/faq-schema/frontblocks-faq-schema.js';
109 if ( ! file_exists( $asset_file ) ) {
110 return;
111 }
112
113 wp_enqueue_script(
114 'frontblocks-faq-schema',
115 FRBL_PLUGIN_URL . 'assets/faq-schema/frontblocks-faq-schema.js',
116 array( 'wp-hooks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-compose' ),
117 FRBL_VERSION,
118 true
119 );
120 }
121 }
122