PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
3.13.3 3.13.2 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 All 261 releases
← All changes | includes/core/rest-api/Controllers/DashboardController.php +201 -17 3.10.9 → 3.13.3 View file →
@@ -16,8 +16,19 @@
16 16 class DashboardController extends Wpfnl_REST_Controller
17 17 {
18 18
19 19 /**
20 + * Transient holding the last known answer to "is the onboarding checklist
21 + * on show?", read by the admin page so it can decide whether to hold space
22 + * for it while the real status loads.
23 + *
24 + * @var string
25 + * @since 3.13.3
26 + */
27 + const ONBOARDING_VISIBILITY_KEY = 'wpfnl_onboarding_should_show';
28 +
29 +
30 + /**
20 31 * Endpoint namespace.
21 32 *
22 33 * @var string
23 34 * @since 3.2.0
@@ -131,8 +142,20 @@
131 142 'permission_callback' => array( $this, 'get_items_permissions_check' ),
132 143 ),
133 144 )
134 145 );
146 +
147 + register_rest_route(
148 + $this->namespace,
149 + '/' . $this->rest_base . '/sync-cache',
150 + array(
151 + array(
152 + 'methods' => \WP_REST_Server::CREATABLE,
153 + 'callback' => array( $this, 'sync_cache' ),
154 + 'permission_callback' => array( $this, 'get_items_permissions_check' ),
155 + ),
156 + )
157 + );
135 158 }
136 159
137 160 /**
138 161 * Get stats arguments
@@ -169,10 +192,51 @@
169 192 }
170 193
171 194
172 195 /**
173 - * Get overview data of funnels
196 + * Cache payload version. Bump whenever the shape or the meaning of a cached
197 + * report response changes, so entries written by an older build are ignored
198 + * instead of served until they expire.
174 199 *
200 + * @var string
201 + * @since 3.13.0
202 + */
203 + const CACHE_VERSION = 'v2';
204 +
205 +
206 + /**
207 + * Build a cache key from a prefix and parameter array.
208 + *
209 + * @param string $prefix
210 + * @param array $params
211 + * @return string
212 + *
213 + * @since 3.9.6
214 + */
215 + protected function get_cache_key( $prefix, $params ) {
216 + return 'wpfnl_dash_' . $prefix . '_' . self::CACHE_VERSION . '_' . md5( serialize( $params ) );
217 + }
218 +
219 +
220 + /**
221 + * Delete all dashboard transients from the database.
222 + * Called by the sync-cache endpoint or when cache must be invalidated.
223 + *
224 + * @since 3.9.6
225 + */
226 + public static function delete_dashboard_cache() {
227 + global $wpdb;
228 + $wpdb->query(
229 + "DELETE FROM {$wpdb->options}
230 + WHERE option_name LIKE '_transient_wpfnl_dash_%'
231 + OR option_name LIKE '_transient_timeout_wpfnl_dash_%'"
232 + );
233 + }
234 +
235 +
236 + /**
237 + * Get overview data of funnels (with 24-hour server-side cache).
238 + *
175 239 * @param \WP_REST_Request $request
176 240 * @return \WP_Error|\WP_REST_Response
177 241 * @throws \Exception
178 242 *
@@ -179,18 +243,27 @@
179 243 * @since 3.2.0
180 244 */
181 245 public function get_overview(\WP_REST_Request $request)
182 246 {
183 - $params = $request->get_params();
184 - $start_date = isset($params['after']) ? $params['after'] : $this->default_after()->format('Y-m-d H:i:s');
185 - $end_date = isset($params['before']) ? $params['before'] : $this->default_before()->format('Y-m-d H:i:s');
186 - $response = ReportGenerator::get_overview($start_date, $end_date);
187 - return rest_ensure_response($response);
247 + $params = $request->get_params();
248 + $start_date = isset($params['after']) ? $params['after'] : $this->default_after()->format('Y-m-d H:i:s');
249 + $end_date = isset($params['before']) ? $params['before'] : $this->default_before()->format('Y-m-d H:i:s');
250 +
251 + $cache_key = $this->get_cache_key( 'ov', array( $start_date, $end_date ) );
252 + $cached = get_transient( $cache_key );
253 + if ( false !== $cached ) {
254 + return rest_ensure_response( $cached );
255 + }
256 +
257 + $response = ReportGenerator::get_overview( $start_date, $end_date );
258 + set_transient( $cache_key, $response, DAY_IN_SECONDS );
259 +
260 + return rest_ensure_response( $response );
188 261 }
189 262
190 263
191 264 /**
192 - * Get stats of the funnels with intervals period
265 + * Get stats of the funnels with intervals period (with 24-hour server-side cache).
193 266 *
194 267 * @param \WP_REST_Request $request
195 268 * @return \WP_Error|\WP_REST_Response
196 269 * @throws \Exception
@@ -198,14 +271,23 @@
198 271 * @since 3.2.0
199 272 */
200 273 public function get_stats(\WP_REST_Request $request)
201 274 {
202 - $params = $request->get_params();
203 - $start_date = isset($params['after']) ? $params['after'] : $this->default_after()->format('Y-m-d H:i:s');
204 - $end_date = isset($params['before']) ? $params['before'] : $this->default_before()->format('Y-m-d H:i:s');
205 - $interval = isset($params['interval']) ? $params['interval'] : 'day';
206 - $response = ReportGenerator::get_stats($start_date, $end_date, $interval);
207 - return rest_ensure_response($response);
275 + $params = $request->get_params();
276 + $start_date = isset($params['after']) ? $params['after'] : $this->default_after()->format('Y-m-d H:i:s');
277 + $end_date = isset($params['before']) ? $params['before'] : $this->default_before()->format('Y-m-d H:i:s');
278 + $interval = isset($params['interval']) ? $params['interval'] : 'day';
279 +
280 + $cache_key = $this->get_cache_key( 'st', array( $start_date, $end_date, $interval ) );
281 + $cached = get_transient( $cache_key );
282 + if ( false !== $cached ) {
283 + return rest_ensure_response( $cached );
284 + }
285 +
286 + $response = ReportGenerator::get_stats( $start_date, $end_date, $interval );
287 + set_transient( $cache_key, $response, DAY_IN_SECONDS );
288 +
289 + return rest_ensure_response( $response );
208 290 }
209 291
210 292
211 293 /**
@@ -217,15 +299,42 @@
217 299 * @since 3.2.0
218 300 */
219 301 public function get_top_funnels(\WP_REST_Request $request)
220 302 {
303 + $params = $request->get_params();
304 + $start_date = isset( $params['after'] ) ? $params['after'] : $this->default_after()->format( 'Y-m-d H:i:s' );
305 + $end_date = isset( $params['before'] ) ? $params['before'] : $this->default_before()->format( 'Y-m-d H:i:s' );
306 +
221 307 $response['status'] = true;
222 - $response['data'] = ReportGenerator::get_top_funnels();
223 - return rest_ensure_response($response);
308 + $response['data'] = ReportGenerator::get_top_funnels( $start_date, $end_date );
309 + return rest_ensure_response( $response );
224 310 }
225 311
226 312
227 313 /**
314 + * Remember whether the checklist was on show, for the next page load.
315 + *
316 + * Working this out costs several queries, so the admin page cannot ask
317 + * before it prints — but it can read the last answer. The React dashboard
318 + * uses it for one thing: whether to hold space for the checklist while the
319 + * real status loads. Someone who is done with onboarding then sees no
320 + * placeholder at all, instead of a skeleton that resolves to nothing.
321 + *
322 + * Short lived on purpose: it only has to outlive the gap between one page
323 + * load and the next, and a stale answer costs at most one wrong placeholder.
324 + *
325 + * @param bool $is_visible Whether the checklist is on show.
326 + *
327 + * @return void
328 + * @since 3.13.3
329 + */
330 + private function remember_onboarding_visibility( $is_visible )
331 + {
332 + set_transient( self::ONBOARDING_VISIBILITY_KEY, $is_visible ? 'yes' : 'no', 15 * MINUTE_IN_SECONDS );
333 + }
334 +
335 +
336 + /**
228 337 * Get onboarding status.
229 338 *
230 339 * Determines whether the onboarding section should be shown on the dashboard.
231 340 * The onboarding is hidden when ALL three conditions are met:
@@ -242,8 +351,10 @@
242 351 {
243 352 // ── Check if onboarding has been dismissed ──
244 353 $is_dismissed = get_transient( 'wpfnl_onboarding_dismissed' );
245 354 if ( $is_dismissed ) {
355 + $this->remember_onboarding_visibility( false );
356 +
246 357 return rest_ensure_response( array(
247 358 'success' => true,
248 359 'show_onboarding' => false,
249 360 'steps' => array(),
@@ -363,8 +474,61 @@
363 474 }
364 475 }
365 476 }
366 477
478 + // ── Build the checklist deep links ──
479 + // The checklist actions should drop the user into the place where the step
480 + // actually gets done, rather than the funnel list: the order bump tab of the
481 + // newest funnel's checkout step, and the canvas of that funnel for the upsell.
482 + // Drafts count too — a funnel does not need to be live to be worked on.
483 + $order_bump_url = '';
484 + $funnel_builder_url = '';
485 + $recent_funnels = get_posts(array(
486 + 'post_type' => WPFNL_FUNNELS_POST_TYPE,
487 + 'post_status' => array('publish', 'draft'),
488 + 'numberposts' => 10,
489 + 'orderby' => 'date',
490 + 'order' => 'DESC',
491 + 'fields' => 'ids',
492 + 'meta_query' => $exclude_sc_meta,
493 + ));
494 +
495 + if (!empty($recent_funnels)) {
496 + // The upsell step is added on the canvas, so that is where its action goes.
497 + $funnel_builder_url = admin_url(
498 + sprintf('admin.php?page=edit_funnel&id=%d', $recent_funnels[0])
499 + );
500 + }
501 +
502 + foreach ($recent_funnels as $recent_funnel_id) {
503 + $recent_steps = Wpfnl_functions::get_steps($recent_funnel_id);
504 +
505 + if (!is_array($recent_steps) || empty($recent_steps)) {
506 + continue;
507 + }
508 +
509 + foreach ($recent_steps as $recent_step) {
510 + $recent_step_type = isset($recent_step['step_type']) ? $recent_step['step_type'] : '';
511 + $recent_step_id = isset($recent_step['id']) ? (int)$recent_step['id'] : 0;
512 +
513 + if ('checkout' !== $recent_step_type || !$recent_step_id) {
514 + continue;
515 + }
516 +
517 + // The hash is what tells the canvas which step settings drawer and
518 + // tab to open once the funnel editor has finished loading.
519 + $order_bump_url = admin_url(
520 + sprintf(
521 + 'admin.php?page=edit_funnel&id=%d&step_id=%d#%d/settings/order-bump/',
522 + $recent_funnel_id,
523 + $recent_step_id,
524 + $recent_step_id
525 + )
526 + );
527 + break 2;
528 + }
529 + }
530 +
367 531 // ── Step 3: Check for real orders through funnels ──
368 532 if ($is_wc_active && !empty($funnels)) {
369 533 global $wpdb;
370 534 $stats_table = $wpdb->prefix . 'wpfnl_stats';
@@ -388,11 +552,10 @@
388 552
389 553 // ── Build checklist step completion data ──
390 554 $completed_count = 0;
391 555 $steps_status = array(
392 - 'woocommerce_connected' => $is_wc_active,
556 + 'store_checkout_created' => $has_store_checkout_created,
393 557 'funnel_created' => $has_funnel_created,
394 - 'store_checkout_created' => $has_store_checkout_created,
395 558 'order_bump_added' => $has_order_bump,
396 559 'upsell_added' => $has_upsell,
397 560 'test_order_placed' => $has_real_order,
398 561 'funnel_live' => $has_live_funnel,
@@ -406,8 +569,10 @@
406 569
407 570 $total_steps = count($steps_status);
408 571 $progress_percent = $total_steps > 0 ? round(($completed_count / $total_steps) * 100) : 0;
409 572
573 + $this->remember_onboarding_visibility( !$should_hide_onboarding );
574 +
410 575 return rest_ensure_response(array(
411 576 'success' => true,
412 577 'show_onboarding' => !$should_hide_onboarding,
413 578 'steps' => $steps_status,
@@ -413,8 +578,10 @@
413 578 'steps' => $steps_status,
414 579 'completed_count' => $completed_count,
415 580 'total_steps' => $total_steps,
416 581 'progress_percent' => $progress_percent,
582 + 'order_bump_url' => $order_bump_url,
583 + 'funnel_builder_url' => $funnel_builder_url,
417 584 ));
418 585 }
419 586
420 587 /**
@@ -445,8 +612,25 @@
445 612 'has_funnels' => (int) $funnel_count > 0,
446 613 'funnel_count' => (int) $funnel_count
447 614 ) );
448 615 }
616 +
617 + /**
618 + * Clear all dashboard transient caches so the next request fetches fresh data.
619 + *
620 + * @param \WP_REST_Request $request
621 + * @return \WP_REST_Response
622 + *
623 + * @since 3.9.6
624 + */
625 + public function sync_cache( \WP_REST_Request $request ) {
626 + self::delete_dashboard_cache();
627 + return rest_ensure_response( array(
628 + 'success' => true,
629 + 'message' => __( 'Dashboard cache cleared. Data will refresh on next load.', 'wpfnl' ),
630 + ) );
631 + }
632 +
449 633
450 634 /**
451 635 * Dismiss the onboarding checklist
452 636 *