PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.1
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.1
3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 2.8.17 2.8.18 All 259 releases
wpfunnels / includes / core / rest-api / Controllers / class-storecheckout-controller.php

class-storecheckout-controller.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.1, at includes/core/rest-api/Controllers/class-storecheckout-controller.php

552 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Store Checkout Controller
4 *
5 * @package WPFunnels\Rest\Controllers
6 * @since 3.5.0
7 */
8
9 namespace WPFunnels\Rest\Controllers;
10
11 use WP_Error;
12 use WP_REST_Request;
13 use WP_REST_Response;
14 use WP_REST_Server;
15 use WPFunnels\Wpfnl_functions;
16 use WPFunnels\WooCommerce\Wpfnl_Store_Checkout_Conditions;
17
18 /**
19 * Class StoreCheckoutController
20 * Handles Store Checkout specific operations
21 *
22 * @package WPFunnels\Rest\Controllers
23 * @since 3.5.0
24 */
25 class StoreCheckoutController extends Wpfnl_REST_Controller {
26
27 /**
28 * Endpoint namespace.
29 *
30 * @var string
31 */
32 protected $namespace = 'wpfunnels/v1';
33
34 /**
35 * Route base.
36 *
37 * @var string
38 */
39 protected $rest_base = 'store-checkout';
40
41
42 /**
43 * Check user permission
44 *
45 * @param $request
46 *
47 * @return bool|WP_Error
48 */
49 public function update_items_permissions_check( $request ) {
50 if ( ! Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'funnels' ) ) {
51 return new WP_Error( 'wpfunnels_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'wpfnl' ), array( 'status' => rest_authorization_required_code() ) );
52 }
53 return true;
54 }
55
56
57 /**
58 * Check user read permission
59 *
60 * @param WP_REST_Request $request
61 *
62 * @return WP_Error|boolean
63 */
64 public function get_items_permissions_check( $request ) {
65 if ( ! Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'funnels' ) ) {
66 return new WP_Error( 'wpfunnels_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'wpfnl' ), array( 'status' => rest_authorization_required_code() ) );
67 }
68 return true;
69 }
70
71
72 /**
73 * Register routes
74 */
75 public function register_routes() {
76 // Check if Store Checkout exists
77 register_rest_route(
78 $this->namespace,
79 '/' . $this->rest_base . '/exists',
80 array(
81 array(
82 'methods' => WP_REST_Server::READABLE,
83 'callback' => array( $this, 'check_exists' ),
84 'permission_callback' => array( $this, 'get_items_permissions_check' ),
85 ),
86 )
87 );
88
89 // Get Store Checkout details (first / any)
90 register_rest_route(
91 $this->namespace,
92 '/' . $this->rest_base . '/get',
93 array(
94 array(
95 'methods' => WP_REST_Server::READABLE,
96 'callback' => array( $this, 'get_store_checkout' ),
97 'permission_callback' => array( $this, 'get_items_permissions_check' ),
98 ),
99 )
100 );
101
102 // List all Store Checkout funnels
103 register_rest_route(
104 $this->namespace,
105 '/' . $this->rest_base . '/list',
106 array(
107 array(
108 'methods' => WP_REST_Server::READABLE,
109 'callback' => array( $this, 'list_store_checkouts' ),
110 'permission_callback' => array( $this, 'get_items_permissions_check' ),
111 ),
112 )
113 );
114
115 // Validate Store Checkout creation (always allowed in multi-checkout mode)
116 register_rest_route(
117 $this->namespace,
118 '/' . $this->rest_base . '/validate-creation',
119 array(
120 array(
121 'methods' => WP_REST_Server::READABLE,
122 'callback' => array( $this, 'validate_creation' ),
123 'permission_callback' => array( $this, 'get_items_permissions_check' ),
124 ),
125 )
126 );
127
128 // Save conditions for a store checkout step
129 register_rest_route(
130 $this->namespace,
131 '/' . $this->rest_base . '/conditions/save',
132 array(
133 array(
134 'methods' => WP_REST_Server::EDITABLE,
135 'callback' => array( $this, 'save_conditions' ),
136 'permission_callback' => array( $this, 'update_items_permissions_check' ),
137 'args' => array(
138 'step_id' => array(
139 'description' => __( 'Checkout step ID.', 'wpfnl' ),
140 'type' => 'integer',
141 'required' => true,
142 'validate_callback' => function( $value ) {
143 return is_numeric( $value );
144 },
145 ),
146 'condition' => array(
147 'description' => __( 'Condition configuration object.', 'wpfnl' ),
148 'type' => 'object',
149 'required' => true,
150 ),
151 ),
152 ),
153 )
154 );
155
156 // Get conditions for a store checkout step
157 register_rest_route(
158 $this->namespace,
159 '/' . $this->rest_base . '/conditions/get',
160 array(
161 array(
162 'methods' => WP_REST_Server::READABLE,
163 'callback' => array( $this, 'get_conditions' ),
164 'permission_callback' => array( $this, 'get_items_permissions_check' ),
165 'args' => array(
166 'step_id' => array(
167 'description' => __( 'Checkout step ID.', 'wpfnl' ),
168 'type' => 'integer',
169 'required' => true,
170 'validate_callback' => function( $value ) {
171 return is_numeric( $value );
172 },
173 ),
174 ),
175 ),
176 )
177 );
178
179 // Search products for condition UI
180 register_rest_route(
181 $this->namespace,
182 '/' . $this->rest_base . '/search/products',
183 array(
184 array(
185 'methods' => WP_REST_Server::READABLE,
186 'callback' => array( $this, 'search_products' ),
187 'permission_callback' => array( $this, 'get_items_permissions_check' ),
188 'args' => array(
189 'search' => array(
190 'type' => 'string',
191 'default' => '',
192 'sanitize_callback' => 'sanitize_text_field',
193 ),
194 ),
195 ),
196 )
197 );
198
199 // Search product terms (categories or tags) for condition UI
200 register_rest_route(
201 $this->namespace,
202 '/' . $this->rest_base . '/search/terms',
203 array(
204 array(
205 'methods' => WP_REST_Server::READABLE,
206 'callback' => array( $this, 'search_terms' ),
207 'permission_callback' => array( $this, 'get_items_permissions_check' ),
208 'args' => array(
209 'taxonomy' => array(
210 'type' => 'string',
211 'required' => true,
212 'enum' => array( 'product_cat', 'product_tag' ),
213 'sanitize_callback' => 'sanitize_key',
214 ),
215 'search' => array(
216 'type' => 'string',
217 'default' => '',
218 'sanitize_callback' => 'sanitize_text_field',
219 ),
220 ),
221 ),
222 )
223 );
224 }
225
226
227 /**
228 * Check if Store Checkout exists
229 *
230 * @param WP_REST_Request $request
231 * @return WP_REST_Response
232 */
233 public function check_exists( $request ) {
234 $store_checkout = $this->get_store_checkout_funnel();
235
236 $response = array(
237 'exists' => !empty( $store_checkout ),
238 'funnel_id' => $store_checkout ? $store_checkout->ID : null,
239 );
240
241 return rest_ensure_response( $response );
242 }
243
244
245 /**
246 * Get Store Checkout funnel details (first one found — legacy endpoint)
247 *
248 * @param WP_REST_Request $request
249 * @return WP_REST_Response|WP_Error
250 */
251 public function get_store_checkout( $request ) {
252 $store_checkout = $this->get_store_checkout_funnel();
253
254 if ( !$store_checkout ) {
255 return new WP_Error(
256 'no_store_checkout',
257 __( 'No Store Checkout found.', 'wpfnl' ),
258 array( 'status' => 404 )
259 );
260 }
261
262 $response = array(
263 'success' => true,
264 'funnel_id' => $store_checkout->ID,
265 'funnel_name' => get_the_title( $store_checkout->ID ),
266 'edit_url' => add_query_arg(
267 array(
268 'page' => WPFNL_EDIT_FUNNEL_SLUG,
269 'id' => $store_checkout->ID,
270 'step_id' => 0,
271 ),
272 admin_url( 'admin.php' )
273 ),
274 );
275
276 return rest_ensure_response( $response );
277 }
278
279
280 /**
281 * List all Store Checkout funnels with their condition info.
282 *
283 * @param WP_REST_Request $request
284 * @return WP_REST_Response
285 * @since 3.6.0
286 */
287 public function list_store_checkouts( $request ) {
288 $this->ensure_conditions_class();
289
290 $funnels = Wpfnl_Store_Checkout_Conditions::get_all_store_checkout_funnels();
291 $items = array();
292
293 foreach ( $funnels as $funnel ) {
294 $step_id = Wpfnl_Store_Checkout_Conditions::get_checkout_step_id_for_funnel( $funnel->ID );
295 $condition = $step_id
296 ? Wpfnl_Store_Checkout_Conditions::get_condition( $step_id )
297 : array( 'condition_type' => 'all' );
298
299 $items[] = array(
300 'funnel_id' => $funnel->ID,
301 'funnel_name' => get_the_title( $funnel->ID ),
302 'status' => $funnel->post_status,
303 'created_at' => $funnel->post_date,
304 'step_id' => $step_id,
305 'condition' => $condition,
306 'edit_url' => add_query_arg(
307 array(
308 'page' => WPFNL_EDIT_FUNNEL_SLUG,
309 'id' => $funnel->ID,
310 'step_id' => 0,
311 ),
312 admin_url( 'admin.php' )
313 ),
314 );
315 }
316
317 return rest_ensure_response( array(
318 'success' => true,
319 'items' => $items,
320 ) );
321 }
322
323
324 /**
325 * Validate if Store Checkout can be created.
326 *
327 * Since 3.6.0 multiple store checkouts are allowed — creation is always
328 * permitted.
329 *
330 * @param WP_REST_Request $request
331 * @return WP_REST_Response
332 */
333 public function validate_creation( $request ) {
334 return rest_ensure_response( array(
335 'can_create' => true,
336 'message' => __( 'Store Checkout can be created.', 'wpfnl' ),
337 ) );
338 }
339
340
341 /**
342 * Save conditions for a store-checkout step.
343 *
344 * @param WP_REST_Request $request
345 * @return WP_REST_Response|WP_Error
346 * @since 3.6.0
347 */
348 public function save_conditions( $request ) {
349 $this->ensure_conditions_class();
350
351 $step_id = (int) $request->get_param( 'step_id' );
352 $condition = $request->get_param( 'condition' );
353
354 if ( ! $step_id || WPFNL_STEPS_POST_TYPE !== get_post_type( $step_id ) ) {
355 return new WP_Error(
356 'invalid_step',
357 __( 'Invalid step ID provided.', 'wpfnl' ),
358 array( 'status' => 400 )
359 );
360 }
361
362 if ( ! is_array( $condition ) ) {
363 return new WP_Error(
364 'invalid_condition',
365 __( 'Condition must be an object.', 'wpfnl' ),
366 array( 'status' => 400 )
367 );
368 }
369
370 $saved = Wpfnl_Store_Checkout_Conditions::save_condition( $step_id, $condition );
371
372 if ( ! $saved ) {
373 return new WP_Error(
374 'save_failed',
375 __( 'Failed to save condition.', 'wpfnl' ),
376 array( 'status' => 500 )
377 );
378 }
379
380 return rest_ensure_response( array(
381 'success' => true,
382 'condition' => Wpfnl_Store_Checkout_Conditions::get_condition( $step_id ),
383 'message' => __( 'Condition saved successfully.', 'wpfnl' ),
384 ) );
385 }
386
387
388 /**
389 * Get conditions for a store-checkout step.
390 *
391 * @param WP_REST_Request $request
392 * @return WP_REST_Response|WP_Error
393 * @since 3.6.0
394 */
395 public function get_conditions( $request ) {
396 $this->ensure_conditions_class();
397
398 $step_id = (int) $request->get_param( 'step_id' );
399
400 if ( ! $step_id || WPFNL_STEPS_POST_TYPE !== get_post_type( $step_id ) ) {
401 // If the step_id is actually a funnel ID, try to resolve the checkout step from it.
402 $resolved_step_id = null;
403 if ( $step_id && WPFNL_FUNNELS_POST_TYPE === get_post_type( $step_id ) ) {
404 $resolved_step_id = Wpfnl_Store_Checkout_Conditions::get_checkout_step_id_for_funnel( $step_id );
405 }
406
407 if ( ! $resolved_step_id ) {
408 return new WP_Error(
409 'invalid_step',
410 __( 'Invalid step ID provided.', 'wpfnl' ),
411 array( 'status' => 400 )
412 );
413 }
414
415 $step_id = $resolved_step_id;
416 }
417
418 $condition = Wpfnl_Store_Checkout_Conditions::get_condition( $step_id );
419
420 return rest_ensure_response( array(
421 'success' => true,
422 'step_id' => $step_id,
423 'condition' => $condition,
424 ) );
425 }
426
427
428 /**
429 * Get Store Checkout funnel if exists (returns first one — legacy helper)
430 *
431 * @return WP_Post|false
432 */
433 private function get_store_checkout_funnel() {
434 $args = array(
435 'post_type' => WPFNL_FUNNELS_POST_TYPE,
436 'posts_per_page' => 1,
437 'post_status' => array( 'publish', 'draft' ),
438 'meta_query' => array(
439 array(
440 'key' => '_wpfnl_funnel_type',
441 'value' => 'store_checkout',
442 ),
443 ),
444 );
445
446 $funnels = get_posts( $args );
447 return !empty( $funnels ) ? $funnels[0] : false;
448 }
449
450
451 /**
452 * Search WooCommerce products for the conditions UI.
453 *
454 * @param WP_REST_Request $request
455 * @return WP_REST_Response
456 * @since 3.6.0
457 */
458 public function search_products( $request ) {
459 $search = $request->get_param( 'search' );
460 $results = array();
461
462 $args = array(
463 'post_type' => 'product',
464 'post_status' => 'publish',
465 'posts_per_page' => 30,
466 'fields' => 'ids',
467 );
468
469 if ( ! empty( $search ) ) {
470 $args['s'] = $search;
471 }
472
473 $ids = get_posts( $args );
474
475 foreach ( $ids as $id ) {
476 $product = wc_get_product( $id );
477 if ( ! $product ) {
478 continue;
479 }
480 $results[] = array(
481 'id' => $id,
482 'name' => $product->get_name(),
483 );
484 }
485
486 return rest_ensure_response( array(
487 'success' => true,
488 'items' => $results,
489 ) );
490 }
491
492
493 /**
494 * Search WooCommerce product taxonomy terms for the conditions UI.
495 *
496 * @param WP_REST_Request $request
497 * @return WP_REST_Response|WP_Error
498 * @since 3.6.0
499 */
500 public function search_terms( $request ) {
501 $taxonomy = $request->get_param( 'taxonomy' );
502 $search = $request->get_param( 'search' );
503
504 if ( ! in_array( $taxonomy, array( 'product_cat', 'product_tag' ), true ) ) {
505 return new WP_Error(
506 'invalid_taxonomy',
507 __( 'Invalid taxonomy. Must be product_cat or product_tag.', 'wpfnl' ),
508 array( 'status' => 400 )
509 );
510 }
511
512 $args = array(
513 'taxonomy' => $taxonomy,
514 'hide_empty' => false,
515 'number' => 50,
516 );
517
518 if ( ! empty( $search ) ) {
519 $args['search'] = $search;
520 }
521
522 $terms = get_terms( $args );
523 $results = array();
524
525 if ( ! is_wp_error( $terms ) ) {
526 foreach ( $terms as $term ) {
527 $results[] = array(
528 'id' => $term->term_id,
529 'name' => $term->name,
530 );
531 }
532 }
533
534 return rest_ensure_response( array(
535 'success' => true,
536 'items' => $results,
537 ) );
538 }
539
540
541 /**
542 * Ensure the conditions class is loaded.
543 *
544 * @since 3.6.0
545 */
546 private function ensure_conditions_class() {
547 if ( ! class_exists( 'WPFunnels\\WooCommerce\\Wpfnl_Store_Checkout_Conditions' ) ) {
548 require_once WPFNL_DIR . 'includes/core/woocommerce/class-wpfnl-store-checkout-conditions.php';
549 }
550 }
551 }
552