PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
← All changes | admin-core/api/learn.php +20 -294 3.0.1 → 3.3.0 View file →
@@ -80,11 +80,10 @@
80 80 * @return \WP_REST_Response
81 81 */
82 82 public function get_learn_sections( $request ) {
83 83
84 - // Get completed modules from option.
85 - $completed_modules = get_option( 'wcf_learn_data', array() );
86 - $completed_modules = is_array( $completed_modules ) ? $completed_modules : array();
84 + // Resolved completion state — manual checks plus auto-derived state.
85 + $learn_progress = \Cartflows_Learn_Progress::get_instance();
87 86
88 87 $sections = array(
89 88 array(
90 89 'id' => 'funnel-basics',
@@ -103,9 +102,9 @@
103 102 'plugin_init' => 'woocommerce/woocommerce.php',
104 103 ),
105 104 'learn_how' => false,
106 105 'is_pro' => false,
107 - 'completed' => $this->is_module_completed( 'install-woocommerce', 'active' === \Cartflows_Helper::get_plugin_status( 'woocommerce/woocommerce.php', true ), $completed_modules ),
106 + 'completed' => $learn_progress->is_completed( 'install-woocommerce' ),
108 107 'plugin_status' => \Cartflows_Helper::get_plugin_status( 'woocommerce/woocommerce.php', true ),
109 108 ),
110 109 array(
111 110 'id' => 'create-your-first-funnel',
@@ -116,11 +115,11 @@
116 115 'action_type' => 'redirect',
117 116 'action_data' => array(
118 117 'url' => admin_url( 'admin.php?page=cartflows&path=flows' ),
119 118 ),
120 - 'learn_how' => esc_url( 'https://cartflows.com/docs/how-to-create-your-first-cartflows-funnel/?utm_source=dashboard&utm_medium=cartflows&utm_campaign=learn_how' ),
119 + 'learn_how' => esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/how-to-create-your-first-cartflows-funnel/', array( 'utm_campaign' => 'learn_how' ) ) ),
121 120 'is_pro' => false,
122 - 'completed' => $this->is_module_completed( 'create-your-first-funnel', intval( wp_count_posts( CARTFLOWS_FLOW_POST_TYPE )->publish ) > 0, $completed_modules ),
121 + 'completed' => $learn_progress->is_completed( 'create-your-first-funnel' ),
123 122 ),
124 123 ),
125 124 ),
126 125 array(
@@ -137,11 +136,11 @@
137 136 'action_type' => 'redirect',
138 137 'action_data' => array(
139 138 'url' => admin_url( 'admin.php?page=cartflows&path=flows' ),
140 139 ),
141 - 'learn_how' => esc_url( 'https://cartflows.com/docs/editing-and-customising-funnel-steps/?utm_source=dashboard&utm_medium=cartflows&utm_campaign=learn_how' ),
140 + 'learn_how' => esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/editing-and-customising-funnel-steps/', array( 'utm_campaign' => 'learn_how' ) ) ),
142 141 'is_pro' => false,
143 - 'completed' => $this->is_module_completed( 'edit-design-funnel-pages-steps', $this->has_step_been_edited(), $completed_modules ),
142 + 'completed' => $learn_progress->is_completed( 'edit-design-funnel-pages-steps' ),
144 143 ),
145 144 ),
146 145 ),
147 146 array(
@@ -158,11 +157,11 @@
158 157 'action_type' => 'redirect',
159 158 'action_data' => array(
160 159 'url' => admin_url( 'edit.php?post_type=product' ),
161 160 ),
162 - 'learn_how' => esc_url( 'https://cartflows.com/docs/how-to-add-products-in-woocommerce/?utm_source=dashboard&utm_medium=cartflows&utm_campaign=learn_how' ),
161 + 'learn_how' => esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/how-to-add-products-in-woocommerce/', array( 'utm_campaign' => 'learn_how' ) ) ),
163 162 'is_pro' => false,
164 - 'completed' => $this->is_module_completed( 'add-products', intval( wp_count_posts( 'product' )->publish ) > 0, $completed_modules ),
163 + 'completed' => $learn_progress->is_completed( 'add-products' ),
165 164 ),
166 165 array(
167 166 'id' => 'assign-products-to-checkout',
168 167 'title' => __( 'Assign Products to Checkout', 'cartflows' ),
@@ -172,11 +171,11 @@
172 171 'action_type' => 'redirect',
173 172 'action_data' => array(
174 173 'url' => admin_url( 'admin.php?page=cartflows&path=flows' ),
175 174 ),
176 - 'learn_how' => esc_url( 'https://cartflows.com/docs/how-to-add-assign-products-to-a-checkout-step-in-cartflows/?utm_source=dashboard&utm_medium=cartflows&utm_campaign=learn_how' ),
175 + 'learn_how' => esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/how-to-add-assign-products-to-a-checkout-step-in-cartflows/', array( 'utm_campaign' => 'learn_how' ) ) ),
177 176 'is_pro' => false,
178 - 'completed' => $this->is_module_completed( 'assign-products-to-checkout', $this->is_checkout_product_is_assigned(), $completed_modules ),
177 + 'completed' => $learn_progress->is_completed( 'assign-products-to-checkout' ),
179 178 ),
180 179 ),
181 180 ),
182 181 array(
@@ -195,9 +194,9 @@
195 194 'url' => admin_url( 'admin.php?page=wc-settings&tab=checkout' ),
196 195 ),
197 196 'learn_how' => false,
198 197 'is_pro' => false,
199 - 'completed' => $this->is_module_completed( 'connect-payment-gateway', $this->check_supported_payment_gateway_used(), $completed_modules ),
198 + 'completed' => $learn_progress->is_completed( 'connect-payment-gateway' ),
200 199 ),
201 200 ),
202 201 ),
203 202 array(
@@ -217,9 +216,9 @@
217 216 'plugin_init' => 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php',
218 217 ),
219 218 'learn_how' => false,
220 219 'is_pro' => false,
221 - 'completed' => $this->is_module_completed( 'enable-cart-abandonment-tracking', 'active' === \Cartflows_Helper::get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php', true ), $completed_modules ),
220 + 'completed' => $learn_progress->is_completed( 'enable-cart-abandonment-tracking' ),
222 221 'plugin_status' => \Cartflows_Helper::get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php', true ),
223 222 ),
224 223 array(
225 224 'id' => 'setup-recovery-emails',
@@ -232,9 +231,9 @@
232 231 'url' => admin_url( 'admin.php?page=woo-cart-abandonment-recovery&path=follow-up-templates' ),
233 232 ),
234 233 'learn_how' => false,
235 234 'is_pro' => false,
236 - 'completed' => $this->is_module_completed( 'setup-recovery-emails', $this->check_folloup_emails_created(), $completed_modules ),
235 + 'completed' => $learn_progress->is_completed( 'setup-recovery-emails' ),
237 236 ),
238 237 ),
239 238 ),
240 239 array(
@@ -254,9 +253,9 @@
254 253 'plugin_init' => 'modern-cart/modern-cart.php',
255 254 ),
256 255 'learn_how' => false,
257 256 'is_pro' => false,
258 - 'completed' => $this->is_module_completed( 'enable-modern-cart', 'active' === \Cartflows_Helper::get_plugin_status( 'modern-cart/modern-cart.php', true ), $completed_modules ),
257 + 'completed' => $learn_progress->is_completed( 'enable-modern-cart' ),
259 258 'plugin_status' => \Cartflows_Helper::get_plugin_status( 'modern-cart/modern-cart.php', true ),
260 259 ),
261 260 array(
262 261 'id' => 'setup-your-cart',
@@ -269,9 +268,9 @@
269 268 'url' => admin_url( 'admin.php?page=moderncart_settings' ),
270 269 ),
271 270 'learn_how' => false,
272 271 'is_pro' => false,
273 - 'completed' => $this->is_module_completed( 'setup-your-cart', $this->is_modern_cart_configured(), $completed_modules ),
272 + 'completed' => $learn_progress->is_completed( 'setup-your-cart' ),
274 273 ),
275 274 ),
276 275 ),
277 276 array(
@@ -288,11 +287,11 @@
288 287 'action_type' => 'redirect',
289 288 'action_data' => array(
290 289 'url' => admin_url( 'admin.php?page=cartflows&path=flows' ),
291 290 ),
292 - 'learn_how' => esc_url( 'https://cartflows.com/docs/how-to-add-order-bumps-to-woocommerce-sales-funnel/?utm_source=dashboard&utm_medium=cartflows&utm_campaign=learn_how' ),
291 + 'learn_how' => esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/how-to-add-order-bumps-to-woocommerce-sales-funnel/', array( 'utm_campaign' => 'learn_how' ) ) ),
293 292 'is_pro' => ! _is_cartflows_pro(),
294 - 'completed' => $this->is_module_completed( 'add-order-bump', $this->has_published_order_bump(), $completed_modules ),
293 + 'completed' => $learn_progress->is_completed( 'add-order-bump' ),
295 294 ),
296 295 array(
297 296 'id' => 'setup-upsell-downsell-offers',
298 297 'title' => __( 'Setup Upsell / Downsell Offers', 'cartflows' ),
@@ -302,11 +301,11 @@
302 301 'action_type' => 'redirect',
303 302 'action_data' => array(
304 303 'url' => admin_url( 'admin.php?page=cartflows&path=flows' ),
305 304 ),
306 - 'learn_how' => esc_url( 'https://cartflows.com/docs/how-to-create-one-click-upsell-and-downsell-offers-in-cartflows/?utm_source=dashboard&utm_medium=cartflows&utm_campaign=learn_how' ),
305 + 'learn_how' => esc_url( \Cartflows_Helper::get_kb_doc_link( 'https://cartflows.com/docs/how-to-create-one-click-upsell-and-downsell-offers-in-cartflows/', array( 'utm_campaign' => 'learn_how' ) ) ),
307 306 'is_pro' => ! _is_cartflows_pro(),
308 - 'completed' => $this->is_module_completed( 'setup-upsell-downsell-offers', $this->has_published_offer_step(), $completed_modules ),
307 + 'completed' => $learn_progress->is_completed( 'setup-upsell-downsell-offers' ),
309 308 ),
310 309 ),
311 310 ),
312 311 );
@@ -314,281 +313,8 @@
314 313 $response = new \WP_REST_Response( $sections );
315 314 $response->set_status( 200 );
316 315
317 316 return $response;
318 - }
319 -
320 - /**
321 - * Check whether a module is completed.
322 - *
323 - * @param string $module_id Module identifier.
324 - * @param bool $is_auto_completed Whether the module is auto-completed.
325 - * @param array $completed_modules Stored list of completed modules.
326 - *
327 - * @return bool Whether the module should be treated as completed.
328 - */
329 - private function is_module_completed( $module_id, $is_auto_completed, $completed_modules ) {
330 -
331 - if ( $is_auto_completed ) {
332 - return true;
333 - }
334 -
335 - return in_array( $module_id, $completed_modules, true );
336 - }
337 -
338 - /**
339 - * Checks if any checkout step has a product assigned.
340 - *
341 - * Iterates through recent checkout steps and determines
342 - * if at least one has an assigned product in the 'wcf-checkout-products' meta.
343 - *
344 - * @since 2.2.2
345 - *
346 - * @return bool True if a checkout step has a product assigned, otherwise false.
347 - */
348 - public function is_checkout_product_is_assigned() {
349 -
350 - // Fetch the 10 most recently modified published checkout steps that have the 'wcf-checkout-products' meta key.
351 - $steps = get_posts(
352 - array(
353 - 'post_type' => CARTFLOWS_STEP_POST_TYPE, // Only 'cartflows_step' post type.
354 - 'post_status' => array( 'publish' ), // Only published posts.
355 - 'posts_per_page' => 10, // Limit to 10 results for performance.
356 - 'orderby' => 'modified', // Order by last modified date.
357 - 'order' => 'DESC', // Most recently modified first.
358 - 'fields' => 'ids', // Only retrieve post IDs for efficiency.
359 - 'meta_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
360 - array(
361 - 'key' => 'wcf-step-type', // Must be a checkout step.
362 - 'value' => 'checkout',
363 - ),
364 - array(
365 - 'key' => 'wcf-checkout-products', // Step must have checkout products assigned.
366 - 'compare' => 'EXISTS',
367 - ),
368 - ),
369 - )
370 - );
371 -
372 - // If no eligible steps found, return false.
373 - if ( empty( $steps ) ) {
374 - return false;
375 - }
376 -
377 - // Loop through each checkout step.
378 - foreach ( $steps as $step_id ) {
379 - // Get assigned products for current step.
380 - $products = get_post_meta( $step_id, 'wcf-checkout-products', true );
381 -
382 - // Skip if there are no products or the first product entry is empty.
383 - if ( ! is_array( $products ) || empty( $products ) || empty( $products[0]['product'] ) ) {
384 - continue;
385 - }
386 -
387 - // Get the product ID of the first assigned product.
388 - $product_id = $products[0]['product'];
389 -
390 - // Check if a valid numeric product ID is assigned.
391 - if ( is_numeric( $product_id ) && (int) $product_id > 0 ) {
392 - return true; // A product has been assigned to at least one checkout step.
393 - }
394 - }
395 -
396 - // If no checkout step with a valid product was found, return false.
397 - return false;
398 - }
399 -
400 - /**
401 - * Check if a configured WooCommerce payment gateway is available.
402 - *
403 - * For the learn step we only need to know whether the store has at least one
404 - * payment gateway plugin that is installed, configured, and enabled in either
405 - * live or sandbox/test mode. WooCommerce's
406 - * WC_Payment_Gateways::get_available_payment_gateways() already returns only
407 - * gateways that are enabled and ready to use, so we can rely on that.
408 - *
409 - * @since 2.2.2
410 - *
411 - * @return bool True if at least one gateway is available, otherwise false.
412 - */
413 - public function check_supported_payment_gateway_used() {
414 -
415 - // Ensure WooCommerce is active before checking gateways.
416 - if ( ! function_exists( 'WC' ) ) {
417 - return false;
418 - }
419 -
420 - $payment_gateways = WC()->payment_gateways();
421 -
422 - if ( ! is_object( $payment_gateways ) || ! method_exists( $payment_gateways, 'get_available_payment_gateways' ) ) {
423 - return false;
424 - }
425 -
426 - // Get gateways that are installed, configured, and enabled (live or sandbox).
427 - $available_gateways = $payment_gateways->get_available_payment_gateways();
428 -
429 - if ( empty( $available_gateways ) || ! is_array( $available_gateways ) ) {
430 - return false;
431 - }
432 -
433 - // If at least one gateway is available, consider the requirement satisfied.
434 - return true;
435 - }
436 -
437 - /**
438 - * Check if the user has created and activated follow-up recovery emails.
439 - *
440 - * First verifies that the Woo Cart Abandonment Recovery plugin is active.
441 - * If it is, queries the plugin's email templates table to confirm at least
442 - * one template exists with `is_activated = 1`.
443 - *
444 - * @since 2.2.2
445 - *
446 - * @return bool True if at least one active follow-up email template exists, otherwise false.
447 - */
448 - public function check_folloup_emails_created() {
449 -
450 - // Bail early if the Cart Abandonment Recovery plugin is not active.
451 - if ( 'active' !== \Cartflows_Helper::get_plugin_status( 'woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php', true ) ) {
452 - return false;
453 - }
454 -
455 - global $wpdb;
456 -
457 - $template_table = $wpdb->prefix . 'cartflows_ca_email_templates';
458 -
459 - // Check whether the table exists before querying it.
460 - $table_exists = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
461 - $wpdb->prepare( 'SHOW TABLES LIKE %s', $template_table )
462 - );
463 -
464 - if ( ! $table_exists ) {
465 - return false;
466 - }
467 -
468 - // Count active follow-up email templates.
469 - $active_count = (int) $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
470 - $wpdb->prepare(
471 - "SELECT COUNT(*) FROM {$wpdb->prefix}cartflows_ca_email_templates WHERE is_activated = %d", //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
472 - 1
473 - )
474 - );
475 -
476 - return $active_count > 0;
477 - }
478 -
479 - /**
480 - * Check if any published checkout step has at least one order bump configured.
481 - *
482 - * Queries published checkout steps for the presence of the 'wcf-order-bumps'
483 - * meta key. Only post IDs are retrieved for performance.
484 - *
485 - * @since 2.2.2
486 - *
487 - * @return bool True if at least one published checkout step has an order bump, otherwise false.
488 - */
489 - private function has_published_order_bump() {
490 -
491 - // Look for any published checkout step that has an order bump configured.
492 - $steps = get_posts(
493 - array(
494 - 'post_type' => CARTFLOWS_STEP_POST_TYPE,
495 - 'post_status' => 'publish',
496 - 'posts_per_page' => 1,
497 - 'fields' => 'ids',
498 - 'meta_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
499 - array(
500 - 'key' => 'wcf-step-type',
501 - 'value' => 'checkout',
502 - ),
503 - array(
504 - 'key' => 'wcf-order-bumps',
505 - 'compare' => 'EXISTS',
506 - ),
507 - ),
508 - )
509 - );
510 -
511 - return ! empty( $steps );
512 - }
513 -
514 - /**
515 - * Check if any published offer step (upsell or downsell) exists.
516 - *
517 - * Queries published steps whose 'wcf-step-type' is 'upsell' or 'downsell'.
518 - * Only post IDs are retrieved for performance.
519 - *
520 - * @since 2.2.2
521 - *
522 - * @return bool True if at least one published upsell or downsell step exists, otherwise false.
523 - */
524 - private function has_published_offer_step() {
525 -
526 - // Look for any published upsell or downsell step.
527 - $offer_steps = get_posts(
528 - array(
529 - 'post_type' => CARTFLOWS_STEP_POST_TYPE,
530 - 'post_status' => 'publish',
531 - 'posts_per_page' => 1,
532 - 'fields' => 'ids',
533 - 'meta_query' => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
534 - array(
535 - 'key' => 'wcf-step-type',
536 - 'value' => array( 'upsell', 'downsell' ),
537 - 'compare' => 'IN',
538 - ),
539 - ),
540 - )
541 - );
542 -
543 - return ! empty( $offer_steps );
544 - }
545 -
546 - /**
547 - * Check if any published CartFlows step exists.
548 - *
549 - * @since 2.2.2
550 - *
551 - * @return bool True if at least one published step exists, otherwise false.
552 - */
553 - private function has_step_been_edited() {
554 - $steps = get_posts(
555 - array(
556 - 'post_type' => CARTFLOWS_STEP_POST_TYPE,
557 - 'post_status' => 'publish',
558 - 'posts_per_page' => 1,
559 - 'fields' => 'ids',
560 - )
561 - );
562 - return ! empty( $steps );
563 - }
564 -
565 - /**
566 - * Check if the Modern Cart plugin is active and has been configured by the user.
567 - *
568 - * Modern Cart only writes its settings options to the database when the user
569 - * explicitly saves the settings page. A fresh install has none of these options,
570 - * so their presence reliably indicates that the user has configured the plugin.
571 - *
572 - * @since 2.2.2
573 - *
574 - * @return bool True if Modern Cart is active and at least one settings option exists, otherwise false.
575 - */
576 - private function is_modern_cart_configured() {
577 -
578 - if ( 'active' !== \Cartflows_Helper::get_plugin_status( 'modern-cart/modern-cart.php', true ) ) {
579 - return false;
580 - }
581 -
582 - $setting_keys = array( 'moderncart_setting', 'moderncart_cart', 'moderncart_floating', 'moderncart_appearance' );
583 -
584 - foreach ( $setting_keys as $key ) {
585 - if ( false !== get_option( $key, false ) ) {
586 - return true;
587 - }
588 - }
589 -
590 - return false;
591 317 }
592 318
593 319 /**
594 320 * Check whether a given request has permission to read notes.