PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Editors/BlockEditor/Blocks/FAQ.php +162 -100 3.5.04.9.2 View file →
@@ -1,123 +1,185 @@
1 1 <?php
2 +namespace WPDeveloper\BetterDocs\Editors\BlockEditor\Blocks;
2 3
3 -namespace WPDeveloper\BetterDocs\Editors\BlockEditor\Blocks;
4 +if ( ! defined( 'ABSPATH' ) ) {
5 + exit;
6 +}
4 7
8 +
5 9 use WPDeveloper\BetterDocs\Editors\BlockEditor\Block;
10 +use WPDeveloper\BetterDocs\FrontEnd\WooProductFAQ as WooFAQRenderer;
6 11
7 12 class FAQ extends Block {
8 - protected $editor_scripts = [
9 - 'betterdocs-faq',
10 - 'betterdocs-blocks-editor'
11 - ];
13 + protected $editor_scripts = [
14 + 'betterdocs-faq',
15 + 'betterdocs-blocks-editor'
16 + ];
12 17
13 - protected $editor_styles = [
14 - 'betterdocs-faq',
15 - 'betterdocs-blocks-editor'
16 - ];
18 + protected $editor_styles = [
19 + 'betterdocs-faq',
20 + 'betterdocs-blocks-editor'
21 + ];
17 22
18 - protected $frontend_styles = [
19 - 'betterdocs-faq'
20 - ];
23 + protected $frontend_styles = [
24 + 'betterdocs-faq'
25 + ];
21 26
22 - protected $frontend_scripts = [
23 - 'betterdocs-faq'
24 - ];
27 + protected $frontend_scripts = [
28 + 'betterdocs-faq'
29 + ];
25 30
26 - /**
27 - * unique name of block
28 - * @return string
29 - */
30 - public function get_name() {
31 - return 'faq';
32 - }
31 + /**
32 + * unique name of block
33 + * @return string
34 + */
35 + public function get_name() {
36 + return 'faq';
37 + }
33 38
34 - public function get_default_attributes() {
35 - return [
36 - 'blockId' => '',
37 - 'resOption' => 'Desktop',
38 - 'blockRoot' => 'better_docs',
39 - 'blockMeta' => null,
40 - 'faqLayout' => 'layout-1',
41 - 'faqSectionText' => 'Frequently Asked Questions',
42 - 'faqSectionTitleColor' => null,
43 - 'includeFaqGroup' => '',
44 - 'excludeFaqGroup' => '',
45 - 'faqGroupTitleColor' => null,
46 - 'faqGroupTitleHoverColor' => null,
47 - 'faqListColor' => null,
48 - 'faqContentColor' => null,
49 - 'faqIconColor' => null,
50 - 'faqGroupTitleTypography' => null,
51 - 'faqPerPage' => 9,
39 + public function get_default_attributes() {
40 + return [
41 + 'blockId' => '',
42 + 'resOption' => 'Desktop',
43 + 'blockRoot' => 'better_docs',
44 + 'blockMeta' => null,
45 + 'faqLayout' => 'layout-1',
46 + 'faqSectionText' => 'Frequently Asked Questions',
47 + 'faqSectionTitleTag' => 'h2',
48 + 'faqGroupTitleTag' => 'h3',
49 + 'faqSectionTitleColor' => null,
50 + // JSON string of `[{value:int, label:string}]`; a bare id array (`[5]`) is also accepted since 4.9.0.
51 + 'includeFaqGroup' => '',
52 + 'excludeFaqGroup' => '',
53 + 'faqGroupTitleColor' => null,
54 + 'faqGroupTitleHoverColor' => null,
55 + 'faqListColor' => null,
56 + 'faqContentColor' => null,
57 + 'faqIconColor' => null,
58 + 'faqGroupTitleTypography' => null,
59 + 'faqPerPage' => 9,
52 60
53 - 'showButtonIcon' => true,
54 - 'buttonColor' => '#528ffe'
55 - ];
56 - }
61 + 'showButtonIcon' => true,
62 + 'buttonColor' => '#528ffe',
63 + 'faqTaxonomy' => 'betterdocs_faq_category'
64 + ];
65 + }
57 66
58 - public function render( $attributes, $content ) {
67 + public function render( $attributes, $content ) {
68 + if ( ( $this->attributes['faqTaxonomy'] ?? '' ) === 'product_assignments' ) {
69 + $this->render_product_assignments();
70 + return;
71 + }
59 72
60 - add_filter( 'betterdocs_header_layout_sequence', [$this, 'header_sequence'], 10, 4 );
61 - $this->views( 'layouts/faq' );
62 - remove_filter( 'betterdocs_header_layout_sequence', [$this, 'header_sequence'], 10 );
63 - }
73 + add_filter( 'betterdocs_header_layout_sequence', [ $this, 'header_sequence' ], 10, 4 );
74 + $this->views( 'layouts/faq' );
75 + remove_filter( 'betterdocs_header_layout_sequence', [ $this, 'header_sequence' ], 10 );
76 + }
64 77
65 - public function get_groups_ids( $json ) {
66 - $data = json_decode( $json, true );
67 - $ids = '';
68 - if ( $data !== null ) {
69 - $ids = implode( ',', array_column( $data, 'value' ) );
70 - }
78 + private function render_product_assignments() {
79 + if ( ! class_exists( 'WooCommerce' ) || ! is_product() ) {
80 + return;
81 + }
71 82
72 - return $ids;
73 - }
83 + // In FSE templates, top-level blocks render outside the loop, so
84 + // get_the_ID() can be 0 — fall back to the queried product.
85 + $product_id = get_the_ID();
86 + if ( ! $product_id ) {
87 + $product_id = get_queried_object_id();
88 + }
89 + if ( ! $product_id ) {
90 + return;
91 + }
74 92
75 - public function view_params() {
76 - $attributes = &$this->attributes;
77 - $exclude = $this->get_groups_ids( $attributes['excludeFaqGroup'] );
78 - $include = $this->get_groups_ids( $attributes['includeFaqGroup'] );
93 + /** @var WooFAQRenderer $renderer */
94 + $renderer = betterdocs()->container->get( WooFAQRenderer::class );
79 95
80 - $terms_query = betterdocs()->query->faq_terms_query_args( $include, $exclude );
96 + // This block is the explicit placement; don't also inject into the tab/summary.
97 + $renderer->suppress_auto_placement();
98 + $renderer->render_for_product( $product_id, $this->attributes['faqLayout'] ?? null );
99 + }
81 100
82 - return wp_parse_args( [
83 - 'enable' => true,
84 - 'have_posts' => true,
85 - 'widget' => $this,
86 - 'layout' => $attributes['faqLayout'],
87 - 'terms_query_args' => $terms_query,
88 - 'shortcode_attr' => [
89 - 'group_exclude' => $exclude,
90 - 'class' => 'betterdocs-faq-' . $attributes['faqLayout'] . ' ' . $attributes['blockId'],
91 - 'groups' => $include,
92 - 'is_gutenberg' => true,
93 - 'faq_heading' => $attributes['faqSectionText'],
94 - 'faq_section_title_color' => $attributes['faqSectionTitleColor'],
95 - 'include_faq_group' => $attributes['includeFaqGroup'],
96 - 'exclude_faq_group' => $attributes['excludeFaqGroup'],
97 - 'faq_group_title_color' => $attributes['faqGroupTitleColor'],
98 - 'faq_group_title_hover_color' => $attributes['faqGroupTitleHoverColor'],
99 - 'faq_list_color' => $attributes['faqListColor'],
100 - 'faq_content_color' => $attributes['faqContentColor'],
101 - 'faq_icon_color' => $attributes['faqIconColor'],
102 - 'faq_group_title_typography' => $attributes['faqGroupTitleTypography'],
103 - 'faq_per_page' => $attributes['faqPerPage'],
104 - 'show_button_icon' => $attributes['showButtonIcon'],
105 - 'button_icon_position' => $attributes['faqLayout'] == 'layout-1' ? 'after' : 'before',
106 - 'button_color' => $attributes['buttonColor']
107 - ]
101 + /**
102 + * Flatten a group-selection attribute into a comma separated list of term ids.
103 + *
104 + * The attribute is a JSON string. The editor writes
105 + * `[{"value":5,"label":"Install"}]`; REST clients and other programmatic
106 + * writers commonly write the bare form `[5]`. Both are accepted — before
107 + * 4.9.0 the bare form flattened to an empty string, which
108 + * `Query::faq_terms_query_args()` reads as "no filter", so every group
109 + * rendered and the block warned on every view.
110 + *
111 + * @since 4.9.0 Bare ids are accepted alongside `{ value, label }` objects;
112 + * ids are cast with `absint()` and de-duplicated.
113 + *
114 + * @param mixed $json JSON encoded list of term ids or `{ value, label }` objects.
115 + *
116 + * @return string Comma separated term ids, or '' when there is nothing to filter by.
117 + */
118 + public function get_groups_ids( $json ) {
119 + $data = is_string( $json ) ? json_decode( $json, true ) : $json;
120 + $ids = array_filter( self::normalize_id_list( $data ), 'is_numeric' );
121 + $ids = array_unique( array_map( 'absint', $ids ) );
108 122
109 - ] );
110 - }
123 + return implode( ',', $ids );
124 + }
111 125
112 - public function header_sequence( $_layout_sequence, $layout, $widget_type, $_defined_vars ) {
113 - $_new_layout_sequence = [
114 - [
115 - 'class' => 'betterdocs-category-title-counts',
116 - 'sequence' => ['category_title', 'category_counts']
117 - ],
118 - 'category_description'
119 - ];
126 + public function view_params() {
127 + $attributes = &$this->attributes;
120 128
121 - return $_new_layout_sequence;
122 - }
129 + // Sanitize the layout attribute to prevent path traversal (LFI).
130 + $attributes['faqLayout'] = sanitize_file_name( $attributes['faqLayout'] );
131 + $exclude = $this->get_groups_ids( $attributes['excludeFaqGroup'] );
132 + $include = $this->get_groups_ids( $attributes['includeFaqGroup'] );
133 + $taxonomy = sanitize_key( $attributes['faqTaxonomy'] ?? 'betterdocs_faq_category' );
134 +
135 + $terms_query = betterdocs()->query->faq_terms_query_args( $include, $exclude, [], $taxonomy );
136 +
137 + return wp_parse_args(
138 + [
139 + 'enable' => true,
140 + 'have_posts' => true,
141 + 'widget' => $this,
142 + 'layout' => $attributes['faqLayout'],
143 + 'terms_query_args' => $terms_query,
144 + 'shortcode_attr' => [
145 + 'group_exclude' => $exclude,
146 + 'class' => 'betterdocs-faq-' . $attributes['faqLayout'] . ' ' . $attributes['blockId'],
147 + 'groups' => $include,
148 + 'is_gutenberg' => true,
149 + 'faq_heading' => $attributes['faqSectionText'],
150 + 'faq_section_title_tag' => $attributes['faqSectionTitleTag'],
151 + 'faq_group_title_tag' => $attributes['faqGroupTitleTag'],
152 + 'faq_section_title_color' => $attributes['faqSectionTitleColor'],
153 + 'include_faq_group' => $this->string_to_array( $attributes['includeFaqGroup'] ),
154 + 'exclude_faq_group' => $this->string_to_array( $attributes['excludeFaqGroup'] ),
155 + 'faq_group_title_color' => $attributes['faqGroupTitleColor'],
156 + 'faq_group_title_hover_color' => $attributes['faqGroupTitleHoverColor'],
157 + 'faq_list_color' => $attributes['faqListColor'],
158 + 'faq_content_color' => $attributes['faqContentColor'],
159 + 'faq_icon_color' => $attributes['faqIconColor'],
160 + 'faq_group_title_typography' => $attributes['faqGroupTitleTypography'],
161 + 'faq_per_page' => $attributes['faqPerPage'],
162 + 'show_button_icon' => $attributes['showButtonIcon'],
163 + 'button_icon_position' => $attributes['faqLayout'] == 'layout-1' || $attributes['faqLayout'] == 'layout-3' || $attributes['faqLayout'] == 'layout-4' ? 'after' : 'before',
164 + 'button_color' => $attributes['buttonColor'],
165 + 'faq_taxonomy' => $taxonomy,
166 + // Per-block order; empty falls back to the global FAQ order.
167 + 'order' => isset( $attributes['faqOrder'] ) ? $attributes['faqOrder'] : ''
168 + ]
169 +
170 + ]
171 + );
172 + }
173 +
174 + public function header_sequence( $_layout_sequence, $layout, $widget_type, $_defined_vars ) {
175 + $_new_layout_sequence = [
176 + [
177 + 'class' => 'betterdocs-category-title-counts',
178 + 'sequence' => [ 'category_title', 'category_counts' ]
179 + ],
180 + 'category_description'
181 + ];
182 +
183 + return $_new_layout_sequence;
184 + }
123 185 }