PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.0
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 3.5.2 All 199 releases
betterdocs / includes / REST / WooProductFAQ.php

WooProductFAQ.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.0, at includes/REST/WooProductFAQ.php

213 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\REST;
4
5 use WP_Query;
6 use WP_REST_Request;
7 use WPDeveloper\BetterDocs\Core\BaseAPI;
8
9 /**
10 * REST endpoints backing the WooCommerce "Product FAQ" tab.
11 *
12 * Provides the option sources used by the Create/Update Product FAQ Group screen
13 * (product categories + product search) and the Display Settings read/write.
14 * Group assignments themselves are stored on each Product FAQ group term and
15 * saved through the FAQ Builder create/update category endpoints.
16 */
17 class WooProductFAQ extends BaseAPI {
18 /**
19 * Option key holding the WooCommerce FAQ display settings.
20 */
21 const DISPLAY_OPTION = 'betterdocs_woo_faq_display';
22
23 /**
24 * Default display settings. Shared with the frontend renderer (Step 7) so
25 * saved + unsaved sites behave identically.
26 *
27 * @return array
28 */
29 public static function display_defaults() {
30 return [
31 'enable' => true,
32 'placement' => 'product_tab',
33 'tab_title' => __( 'FAQ', 'betterdocs' ),
34 'layout' => 'layout-3',
35 'enable_schema' => true,
36 ];
37 }
38
39 /**
40 * Saved display settings merged over the defaults.
41 *
42 * @return array
43 */
44 public static function get_display_settings() {
45 $saved = get_option( self::DISPLAY_OPTION, [] );
46 if ( ! is_array( $saved ) ) {
47 $saved = [];
48 }
49 return wp_parse_args( $saved, self::display_defaults() );
50 }
51
52 /**
53 * Admin-only: assignments are part of the FAQ management surface.
54 */
55 public function permission_check() {
56 return current_user_can( 'edit_others_posts' );
57 }
58
59 public function register() {
60 // The whole tab is WooCommerce-only — no point exposing the routes
61 // when WooCommerce (and therefore product_cat) is absent.
62 if ( ! class_exists( 'WooCommerce' ) ) {
63 return;
64 }
65
66 $this->get( 'woo-product-faq/product-categories', [ $this, 'get_product_categories' ], [
67 'search' => [ 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field' ],
68 ] );
69
70 // Note: deliberately NOT 'product-search' — Pro registers that path. Use
71 // a distinct route so the two never collide while Pro is active.
72 $this->get( 'woo-product-faq/assignable-products', [ $this, 'search_products' ], [
73 'q' => [ 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field' ],
74 'include' => [ 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field' ],
75 ] );
76
77 $this->get( 'woo-product-faq/display-settings', [ $this, 'get_display_settings_route' ] );
78
79 $this->post( 'woo-product-faq/display-settings', [ $this, 'save_display_settings' ], [
80 'settings' => [ 'type' => 'object', 'required' => true ],
81 ] );
82 }
83
84 /**
85 * GET /woo-product-faq/product-categories
86 *
87 * WooCommerce product categories as selector options ({ id, name }) for the
88 * Create/Update Product FAQ Group screen. Optional `search` narrows by name.
89 */
90 public function get_product_categories( WP_REST_Request $request ) {
91 $search = (string) $request->get_param( 'search' );
92
93 $args = [
94 'taxonomy' => 'product_cat',
95 'hide_empty' => false,
96 'orderby' => 'name',
97 'order' => 'ASC',
98 'number' => 100,
99 ];
100 if ( '' !== $search ) {
101 $args['search'] = $search;
102 }
103
104 // Drop WooCommerce's default "Uncategorized" product category — it is a
105 // placeholder, not a real category to assign a FAQ group to.
106 $default_cat = (int) get_option( 'default_product_cat', 0 );
107 if ( $default_cat ) {
108 $args['exclude'] = [ $default_cat ];
109 }
110
111 $terms = get_terms( $args );
112 if ( is_wp_error( $terms ) ) {
113 $terms = [];
114 }
115
116 $data = array_values( array_map( function ( $term ) {
117 return [
118 'id' => (int) $term->term_id,
119 'name' => $term->name,
120 ];
121 }, $terms ) );
122
123 return $this->success( $data );
124 }
125
126 /**
127 * GET /woo-product-faq/product-search
128 *
129 * Searchable product lookup for the Products selector. Pass `q` to search by
130 * title, or `include` (comma-separated IDs) to resolve specific products to
131 * { id, name } for prefilling the chips when editing a group.
132 */
133 public function search_products( WP_REST_Request $request ) {
134 $include = array_filter( array_map( 'intval', explode( ',', (string) $request->get_param( 'include' ) ) ) );
135
136 $args = [
137 'post_type' => 'product',
138 'post_status' => 'publish',
139 'posts_per_page' => 20,
140 'orderby' => 'title',
141 'order' => 'ASC',
142 'no_found_rows' => true,
143 ];
144
145 if ( ! empty( $include ) ) {
146 $args['post__in'] = $include;
147 $args['posts_per_page'] = count( $include );
148 $args['orderby'] = 'post__in';
149 } else {
150 $args['s'] = (string) $request->get_param( 'q' );
151 }
152
153 $query = new WP_Query( $args );
154
155 $results = array_map( function ( $product ) {
156 return [
157 'id' => (int) $product->ID,
158 'name' => $product->post_title,
159 ];
160 }, $query->posts );
161
162 return $this->success( $results );
163 }
164
165 /**
166 * GET /woo-product-faq/display-settings
167 */
168 public function get_display_settings_route( WP_REST_Request $request ) {
169 return $this->success( self::get_display_settings() );
170 }
171
172 /**
173 * POST /woo-product-faq/display-settings
174 *
175 * Persist the WooCommerce FAQ display settings (sanitized against the known
176 * keys; unknown keys are dropped).
177 */
178 public function save_display_settings( WP_REST_Request $request ) {
179 $incoming = (array) $request->get_param( 'settings' );
180 $defaults = self::display_defaults();
181
182 $allowed_placements = [ 'product_tab', 'after_summary', 'before_summary' ];
183 $allowed_layouts = [ 'layout-1', 'layout-2', 'layout-3' ];
184
185 $placement = isset( $incoming['placement'] ) ? sanitize_key( $incoming['placement'] ) : $defaults['placement'];
186 if ( ! in_array( $placement, $allowed_placements, true ) ) {
187 $placement = $defaults['placement'];
188 }
189
190 $layout = isset( $incoming['layout'] ) ? sanitize_key( $incoming['layout'] ) : $defaults['layout'];
191 if ( ! in_array( $layout, $allowed_layouts, true ) ) {
192 $layout = $defaults['layout'];
193 }
194
195 $tab_title = isset( $incoming['tab_title'] ) ? sanitize_text_field( $incoming['tab_title'] ) : $defaults['tab_title'];
196 if ( '' === $tab_title ) {
197 $tab_title = $defaults['tab_title'];
198 }
199
200 $settings = [
201 'enable' => ! empty( $incoming['enable'] ),
202 'placement' => $placement,
203 'tab_title' => $tab_title,
204 'layout' => $layout,
205 'enable_schema' => ! empty( $incoming['enable_schema'] ),
206 ];
207
208 update_option( self::DISPLAY_OPTION, $settings );
209
210 return $this->success( $settings );
211 }
212 }
213