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/classes/class-wpfnl-admin-menus.php +303 -34 3.11.1 → 3.13.3 View file →
@@ -21,9 +21,11 @@
21 21 {
22 22 add_action('admin_menu', [$this, 'register_plugin_menus']);
23 23 add_filter('admin_head', [$this, 'remove_submenu'], 10, 2);
24 24 add_filter('admin_head', [$this, 'remove_notices_from_funnel_window'], 10, 2);
25 + add_action('in_admin_header', [$this, 'remove_notices_from_react_admin']);
25 26 add_action('admin_init', [$this, 'disallow_all_step_view']);
27 + add_action('admin_init', [$this, 'maybe_redirect_to_react_admin']);
26 28 add_action('admin_footer', [$this, 'doc_link_with_new_page']);
27 29 add_filter('wpfnl_dashboard_nav_lists', [$this, 'add_store_checkout_nav'], 15, 1);
28 30 add_action('admin_bar_menu', [$this, 'register_admin_bar_menu'], 100);
29 31
@@ -34,9 +36,9 @@
34 36 add_filter( 'parent_file', function( $parent_file ) {
35 37 return WPFNL_MAIN_PAGE_SLUG;
36 38 });
37 39 add_filter( 'submenu_file', function( $submenu_file ) {
38 - return WPFNL_STORE_CHECKOUT_SLUG;
40 + return self::react_admin_slug( WPFNL_STORE_CHECKOUT_SLUG );
39 41 });
40 42 } else {
41 43 add_filter( "admin_body_class", array($this, 'add_folded_menu_class') );
42 44 }
@@ -44,8 +46,66 @@
44 46 }
45 47
46 48
47 49 /**
50 + * Screens that live as routes of the React single-page admin.
51 + *
52 + * Keyed by the WordPress page slug each screen had before the move; the
53 + * value is the hash route that replaced it. Three things read this: the
54 + * submenu items, so a click only changes the fragment; the hidden
55 + * registrations of the old pages; and maybe_redirect_to_react_admin(),
56 + * which sends those old pages to the same routes.
57 + *
58 + * @return array Legacy page slug => hash route.
59 + * @since 3.13.7
60 + */
61 + public static function react_routes()
62 + {
63 + $routes = [
64 + WPFNL_FUNNEL_PAGE_SLUG => '#/funnels',
65 + WPFNL_TRASH_FUNNEL_SLUG => '#/funnels/trash',
66 + WPFNL_STORE_CHECKOUT_SLUG => '#/store-checkout',
67 + WPFNL_AUTOMATIONS_SLUG => '#/automation',
68 + WPFNL_TEMPLATE_PAGE_SLUG => '#/funnel-templates',
69 + WPFNL_AI_FUNNEL_PAGE_SLUG => '#/ai-assistant',
70 + WPFNL_GLOBAL_SETTINGS_SLUG => '#/settings',
71 + ];
72 +
73 + /*
74 + * Store Checkout takes over the WooCommerce checkout, so without
75 + * WooCommerce the screen has nothing to list and its route has nothing
76 + * to show. That case keeps its server-rendered view, which offers to
77 + * install WooCommerce — see render_store_checkout_page().
78 + */
79 + if ( ! Wpfnl_functions::is_wc_active() ) {
80 + unset( $routes[ WPFNL_STORE_CHECKOUT_SLUG ] );
81 + }
82 +
83 + return $routes;
84 + }
85 +
86 +
87 + /**
88 + * Menu slug that opens a screen without leaving the React admin.
89 + *
90 + * The app is one WordPress page with the route in the fragment, so a link
91 + * of the form `wpfunnels#/funnels` moves between screens without a page
92 + * load. A screen with no route — Store Checkout on a site without
93 + * WooCommerce — keeps its own page slug.
94 + *
95 + * @param string $page Legacy page slug of the screen.
96 + * @return string Slug to register the menu item under.
97 + * @since 3.13.7
98 + */
99 + public static function react_admin_slug( $page )
100 + {
101 + $routes = self::react_routes();
102 +
103 + return isset( $routes[ $page ] ) ? WPFNL_MAIN_PAGE_SLUG . $routes[ $page ] : $page;
104 + }
105 +
106 +
107 + /**
48 108 * Register plugin menus and submenus
49 109 *
50 110 * @since 1.0.0
51 111 */
@@ -63,9 +123,15 @@
63 123 6
64 124 );
65 125
66 126
67 - // Register dashboard page for WPF
127 + // Every screen below is a route of the React single-page admin, which
128 + // WordPress serves as one page: `?page=wpfunnels`, with the route in
129 + // the fragment. The submenu items carry that fragment in their slugs,
130 + // so moving between them only changes the hash and the browser never
131 + // reloads the admin — the same way the app's own top navigation
132 + // behaves. The pages these screens used to be are registered after
133 + // them, hidden from the menu, so every old link still resolves.
68 134 add_submenu_page(
69 135 WPFNL_MAIN_PAGE_SLUG,
70 136 __('Dashboard', 'wpfnl'),
71 137 __('Dashboard', 'wpfnl'),
@@ -73,23 +139,38 @@
73 139 WPFNL_MAIN_PAGE_SLUG,
74 140 [$this, 'render']
75 141 );
76 142
143 + // Alias the React admin was first built under. Kept registered so links
144 + // and bookmarks from that period still resolve, but hidden from the menu
145 + // by remove_submenu() — the dashboard entry above is the real one.
77 146 add_submenu_page(
78 147 WPFNL_MAIN_PAGE_SLUG,
148 + __('Dashboard', 'wpfnl'),
149 + __('Dashboard', 'wpfnl'),
150 + 'wpf_manage_funnels',
151 + WPFNL_REACT_ADMIN_SLUG,
152 + [$this, 'render']
153 + );
154 +
155 + add_submenu_page(
156 + WPFNL_MAIN_PAGE_SLUG,
79 157 __('Funnels', 'wpfnl'),
80 158 __('Funnels', 'wpfnl'),
81 159 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
82 - WPFNL_FUNNEL_PAGE_SLUG,
160 + self::react_admin_slug( WPFNL_FUNNEL_PAGE_SLUG ),
83 161 [$this, 'render_funnels_page']
84 162 );
85 163
164 + // Without WooCommerce this one has no route to open: react_admin_slug()
165 + // hands back the page slug and the server-rendered view stays, which is
166 + // what offers to install WooCommerce.
86 167 add_submenu_page(
87 168 WPFNL_MAIN_PAGE_SLUG,
88 169 __('Store Checkout', 'wpfnl'),
89 170 __('Store Checkout', 'wpfnl'),
90 171 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
91 - WPFNL_STORE_CHECKOUT_SLUG,
172 + self::react_admin_slug( WPFNL_STORE_CHECKOUT_SLUG ),
92 173 [$this, 'render_store_checkout_page']
93 174 );
94 175
95 176 add_submenu_page(
@@ -96,12 +177,18 @@
96 177 WPFNL_MAIN_PAGE_SLUG,
97 178 __('Automations', 'wpfnl'),
98 179 __('Automations', 'wpfnl'),
99 180 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
100 - WPFNL_AUTOMATIONS_SLUG,
181 + self::react_admin_slug( WPFNL_AUTOMATIONS_SLUG ),
101 182 [$this, 'render_automations_page']
102 183 );
103 184
185 + // Asked below and by render_integration_page(); admin_menu runs before
186 + // WordPress loads the plugin API on its own.
187 + if ( ! function_exists( 'is_plugin_active' ) ) {
188 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
189 + }
190 +
104 191 $integrations_in_pro = defined( 'WPFNL_PRO_VERSION' ) && version_compare( WPFNL_PRO_VERSION, '2.9.0', '>=' );
105 192 $legacy_addon_active = is_plugin_active( 'wpfunnels-pro-integrations/wpfunnels-pro-integrations.php' );
106 193
107 194 if ( $integrations_in_pro || ! $legacy_addon_active ) {
@@ -128,9 +215,9 @@
128 215 WPFNL_MAIN_PAGE_SLUG,
129 216 __('Templates', 'wpfnl'),
130 217 __('Templates', 'wpfnl'),
131 218 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
132 - WPFNL_TEMPLATE_PAGE_SLUG,
219 + self::react_admin_slug( WPFNL_TEMPLATE_PAGE_SLUG ),
133 220 [$this, 'render_funnel_template_page']
134 221 );
135 222
136 223 // add_submenu_page(
@@ -141,23 +228,56 @@
141 228 // WPFNL_ADDONS_SLUG,
142 229 // [$this, 'render_addons_page']
143 230 // );
144 231
145 -
146 - // Show Integrations menu if WPFunnels Pro Integrations is deactivated
147 - if ( ! function_exists( 'is_plugin_active' ) ) {
148 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
149 - }
150 -
232 + // Settings is a route of the React admin (`#/settings`); the page it
233 + // used to be is registered below, hidden, and redirected to that route.
151 234 add_submenu_page(
152 235 WPFNL_MAIN_PAGE_SLUG,
153 236 __('Settings', 'wpfnl'),
154 237 __('Settings', 'wpfnl'),
155 238 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
156 - WPFNL_GLOBAL_SETTINGS_SLUG,
239 + self::react_admin_slug( WPFNL_GLOBAL_SETTINGS_SLUG ),
157 240 [$this, 'render_settings_page']
158 241 );
159 242
243 + add_submenu_page(
244 + WPFNL_MAIN_PAGE_SLUG,
245 + __('AI Assistant', 'wpfnl'),
246 + __('AI Assistant', 'wpfnl'),
247 + Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
248 + self::react_admin_slug( WPFNL_AI_FUNNEL_PAGE_SLUG ),
249 + [$this, 'render_ai_funnel_page']
250 + );
251 +
252 + /*
253 + * The pages those screens used to be, each still answering on its own
254 + * slug so bookmarks, the admin bar and anything built from the slugs
255 + * keep working — admin_init sends them on to the matching route. They
256 + * are dropped from the menu by remove_submenu(); the items above are
257 + * what people click, and those never leave the page.
258 + */
259 + $legacy_renderers = [
260 + WPFNL_FUNNEL_PAGE_SLUG => 'render_funnels_page',
261 + WPFNL_TRASH_FUNNEL_SLUG => 'render_trash_funnel_page',
262 + WPFNL_STORE_CHECKOUT_SLUG => 'render_store_checkout_page',
263 + WPFNL_AUTOMATIONS_SLUG => 'render_automations_page',
264 + WPFNL_TEMPLATE_PAGE_SLUG => 'render_funnel_template_page',
265 + WPFNL_AI_FUNNEL_PAGE_SLUG => 'render_ai_funnel_page',
266 + WPFNL_GLOBAL_SETTINGS_SLUG => 'render_settings_page',
267 + ];
268 +
269 + foreach ( array_keys( self::react_routes() ) as $legacy_slug ) {
270 + add_submenu_page(
271 + WPFNL_MAIN_PAGE_SLUG,
272 + __('WPFunnels', 'wpfnl'),
273 + __('WPFunnels', 'wpfnl'),
274 + Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
275 + $legacy_slug,
276 + [$this, $legacy_renderers[ $legacy_slug ]]
277 + );
278 + }
279 +
160 280 // add_submenu_page(
161 281 // WPFNL_MAIN_PAGE_SLUG,
162 282 // __('Documentation', 'wpfnl'),
163 283 // '<span id="wpfnl-documentation">'. __('Documentation', 'wpfnl').'</span>',
@@ -173,17 +293,8 @@
173 293 WPFNL_EDIT_FUNNEL_SLUG,
174 294 [$this, 'render_edit_funnel_page']
175 295 );
176 296
177 - add_submenu_page(
178 - WPFNL_MAIN_PAGE_SLUG,
179 - __('Trash', 'wpfnl'),
180 - __('Trash', 'wpfnl'),
181 - Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
182 - WPFNL_TRASH_FUNNEL_SLUG,
183 - [$this, 'render_trash_funnel_page']
184 - );
185 -
186 297 add_submenu_page(
187 298 WPFNL_MAIN_PAGE_SLUG,
188 299 __('Request a Feature', 'wpfnl'),
189 300 '<span id="wpfnl-request-feature">'. __('Request a Feature', 'wpfnl').'</span>',
@@ -228,13 +339,105 @@
228 339 *
229 340 * @since 3.1.7
230 341 */
231 342 public function render() {
232 - include_once WPFNL_ADMIN_DIR . 'views/base.php';
343 + include_once WPFNL_ADMIN_DIR . 'views/react-admin.php';
233 344 }
234 345
235 346
347 + /**
348 + * Send the screens that moved into the React admin to their routes.
349 + *
350 + * Each screen in react_routes() was its own WordPress page with a
351 + * server-rendered view. They are routes of the single-page admin now (see
352 + * src/admin/pages), so the pages are kept registered — bookmarks and every
353 + * link built from the slugs still resolve — and each one hands over to the
354 + * route that replaced it.
355 + *
356 + * The menu items no longer come through here: they carry their route in the
357 + * fragment of their own slug (see react_admin_slug), so clicking one moves
358 + * between screens without a page load at all. This is the path for links
359 + * made before the move, which still name the old page.
360 + *
361 + * @return void
362 + * @since 3.13.5
363 + */
364 + public function maybe_redirect_to_react_admin() {
365 + if ( wp_doing_ajax() || wp_doing_cron() ) {
366 + return;
367 + }
236 368
369 + $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
370 +
371 + $routes = self::react_routes();
372 +
373 + if ( ! isset( $routes[ $page ] ) ) {
374 + return;
375 + }
376 +
377 + /**
378 + * Filters whether a legacy screen redirects to its React admin route.
379 + *
380 + * @param bool $redirect Whether to redirect. Default true.
381 + * @param string $page Page slug being visited.
382 + *
383 + * @since 3.13.5
384 + */
385 + if ( ! apply_filters( 'wpfunnels/redirect_to_react_admin', true, $page ) ) {
386 + return;
387 + }
388 +
389 + $url = admin_url( 'admin.php?page=' . WPFNL_MAIN_PAGE_SLUG ) . $routes[ $page ];
390 +
391 + // A search carried over from the legacy funnel list keeps working: the
392 + // React list reads it out of the hash route's query string.
393 + $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
394 +
395 + if ( $search ) {
396 + $url .= '?s=' . rawurlencode( $search );
397 + }
398 +
399 + /*
400 + * The AI Assistant's own parameters travel the same way. `conversation_id`
401 + * is the deep link into one thread; `wpfnl_prompt` (with an optional
402 + * `wpfnl_context`) is the hand-off from a prompt typed elsewhere in the
403 + * admin, which the page sends on arrival. Every link to the old page
404 + * carries them in the query string, so they have to come across into the
405 + * hash route's own query string, where the router can read them.
406 + */
407 + if ( WPFNL_AI_FUNNEL_PAGE_SLUG === $page ) {
408 + $carried = [];
409 +
410 + $conversation_id = isset( $_GET['conversation_id'] ) ? absint( wp_unslash( $_GET['conversation_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
411 +
412 + if ( $conversation_id ) {
413 + $carried['conversation_id'] = $conversation_id;
414 + }
415 +
416 + $prompt = isset( $_GET['wpfnl_prompt'] ) ? sanitize_textarea_field( wp_unslash( $_GET['wpfnl_prompt'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
417 +
418 + if ( $prompt ) {
419 + $carried['wpfnl_prompt'] = $prompt;
420 +
421 + $context = isset( $_GET['wpfnl_context'] ) ? sanitize_key( wp_unslash( $_GET['wpfnl_context'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
422 +
423 + if ( $context ) {
424 + $carried['wpfnl_context'] = $context;
425 + }
426 + }
427 +
428 + if ( $carried ) {
429 + $url .= '?' . http_build_query( $carried );
430 + }
431 + }
432 +
433 + if ( wp_safe_redirect( $url ) ) {
434 + exit;
435 + }
436 + }
437 +
438 +
439 +
237 440 public function render_email_builder_page() { ?>
238 441 <div id="email-builder"></div>
239 442 <?php }
240 443
@@ -246,13 +449,24 @@
246 449 * @since 1.0.0
247 450 */
248 451 public function render_funnels_page()
249 452 {
250 - Wpfnl::$instance->module_manager->get_admin_modules('funnels')->get_view();
453 + $this->render();
251 454 }
252 455
253 456
254 457 /**
458 + * Render AI funnel page.
459 + *
460 + * @since 3.10.0
461 + */
462 + public function render_ai_funnel_page()
463 + {
464 + $this->render();
465 + }
466 +
467 +
468 + /**
255 469 * Render funnel template page
256 470 *
257 471 * @since 1.0.0
258 472 */
@@ -257,9 +471,9 @@
257 471 * @since 1.0.0
258 472 */
259 473 public function render_funnel_template_page()
260 474 {
261 - require WPFNL_DIR . '/admin/partials/templates.php';
475 + $this->render();
262 476 }
263 477
264 478
265 479 /**
@@ -292,9 +506,9 @@
292 506 * @since 3.1.8
293 507 */
294 508 public function render_trash_funnel_page()
295 509 {
296 - Wpfnl::$instance->module_manager->get_admin_modules('funnels')->get_view();
510 + $this->render();
297 511 }
298 512
299 513 /**
300 514 * Render create funnel page
@@ -337,15 +551,27 @@
337 551 /**
338 552 * Render Store Checkout page
339 553 *
340 554 * Since 3.6.0 multiple store checkouts are allowed, so this page always
341 - * shows the listing rather than redirecting to a single canvas.
555 + * shows the listing rather than redirecting to a single canvas. Since
556 + * 3.13.6 that listing is the React route `#/store-checkout`.
342 557 *
343 558 * @since 3.5.0
344 559 */
345 560 public function render_store_checkout_page()
346 561 {
347 - require WPFNL_DIR . '/admin/modules/store-checkout/views/store-checkout-listing.php';
562 + // Store Checkout takes over the WooCommerce checkout, so the screen is
563 + // meaningless without WooCommerce. Offer to install/activate it instead
564 + // of rendering an empty listing.
565 + if ( ! Wpfnl_functions::is_wc_active() ) {
566 + require WPFNL_DIR . '/admin/modules/store-checkout/views/woocommerce-required.php';
567 + return;
568 + }
569 +
570 + // The listing is a route of the React admin now (`#/store-checkout`),
571 + // which maybe_redirect_to_react_admin() sends this page to. Rendering
572 + // the app here keeps the page working if that redirect is filtered off.
573 + $this->render();
348 574 }
349 575
350 576 /**
351 577 * Render Integration page
@@ -410,9 +636,21 @@
410 636 */
411 637 public function remove_submenu()
412 638 {
413 639 remove_submenu_page(WPFNL_MAIN_PAGE_SLUG, 'edit_funnel');
414 - remove_submenu_page(WPFNL_MAIN_PAGE_SLUG, 'trash_funnels');
640 + // The React admin's legacy alias; it renders the same dashboard as the
641 + // entry above it, so only one of the two belongs in the menu.
642 + remove_submenu_page(WPFNL_MAIN_PAGE_SLUG, WPFNL_REACT_ADMIN_SLUG);
643 +
644 + /*
645 + * The pages the React screens used to be. They stay registered — the
646 + * callbacks still answer, so old links resolve — but the menu items
647 + * people click are the ones registered alongside them with the route
648 + * in the fragment, which never reload the page.
649 + */
650 + foreach ( array_keys( self::react_routes() ) as $legacy_slug ) {
651 + remove_submenu_page( WPFNL_MAIN_PAGE_SLUG, $legacy_slug );
652 + }
415 653 }
416 654
417 655
418 656 /**
@@ -426,9 +664,9 @@
426 664 }
427 665 if (('edit_funnel' == sanitize_text_field( $_GET['page'] ) )) {
428 666 remove_all_actions( 'admin_notices' );
429 667 }
430 - if ( 'wp_funnels' == sanitize_text_field( $_GET['page'] ) || 'wpf_templates' == sanitize_text_field( $_GET['page'] ) || 'trash_funnels' == sanitize_text_field( $_GET['page'] ) || 'wpfnl_settings' == sanitize_text_field( $_GET['page'] ) || 'wpf-license' == sanitize_text_field( $_GET['page'] ) ) {
668 + if ( 'wp_funnels' == sanitize_text_field( $_GET['page'] ) || 'wpf_templates' == sanitize_text_field( $_GET['page'] ) || 'trash_funnels' == sanitize_text_field( $_GET['page'] ) || 'wpfnl_settings' == sanitize_text_field( $_GET['page'] ) || 'wpf-license' == sanitize_text_field( $_GET['page'] ) || WPFNL_AI_FUNNEL_PAGE_SLUG == sanitize_text_field( $_GET['page'] ) ) {
431 669 add_action('admin_footer', array( $this, 'remove_admin_notices' ));
432 670 }
433 671
434 672 }
@@ -495,8 +733,39 @@
495 733 }
496 734
497 735
498 736 /**
737 + * Drop every admin notice on the React admin screen.
738 + *
739 + * The React admin is a single-page app that owns the whole content column,
740 + * so a notice printed above it by WordPress or by an unrelated plugin (the
741 + * Stripe "Apple Pay" warning, update nags, review prompts) pushes the app
742 + * down and breaks the layout. Unhooking the notice actions stops them from
743 + * being rendered at all rather than hiding them after the fact; anything a
744 + * plugin injects later from JavaScript is caught by the CSS fallback in
745 + * src/scss/style.scss.
746 + *
747 + * Runs on `in_admin_header`, the last hook to fire before WordPress renders
748 + * the notices, so a plugin that registers one late is still caught.
749 + *
750 + * @return void
751 + * @since 3.13.2
752 + */
753 + public function remove_notices_from_react_admin() {
754 + $page = empty( $_GET['page'] ) ? '' : sanitize_text_field( wp_unslash( $_GET['page'] ) );
755 +
756 + if ( ! in_array( $page, array( WPFNL_MAIN_PAGE_SLUG, WPFNL_REACT_ADMIN_SLUG ), true ) ) {
757 + return;
758 + }
759 +
760 + remove_all_actions( 'admin_notices' );
761 + remove_all_actions( 'all_admin_notices' );
762 + remove_all_actions( 'user_admin_notices' );
763 + remove_all_actions( 'network_admin_notices' );
764 + }
765 +
766 +
767 + /**
499 768 * Add Store Checkout to dashboard navigation
500 769 *
501 770 * @param array $list Navigation list
502 771 * @return array Modified navigation list
@@ -555,19 +824,19 @@
555 824 ],
556 825 [
557 826 'id' => 'wpfunnels-funnels',
558 827 'title' => __('Funnels', 'wpfnl'),
559 - 'href' => admin_url('admin.php?page=' . WPFNL_FUNNEL_PAGE_SLUG),
828 + 'href' => admin_url('admin.php?page=' . self::react_admin_slug( WPFNL_FUNNEL_PAGE_SLUG )),
560 829 ],
561 830 [
562 831 'id' => 'wpfunnels-store-checkout',
563 832 'title' => __('Store Checkout', 'wpfnl'),
564 - 'href' => admin_url('admin.php?page=' . WPFNL_STORE_CHECKOUT_SLUG),
833 + 'href' => admin_url('admin.php?page=' . self::react_admin_slug( WPFNL_STORE_CHECKOUT_SLUG )),
565 834 ],
566 835 [
567 836 'id' => 'wpfunnels-automations',
568 837 'title' => __('Automations', 'wpfnl'),
569 - 'href' => admin_url('admin.php?page=' . WPFNL_AUTOMATIONS_SLUG),
838 + 'href' => admin_url('admin.php?page=' . self::react_admin_slug( WPFNL_AUTOMATIONS_SLUG )),
570 839 ],
571 840 ];
572 841
573 842 if ( ! function_exists( 'is_plugin_active' ) ) {
@@ -587,9 +856,9 @@
587 856
588 857 $subnodes[] = [
589 858 'id' => 'wpfunnels-templates',
590 859 'title' => __('Templates', 'wpfnl'),
591 - 'href' => admin_url('admin.php?page=' . WPFNL_TEMPLATE_PAGE_SLUG),
860 + 'href' => admin_url('admin.php?page=' . WPFNL_MAIN_PAGE_SLUG) . '#/funnel-templates',
592 861 ];
593 862
594 863 $subnodes[] = [
595 864 'id' => 'wpfunnels-settings',