PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.2
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.2
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
wpfunnels / includes / core / classes / class-wpfnl-admin-menus.php

class-wpfnl-admin-menus.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.2, at includes/core/classes/class-wpfnl-admin-menus.php

874 lines 28.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin menus
4 *
5 * @package
6 */
7 namespace WPFunnels\Menu;
8
9 use WPFunnels\Admin\SetupWizard;
10 use WPFunnels\Wpfnl;
11 use WPFunnels\Wpfnl_functions;
12
13 /**
14 * Class Wpfnl_Menus
15 *
16 * @package Wpfnl
17 */
18 class Wpfnl_Menus
19 {
20 public function __construct()
21 {
22 add_action('admin_menu', [$this, 'register_plugin_menus']);
23 add_filter('admin_head', [$this, 'remove_submenu'], 10, 2);
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']);
26 add_action('admin_init', [$this, 'disallow_all_step_view']);
27 add_action('admin_init', [$this, 'maybe_redirect_to_react_admin']);
28 add_action('admin_footer', [$this, 'doc_link_with_new_page']);
29 add_filter('wpfnl_dashboard_nav_lists', [$this, 'add_store_checkout_nav'], 15, 1);
30 add_action('admin_bar_menu', [$this, 'register_admin_bar_menu'], 100);
31
32 if( isset($_GET['page']) && 'edit_funnel' === $_GET['page'] ) {
33 $funnel_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
34 $funnel_type = $funnel_id ? get_post_meta($funnel_id, '_wpfnl_funnel_type', true) : '';
35 if ( 'store_checkout' === $funnel_type ) {
36 add_filter( 'parent_file', function( $parent_file ) {
37 return WPFNL_MAIN_PAGE_SLUG;
38 });
39 add_filter( 'submenu_file', function( $submenu_file ) {
40 return self::react_admin_slug( WPFNL_STORE_CHECKOUT_SLUG );
41 });
42 } else {
43 add_filter( "admin_body_class", array($this, 'add_folded_menu_class') );
44 }
45 }
46 }
47
48
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 /**
108 * Register plugin menus and submenus
109 *
110 * @since 1.0.0
111 */
112 public function register_plugin_menus()
113 {
114 $role_permission = Wpfnl_functions::get_general_settings();
115
116 add_menu_page(
117 'WPFunnels',
118 'WPFunnels',
119 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
120 WPFNL_MAIN_PAGE_SLUG,
121 '',
122 WPFNL_DIR_URL . 'admin/assets/images/funnel.svg',
123 6
124 );
125
126
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.
134 add_submenu_page(
135 WPFNL_MAIN_PAGE_SLUG,
136 __('Dashboard', 'wpfnl'),
137 __('Dashboard', 'wpfnl'),
138 'wpf_manage_funnels',
139 WPFNL_MAIN_PAGE_SLUG,
140 [$this, 'render']
141 );
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.
146 add_submenu_page(
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,
157 __('Funnels', 'wpfnl'),
158 __('Funnels', 'wpfnl'),
159 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
160 self::react_admin_slug( WPFNL_FUNNEL_PAGE_SLUG ),
161 [$this, 'render_funnels_page']
162 );
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.
167 add_submenu_page(
168 WPFNL_MAIN_PAGE_SLUG,
169 __('Store Checkout', 'wpfnl'),
170 __('Store Checkout', 'wpfnl'),
171 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
172 self::react_admin_slug( WPFNL_STORE_CHECKOUT_SLUG ),
173 [$this, 'render_store_checkout_page']
174 );
175
176 add_submenu_page(
177 WPFNL_MAIN_PAGE_SLUG,
178 __('Automations', 'wpfnl'),
179 __('Automations', 'wpfnl'),
180 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
181 self::react_admin_slug( WPFNL_AUTOMATIONS_SLUG ),
182 [$this, 'render_automations_page']
183 );
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
191 $integrations_in_pro = defined( 'WPFNL_PRO_VERSION' ) && version_compare( WPFNL_PRO_VERSION, '2.9.0', '>=' );
192 $legacy_addon_active = is_plugin_active( 'wpfunnels-pro-integrations/wpfunnels-pro-integrations.php' );
193
194 if ( $integrations_in_pro || ! $legacy_addon_active ) {
195 // Pro owns the integrations menu; access is controlled by plan check inside the page renderer.
196 add_submenu_page(
197 WPFNL_MAIN_PAGE_SLUG,
198 __('Integrations', 'wpfnl'),
199 __('Integrations', 'wpfnl'),
200 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
201 WPFNL_INTEGRATIONS_MAIN_PAGE_SLUG,
202 [$this, 'render_integration_page']
203 );
204 } else {
205 /**
206 * After setup menu of WPFunnels.
207 * Legacy fallback for users still running the old addon.
208 *
209 * @since 1.0.0
210 */
211 do_action( 'wpfunnels/after_setup_menu');
212 }
213
214 add_submenu_page(
215 WPFNL_MAIN_PAGE_SLUG,
216 __('Templates', 'wpfnl'),
217 __('Templates', 'wpfnl'),
218 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
219 self::react_admin_slug( WPFNL_TEMPLATE_PAGE_SLUG ),
220 [$this, 'render_funnel_template_page']
221 );
222
223 // add_submenu_page(
224 // WPFNL_MAIN_PAGE_SLUG,
225 // __('Add-ons', 'wpfnl'),
226 // __('Add-ons', 'wpfnl'),
227 // Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
228 // WPFNL_ADDONS_SLUG,
229 // [$this, 'render_addons_page']
230 // );
231
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.
234 add_submenu_page(
235 WPFNL_MAIN_PAGE_SLUG,
236 __('Settings', 'wpfnl'),
237 __('Settings', 'wpfnl'),
238 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
239 self::react_admin_slug( WPFNL_GLOBAL_SETTINGS_SLUG ),
240 [$this, 'render_settings_page']
241 );
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
280 // add_submenu_page(
281 // WPFNL_MAIN_PAGE_SLUG,
282 // __('Documentation', 'wpfnl'),
283 // '<span id="wpfnl-documentation">'. __('Documentation', 'wpfnl').'</span>',
284 // Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
285 // 'https://getwpfunnels.com/resources/'
286 // );
287
288 add_submenu_page(
289 WPFNL_MAIN_PAGE_SLUG,
290 __('Edit Funnel', 'wpfnl'),
291 __('Edit Funnel', 'wpfnl'),
292 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
293 WPFNL_EDIT_FUNNEL_SLUG,
294 [$this, 'render_edit_funnel_page']
295 );
296
297 add_submenu_page(
298 WPFNL_MAIN_PAGE_SLUG,
299 __('Request a Feature', 'wpfnl'),
300 '<span id="wpfnl-request-feature">'. __('Request a Feature', 'wpfnl').'</span>',
301 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
302 'https://getwpfunnels.com/ideas/'
303 );
304
305 add_submenu_page(
306 WPFNL_MAIN_PAGE_SLUG,
307 __('Email Builder', 'wpfnl'),
308 __('Email Builder', 'wpfnl'),
309 Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
310 'email-builder',
311 [$this, 'render_email_builder_page']
312 );
313
314 // if ( !Wpfnl_functions::is_wpfnl_pro_activated() ) {
315
316 // add_submenu_page(
317 // WPFNL_MAIN_PAGE_SLUG,
318 // __('Free vs Pro', 'wpfnl'),
319 // __('Free vs Pro', 'wpfnl'),
320 // Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
321 // 'wpf_feature_comparison',
322 // [$this, 'render_feature_comparison_page']
323 // );
324
325
326 // add_submenu_page(
327 // WPFNL_MAIN_PAGE_SLUG,
328 // __('Go Pro', 'wpfnl'),
329 // '<span id="wpfnl-go-pro" class="dashicons dashicons-star-filled" style="font-size: 17px; color:#1fb3fb;"></span> ' . __('Go Pro', 'wpfnl'),
330 // Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission ),
331 // 'https://getwpfunnels.com/pricing/'
332 // );
333 // }
334 }
335
336
337 /**
338 * Render the admin view of WPFunnels
339 *
340 * @since 3.1.7
341 */
342 public function render() {
343 include_once WPFNL_ADMIN_DIR . 'views/react-admin.php';
344 }
345
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 }
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
440 public function render_email_builder_page() { ?>
441 <div id="email-builder"></div>
442 <?php }
443
444
445
446 /**
447 * Render funnel page
448 *
449 * @since 1.0.0
450 */
451 public function render_funnels_page()
452 {
453 $this->render();
454 }
455
456
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 /**
469 * Render funnel template page
470 *
471 * @since 1.0.0
472 */
473 public function render_funnel_template_page()
474 {
475 $this->render();
476 }
477
478
479 /**
480 * Render feature comparison page
481 *
482 * @return void
483 * @since 3.4.13
484 */
485 public function render_feature_comparison_page() {
486 require WPFNL_DIR . '/admin/partials/feature-comparison.php';
487 }
488
489
490 /**
491 * Render edit funnel page.
492 *
493 * @since 1.0.0
494 */
495 public function render_edit_funnel_page()
496 {
497 $funnel_id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS);
498 Wpfnl::$instance->module_manager->get_admin_modules('funnel')->init($funnel_id);
499 Wpfnl::$instance->module_manager->get_admin_modules('funnel')->get_view();
500 }
501
502
503 /**
504 * Render trash funnel page.
505 *
506 * @since 3.1.8
507 */
508 public function render_trash_funnel_page()
509 {
510 $this->render();
511 }
512
513 /**
514 * Render create funnel page
515 *
516 * @since 1.0.0
517 */
518 public function render_create_funnel_page()
519 {
520 Wpfnl::$instance->module_manager->get_admin_modules('create-funnel')->get_view();
521 }
522
523
524 /**
525 * Render settings page
526 *
527 * @since 1.0.0
528 */
529 public function render_settings_page()
530 {
531 Wpfnl::$instance->module_manager->get_admin_modules('settings')->get_view();
532 }
533
534 public function render_automations_page()
535 {
536 Wpfnl::$instance->module_manager->get_admin_modules('automations')->get_view();
537 }
538
539
540 /**
541 * Render addons page
542 *
543 * @since 1.0.0
544 */
545 public function render_addons_page()
546 {
547 require_once WPFNL_DIR . 'admin/modules/addons/view.php';
548 }
549
550
551 /**
552 * Render Store Checkout page
553 *
554 * Since 3.6.0 multiple store checkouts are allowed, so this page always
555 * shows the listing rather than redirecting to a single canvas. Since
556 * 3.13.6 that listing is the React route `#/store-checkout`.
557 *
558 * @since 3.5.0
559 */
560 public function render_store_checkout_page()
561 {
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();
574 }
575
576 /**
577 * Render Integration page
578 *
579 * @since 1.0.0
580 */
581 public function render_integration_page()
582 {
583 // Show Integrations page only if WPFunnels Pro Integrations is deactivated
584 if ( ! function_exists( 'is_plugin_active' ) ) {
585 require_once ABSPATH . 'wp-admin/includes/plugin.php';
586 }
587
588 if ( has_action( 'wpfunnels/render_integrations_page' ) ) {
589 // Pro plugin has registered its renderer — delegate (plan gate applied inside).
590 do_action( 'wpfunnels/render_integrations_page' );
591 } elseif ( ! is_plugin_active( 'wpfunnels-pro-integrations/wpfunnels-pro-integrations.php' ) ) {
592 require_once WPFNL_DIR . 'admin/modules/integration/view.php';
593 }
594 }
595
596 /**
597 * Get Store Checkout funnel if exists
598 *
599 * @return WP_Post|false
600 * @since 3.5.0
601 */
602 private function get_store_checkout_funnel()
603 {
604 $args = [
605 'post_type' => WPFNL_FUNNELS_POST_TYPE,
606 'posts_per_page' => 1,
607 'post_status' => ['publish', 'draft'],
608 'meta_query' => [
609 [
610 'key' => '_wpfnl_funnel_type',
611 'value' => 'store_checkout',
612 ],
613 ],
614 ];
615
616 $funnels = get_posts($args);
617 return !empty($funnels) ? $funnels[0] : false;
618 }
619
620
621
622
623 /**
624 * Render license page for funnel
625 *
626 * @since 2.0.0
627 */
628 public function render_license() {
629 require WPFNL_DIR . '/admin/partials/license.php';
630 }
631
632 /**
633 * Remove submenu from plugin menu
634 *
635 * @since 1.0.0
636 */
637 public function remove_submenu()
638 {
639 remove_submenu_page(WPFNL_MAIN_PAGE_SLUG, 'edit_funnel');
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 }
653 }
654
655
656 /**
657 * Remove all notices from funnel window
658 *
659 * @since 2.0.0
660 */
661 public function remove_notices_from_funnel_window() {
662 if (empty($_GET['page'])) {
663 return;
664 }
665 if (('edit_funnel' == sanitize_text_field( $_GET['page'] ) )) {
666 remove_all_actions( 'admin_notices' );
667 }
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'] ) ) {
669 add_action('admin_footer', array( $this, 'remove_admin_notices' ));
670 }
671
672 }
673
674
675
676 /**
677 * Force user to visit all steps page
678 *
679 * @since 1.0.0
680 */
681 public function disallow_all_step_view()
682 {
683 global $pagenow;
684 if ('edit.php' === $pagenow && isset($_GET['post_type']) && WPFNL_STEPS_POST_TYPE === sanitize_text_field($_GET['post_type'])) {
685 $funnel_link = add_query_arg(
686 [
687 'page' => WPFNL_MAIN_PAGE_SLUG,
688 ],
689 admin_url('admin.php')
690 );
691 wp_safe_redirect( wp_sanitize_redirect( esc_url_raw( $funnel_link ) ) );
692 exit;
693 }
694 }
695
696 /**
697 * Redirect user to pro url
698 */
699 public function redirect_to_pro()
700 {
701 $url = 'https://getwpfunnels.com/pricing/';
702 wp_safe_redirect( wp_sanitize_redirect( esc_url_raw( $url ) ) );
703 exit();
704 }
705
706
707 /**
708 * Open with new page when documenation is clicked
709 */
710 public function doc_link_with_new_page(){
711 ?>
712 <script type="text/javascript">
713 jQuery(document).ready(function($) {
714 $('#wpfnl-documentation').parent().attr('target','_blank');
715 $('#wpfnl-request-feature').parent().attr('target','_blank');
716 $('#wpfnl-go-pro').parent().attr('target','_blank');
717 });
718 </script>
719 <?php
720 }
721
722
723 public function add_folded_menu_class($classes) {
724 return $classes." folded";
725 }
726
727
728 /**
729 * Remove admin notices
730 */
731 public function remove_admin_notices() {
732 echo '<style>.update-nag, .updated, .error, .is-dismissible, .notice { display: none; } .wpfnl-import-notice {display: block!important; margin: 15px 15px 15px 0px;} .wpfunnels-notice {display: block; margin-left: 0px; margin-right: 25px;}</style>';
733 }
734
735
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 /**
768 * Add Store Checkout to dashboard navigation
769 *
770 * @param array $list Navigation list
771 * @return array Modified navigation list
772 * @since 3.5.0
773 */
774 public function add_store_checkout_nav( $list ) {
775 if ( ! is_array( $list ) ) {
776 return $list;
777 }
778
779 // Insert Store Checkout before settings
780 $new_list = array();
781
782 foreach ( $list as $key => $item ) {
783 // Insert Store Checkout before settings
784 if ( $key === 'settings' ) {
785 $new_list['store_checkout'] = array(
786 'title' => __('Store Checkout', 'wpfnl'),
787 'icon' => 'storewide-checkout-icon',
788 'page' => 'store_checkout',
789 );
790 }
791
792 $new_list[$key] = $item;
793 }
794
795 return $new_list;
796 }
797
798
799 /**
800 * Register WPFunnels nodes in the WordPress admin bar.
801 *
802 * @param WP_Admin_Bar $wp_admin_bar
803 * @since 3.10.9
804 */
805 public function register_admin_bar_menu( $wp_admin_bar ) {
806 $role_permission = Wpfnl_functions::get_general_settings();
807 $capability = Wpfnl_functions::role_permission_to_allow_wpfunnel( $role_permission );
808
809 if ( ! current_user_can( $capability ) ) {
810 return;
811 }
812
813 $wp_admin_bar->add_node([
814 'id' => 'wpfunnels',
815 'title' => __('WPFunnels', 'wpfnl'),
816 'href' => admin_url('admin.php?page=' . WPFNL_MAIN_PAGE_SLUG),
817 ]);
818
819 $subnodes = [
820 [
821 'id' => 'wpfunnels-dashboard',
822 'title' => __('Dashboard', 'wpfnl'),
823 'href' => admin_url('admin.php?page=' . WPFNL_MAIN_PAGE_SLUG),
824 ],
825 [
826 'id' => 'wpfunnels-funnels',
827 'title' => __('Funnels', 'wpfnl'),
828 'href' => admin_url('admin.php?page=' . self::react_admin_slug( WPFNL_FUNNEL_PAGE_SLUG )),
829 ],
830 [
831 'id' => 'wpfunnels-store-checkout',
832 'title' => __('Store Checkout', 'wpfnl'),
833 'href' => admin_url('admin.php?page=' . self::react_admin_slug( WPFNL_STORE_CHECKOUT_SLUG )),
834 ],
835 [
836 'id' => 'wpfunnels-automations',
837 'title' => __('Automations', 'wpfnl'),
838 'href' => admin_url('admin.php?page=' . self::react_admin_slug( WPFNL_AUTOMATIONS_SLUG )),
839 ],
840 ];
841
842 if ( ! function_exists( 'is_plugin_active' ) ) {
843 require_once ABSPATH . 'wp-admin/includes/plugin.php';
844 }
845
846 $integrations_in_pro = defined('WPFNL_PRO_VERSION') && version_compare(WPFNL_PRO_VERSION, '2.9.0', '>=');
847 $legacy_addon_active = is_plugin_active('wpfunnels-pro-integrations/wpfunnels-pro-integrations.php');
848
849 if ( $integrations_in_pro || ! $legacy_addon_active ) {
850 $subnodes[] = [
851 'id' => 'wpfunnels-integrations',
852 'title' => __('Integrations', 'wpfnl'),
853 'href' => admin_url('admin.php?page=' . WPFNL_INTEGRATIONS_MAIN_PAGE_SLUG),
854 ];
855 }
856
857 $subnodes[] = [
858 'id' => 'wpfunnels-templates',
859 'title' => __('Templates', 'wpfnl'),
860 'href' => admin_url('admin.php?page=' . WPFNL_MAIN_PAGE_SLUG) . '#/funnel-templates',
861 ];
862
863 $subnodes[] = [
864 'id' => 'wpfunnels-settings',
865 'title' => __('Settings', 'wpfnl'),
866 'href' => admin_url('admin.php?page=' . WPFNL_GLOBAL_SETTINGS_SLUG),
867 ];
868
869 foreach ( $subnodes as $node ) {
870 $wp_admin_bar->add_node( array_merge( $node, ['parent' => 'wpfunnels'] ) );
871 }
872 }
873 }
874