PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.4
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.4
3.13.4 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 All 262 releases
wpfunnels / admin / class-wpfnl-admin.php

class-wpfnl-admin.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.4, at admin/class-wpfnl-admin.php

2,578 lines 86.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Admin class
5 *
6 * @package
7 */
8
9 namespace WPFunnels\Admin;
10
11 use WPFunnels\Admin\BatchProcessing\BatchProcessingController;
12 use WPFunnels\Admin\Migrations\MigrationManager;
13 use WPFunnels\Wpfnl;
14 use WPFunnels\Wpfnl_functions;
15 use MRM\Common\MrmCommon;
16
17
18 /**
19 * The admin-specific functionality of the plugin.
20 *
21 * @link https://rextheme.com
22 * @since 1.0.0
23 *
24 * @package WPFunnels
25 * @subpackage WPFunnels/Admin
26 */
27
28 /**
29 * The admin-specific functionality of the plugin.
30 *
31 * Defines the plugin name, version, and two examples hooks for how to
32 * enqueue the admin-specific stylesheet and JavaScript.
33 *
34 * @package Wpfnl
35 * @subpackage Wpfnl/admin
36 * @author RexTheme <[email protected]>
37 */
38 class Wpfnl_Admin
39 {
40 /**
41 * Wpfunnels feed option key.
42 */
43 const WPFNL_FEED_OPTION_KEY = 'wpfnl_remote_info_feed_data';
44
45 /**
46 * Plugin file of the free Mail Mint plugin, as wordpress.org ships it.
47 */
48 const MAIL_MINT_BASENAME = 'mail-mint/mail-mint.php';
49
50 /**
51 * Plugin file of Mail Mint Pro.
52 */
53 const MAIL_MINT_PRO_BASENAME = 'mail-mint-pro/mail-mint-pro.php';
54
55 /**
56 * Plugin file of WPFunnels Pro.
57 */
58 const WPFNL_PRO_BASENAME = 'wpfunnels-pro/wpfnl-pro.php';
59
60 /**
61 * The ID of this plugin.
62 *
63 * @since 1.0.0
64 * @access private
65 * @var string $plugin_name The ID of this plugin.
66 */
67 private $plugin_name;
68
69 /**
70 * The version of this plugin.
71 *
72 * @since 1.0.0
73 * @access private
74 * @var string $version The current version of this plugin.
75 */
76 private $version;
77
78
79 private $dependency_plugins;
80
81
82 /**
83 * Screens still rendered by the legacy PHP/Vue admin, which is what the
84 * legacy stylesheet and bundle are written against.
85 *
86 * NOTE: 'toplevel_page_wpfunnels' is deliberately absent. That screen is the
87 * React admin, which ships its own namespaced bundle and must not inherit
88 * the legacy admin CSS. The funnel list, its trash tab and the Templates
89 * page are routes of that same app now, so their hooks are gone from here
90 * too.
91 *
92 * @var array
93 */
94 private $page_hooks = [
95 'wpfunnels_page_wpf_feature_comparison',
96 'wpfunnels_page_wp_funnel_settings',
97 'wpfunnels_page_wpfnl_settings',
98 'wpfunnels_page_wp_global_store',
99 'wpfunnels_page_create_funnel',
100 'wpfunnels_page_wpfnl_automations',
101 'wpfunnels_page_wpfnl_addons',
102 'wpfunnels_page_store_checkout',
103 'wpfunnels_page_wpfunnels_integrations',
104 'wpfunnels_page_wpf-license',
105 'wpfunnels_page_email-builder',
106 ];
107
108 /**
109 * Initialize the class and set its properties.
110 *
111 * @param string $plugin_name The name of this plugin.
112 * @param string $version The version of this plugin.
113 *
114 * @since 1.0.0
115 */
116 public function __construct($plugin_name, $version)
117 {
118 $this->plugin_name = $plugin_name;
119 $this->version = $version;
120
121 add_action('admin_enqueue_scripts', [$this, 'enqueue_styles']);
122 add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
123 add_filter('plugin_action_links_' . WPFNL_BASE, [$this, 'add_funnel_action_links']);
124 add_filter('locale', array($this, 'change_local_for_funnel_canvas'));
125 if (is_admin()) {
126 add_filter('admin_body_class', [$this, 'add_wpfunnels_admin_body_class']);
127 add_filter('wp_dropdown_pages', array($this, 'show_funnel_steps_on_reading'));
128 add_filter('gettext', [$this, 'wpfnl_customizing_checkout_text'], 10, 3);
129 add_action('admin_init', [$this, 'wpfnl_add_type_meta']);
130 add_action('admin_init', array($this, 'init_actions'));
131 add_action('deactivated_plugin', [$this, 'wpfnl_deactivated_plugin']);
132 add_action('activated_plugin', [$this, 'wpfnl_activated_plugin']);
133
134 // Register Dashboard Widgets.
135 add_action('wp_dashboard_setup', [$this, 'wpfnl_register_dashboard_widgets']);
136
137 if (isset($_GET['page']) && 'edit_funnel' === $_GET['page']) {
138 wp_cache_flush();
139 }
140 }
141
142 add_action('admin_head', function () {
143 remove_submenu_page(WPFNL_MAIN_PAGE_SLUG, 'email-builder');
144 });
145
146
147 // Remove all admin notices from WPFunnels Dashboard
148 if ($this->is_current_page('wpfunnels')) {
149 remove_all_actions('admin_notices');
150 }
151
152
153 /**
154 * @todo This piece of code will be removed after releasing 3.2.0. We need to introduced this because we have changed the funnel template creation strategy.
155 *
156 */
157 add_action('admin_init', function () {
158 $current_version = get_option('wpfunnels_version');
159 $is_already_synced = get_option('_is_wpfunnels_new_templates_synced', 'no');
160 if (version_compare($current_version, '3.1.2', '>') && 'yes' != $is_already_synced) {
161 delete_option(WPFNL_TEMPLATES_OPTION_KEY . '_wc');
162 delete_option(WPFNL_TEMPLATES_OPTION_KEY . '_lms');
163 delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION);
164 delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION);
165 delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION);
166 delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION);
167 update_option('_is_wpfunnels_new_templates_synced', 'yes');
168 }
169 });
170
171 add_action('admin_init', array($this, 'attach_admin_capabilities'));
172
173 add_action('wpfunnels_tracker_optin', array($this, 'wpfunnels_tracker_optin'), 10);
174 add_action('mint_before_sending_sms', array($this, 'create_mrm_contact'), 10);
175 }
176
177
178 public function init_actions()
179 {
180 $batch_processor = new BatchProcessingController();
181 new MigrationManager($batch_processor);
182
183
184 // Run stat status sync migration directly (independent of Action Scheduler).
185 $stat_sync = new \WPFunnels\Admin\Migrations\StatStatusSyncMigration();
186 $stat_sync->run();
187 }
188
189 /**
190 * Register dashboard widgets.
191 *
192 * Adds a new WPFunnels widgets to WordPress dashboard.
193 *
194 * Fired by `wp_dashboard_setup` action.
195 *
196 * @since 2.6.2
197 */
198 public function wpfnl_register_dashboard_widgets()
199 {
200 wp_add_dashboard_widget('wpfnl-dashboard-overview', esc_html__('WPFunnels Overview', 'wpfnl'), [$this, 'wpfnl_dashboard_overview_widget']);
201 global $wp_meta_boxes;
202
203 $dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
204 $ours = [
205 'wpfnl-dashboard-overview' => $dashboard['wpfnl-dashboard-overview'],
206 ];
207
208 $wp_meta_boxes['dashboard']['normal']['core'] = array_merge($ours, $dashboard);
209 }
210
211
212 /**
213 * WPFunnels dashboard widget.
214 *
215 * Displays the wpfunnels dashboard widget.
216 *
217 * @since 2.6.2
218 */
219 public function wpfnl_dashboard_overview_widget()
220 {
221 $upgrade_to_pro = __('Upgrade to Pro', 'wpfnl');
222 $install_activate = __('Install & Activate', 'wpfnl');
223 $is_mail_mint_active = WPFnl_functions::is_mail_mint_free_active();
224
225 $response = $this->get_wpfnl_feed_data();
226
227 $icons = [
228 'Total Orders' => '<svg width="16" height="17" fill="none" viewBox="0 0 16 17" xmlns="http://www.w3.org/2000/svg"><path fill="#B7A6E9" d="M12.061 8.04a4.015 4.015 0 01-3.478-6.03h-5.74l-.029-.236A2.01 2.01 0 00.82 0H.67a.67.67 0 000 1.34h.15a.67.67 0 01.665.592l.922 7.84a3.35 3.35 0 003.328 2.96h6.997a.67.67 0 000-1.341H5.734a2.01 2.01 0 01-1.89-1.34h7.987a3.35 3.35 0 003.298-2.756l.16-.884a4.01 4.01 0 01-3.228 1.63z"/><path fill="#B7A6E9" d="M10.542 5.994a1.254 1.254 0 00.901.402h.023a1.255 1.255 0 00.894-.37l2.863-2.863a.672.672 0 10-.95-.952l-2.806 2.81-.827-.887a.67.67 0 00-.98.915l.882.945zM4.69 16.082a1.34 1.34 0 100-2.68 1.34 1.34 0 000 2.68zm6.701 0a1.34 1.34 0 100-2.68 1.34 1.34 0 000 2.68z"/></svg>',
229 'Total Revenue' => '<svg width="15" height="15" fill="none" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path fill="#B7A6E9" d="M9.75 15h-4.5C1.5 15 0 13.5 0 9.75v-4.5C0 1.5 1.5 0 5.25 0h4.5C13.5 0 15 1.5 15 5.25v4.5C15 13.5 13.5 15 9.75 15z"/><path fill="#B7A6E9" d="M5.004 9.247c0 .968.742 1.748 1.665 1.748H8.55c.803 0 1.455-.683 1.455-1.523 0-.915-.397-1.237-.99-1.447l-3.022-1.05c-.593-.21-.99-.533-.99-1.448 0-.84.652-1.522 1.455-1.522H8.34c.923 0 1.665.78 1.665 1.747"/><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M5.004 9.247c0 .968.742 1.748 1.665 1.748H8.55c.803 0 1.455-.683 1.455-1.523 0-.915-.397-1.237-.99-1.447l-3.022-1.05c-.593-.21-.99-.533-.99-1.448 0-.84.652-1.522 1.455-1.522H8.34c.923 0 1.665.78 1.665 1.747"/><path fill="#B7A6E9" d="M7.5 3v9-9z"/><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M7.5 3v9"/></svg>',
230 'Order Bump Revenue' => '<svg width="15" height="15" fill="none" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path stroke="#B7A6E9" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.5" d="M5.376 1L3.05 3.333M9.475 1l2.327 2.333"/><path fill="#fff" stroke="#B7A6E9" stroke-width="1.5" d="M1 4.76c0-1.19.636-1.286 1.427-1.286h10c.791 0 1.428.097 1.428 1.286 0 1.382-.637 1.285-1.427 1.285H2.427C1.637 6.045 1 6.142 1 4.76z"/><path fill="#B7A6E9" d="M3.273 13.064L1.79 5.944h10.975l-.89 6.526c0 .712-1.582 1.286-2.373 1.484H5.35c-.237.237-1.483-.495-2.076-.89z"/><path stroke="#B7A6E9" stroke-linecap="round" stroke-width="1.5" d="M1.964 6.142l.906 5.553c.206 1.247.7 2.16 2.54 2.16h3.875c1.999 0 2.294-.874 2.526-2.082l1.08-5.63"/><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M8.91 9.504L7.425 8.021 5.943 9.504M7.426 8.02v3.56"/></svg>',
231 'Upsell/Downsell Revenue' => '<svg width="15" height="15" fill="none" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path fill="#B7A6E9" d="M5.25 15h4.5C13.5 15 15 13.5 15 9.75v-4.5C15 1.5 13.5 0 9.75 0h-4.5C1.5 0 0 1.5 0 5.25v4.5C0 13.5 1.5 15 5.25 15z"/><path fill="#B7A6E9" d="M6.379 5.25h3.37v3.371"/><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.379 5.25h3.37v3.371M9.75 5.25l-4.5 4.5"/></svg>',
232 'Total Leads' => '<svg width="13" height="13" fill="none" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg"><path fill="#B7A6E9" d="M8.45 0h-3.9C1.3 0 0 1.3 0 4.55v3.9c0 2.457.741 3.803 2.509 4.303.143-1.69 1.879-3.023 3.991-3.023 2.113 0 3.848 1.333 3.991 3.023C12.259 12.253 13 10.907 13 8.45v-3.9C13 1.3 11.7 0 8.45 0zM6.5 7.91a2.33 2.33 0 010-4.66 2.33 2.33 0 010 4.66z"/></svg>',
233 ];
234
235
236 $pro_icon_svg = '<div class="pro-icon"><svg width="14" height="14" fill="none" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><circle cx="7" cy="7" r="7" fill="#66CB64"/><path fill="#fff" fill-rule="evenodd" d="M7.623 4.465a.69.69 0 00-1.15 0l-1.36 2.08-1.04-.59c-.52-.294-1.146.168-1.005.743l.706 2.88c.032.13.15.222.285.222h5.977a.292.292 0 00.285-.221l.706-2.88c.141-.576-.485-1.038-1.005-.743l-1.04.588-1.36-2.08zm-.493.312a.098.098 0 00-.165 0l-1.51 2.311a.296.296 0 01-.393.094l-1.28-.725a.097.097 0 00-.144.106l.652 2.659h5.515l.652-2.66a.096.096 0 00-.143-.105l-1.281.725a.296.296 0 01-.393-.094l-1.51-2.31z" clip-rule="evenodd"/><path fill="#fff" d="M4.312 10.377a.291.291 0 00-.293.29.29.29 0 00.293.288h5.472c.161 0 .293-.13.293-.289a.291.291 0 00-.293-.289H4.312z"/><circle cx="10.739" cy="4.987" r=".514" fill="#fff"/><circle cx="3.139" cy="4.987" r=".514" fill="#fff"/><circle cx="7.042" cy="3.139" r=".514" fill="#fff"/><path fill="#fff" d="M9.918 9.404h-5.65L3.241 6.22l1.952.616 1.849-2.465L8.993 6.94l1.746-.822-.821 3.287z"/></svg></div>';
237
238 ?>
239 <div class="wpfnl-dashboard-widget">
240 <div class="wpfnl-overview__header">
241 <div class="wpfnl-overview__logo">
242 <div class="wpfnl-logo-wrapper">
243 <svg width="38" height="28" viewBox="0 0 38 28" fill="none" xmlns="http://www.w3.org/2000/svg">
244 <path d="M7.01532 18H31.9847L34 11H5L7.01532 18Z" fill="#EE8134"></path>
245 <path
246 d="M11.9621 27.2975C12.0923 27.7154 12.4792 28 12.9169 28H26.0831C26.5208 28 26.9077 27.7154 27.0379 27.2975L29 21H10L11.9621 27.2975Z"
247 fill="#6E42D3"></path>
248 <path
249 d="M37.8161 0.65986C37.61 0.247888 37.2609 0 36.8867 0H1.11326C0.739128 0 0.390003 0.247888 0.183972 0.65986C-0.0220592 1.07193 -0.0573873 1.59277 0.0898627 2.04655L1.69781 7H36.3022L37.9102 2.04655C38.0574 1.59287 38.022 1.07193 37.8161 0.65986Z"
250 fill="#6E42D3"></path>
251 </svg>
252 </div>
253 </div>
254
255 <div class="wpfnl-overview__upgrade">
256 <span><?php echo !Wpfnl_functions::is_pro_license_activated() ? __('Upgrade to Pro to get premium features such as one-click upsells, A/B testing and more!', 'wpfnl') : __('Start creating high converting landing pages, sales funnels, and checkout flows in minutes!', 'wpfnl'); ?></span>
257 </div>
258
259 <?php if (!Wpfnl_functions::is_pro_license_activated()) { ?>
260 <div class="wpfnl-overview__create">
261 <a href="https://getwpfunnels.com/pricing/" target="_blank" class="button"><span aria-hidden="true"></span>
262 <?php echo esc_html($upgrade_to_pro); ?></a>
263 </div>
264 <?php
265 }?>
266
267 </div>
268
269 <!-- overview report -->
270 <div class="wpfnl-overview__report">
271 <?php foreach ($response as $report):
272 $icon_html = $icons[$report['title']] ?? '';
273 ?>
274 <div class="report-item">
275 <div class="icon"><?php echo $icon_html; ?></div>
276 <div class="text-wrapper">
277 <p class="title"><?php echo esc_html($report['title']); ?></p>
278 <p class="amount"><?php echo $report['amount']; ?></p>
279 </div>
280
281 <?php if ($report['show_pro'] && !Wpfnl_functions::is_pro_license_activated()): ?>
282 <?php echo $pro_icon_svg; ?>
283 <?php
284 endif; ?>
285
286 </div>
287 <?php
288 endforeach; ?>
289 </div>
290
291 <?php if (!Wpfnl_functions::is_mint_mrm_active()): ?>
292 <!-- install mail mint -->
293 <div
294 class="wpfnl-overview__mailmint <?php echo Wpfnl_functions::is_mint_mrm_active() ? 'is-active' : 'is-disabled'; ?>">
295 <div class="icon-wrapper">
296 <div class="icon">
297 <svg width="34" height="34" fill="none" viewBox="0 0 34 34" xmlns="http://www.w3.org/2000/svg">
298 <circle cx="17" cy="17" r="16.5" fill="#fff" stroke="#573BFF" />
299 <path fill="#573BFF"
300 d="M24.084 14.876l-.634 8.778.905-1.267.996-10.225-9.23 6.153-2.082-2.172-5.972-5.067 7.601 8.868c.272.271.634.362.996.09l7.42-5.158z" />
301 <path fill="#02C4FB"
302 d="M21.822 11.62c-3.891.814-7.24.904-8.416.814l.995 1.086c4.525-.272 6.697-.905 7.24-1.267.362-.272.271-.543.18-.634z" />
303 <path fill="#573BFF"
304 d="M20.374 13.61c-2.444.724-4.525.905-5.249.905l.724.633c2.805-.362 4.163-.905 4.434-1.176.272-.181.181-.362.09-.362z" />
305 <path fill="#573BFF"
306 d="M25.947 11.345c-.212-.033-.418.064-.596.184l-9.05 6.153-4.162-4.525-.749-.793-.79-.835c1.72.18 8.778-.09 12.67-.996 1.81-.452 1.447-1.267 1.266-1.176-8.415 1.176-15.745.452-15.745.452-.403-.034-.617.066-.815.362-.18.272-.09.634.09.905l.453.453 2.082 2.08 5.158 5.43c.271.272.633.272.995.09l8.054-5.52c-.453 4.073-.996 8.145-1.267 9.14-1.81.363-12.397.453-13.845.091-.453-.724-.996-4.253-1.358-7.963-.09-.543-.633-.905-1.176-.634 0 0-.362.177-.362.724.905 8.597 1.81 9.05 2.081 9.23.543.272 4.163.453 7.873.453s7.33-.181 7.873-.634c.18-.18.814-.723 1.9-11.401v-.091c.074-.736-.152-1.113-.58-1.18z" />
307 <path fill="url(#paint0_linear_129_34)" d="M8.474 11.538h2.135l1.687 1.79H9.984l-1.51-1.79z" />
308 <defs>
309 <linearGradient id="paint0_linear_129_34" x1="9.613" x2="10.654" y1="11.538" y2="13.37"
310 gradientUnits="userSpaceOnUse">
311 <stop stop-color="#0C0632" />
312 <stop offset="1" stop-color="#573BFF" />
313 </linearGradient>
314 </defs>
315 </svg>
316 </div>
317 <div class="text-wrapper">
318 <p class="description">
319 <?php echo $is_mail_mint_active ? __('Use email marketing automation for your funnels easily with <b>Mail Mint</b>!', 'wpfnl') : __('Use email marketing automation for your funnels easily with <b>Mail Mint</b>!', 'wpfnl'); ?>
320 </p>
321 </div>
322 </div>
323 <div class="wpfnl-overview__install-mailmint">
324 <?php if (Wpfnl_functions::is_mail_mint_free_active()): ?>
325 <a href="https://getwpfunnels.com/pricing/#mail-mint" target="_blank" class="button">
326 <?php echo $upgrade_to_pro; ?>
327 </a>
328 <?php
329 else: ?>
330 <button id="pro-modal-dropdown-btn" class="button">
331 <span class="loader" style="display:none;"></span>
332 <span class="btn-text"><?php echo $install_activate; ?></span>
333 </button>
334 <?php
335 endif; ?>
336 </div>
337
338 </div>
339 <?php
340 endif; ?>
341
342 <div class="wpfnl-overview__footer wpfnl-divider_top">
343 <ul>
344 <?php foreach ($this->get_wpfunnels_dashboard_overview_widget_footer_actions() as $action_id => $action): ?>
345 <li class="wpfnl-overview__<?php echo esc_attr($action_id); ?>"><a
346 href="<?php echo esc_attr($action['link']); ?>"
347 target="_blank"><?php echo esc_html($action['title']); ?> <span
348 class="screen-reader-text"><?php echo esc_html__('(opens in a new window)', 'wpfnl'); ?></span></a>
349 </li>
350 <?php
351 endforeach; ?>
352 </ul>
353 </div>
354
355 <script>
356 (function ($) {
357
358 const apiService = (path = "", method = "GET", data, content_type = "text/plain") => {
359 return new Promise((resolve, reject) => {
360 jQuery.ajax({
361 url: wpApiSettings.root + path,
362 type: method,
363 data: data,
364 beforeSend: function (xhr) {
365 xhr.setRequestHeader("X-WP-Nonce", wpApiSettings.nonce);
366 },
367 dataType: "json",
368 contentType: content_type,
369 success: resolve,
370 error: reject
371 });
372 });
373 };
374
375 $(document).ready(function () {
376 jQuery("#pro-modal-dropdown-btn").click((e) => {
377 e.preventDefault();
378 var btn = $('#pro-modal-dropdown-btn');
379 var loader = btn.find('.loader');
380 var btnText = btn.find('.btn-text');
381 loader.show();
382 btn.prop('disabled', true);
383 btnText.text('Processing...');
384 apiService("wpfunnels/v1/activate-plugin", 'POST', JSON.stringify({
385 basename: 'mail-mint/mail-mint.php',
386 slug: 'mail-mint',
387 }), 'application/json').then((res) => {
388 var upgradeBtn = $(
389 '<a href="https://getwpfunnels.com/pricing/#mail-mint" target="_blank" class="button" style="margin-left:0;">' +
390 'Upgrade to Pro' +
391 '</a>'
392 );
393 btn.closest('.wpfnl-overview__install-mailmint').html(upgradeBtn);
394 $('.text-wrapper .title').text('Get Mail Mint Pro');
395 $('.text-wrapper .description').html('Use email marketing automation for your funnels easily with <b>Mail Mint</b>!');
396 }).catch((e) => {
397 btn.replaceWith(btnPrevState);
398 addClickEvent();
399 })
400 });
401 });
402 })(jQuery);
403 </script>
404
405 <style>
406 .wpfnl-dashboard-widget {
407 margin-top: -11px;
408 }
409
410 .wpfnl-dashboard-widget .wpfnl-overview__mailmint {
411 display: flex;
412 align-items: center;
413 justify-content: space-between;
414 padding: 16px 20px;
415 border: 1px solid #EEEAF8;
416 margin-bottom: 15px;
417 }
418
419 .wpfnl-dashboard-widget .wpfnl-overview__mailmint.is-active .wpfnl-overview__install-mailmint button {
420 pointer-events: none;
421 cursor: not-allowed;
422 }
423
424 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .icon-wrapper {
425 display: flex;
426 gap: 12px;
427 align-items: center;
428 }
429
430 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .wpfnl-overview__install-mailmint .button {
431 border-radius: 7px;
432 background: #573BFF;
433 color: #FFF;
434 text-align: center;
435 font-family: Inter;
436 font-size: 12px;
437 font-style: normal;
438 font-weight: 500;
439 line-height: 2.6;
440 border: none;
441 }
442
443 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .wpfnl-overview__install-mailmint .button:focus {
444 box-shadow: unset;
445 }
446
447 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .wpfnl-overview__install-mailmint .loader {
448 border: 2px solid #f3f3f3;
449 border-top: 2px solid #573BFF;
450 border-radius: 50%;
451 width: 12px;
452 height: 12px;
453 animation: spin 0.8s linear infinite;
454 display: inline-block;
455 vertical-align: middle;
456 margin-right: 4px;
457 }
458
459 @keyframes spin {
460 0% {
461 transform: rotate(0deg);
462 }
463
464 100% {
465 transform: rotate(360deg);
466 }
467 }
468
469 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .wpfnl-overview__install-mailmint .button:disabled {
470 cursor: not-allowed;
471 opacity: 0.7;
472 background: #573BFF !important;
473 color: #FFF !important;
474 }
475
476
477 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .icon-wrapper .text-wrapper .title {
478 color: #2E3148;
479 font-size: 16px;
480 font-weight: 600;
481 line-height: 1;
482 letter-spacing: -0.32px;
483 margin: 0 0 8px;
484 }
485
486 .wpfnl-dashboard-widget .wpfnl-overview__mailmint .icon-wrapper .text-wrapper .description {
487 margin: 0;
488 line-height: 1;
489 }
490
491 .wpfnl-dashboard-widget .wpfnl-overview__report {
492 display: grid;
493 grid-template-columns: repeat(2, 1fr);
494 border: 1px solid #EEEAF8;
495 margin: 15px 0;
496 }
497
498 .wpfnl-dashboard-widget .wpfnl-overview__report .report-item {
499 display: flex;
500 gap: 15px;
501 padding: 20px;
502 position: relative;
503
504 }
505
506 .wpfnl-dashboard-widget .wpfnl-overview__report .report-item:nth-child(odd)::before {
507 content: "";
508 position: absolute;
509 right: 15%;
510 height: 36px;
511 width: 1px;
512 background: linear-gradient(180deg, rgba(238, 234, 248, 0.00) -2.78%, #EEEAF8 47.1%, rgba(238, 234, 248, 0.00) 104.17%);
513 }
514
515 .wpfnl-dashboard-widget .wpfnl-overview__report .report-item .text-wrapper p {
516 margin: 0;
517 line-height: 1;
518 }
519
520 .wpfnl-dashboard-widget .wpfnl-overview__report .report-item .text-wrapper p.title {
521 color: #2E3148;
522 font-size: 12px;
523 font-weight: 400;
524 margin-bottom: 8px;
525 }
526
527 .wpfnl-dashboard-widget .wpfnl-overview__report .report-item .text-wrapper p.amount {
528 color: #2E3148;
529 font-size: 15px;
530 font-weight: 600;
531 line-height: normal;
532 }
533
534 .wpfnl-dashboard-widget .wpfnl-overview__header {
535 display: flex;
536 align-items: center;
537 justify-content: space-between;
538 border-bottom: 1px solid #eee;
539 gap: 15px;
540 padding: 15px 15px;
541 margin: 0 -12px;
542
543 }
544
545 .wpfnl-dashboard-widget .wpfnl-overview__header .wpfnl-overview__upgrade {
546 padding: 0px 15px;
547 }
548
549 .wpfnl-dashboard-widget .wpfnl-overview__versions {
550 font-size: 0.9em;
551 line-height: 1.5;
552 }
553
554 .wpfnl-dashboard-widget .wpfnl-overview__version {
555 display: block;
556 }
557
558 .wpfnl-dashboard-widget .wpfnl-overview__create a {
559 border-radius: 7px;
560 background: #6E42D3;
561 border: 1px solid #6E42D3;
562 font-style: normal;
563 line-height: 2.28;
564 color: #FFF;
565 text-align: center;
566 font-family: Inter;
567 font-size: 14px;
568 font-weight: 500;
569 }
570
571 .wpfnl-dashboard-widget .wpfnl-overview__create a:hover {
572 background: #6E42D3;
573 color: #FFF;
574 box-shadow: unset;
575 border-color: transparent;
576 }
577
578 .wpfnl-dashboard-widget .wpfnl-overview__create a:focus {
579 background: #6E42D3;
580 color: #FFF;
581 outline: none;
582 box-shadow: unset;
583 border-color: transparent;
584 }
585
586 .wpfnl-dashboard-widget .wpfnl-overview__create .dashicons {
587 font-size: 15px;
588 line-height: 1;
589 position: relative;
590 top: 8px;
591 }
592
593 .wpfnl-dashboard-widget .wpfnl-overview__recently-edited .wpfnl-heading {
594 font-weight: 600 !important;
595 border-bottom: 1px solid #eee;
596 margin: 0 -12px !important;
597 padding: 6px 12px !important;
598 margin-bottom: 13px !important;
599 }
600
601 .wpfnl-dashboard-widget .wpfnl-overview__post {
602 margin-top: 10px;
603 font-weight: 500;
604 }
605
606 .wpfnl-dashboard-widget .wpfnl-overview__post-link {
607 display: inline-block;
608 }
609
610 .wpfnl-dashboard-widget .wpfnl-overview__post-description {
611 margin: 0 0 1.5em;
612 }
613
614 .wpfnl-dashboard-widget .wpfnl-overview__footer {
615 border-top: 1px solid #eee;
616 margin: 0 -12px;
617 padding: 6px 12px;
618 padding-top: 12px;
619 padding-bottom: 0;
620 }
621
622 .wpfnl-dashboard-widget .wpfnl-overview__footer ul {
623 display: -webkit-box;
624 display: -ms-flexbox;
625 display: flex;
626 list-style: none;
627 margin: 0;
628 padding: 0;
629 }
630
631 .wpfnl-dashboard-widget .wpfnl-overview__footer ul li {
632 padding: 0 10px;
633 margin: 0;
634 position: relative;
635 }
636
637 .wpfnl-dashboard-widget .wpfnl-overview__footer .wpfnl-overview__blog a,
638 .wpfnl-dashboard-widget .wpfnl-overview__footer .wpfnl-overview__changelog a,
639 .wpfnl-dashboard-widget .wpfnl-overview__footer .wpfnl-overview__help a {
640 color: #667085;
641 font-size: 12px;
642 font-weight: 400;
643 position: relative;
644 }
645
646 .wpfnl-dashboard-widget .wpfnl-overview__footer .wpfnl-overview__blog a:hover,
647 .wpfnl-dashboard-widget .wpfnl-overview__footer .wpfnl-overview__changelog a:hover,
648 .wpfnl-dashboard-widget .wpfnl-overview__footer .wpfnl-overview__help a:hover {
649 color: #6846CE;
650 }
651
652 .wpfnl-dashboard-widget .wpfnl-overview__footer ul li:not(:last-child)::before {
653 content: "";
654 position: absolute;
655 right: 0;
656 top: 3px;
657 height: 16px;
658 width: 1px;
659 background: linear-gradient(180deg, rgba(238, 234, 248, 0.00) -2.78%, #EEEAF8 47.1%, rgba(238, 234, 248, 0.00) 104.17%);
660 }
661
662 .wpfnl-dashboard-widget .wpfnl-overview__footer ul li:first-child {
663 padding-left: 0;
664 border: none;
665 }
666
667 .wpfnl-dashboard-widget .wpfnl-overview__footer ul li.wpfnl-overview__go-pro a {
668 color: #DF8740;
669 font-size: 12px;
670 font-weight: 400;
671 line-height: normal;
672 }
673 </style>
674 </div>
675 <?php
676 }
677
678 /**
679 * Get wpfunnels feed data.
680 *
681 * Retrieve the feed info data from remote wpfunnels server.
682 *
683 * @access public
684 *
685 * @param bool $force_update Optional. Whether to force the data update or
686 * not. Default is false.
687 *
688 * @return array Feed data.
689 * @since 2.7.16
690 */
691 public function get_wpfnl_feed_data($force_update = false)
692 {
693 return $this->get_wpfnl_info_data($force_update);
694 }
695
696 /**
697 * Get wpfunnles info data.
698 *
699 * This function retrieves post from wpfunnels remote API.
700 *
701 * @access private
702 *
703 * @param bool $force_update Optional. Whether to force the data retrieval or
704 * not. Default is false.
705 *
706 * @return array|false Info data, or false.
707 * @since 2.7.16
708 */
709 private function get_wpfnl_info_data($force_update)
710 {
711 global $wpdb;
712 $response = [
713 [
714 'title' => 'Total Orders',
715 'amount' => $wpdb->get_var($wpdb->prepare("SELECT COUNT(id) FROM {$wpdb->prefix}wpfnl_stats WHERE status = %s", 'completed')),
716 'show_pro' => false
717 ],
718 [
719 'title' => 'Total Revenue',
720 'amount' => Wpfnl_functions::is_wc_active() ? wc_price((int)$wpdb->get_var($wpdb->prepare("SELECT SUM(total_sales) FROM {$wpdb->prefix}wpfnl_stats WHERE status = %s", 'completed'))) : number_format(0, 2),
721 'show_pro' => false
722 ],
723 [
724 'title' => 'Order Bump Revenue',
725 'amount' => Wpfnl_functions::is_wc_active() ? wc_price((int)$wpdb->get_var($wpdb->prepare("SELECT SUM(orderbump_sales) FROM {$wpdb->prefix}wpfnl_stats WHERE status = %s", 'completed'))) : number_format(0, 2),
726 'show_pro' => false
727 ],
728 [
729 'title' => 'Upsell/Downsell Revenue',
730 'amount' => (Wpfnl_functions::is_pro_license_activated() && Wpfnl_functions::is_wc_active()) ? wc_price((int)$wpdb->get_var($wpdb->prepare("SELECT SUM(upsell_sales + downsell_sales) FROM {$wpdb->prefix}wpfnl_stats WHERE status = %s", 'completed'))) : number_format(0, 2),
731 'show_pro' => true
732 ],
733 [
734 'title' => 'Total Leads',
735 'amount' => $wpdb->get_var("SELECT COUNT(id) FROM {$wpdb->prefix}wpfnl_optin_entries"),
736 'show_pro' => false
737 ],
738 ];
739
740 return $response;
741 }
742
743 /**
744 * Get all post from url
745 *
746 * @param $url
747 * @param $args
748 *
749 * @return array
750 */
751 private function remote_get($url, $args = [])
752 {
753 $response = wp_remote_get($url, $args);
754
755 if (is_wp_error($response) || !is_array($response) || !isset($response['body'])) {
756 return [
757 'success' => false,
758 'message' => $response->get_error_message(),
759 'data' => $response,
760 ];
761 }
762
763 // Decode the results.
764 $results = json_decode($response['body'], true);
765
766 if (!is_array($results)) {
767 return new \WP_Error('unexpected_data_format', 'Data was not returned in the expected format.');
768 }
769
770 return [
771 'success' => true,
772 'message' => 'Data successfully retrieved',
773 'data' => json_decode(wp_remote_retrieve_body($response), true),
774 ];
775 }
776
777
778 /**
779 * Get wpfunnels dashboard overview widget footer actions.
780 *
781 * Retrieves the footer action links displayed in wpfunnels dashboard widget.
782 *
783 * @since 1.9.0
784 * @access private
785 */
786 private function get_wpfunnels_dashboard_overview_widget_footer_actions()
787 {
788 $actions = [
789 'go-pro' => [
790 'title' => esc_html__('Upgrade to Pro', 'wpfnl'),
791 'link' => 'https://getwpfunnels.com/pricing/',
792 ],
793 'blog' => [
794 'title' => esc_html__('Blogs', 'wpfnl'),
795 'link' => 'https://getwpfunnels.com/blog/',
796 ],
797 'changelog' => [
798 'title' => esc_html__('Changelogs', 'wpfnl'),
799 'link' => 'https://getwpfunnels.com/changelog/',
800 ],
801 'help' => [
802 'title' => esc_html__('Get Help', 'wpfnl'),
803 'link' => 'https://getwpfunnels.com/docs/getting-started-with-wpfunnels/',
804 ],
805
806 ];
807
808 if (Wpfnl_functions::is_pro_license_activated()) {
809 unset($actions['go-pro']);
810 }
811
812 return $actions;
813 }
814
815
816
817 /**
818 * Funnel action links
819 *
820 * @since 1.0.0
821 */
822 public function add_funnel_action_links($actions)
823 {
824 $documentation = array(
825 '<a href="https://getwpfunnels.com/docs/wpfunnels-wordpress-funnel-builder/" target="_blank" >Documentation</a>',
826 );
827 $is_pro_installed = Wpfnl_functions::is_plugin_installed('wpfunnels-pro/wpfnl-pro.php');
828 if (!defined('WPFNL_PRO_VERSION') && !$is_pro_installed) {
829 $documentation[] = '<a href="https://getwpfunnels.com/pricing/" target="_blank" style="color:#6E42D3;font-weight:bold" >Upgrade to Pro</a>';
830 }
831 $actions = array_merge($actions, $documentation);
832 return $actions;
833 }
834
835 /**
836 * Register the stylesheets for the admin area.
837 *
838 * @since 1.0.0
839 */
840 public function enqueue_styles($hook)
841 {
842
843 /**
844 * This function is provided for demonstration purposes only.
845 *
846 * An instance of this class should be passed to the run() function
847 * defined in Wpfnl_Loader as all of the hooks are defined
848 * in that particular class.
849 *
850 * The Wpfnl_Loader will then create the relationship
851 * between the defined hooks and the functions defined in this
852 * class.
853 */
854 if ('wpfunnels_page_email-builder' === $hook) {
855 wp_enqueue_style($this->plugin_name . '-email-builder', plugin_dir_url(__FILE__) . 'assets/dist/email-builder/email-builder.css', [], $this->version, 'all');
856 }
857
858 if (in_array($hook, $this->page_hooks)) {
859 wp_enqueue_style('wp-color-picker');
860 wp_enqueue_style($this->plugin_name . '-jquery-ui', plugin_dir_url(__FILE__) . 'assets/css/jquery-ui.min.css', [], $this->version, 'all');
861
862 wp_enqueue_style($this->plugin_name, plugin_dir_url(__FILE__) . 'assets/css/wpfnl-admin.css', [], $this->version, 'all');
863
864 do_action('wpfunnels_after_styles_loaded');
865 }
866 }
867
868 /**
869 * Screen hooks the React single-page admin renders on — the WPFunnels
870 * dashboard, plus the alias slug it was first built under.
871 *
872 * The settings page is here too. It normally redirects to `#/settings`
873 * before this ever runs, so nothing loads on the way through; the entry
874 * matters for the case where that redirect is filtered off, when the page
875 * renders the app itself and needs the app's assets to do it.
876 *
877 * @return array Hook suffixes.
878 * @since 3.13.2
879 */
880 private function get_react_admin_hooks()
881 {
882 return array(
883 'toplevel_page_' . WPFNL_MAIN_PAGE_SLUG,
884 'wpfunnels_page_' . WPFNL_REACT_ADMIN_SLUG,
885 'wpfunnels_page_' . WPFNL_GLOBAL_SETTINGS_SLUG,
886 );
887 }
888
889
890 /**
891 * Resolve what the automation screen still needs before it can be used.
892 *
893 * Automations run on four things: WPFunnels and WPFunnels Pro with an
894 * activated licence, and Mail Mint and Mail Mint Pro with an activated
895 * licence. Each of the two Pro stacks walks the same ladder — not on the
896 * site, on the site but switched off, running but unlicensed, satisfied —
897 * and each rung needs a different next step, so the whole thing is resolved
898 * here rather than in the React app: only PHP can tell an uninstalled plugin
899 * from a deactivated one, and only PHP knows the licence state.
900 *
901 * @return array List of dependency descriptors, in the order they should be
902 * presented.
903 * @since 3.13.3
904 */
905 private function get_automation_dependencies()
906 {
907 return array(
908 $this->get_wpfnl_pro_dependency(),
909 $this->get_mail_mint_dependency(),
910 );
911 }
912
913
914 /**
915 * Dependency descriptor for the WPFunnels Pro half of the ladder.
916 *
917 * WPFunnels Pro is not on wordpress.org, so the "not installed" rung is a
918 * purchase link rather than a one-click install.
919 *
920 * @return array Dependency descriptor.
921 * @since 3.13.3
922 */
923 private function get_wpfnl_pro_dependency()
924 {
925 $base = array(
926 'id' => 'wpfunnels-pro',
927 'name' => __('WPFunnels Pro', 'wpfnl'),
928 'basename' => self::WPFNL_PRO_BASENAME,
929 'slug' => 'wpfunnels-pro',
930 'action' => '',
931 'url' => '',
932 'newTab' => false,
933 );
934
935 if ((bool) apply_filters('wpfunnels/is_pro_license_activated', false)) {
936 return array_merge($base, array(
937 'state' => 'satisfied',
938 'message' => '',
939 'label' => '',
940 ));
941 }
942
943 if (Wpfnl_functions::is_plugin_activated(self::WPFNL_PRO_BASENAME)) {
944 return array_merge($base, array(
945 'state' => 'needs_license',
946 'message' => __('WPFunnels Pro is active, but its license has not been activated yet.', 'wpfnl'),
947 'label' => __('Activate License', 'wpfnl'),
948 'url' => esc_url_raw(add_query_arg(array('page' => 'wpf-license'), admin_url('admin.php'))),
949 ));
950 }
951
952 if (Wpfnl_functions::is_plugin_installed(self::WPFNL_PRO_BASENAME)) {
953 return array_merge($base, array(
954 'state' => 'needs_activation',
955 'message' => __('WPFunnels Pro is installed but currently inactive.', 'wpfnl'),
956 'label' => __('Activate WPFunnels Pro', 'wpfnl'),
957 'action' => 'activate',
958 ));
959 }
960
961 return array_merge($base, array(
962 'state' => 'not_installed',
963 'message' => __('Automations need WPFunnels Pro, which is not installed on this site.', 'wpfnl'),
964 'label' => __('Upgrade to Pro', 'wpfnl'),
965 'url' => 'https://getwpfunnels.com/pricing/',
966 'newTab' => true,
967 ));
968 }
969
970
971 /**
972 * Dependency descriptor for the Mail Mint half of the ladder.
973 *
974 * The free plugin comes from wordpress.org, so its first two rungs are
975 * one-click: wpfunnels/v1/activate-plugin installs the package when it is
976 * missing and activates it either way. Mail Mint Pro is a paid plugin, so
977 * its "not installed" rung is a purchase link.
978 *
979 * @return array Dependency descriptor.
980 * @since 3.13.3
981 */
982 private function get_mail_mint_dependency()
983 {
984 $base = array(
985 'id' => 'mail-mint',
986 'name' => __('Mail Mint', 'wpfnl'),
987 'basename' => self::MAIL_MINT_BASENAME,
988 'slug' => 'mail-mint',
989 'action' => '',
990 'url' => '',
991 'newTab' => false,
992 );
993
994 if (Wpfnl_functions::is_mail_mint_pro_license_active() && Wpfnl_functions::is_mail_mint_free_active()) {
995 return array_merge($base, array(
996 'state' => 'satisfied',
997 'message' => '',
998 'label' => '',
999 ));
1000 }
1001
1002 if (!Wpfnl_functions::is_plugin_installed(self::MAIL_MINT_BASENAME)) {
1003 return array_merge($base, array(
1004 'state' => 'not_installed',
1005 'message' => __('Automations need Mail Mint, which is not installed on this site.', 'wpfnl'),
1006 'label' => __('Install Mail Mint', 'wpfnl'),
1007 'action' => 'install',
1008 ));
1009 }
1010
1011 if (!Wpfnl_functions::is_mail_mint_free_active()) {
1012 return array_merge($base, array(
1013 'state' => 'needs_activation',
1014 'message' => __('Mail Mint is installed but currently inactive.', 'wpfnl'),
1015 'label' => __('Activate Mail Mint', 'wpfnl'),
1016 'action' => 'activate',
1017 ));
1018 }
1019
1020 // Mail Mint is running; the rest of the ladder is about its Pro plugin.
1021 if (Wpfnl_functions::is_plugin_activated(self::MAIL_MINT_PRO_BASENAME)) {
1022 return array_merge($base, array(
1023 'state' => 'needs_license',
1024 'name' => __('Mail Mint Pro', 'wpfnl'),
1025 'basename' => self::MAIL_MINT_PRO_BASENAME,
1026 'message' => __('Mail Mint Pro is active, but its license has not been activated yet.', 'wpfnl'),
1027 'label' => __('Activate License', 'wpfnl'),
1028 'url' => esc_url_raw(admin_url('admin.php?page=mrm-admin#/settings/license')),
1029 ));
1030 }
1031
1032 if (Wpfnl_functions::is_plugin_installed(self::MAIL_MINT_PRO_BASENAME)) {
1033 return array_merge($base, array(
1034 'state' => 'needs_activation',
1035 'name' => __('Mail Mint Pro', 'wpfnl'),
1036 'basename' => self::MAIL_MINT_PRO_BASENAME,
1037 'slug' => 'mail-mint-pro',
1038 'message' => __('Mail Mint Pro is installed but currently inactive.', 'wpfnl'),
1039 'label' => __('Activate Mail Mint Pro', 'wpfnl'),
1040 'action' => 'activate',
1041 ));
1042 }
1043
1044 return array_merge($base, array(
1045 'state' => 'not_installed',
1046 'name' => __('Mail Mint Pro', 'wpfnl'),
1047 'basename' => self::MAIL_MINT_PRO_BASENAME,
1048 'slug' => 'mail-mint-pro',
1049 'message' => __('Automations need Mail Mint Pro with an activated license.', 'wpfnl'),
1050 'label' => __('Get Mail Mint Pro', 'wpfnl'),
1051 'url' => 'https://getwpfunnels.com/pricing/#mail-mint',
1052 'newTab' => true,
1053 ));
1054 }
1055
1056
1057 /**
1058 * Work out what the header's Pro call to action should say and where it
1059 * should go.
1060 *
1061 * There are three ways an install can fall short of a licensed Pro, and each
1062 * needs a different next step — telling someone to buy Pro when the plugin is
1063 * sitting deactivated in their plugins list is a dead end. Resolved here
1064 * rather than in the React app because the activation link needs a nonce and
1065 * a capability check.
1066 *
1067 * @return array|null CTA descriptor, or null when Pro is fully licensed and
1068 * no call to action belongs in the header.
1069 * @since 3.13.2
1070 */
1071 private function get_pro_cta()
1072 {
1073 if (!function_exists('get_plugins')) {
1074 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1075 }
1076
1077 $basename = 'wpfunnels-pro/wpfnl-pro.php';
1078
1079 if ((bool) apply_filters('wpfunnels/is_pro_license_activated', false)) {
1080 return null;
1081 }
1082
1083 // Installed and running, but unlicensed — the features stay locked until
1084 // the licence is activated, so send them to the licence screen.
1085 if (Wpfnl_functions::is_plugin_activated($basename)) {
1086 return array(
1087 'state' => 'needs_license',
1088 'label' => __('Activate License', 'wpfnl'),
1089 'title' => __('Activate your WPFunnels Pro license to unlock Pro features', 'wpfnl'),
1090 'url' => esc_url_raw(add_query_arg(array('page' => 'wpf-license'), admin_url('admin.php'))),
1091 'newTab' => false,
1092 );
1093 }
1094
1095 // Present but deactivated — one click away, no purchase needed.
1096 if (Wpfnl_functions::is_plugin_installed($basename)) {
1097 $plugins_url = admin_url('plugins.php');
1098 $can_activate = current_user_can('activate_plugins');
1099
1100 /*
1101 * Built with add_query_arg rather than wp_nonce_url on purpose.
1102 * wp_nonce_url() returns esc_html()'d output, so its separators come
1103 * back as `&#038;`. That is correct for a URL echoed straight into
1104 * markup — the HTML parser decodes it — but this one travels through
1105 * wp_localize_script and is applied by React with setAttribute,
1106 * where nothing decodes entities. The literal `&#038;` would split
1107 * the query string into junk parameters, `plugin` and `_wpnonce`
1108 * would arrive empty, and WordPress would answer the click with
1109 * "The link you followed has expired."
1110 *
1111 * wp_localize_script's own html_entity_decode() does not save us
1112 * either: it only walks top-level scalars, and this URL is nested
1113 * inside the CTA array.
1114 *
1115 * Without the capability the one-click link would only bounce off a
1116 * permission check, so point at the plugins list instead.
1117 */
1118 $url = $can_activate
1119 ? add_query_arg(
1120 array(
1121 'action' => 'activate',
1122 'plugin' => rawurlencode($basename),
1123 '_wpnonce' => wp_create_nonce('activate-plugin_' . $basename),
1124 ),
1125 $plugins_url
1126 )
1127 : $plugins_url;
1128
1129 return array(
1130 'state' => 'needs_activation',
1131 'label' => __('Activate Pro', 'wpfnl'),
1132 'title' => __('WPFunnels Pro is installed but not active — activate it to unlock Pro features', 'wpfnl'),
1133 'url' => esc_url_raw($url),
1134 'newTab' => false,
1135 );
1136 }
1137
1138 // Not on the site at all.
1139 return array(
1140 'state' => 'not_installed',
1141 'label' => __('Upgrade to Pro', 'wpfnl'),
1142 'title' => __('Upgrade to WPFunnels Pro', 'wpfnl'),
1143 'url' => 'https://getwpfunnels.com/pricing/',
1144 'newTab' => true,
1145 );
1146 }
1147
1148
1149 /**
1150 * Enqueue the React single-page admin bundle.
1151 *
1152 * Deliberately kept out of the `$page_hooks` group: the React admin ships
1153 * its own namespaced stylesheet (every class prefixed `rwpfnl`) and must not
1154 * inherit the legacy admin CSS, which is written against the Vue markup.
1155 *
1156 * @param string $hook Current admin screen hook suffix.
1157 *
1158 * @return void
1159 * @since 3.13.2
1160 */
1161 /**
1162 * Data the React template library screen needs.
1163 *
1164 * The same facts the Vue library read off `WPFunnelVars` and
1165 * `template_library_object`, narrowed to what the standalone Templates page
1166 * actually uses. The listing itself still comes from the REST API; this is
1167 * only the context the listing is read in.
1168 *
1169 * @return array Template library context.
1170 * @since 3.13.5
1171 */
1172 private function get_template_library_data()
1173 {
1174 $is_pro_license_active = (bool) apply_filters('wpfunnels/is_pro_license_activated', false);
1175
1176 return array(
1177 'builder' => Wpfnl_functions::get_builder_type(),
1178 'isAnyPluginMissing' => Wpfnl_functions::is_any_plugin_missing(),
1179 'dependencyPlugins' => Wpfnl_functions::get_dependency_plugins_status(),
1180 'types' => Wpfnl_functions::get_template_types(),
1181 // Store Checkout funnels have their own allowance, so they are not
1182 // counted against this one.
1183 'activeFunnels' => Wpfnl_functions::count_non_store_checkout_funnels(),
1184 // -1 once the licence lifts the cap.
1185 'allowedFunnels' => $is_pro_license_active ? -1 : 3,
1186 // The remote-funnel add-on serves the library from elsewhere, and
1187 // this screen then has nothing of its own to fetch.
1188 'isRemote' => Wpfnl_functions::maybe_remote_funnel(),
1189 );
1190 }
1191
1192
1193 /**
1194 * The jQuery UI datepicker defaults WordPress sets when it prints
1195 * jquery-ui-datepicker (wp_localize_jquery_ui_datepicker()). The classic
1196 * analytics drawer's range picker runs on them — its dates go to the
1197 * analytics route in this format — so the React one is handed the same.
1198 *
1199 * @return array
1200 */
1201 private function get_datepicker_defaults()
1202 {
1203 global $wp_locale;
1204
1205 return array(
1206 'monthNames' => array_values($wp_locale->month),
1207 'monthNamesShort' => array_values($wp_locale->month_abbrev),
1208 'dayNames' => array_values($wp_locale->weekday),
1209 'dayNamesShort' => array_values($wp_locale->weekday_abbrev),
1210 'dayNamesMin' => array_values($wp_locale->weekday_initial),
1211 'dateFormat' => str_replace(
1212 array('d', 'j', 'l', 'z', 'F', 'M', 'n', 'm', 'Y', 'y'),
1213 array('dd', 'd', 'DD', 'o', 'MM', 'M', 'm', 'mm', 'yy', 'y'),
1214 get_option('date_format')
1215 ),
1216 'firstDay' => absint(get_option('start_of_week')),
1217 );
1218 }
1219
1220
1221 private function enqueue_react_admin_assets($hook)
1222 {
1223 if (!in_array($hook, $this->get_react_admin_hooks(), true)) {
1224 return;
1225 }
1226
1227 // The shared webpack runtime chunk, when the build has one — see
1228 // enqueue_webpack_runtime().
1229 $runtime_deps = $this->enqueue_webpack_runtime() ? array($this->plugin_name . '-runtime') : array();
1230
1231 // `wp.hooks` is the React canvas's extension surface (see
1232 // src/admin/extensions/hooks.js): depending on it guarantees the global
1233 // exists, so Pro and third-party scripts and the bundle share one instance.
1234 $runtime_deps[] = 'wp-hooks';
1235
1236 wp_enqueue_script(
1237 'wpfnl-react-admin',
1238 plugin_dir_url(__FILE__) . 'assets/dist/js/wpfnl-react-admin.min.js',
1239 $runtime_deps,
1240 $this->version,
1241 true
1242 );
1243
1244 Wpfnl_functions::enqueue_bundle_style('wpfnl-react-admin', 'wpfnl-react-admin-bundle');
1245
1246 // The React admin renders the header action group itself
1247 // (src/admin/components/HeaderActions.jsx), so the standalone
1248 // wpfnl-header-actions bundle is not loaded on this screen — see
1249 // enqueue_header_actions().
1250 $_is_pro_license_active = (bool) apply_filters('wpfunnels/is_pro_license_activated', false);
1251
1252 /*
1253 * Everything after the page name in the title WordPress just printed —
1254 * separator, site name, " — WordPress". The React admin is one WordPress
1255 * page with its routes in the hash, so WordPress titles the tab once,
1256 * with this page's own name ("Dashboard"), and every route below it
1257 * inherits that. The app retitles itself per route (see
1258 * src/admin/utils/documentTitle.js) and needs this tail, which is
1259 * translatable and so cannot be rebuilt in JavaScript.
1260 *
1261 * `$admin_title` is built from `$title`, so it always starts with it;
1262 * the prefix check is only there in case an `admin_title` filter has
1263 * rewritten the whole thing, in which case the app keeps what WordPress
1264 * printed for the page name it resolves. Entities such as `&lsaquo;`
1265 * survive here: wp_localize_script() decodes them on the way out.
1266 */
1267 $_document_title_suffix = '';
1268
1269 if (isset($GLOBALS['title'], $GLOBALS['admin_title'])) {
1270 $_page_title = (string) $GLOBALS['title'];
1271 $_admin_title = (string) $GLOBALS['admin_title'];
1272
1273 if ('' !== $_page_title && 0 === strpos($_admin_title, $_page_title)) {
1274 $_document_title_suffix = substr($_admin_title, strlen($_page_title));
1275 }
1276 }
1277
1278 // Mail Mint's install state is read from get_plugins() below.
1279 if (!function_exists('get_plugins')) {
1280 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1281 }
1282
1283 /*
1284 * Core's plugin updater. The template library route offers to install
1285 * the page builder its templates are authored for when that plugin is
1286 * missing, and `wp.updates` is what does the installing.
1287 */
1288 wp_enqueue_script('updates');
1289
1290 /*
1291 * The media library and the editor. The funnel canvas is a hash route
1292 * of this one page, so there is no narrower screen to hang them on:
1293 * the order bump's image picker opens `wp.media()`, and its description
1294 * is a TinyMCE field started with `wp.editor.initialize()`. Neither
1295 * global exists unless these are enqueued — the legacy edit_funnel
1296 * screen did the same (see enqueue_scripts()).
1297 */
1298 wp_enqueue_media();
1299 wp_enqueue_editor();
1300
1301 wp_localize_script(
1302 'wpfnl-react-admin',
1303 'RWPFNLAdmin',
1304 array(
1305 'rest_api_url' => get_rest_url(),
1306 'nonce' => wp_create_nonce('wp_rest'),
1307 'security' => wp_create_nonce('wpfnl-admin'),
1308 'admin_url' => admin_url(),
1309 'image_path' => WPFNL_URL . 'admin/assets/images',
1310 // Base URL the bundle resolves its async chunks against — the
1311 // canvas editor is code-split (see src/admin/public-path.js).
1312 'dist_url' => WPFNL_URL . 'admin/assets/dist/',
1313 'priceConfig' => Wpfnl_functions::get_wc_price_config(),
1314 // The canvas's layout, so its loading placeholder takes the right shape.
1315 'canvasViewMode' => Wpfnl_functions::get_canvas_view_mode(),
1316 /*
1317 * The symbols the canvas analytics drawer prefixes, decoded as
1318 * the classic canvas's wc_currency / ld_currency are: WooCommerce
1319 * for sales funnels, LearnDash for LMS ones.
1320 */
1321 'analyticsCurrency' => array(
1322 'wc' => function_exists('get_woocommerce_currency_symbol') ? html_entity_decode(get_woocommerce_currency_symbol()) : '$',
1323 'lms' => function_exists('learndash_get_currency_symbol') ? html_entity_decode(learndash_get_currency_symbol()) : '$',
1324 ),
1325 'datepicker' => $this->get_datepicker_defaults(),
1326 'isProActivated' => apply_filters('wpfunnels/is_wpfnl_pro_active', false),
1327 // '0' sends #/integrations back to the legacy PHP page (old Pro, legacy addon, opt-out).
1328 'useReactIntegrations' => \WPFunnels\Menu\Wpfnl_Menus::should_use_react_integrations() ? '1' : '0',
1329 'isWPFIntegrationActive' => apply_filters('wpfunnels/is_integration_license_active', false),
1330 /*
1331 * Mail Mint's licence state, which decides whether the automation
1332 * route shows the promo screen at all or hands the visitor to the
1333 * legacy automation list.
1334 */
1335 'isMailMintProLicenseActive' => Wpfnl_functions::is_mail_mint_pro_license_active(),
1336 // Whether Mail Mint runs at all: the Canvas step cards' automation button, as the classic template_library_object.is_mint_active.
1337 'isMintActive' => Wpfnl_functions::is_mint_mrm_active(),
1338 /*
1339 * What the automation screen still needs, rung by rung, with the
1340 * next step for each. Plugin-installing rungs carry a basename and
1341 * slug the screen posts to wpfunnels/v1/activate-plugin; the rest
1342 * carry a URL.
1343 */
1344 'automationDependencies' => $this->get_automation_dependencies(),
1345 'canInstallPlugins' => current_user_can('install_plugins'),
1346 'canActivatePlugins' => current_user_can('activate_plugins'),
1347 'getText' => Wpfnl_functions::get_text(),
1348 /*
1349 * The plugin's translations as Jed locale data, which src/admin/i18n.js
1350 * hands to the bundled @wordpress/i18n. The bundle carries its own copy
1351 * of that package (it is not a webpack external), so core's JSON
1352 * translation files — what wp_set_script_translations() would register —
1353 * never reach it; the data has to ride on this handle instead.
1354 */
1355 'i18n' => $this->get_jed_locale_data('wpfnl')['locale_data']['wpfnl'],
1356 // Legacy Mail Mint action nodes (delay, sendMail, addTag, …) the
1357 // funnel canvas draws as small settings-only cards, as the classic
1358 // canvas's WPFunnelVars.onlySettingsSteps.
1359 'onlySettingsSteps' => array_values( Wpfnl_functions::get_only_settings_steps() ),
1360 'startOfWeek' => (int) get_option('start_of_week', 0),
1361 /*
1362 * Whether the onboarding checklist was on show last time the
1363 * dashboard asked — 'yes', 'no', or absent when unknown. Only
1364 * decides whether to hold space for it while its real status
1365 * loads; the status itself still comes from the REST call.
1366 */
1367 'onboardingHint' => get_transient(\WPFunnels\Rest\Controllers\DashboardController::ONBOARDING_VISIBILITY_KEY),
1368 /*
1369 * Header action group data. It has to ride on this handle, not on
1370 * the AI-drawer handle the Vue group used: WordPress prints a
1371 * script's localized data immediately before that script's own
1372 * tag, and the React bundle is enqueued first — so anything
1373 * attached to the drawer does not exist yet when React renders.
1374 */
1375 'isProLicenseActive' => $_is_pro_license_active,
1376 'upgradeUrl' => 'https://getwpfunnels.com/pricing/',
1377 'proCta' => $this->get_pro_cta(),
1378 /*
1379 * A/B testing needs a Pro of at least 1.7.3 — the classic canvas's
1380 * template_library_object.is_ab_tesing_available, which also stops
1381 * it drawing running tests an older Pro cannot serve. The licence
1382 * half of the check is isProLicenseActive.
1383 */
1384 'isAbTestingAvailable' => defined('WPFNL_PRO_VERSION') ? version_compare(WPFNL_PRO_VERSION, '1.7.3', '>=') : false,
1385 /*
1386 * What the classic canvas gets from the `wpfunnels/funnel_window_admin_localize`
1387 * filter (Pro's unsupported-gateway warning, the Oxygen notice, …),
1388 * as a real object — see get_canvas_localize().
1389 */
1390 'canvasLocalize' => $this->get_canvas_localize(),
1391 'documentTitleSuffix' => $_document_title_suffix,
1392 /*
1393 * What the template library route runs on. Every value here is
1394 * something only PHP can answer — which page builder the site is
1395 * set up for and whether its plugin is actually there, how much
1396 * of an unlicensed install's funnel allowance is left, which
1397 * template types this install has. Arrives as a real object:
1398 * wp_localize_script only stringifies top-level scalars.
1399 */
1400 'templateLibrary' => $this->get_template_library_data(),
1401 /*
1402 * Login name of the current user, which the MCP settings panel
1403 * writes into its connection snippets. No REST field carries it.
1404 */
1405 'currentUserLogin' => wp_get_current_user()->user_login,
1406 /*
1407 * What the setup wizard route (`#/setup`) reads: plugin and
1408 * builder states, saved settings, the admin's name and email,
1409 * image URLs. Null for users who may not run it, which the
1410 * route takes as "send them to the dashboard".
1411 */
1412 'setupWizard' => current_user_can( 'manage_options' ) ? \WPFunnels\Admin\SetupWizard::get_app_data() : null,
1413 )
1414 );
1415
1416 /**
1417 * Fires once the React admin's script and its localized data are queued.
1418 *
1419 * The counterpart of the classic canvas's `wpfunnels_after_scripts_loaded`
1420 * (which fires after the Vue canvas's scripts), so an add-on that enqueues
1421 * its own script here reaches the React screens too. Such a script should
1422 * depend on `wp-hooks` and register its `wpfunnels.canvas.*` filters there.
1423 *
1424 * @since 3.12.0
1425 */
1426 do_action('wpfunnels_after_scripts_loaded');
1427 }
1428
1429
1430 /**
1431 * What the classic canvas's `wpfunnels/funnel_window_admin_localize` filter
1432 * collects, for the React canvas.
1433 *
1434 * Pro adds its unsupported-gateway warning through that filter and WPFunnels
1435 * its Oxygen one, so applying it here is what lets both reach the React
1436 * canvas without either knowing it exists. Notice texts are HTML, which is
1437 * why they are run through wp_kses_post() — the React bar renders them as
1438 * markup.
1439 *
1440 * @return array Filter result, always an array (a callback returning nothing
1441 * must not erase the others'), `notices` normalised.
1442 */
1443 private function get_canvas_localize()
1444 {
1445 $localize = apply_filters('wpfunnels/funnel_window_admin_localize', array());
1446
1447 if (!is_array($localize)) {
1448 $localize = array();
1449 }
1450
1451 /**
1452 * Filters the data the React funnel canvas gets from PHP.
1453 *
1454 * Applied after `wpfunnels/funnel_window_admin_localize`, which the
1455 * classic canvas also reads, so code written for the React canvas only
1456 * hooks here. Arrives in the browser as `RWPFNLAdmin.canvasLocalize`,
1457 * then runs through the `wpfunnels.canvas.localize` JS filter (see
1458 * src/admin/extensions/hooks.js). `notices` entries are
1459 * `{ type, notice_type, notice_texts }`, with `notice_texts` as HTML
1460 * (passed through wp_kses_post).
1461 *
1462 * @param array $localize Canvas data.
1463 *
1464 * @since 3.13.8
1465 */
1466 $localize = apply_filters('wpfunnels/react_canvas_localize', $localize);
1467
1468 if (!is_array($localize)) {
1469 $localize = array();
1470 }
1471
1472 $notices = array();
1473
1474 if (!empty($localize['notices']) && is_array($localize['notices'])) {
1475 foreach ($localize['notices'] as $notice) {
1476 if (!is_array($notice) || empty($notice['notice_texts'])) {
1477 continue;
1478 }
1479
1480 $notice['notice_texts'] = wp_kses_post($notice['notice_texts']);
1481 $notices[] = $notice;
1482 }
1483 }
1484
1485 $localize['notices'] = $notices;
1486
1487 return $localize;
1488 }
1489
1490
1491 /**
1492 * Enqueue the webpack runtime chunk, when the build has one.
1493 *
1494 * A production build splits the runtime every entry shares into its own
1495 * chunk, which has to load once and before them. A development build keeps
1496 * it inside each entry and emits no such file — enqueuing it anyway only
1497 * produced a 404 — hence the existence check.
1498 *
1499 * @return bool Whether the runtime was enqueued, i.e. whether a bundle
1500 * should list it as a dependency.
1501 * @since 3.13.8
1502 */
1503 private function enqueue_webpack_runtime()
1504 {
1505 if (!file_exists(WPFNL_DIR . '/admin/assets/dist/runtime/index.min.js')) {
1506 return false;
1507 }
1508
1509 wp_enqueue_script($this->plugin_name . '-runtime', plugin_dir_url(__FILE__) . 'assets/dist/runtime/index.min.js', array(), $this->version, true);
1510
1511 return true;
1512 }
1513
1514
1515 /**
1516 * The header action group on the screens that are still rendered by PHP.
1517 *
1518 * admin/partials/dashboard-nav.php prints a #wpfnl-header-actions slot on
1519 * each of them, and the `wpfnl-header-actions` bundle mounts the React
1520 * admin's own HeaderActions component into it (src/header-actions/). It reads
1521 * the same `RWPFNLAdmin` global as the React admin, limited to what the
1522 * group uses: the REST root and nonce for the AI drawer, the admin URL for
1523 * the Settings link, and the Pro call to action.
1524 *
1525 * Not loaded on the funnel canvas, which prints no slot, nor on a React
1526 * admin screen, which renders the group itself (the settings hook is on
1527 * both lists).
1528 *
1529 * @param string $hook Current admin page hook.
1530 * @return void
1531 * @since 3.13.8
1532 */
1533 private function enqueue_header_actions($hook)
1534 {
1535 if (in_array($hook, $this->get_react_admin_hooks(), true)) {
1536 return;
1537 }
1538
1539 $deps = $this->enqueue_webpack_runtime() ? array($this->plugin_name . '-runtime') : array();
1540
1541 wp_enqueue_script('wpfnl-header-actions', plugin_dir_url(__FILE__) . 'assets/dist/js/wpfnl-header-actions.min.js', $deps, $this->version, true);
1542 Wpfnl_functions::enqueue_bundle_style('wpfnl-header-actions', 'wpfnl-header-actions-bundle');
1543
1544 wp_localize_script(
1545 'wpfnl-header-actions',
1546 'RWPFNLAdmin',
1547 array(
1548 'rest_api_url' => get_rest_url(),
1549 'nonce' => wp_create_nonce('wp_rest'),
1550 'admin_url' => admin_url(),
1551 'isProLicenseActive' => (bool) apply_filters('wpfunnels/is_pro_license_activated', false),
1552 'upgradeUrl' => 'https://getwpfunnels.com/pricing/',
1553 'proCta' => $this->get_pro_cta(),
1554 )
1555 );
1556 }
1557
1558
1559 /**
1560 * Register the JavaScript for the admin area.
1561 *
1562 * @since 1.0.0
1563 */
1564 public function enqueue_scripts($hook)
1565 {
1566 /**
1567 * This function is provided for demonstration purposes only.
1568 *
1569 * An instance of this class should be passed to the run() function
1570 * defined in Wpfnl_Loader as all of the hooks are defined
1571 * in that particular class.
1572 *
1573 * The Wpfnl_Loader will then create the relationship
1574 * between the defined hooks and the functions defined in this
1575 * class.
1576 */
1577
1578
1579 $screen = get_current_screen();
1580
1581
1582 $this->enqueue_react_admin_assets($hook);
1583
1584
1585 if ('dashboard' === $screen->id || 'plugins' === $screen->id) {
1586 wp_enqueue_script($this->plugin_name . '-global', plugin_dir_url(__FILE__) . 'assets/js/wpfnl-global.js', ['jquery', 'jquery-ui-sortable'], $this->version, true);
1587 }
1588
1589 if ('wpfunnels_page_email-builder' === $hook) {
1590 $is_wc_installed = 'no';
1591 $is_wpfunnels_installed = 'no';
1592 if (is_plugin_active('woocommerce/woocommerce.php')) {
1593 $is_wc_installed = 'yes';
1594 }
1595 if (is_plugin_active('wpfunnels/wpfunnels.php')) {
1596 $is_wpfunnels_installed = 'yes';
1597 }
1598 $dependency = require_once WPFNL_PATH . '/admin/assets/dist/email-builder/main.min.asset.php';
1599 wp_enqueue_script(
1600 $this->plugin_name . '-email-builder',
1601 plugin_dir_url(__FILE__) . 'assets/dist/email-builder/main.min.js',
1602 $dependency['dependencies'],
1603 $this->version,
1604 true
1605 );
1606 wp_localize_script(
1607 $this->plugin_name . '-email-builder',
1608 'WPFEmailBuilderVars',
1609 array(
1610 'editor_data_source' => $this->get_editor_source(),
1611 'post_types' => MrmCommon::get_all_post_types(),
1612 'is_wc_installed' => $is_wc_installed,
1613 'is_mint_active' => Wpfnl_functions::is_mint_mrm_active(),
1614 )
1615 );
1616 }
1617
1618 // Store Checkout without WooCommerce: power the "Plugin Required" modal
1619 // rendered by admin/modules/store-checkout/views/woocommerce-required.php.
1620 if ('wpfunnels_page_' . WPFNL_STORE_CHECKOUT_SLUG === $hook && !Wpfnl_functions::is_wc_active()) {
1621 $wc_plugin = 'woocommerce/woocommerce.php';
1622 $wc_status = Wpfnl_functions::is_plugin_installed($wc_plugin) ? 'inactive' : 'not-installed';
1623 $wc_label = 'not-installed' === $wc_status
1624 ? __('Install WooCommerce', 'wpfnl')
1625 : __('Activate WooCommerce', 'wpfnl');
1626
1627 wp_enqueue_script(
1628 $this->plugin_name . '-store-checkout-woo',
1629 plugin_dir_url(__FILE__) . 'assets/js/wpfnl-store-checkout-woo.js',
1630 ['jquery', 'updates'],
1631 $this->version,
1632 true
1633 );
1634 wp_localize_script($this->plugin_name . '-store-checkout-woo', 'wpfnlStoreCheckoutWoo', array(
1635 'ajaxUrl' => admin_url('admin-ajax.php'),
1636 'nonce' => wp_create_nonce('wpfnl_activate_plugin_nonce'),
1637 'plugin' => $wc_plugin,
1638 'slug' => 'woocommerce',
1639 'status' => $wc_status,
1640 'buttonLabel' => $wc_label,
1641 'i18n' => array(
1642 'installing' => __('Installing', 'wpfnl'),
1643 'activating' => __('Activating', 'wpfnl'),
1644 'redirecting' => __('Redirecting', 'wpfnl'),
1645 'error' => __('Something went wrong. Please install and activate WooCommerce manually.', 'wpfnl'),
1646 ),
1647 ));
1648 }
1649
1650
1651 if (in_array($hook, $this->page_hooks)) {
1652
1653 // The header action group — Knowledge Base, AI Assistant, Settings,
1654 // Upgrade — in the #wpfnl-header-actions slot of these screens.
1655 $this->enqueue_header_actions($hook);
1656
1657 $is_wc_installed = 'no';
1658 $is_wpfunnels_installed = 'no';
1659 if (is_plugin_active('woocommerce/woocommerce.php')) {
1660 $is_wc_installed = 'yes';
1661 }
1662 if (is_plugin_active('wpfunnels/wpfunnels.php')) {
1663 $is_wpfunnels_installed = 'yes';
1664 }
1665
1666 wp_enqueue_script($this->plugin_name . '-select2', plugin_dir_url(__FILE__) . 'assets/js/select2.min.js', ['jquery'], $this->version, true);
1667 $products = array();
1668 if (isset($_GET['step_id'])) {
1669 $step_id = sanitize_text_field($_GET['step_id']);
1670 if ($step_id) {
1671 if (Wpfnl_functions::check_if_this_is_step_type_by_id($step_id, 'checkout')) {
1672 $products = get_post_meta($step_id, '_wpfnl_checkout_products', true);
1673 if (empty($products)) {
1674 $products = [];
1675 }
1676 }
1677 }
1678 }
1679
1680 $funnel_id = '';
1681 $funnel_title = '';
1682 $step_id = '';
1683 if (isset($_GET['id'])) {
1684 $funnel_id = $_GET['id'];
1685 $funnel_title = html_entity_decode(get_the_title($funnel_id));
1686 if (isset($_GET['step_id'])) {
1687 $step_id = filter_input(INPUT_GET, 'step_id', FILTER_VALIDATE_INT);
1688 }
1689
1690 // For store checkout funnels, resolve the checkout step ID when not in URL.
1691 if ( empty( $step_id ) && 'store_checkout' === get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ) ) {
1692 $step_id = \WPFunnels\WooCommerce\Wpfnl_Store_Checkout_Conditions::get_checkout_step_id_for_funnel( $funnel_id );
1693 }
1694 }
1695
1696 /**
1697 * Get funnel preview link
1698 */
1699 $steps = get_post_meta($funnel_id, '_steps_order', true);
1700 $funnel_preview_link = '#';
1701 $response['success'] = false;
1702 if ($steps) {
1703 if (isset($steps[0]) && $steps[0]['id']) {
1704 $funnel_preview_link = get_post_permalink($steps[0]['id']);
1705 }
1706 }
1707
1708
1709 wp_enqueue_script('jquery-ui-datepicker');
1710 wp_enqueue_editor();
1711 wp_enqueue_script('wp-color-picker');
1712 wp_enqueue_media();
1713
1714 wp_enqueue_script($this->plugin_name . '-jed', plugin_dir_url(__FILE__) . 'assets/js/jed.js', ['jquery'], $this->version, true);
1715 wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'assets/js/wpfnl-admin.js', ['jquery', 'jquery-ui-sortable'], $this->version, true);
1716
1717 wp_enqueue_script($this->plugin_name . '-backbone-marionette', plugin_dir_url(__FILE__) . 'assets/lib/backbone/backbone.marionette.min.js', ['backbone'], $this->version, true);
1718 wp_enqueue_script($this->plugin_name . '-backbone-radio', plugin_dir_url(__FILE__) . 'assets/lib/backbone/backbone.radio.min.js', ['backbone'], $this->version, true);
1719
1720 $general_settings = Wpfnl_functions::get_general_settings();
1721 $builder = $general_settings['builder'];
1722 $funnel_builder_mode = Wpfnl_functions::get_default_funnel_builder_mode();
1723 $canvas_view_mode = Wpfnl_functions::get_canvas_view_mode();
1724
1725 /**
1726 * This code snippet will check if pro addons is activated or not. if not activated
1727 * Total number of funnels will be maximum 3, otherwise customer can add as more funnels
1728 * As they want
1729 */
1730 $is_pro_active = apply_filters('wpfunnels/is_pro_license_activated', false);
1731 $count_funnels = Wpfnl_functions::count_non_store_checkout_funnels( array( 'publish', 'draft', 'trash' ) );
1732 $count_active_funnels = Wpfnl_functions::count_non_store_checkout_funnels();
1733 $total_allowed_funnels = 3;
1734 if ($is_pro_active) {
1735 $total_allowed_funnels = -1;
1736 }
1737 $count_store_checkouts = Wpfnl_functions::count_store_checkout_funnels();
1738 $total_allowed_store_checkouts = $is_pro_active ? -1 : 3;
1739
1740 $wc_currency_symbol = '$';
1741 if (function_exists('get_woocommerce_currency_symbol')) {
1742 $wc_currency_symbol = html_entity_decode(get_woocommerce_currency_symbol());
1743 }
1744 $ld_currency_symbol = '$';
1745 if (function_exists('learndash_get_currency_symbol')) {
1746 $ld_currency_symbol = html_entity_decode(learndash_get_currency_symbol());
1747 }
1748
1749 $product_url = esc_url_raw(
1750 add_query_arg(
1751 array(
1752 'post_type' => 'product',
1753 'wpfunnels' => 'yes',
1754 ),
1755 admin_url('post-new.php')
1756 )
1757 );
1758
1759 wp_localize_script($this->plugin_name, 'WPFunnelVars', array(
1760 'ajaxurl' => admin_url('admin-ajax.php'),
1761 'rest_api_url' => get_rest_url(),
1762 'security' => wp_create_nonce('wpfnl-admin'),
1763 'import_nonce' => wp_create_nonce('wpfnl_import_funnels_nonce'),
1764 'admin_url' => admin_url(),
1765 'edit_funnel_url' => admin_url('admin.php?page=edit_funnel'),
1766 'i18n' => array('wpfnl' => $this->get_jed_locale_data('wpfnl')),
1767 'current_user_id' => get_current_user_id(),
1768 'is_wc_installed' => $is_wc_installed,
1769 'is_wpfunnels_installed' => $is_wpfunnels_installed,
1770 'products' => $products,
1771 'funnel_id' => $funnel_id,
1772 'step_id' => $step_id,
1773 'funnel_title' => $funnel_title,
1774 'funnel_preview_link' => $funnel_preview_link,
1775 'site_url' => site_url(),
1776 'image_path' => WPFNL_URL . 'admin/assets/images',
1777 'placeholder_image_path' => WPFNL_URL . 'admin/assets/images/ob_placeholder.png',
1778 'nonce' => wp_create_nonce('wp_rest'),
1779 'isNewFunnel' => \Wpfnl_Activator::is_new_install() ? true : false,
1780 'isProActivated' => $is_pro_active,
1781 'totalFunnels' => $count_funnels,
1782 'count_active_funnels' => $count_active_funnels,
1783 'totalStoreCheckouts' => $count_store_checkouts,
1784 'totalAllowedStoreCheckouts' => $total_allowed_store_checkouts,
1785 'product_url' => $product_url,
1786 'totalAllowedFunnels' => $total_allowed_funnels,
1787 'builder' => $builder,
1788 'funnel_builder_mode' => $funnel_builder_mode,
1789 'canvas_view_mode' => $canvas_view_mode,
1790 'dependencyPlugins' => Wpfnl_functions::get_dependency_plugins_status(),
1791 'isAnyPluginMissing' => Wpfnl_functions::is_any_plugin_missing(),
1792 'isGlobalFunnelActivated' => Wpfnl_functions::is_global_funnel_activated(),
1793 'isGlobalFunnel' => Wpfnl_functions::is_global_funnel($funnel_id),
1794 'GBFVersion' => defined('WPFNL_PRO_GBF_VERSION') ? WPFNL_PRO_GBF_VERSION : ( defined('WPFNL_PRO_GB_VERSION') ? WPFNL_PRO_GB_VERSION : '' ),
1795 'isSkipOffer' => $this->is_skip_offer(),
1796 'paymentMethod' => $this->get_payment_method(),
1797 'shippingMethod' => $this->get_shipping_method(),
1798 'isGbf' => $this->maybe_gbf($funnel_id),
1799 'isLms' => $this->maybe_lms($funnel_id),
1800 'lmsProvider' => $this->get_lms_provider(),
1801 'isLmsActivated' => Wpfnl_functions::is_lms_addon_active(),
1802 'isAnyLmsPluginActive' => Wpfnl_functions::is_any_lms_plugin_active(),
1803 'isLmsSettings' => Wpfnl_functions::is_enable_lms_settings(),
1804 'isLmsDisbaled' => $this->maybe_lms_settings_disbaled(),
1805 'lmsVersion' => (defined('WPFNL_PRO_LMS_VERSION') || defined('WPFUNNELS_PRO_LMS_VERSION')) && defined('LEARNDASH_VERSION') ? version_compare(LEARNDASH_VERSION, '4.2.1.1', '>=') : false,
1806 'individual_funnel_type' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true),
1807 'isStoreCheckout' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'store_checkout',
1808 'isSaleFunnel' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'wc' || get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'store_checkout',
1809 'gbf_set_condition_steps' => $this->get_gbf_steps($funnel_id),
1810 'reconfigurable_condition_data' => $this->get_reconfigurable_condition_data($funnel_id),
1811 'mint_steps' => $this->mint_step_settings($funnel_id),
1812 'mint_tags' => Wpfnl_functions::get_mint_contact_groups('tags'),
1813 'mint_lists' => Wpfnl_functions::get_mint_contact_groups('lists'),
1814 'mint_sequences' => Wpfnl_functions::get_sequences(),
1815 'obList' => Wpfnl_functions::get_all_ob($funnel_id),
1816 'upsellSteps' => Wpfnl_functions::get_selected_steps('upsell', $funnel_id),
1817 'downsellSteps' => Wpfnl_functions::get_selected_steps('downsell', $funnel_id),
1818 'landingAndCustomSteps' => Wpfnl_functions::get_selected_steps('landing', $funnel_id),
1819 'customSteps' => Wpfnl_functions::get_selected_steps('custom', $funnel_id),
1820 'onlySettingsSteps' => Wpfnl_functions::get_only_settings_steps(),
1821 'isWCML' => defined('WCML_VERSION'),
1822 'cfeDefault' => Wpfnl_functions::get_cfe_default_fields(),
1823 'adminEmail' => get_bloginfo('admin_email'),
1824 'siteName' => get_option('blogname'),
1825 'mailMintEmail' => Wpfnl_functions::get_mailmint_email(),
1826 'email_settings' => Wpfnl_functions::get_mailmint_email_settings(),
1827 'mint_twillo_settings' => Wpfnl_functions::get_mailmint_twillo_settings(),
1828 'emailBuilderUrl' => admin_url() . 'admin.php?page=email-builder',
1829 'currentUrl' => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]",
1830 'wc_currency' => $wc_currency_symbol,
1831 'ld_currency' => $ld_currency_symbol,
1832 'getText' => Wpfnl_functions::get_text(),
1833 'user_role_manager_data' => Wpfnl_functions::get_user_role_settings(),
1834 'availableGateways' => Wpfnl_functions::get_enabled_payment_gateways(),
1835 'guidedTour' => Wpfnl_functions::get_guided_tour(),
1836 'isWPFIntegrationActive' => apply_filters( 'wpfunnels/is_integration_license_active', false ),
1837 ));
1838 wp_localize_script(
1839 $this->plugin_name,
1840 'template_library_object',
1841 array(
1842 'ajaxurl' => esc_url_raw(admin_url('admin-ajax.php')),
1843 'rest_api_url' => esc_url_raw(get_rest_url()),
1844 'dashboard_url' => ( $funnel_id && get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ) === 'store_checkout' )
1845 ? esc_url_raw(admin_url('admin.php?page=store_checkout'))
1846 : esc_url_raw(admin_url('admin.php?page=' . WPFNL_FUNNEL_PAGE_SLUG)),
1847 'settings_url' => esc_url_raw(admin_url('admin.php?page=settings')),
1848 'home_url' => esc_url_raw(home_url()),
1849 'funnel_id' => $funnel_id,
1850 'is_pro' => Wpfnl_functions::is_pro_license_activated(),
1851 'is_ab_tesing_available' => defined('WPFNL_PRO_VERSION') ? version_compare(WPFNL_PRO_VERSION, "1.7.3", ">=") : false,
1852 'is_webhook_licensed' => Wpfnl_functions::is_webhook_license_activated(),
1853 'pro_url' => add_query_arg('wpfnl-dashboard', '1', GETWPFUNNEL_PRICING_URL),
1854 'nonce' => wp_create_nonce('wp_rest'),
1855 'image_path' => WPFNL_URL . 'admin/assets/images',
1856 'template_type' => Wpfnl_functions::get_template_types(),
1857 'countries' => Wpfnl_functions::get_countries(),
1858 'supported_steps' => Wpfnl_functions::get_supported_step_type(),
1859 'is_mint_active' => Wpfnl_functions::is_mint_mrm_active(),
1860 'settingsSteps' => Wpfnl_functions::get_settings_steps(),
1861 'isRemote' => Wpfnl_functions::maybe_remote_funnel(),
1862 'isStoreCheckout' => 'wpfunnels_page_store_checkout' === $hook,
1863 )
1864 );
1865
1866 do_action('wpfunnels_after_scripts_loaded');
1867 }
1868 }
1869
1870
1871 public function get_lms_provider()
1872 {
1873 $lms_settings = get_option('_wpfunnels_lms_settings', array());
1874 $provider_id = isset($lms_settings['lms_provider']) ? $lms_settings['lms_provider'] : 'learndash';
1875 return $provider_id;
1876 }
1877
1878
1879 /**
1880 * Get the reconfigurable condition data for a funnel
1881 */
1882 public function get_reconfigurable_condition_data($funnel_id)
1883 {
1884 if (!$funnel_id) {
1885 return [];
1886 }
1887 $data = get_post_meta($funnel_id, '_wpfnl_reconfigurable_condition_data', true);
1888 if (!is_array($data) || empty($data)) {
1889 return [];
1890 }
1891 return $data;
1892 }
1893
1894
1895 /**
1896 * Get all setps where GBF conditions is already set
1897 *
1898 * @param Integer $funnel_id
1899 *
1900 * @since 2.6.1
1901 */
1902 private function get_gbf_steps($funnel_id)
1903 {
1904 $gbf_steps = [];
1905 if ($funnel_id) {
1906 $funnel_type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true);
1907 $is_gbf = get_post_meta($funnel_id, 'is_global_funnel', true);
1908
1909 if ('yes' === $is_gbf) {
1910 $steps = Wpfnl_functions::get_steps($funnel_id);
1911 if (is_array($steps)) {
1912 foreach ($steps as $step) {
1913 if (isset($step['id'], $step['step_type'])) {
1914 if ('checkout' === $step['step_type']) {
1915 $conditions = get_post_meta($funnel_id, 'global_funnel_start_condition', true);
1916 if (is_array($conditions)) {
1917 array_push($gbf_steps, $step['id']);
1918 }
1919 }
1920 elseif ('upsell' === $step['step_type'] || 'downsell' === $step['step_type']) {
1921 $conditions = get_post_meta($step['id'], 'global_funnel_' . $step['step_type'] . '_rules', true);
1922 if (is_array($conditions)) {
1923 array_push($gbf_steps, $step['id']);
1924 }
1925 }
1926 }
1927 }
1928 }
1929 } elseif ('store_checkout' === $funnel_type) {
1930 $steps = Wpfnl_functions::get_steps($funnel_id);
1931 if (is_array($steps)) {
1932 foreach ($steps as $step) {
1933 if (isset($step['id'], $step['step_type']) && 'checkout' === $step['step_type']) {
1934 $raw = get_post_meta($step['id'], '_wpfnl_store_checkout_condition', true);
1935 $condition = is_string($raw) ? json_decode($raw, true) : $raw;
1936 if (is_array($condition) && isset($condition['condition_type']) && 'rules' === $condition['condition_type']) {
1937 array_push($gbf_steps, $step['id']);
1938 }
1939 }
1940 }
1941 }
1942 }
1943 }
1944
1945 return $gbf_steps;
1946 }
1947
1948
1949 /**
1950 * Returns Jed-formatted localization data.
1951 *
1952 * @param string $domain Translation domain.
1953 *
1954 * @return array
1955 */
1956 public function get_jed_locale_data($domain, $language_dir = null)
1957 {
1958 $plugin_translations = $this->get_translations_for_plugin_domain($domain, $language_dir);
1959 $translations = get_translations_for_domain($domain);
1960
1961 $locale = array(
1962 'domain' => $domain,
1963 'locale_data' => array(
1964 $domain => array(
1965 '' => array(
1966 'domain' => $domain,
1967 'lang' => is_admin() ? get_user_locale() : get_locale(),
1968 ),
1969 ),
1970 ),
1971 );
1972
1973 if (!empty($translations->headers['Plural-Forms'])) {
1974 $locale['locale_data'][$domain]['']['plural_forms'] = $translations->headers['Plural-Forms'];
1975 }
1976 else if (!empty($plugin_translations['header'])) {
1977 $locale['locale_data'][$domain]['']['plural_forms'] = $plugin_translations['header']['Plural-Forms'];
1978 }
1979
1980 $entries = array_merge($plugin_translations['translations'], $translations->entries);
1981
1982 foreach ($entries as $msgid => $entry) {
1983 $locale['locale_data'][$domain][$msgid] = $entry->translations;
1984 }
1985
1986 return $locale;
1987 }
1988
1989
1990 /**
1991 * Get translactions for WePos plugin
1992 *
1993 * @param string $domain
1994 * @param string $language_dir
1995 *
1996 * @return array
1997 */
1998 public function get_translations_for_plugin_domain($domain, $language_dir = null)
1999 {
2000 if ($language_dir == null) {
2001 $language_dir = WPFNL_PATH . '/languages/';
2002 }
2003 $languages = get_available_languages($language_dir);
2004 $get_site_lang = is_admin() ? get_user_locale() : get_locale();
2005 $mo_file_name = $domain . '-' . $get_site_lang;
2006 $translations = [];
2007
2008 if (in_array($mo_file_name, $languages) && file_exists($language_dir . $mo_file_name . '.mo')) {
2009 $mo = new \MO();
2010 if ($mo->import_from_file($language_dir . $mo_file_name . '.mo')) {
2011 $translations = $mo->entries;
2012 }
2013 }
2014
2015 return [
2016 'header' => isset($mo) ? $mo->headers : '',
2017 'translations' => $translations,
2018 ];
2019 }
2020
2021
2022 /**
2023 * Check GBF version for skip offer
2024 */
2025 public function is_skip_offer()
2026 {
2027 // GBF is now built into WPFunnels Pro — always available when GBF is activated.
2028 if ( Wpfnl_functions::is_global_funnel_activated() ) {
2029 return true;
2030 }
2031 // Legacy: standalone addon still active.
2032 if ( defined('WPFNL_PRO_GB_VERSION') ) {
2033 return version_compare( WPFNL_PRO_GB_VERSION, '1.0.9', '>=' );
2034 }
2035 return false;
2036 }
2037
2038 public function mint_step_settings($funnel_id)
2039 {
2040 $steps = get_post_meta($funnel_id, '_steps', true);
2041 $mint_steps = [];
2042 if (is_array($steps)) {
2043 foreach ($steps as $step) {
2044 $mint_settings['stepID'] = $step['id'];
2045 $value = get_post_meta($step['id'], '_wpfnl_automation_steps', true);
2046 $mint_settings['value'] = is_array($value) ? $value : [];
2047 array_push($mint_steps, $mint_settings);
2048 }
2049 }
2050 return $mint_steps;
2051 }
2052
2053
2054 /**
2055 * Define w2cloud routes
2056 *
2057 * @return array
2058 */
2059 public function get_wpfnl_routes()
2060 {
2061 $routes = array(
2062 array(
2063 'path' => '/',
2064 'name' => 'home',
2065 'component' => 'Home'
2066 )
2067 );
2068 return apply_filters('wpfnl_routes', $routes);
2069 }
2070
2071 /**
2072 * Get all active Payment Method
2073 *
2074 * @return array
2075 */
2076 public function get_payment_method()
2077 {
2078 if (Wpfnl_functions::is_wc_active()) {
2079 $gateways = WC()->payment_gateways->get_available_payment_gateways();
2080 $enabled_gateways = [];
2081
2082 if ($gateways) {
2083 foreach ($gateways as $key => $gateway) {
2084 if ($gateway->enabled == 'yes') {
2085 $enabled_gateways[$key] = $gateway->method_title;
2086 }
2087 }
2088 }
2089 return $enabled_gateways;
2090 }
2091 return [];
2092 }
2093
2094 /**
2095 * Get all shipping methods
2096 *
2097 * @return array
2098 */
2099 public function get_shipping_method()
2100 {
2101 if (Wpfnl_functions::is_wc_active()) {
2102 $available_shipping = WC()->shipping()->get_shipping_methods();
2103 $enabled_shipping = [];
2104 if ($available_shipping) {
2105 foreach ($available_shipping as $key => $shipping) {
2106 $enabled_shipping[$key] = $shipping->method_title;
2107 }
2108 }
2109 return $enabled_shipping;
2110 }
2111 return [];
2112 }
2113
2114
2115 /**
2116 * Show funnel steps on reading
2117 *
2118 * @param $output
2119 *
2120 * @return mixed
2121 */
2122 public function show_funnel_steps_on_reading($output)
2123 {
2124 global $pagenow;
2125 if (('options-reading.php' === $pagenow || 'customize.php' === $pagenow) && preg_match('#page_on_front#', $output)) {
2126 $output = $this->show_steps_options($output);
2127 }
2128 return $output;
2129 }
2130
2131
2132 /**
2133 * Show steps pages on reading
2134 *
2135 * @param $output
2136 *
2137 * @return string
2138 */
2139 private function show_steps_options($output)
2140 {
2141 $options = '';
2142
2143 $steps = get_posts(
2144 array(
2145 'post_type' => WPFNL_STEPS_POST_TYPE,
2146 'posts_per_page' => -1,
2147 'numberposts' => 100,
2148 'orderby' => 'title',
2149 'order' => 'ASC',
2150 'post_status' => 'publish',
2151 'meta_query' => array(
2152 'relation' => 'OR',
2153 array(
2154 'key' => '_step_type',
2155 'value' => 'landing',
2156 ),
2157 array(
2158 'key' => '_step_type',
2159 'value' => 'checkout',
2160 )
2161 ),
2162 )
2163 );
2164
2165 if ($steps && is_array($steps)) {
2166 $front_page_id = get_option('page_on_front');
2167 foreach ($steps as $step) {
2168 $selected = selected($front_page_id, $step->ID, false);
2169 $post_type_obj = get_post_type_object($step->post_type);
2170 $options .= "<option value=\"{$step->ID}\"{$selected}>{$step->post_title} (WPFunnels {$post_type_obj->labels->singular_name})</option>";
2171 }
2172 $options .= '</select>';
2173 $output = str_replace('</select>', $options, $output);
2174 }
2175 return $output;
2176 }
2177
2178
2179 /**
2180 * Get notice for builder
2181 *
2182 * @return array
2183 */
2184 public function get_builder_notice()
2185 {
2186 $builder = Wpfnl_functions::get_builder_type();
2187 $notices = array();
2188 if ('divi-builder' === $builder) {
2189 $notices['notices'][] = array();
2190 }
2191 return $notices;
2192 }
2193
2194
2195 /**
2196 * Change text in order details page
2197 */
2198 public function wpfnl_customizing_checkout_text($translated_text, $untranslated_text, $domain)
2199 {
2200
2201 if (is_admin()) {
2202 if ($translated_text == 'Coupon(s):') {
2203 $translated_text = __('Discount:', 'wpfnl');
2204 }
2205 }
2206
2207 return $translated_text;
2208 }
2209
2210
2211 /**
2212 * Change local for if this is funnel canvas window
2213 *
2214 * @param $local
2215 *
2216 * @return string
2217 *
2218 * @since 2.3.8
2219 */
2220 public function change_local_for_funnel_canvas($local)
2221 {
2222 if (Wpfnl_functions::is_funnel_canvas_window()) {
2223 return 'en_US';
2224 }
2225 return $local;
2226 }
2227
2228
2229 /**
2230 * Add type meta
2231 *
2232 * @since 2.4.4
2233 */
2234 public function wpfnl_add_type_meta()
2235 {
2236 Wpfnl_functions::add_type_meta();
2237 }
2238
2239
2240 /**
2241 * Maybe Global funnel
2242 *
2243 * @param String
2244 *
2245 * @return Bool
2246 * @since 2.4.11
2247 */
2248 public function maybe_gbf($funnel_id)
2249 {
2250
2251 if ( Wpfnl_functions::is_global_funnel_activated() ) {
2252 if ($funnel_id) {
2253 $is_gbf = get_post_meta($funnel_id, 'is_global_funnel', true);
2254 if ('yes' == $is_gbf) {
2255 return true;
2256 }
2257 }
2258 }
2259
2260 return false;
2261 }
2262
2263 /**
2264 * Check funnel type is lms or not by funnel_id
2265 *
2266 * @return Bool
2267 */
2268 public function maybe_lms($funnel_id)
2269 {
2270
2271 $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true);
2272 if ('lms' === $type) {
2273 return true;
2274 }
2275 return false;
2276 }
2277
2278 /**
2279 * May be lms settings is disbaled
2280 */
2281 public function maybe_lms_settings_disbaled()
2282 {
2283 return Wpfnl_functions::is_wc_active() && Wpfnl_functions::is_lms_addon_active();
2284 }
2285
2286
2287 /**
2288 * Trigger when any plugin is deactivated
2289 *
2290 * @param $filename
2291 *
2292 * @since 2.5.1
2293 */
2294 public function wpfnl_deactivated_plugin($filename)
2295 {
2296 if ('woocommerce/woocommerce.php' === $filename) {
2297 Wpfnl_functions::wpfnl_delete_transient();
2298 }
2299 elseif ('sfwd-lms/sfwd_lms.php' === $filename) {
2300 Wpfnl_functions::wpfnl_delete_transient();
2301 }
2302 elseif ('wpfunnels-pro-lms/wpfunnels-pro-lms.php' === $filename) {
2303 Wpfnl_functions::wpfnl_delete_transient();
2304 }
2305 elseif ('qubely/qubely.php' === $filename) {
2306 Wpfnl_functions::wpfnl_delete_transient();
2307 delete_option(WPFNL_TEMPLATES_OPTION_KEY . '_wc');
2308 delete_option(WPFNL_TEMPLATES_OPTION_KEY . '_lms');
2309 delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION);
2310 delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION);
2311 delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION);
2312 delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION);
2313 }
2314 }
2315
2316
2317 /**
2318 * Trigger when any plugin is activated
2319 *
2320 * @param $filename
2321 *
2322 * @since 2.5.1
2323 */
2324 public function wpfnl_activated_plugin($filename)
2325 {
2326 if ('woocommerce/woocommerce.php' === $filename) {
2327 Wpfnl_functions::wpfnl_delete_transient();
2328 }
2329 elseif ('sfwd-lms/sfwd_lms.php' === $filename) {
2330 if (Wpfnl_functions::is_lms_addon_active()) {
2331 Wpfnl_functions::wpfnl_delete_transient();
2332 }
2333 }
2334 elseif ('wpfunnels-pro-lms/wpfunnels-pro-lms.php' === $filename) {
2335 Wpfnl_functions::wpfnl_delete_transient();
2336 }
2337 elseif ('qubely/qubely.php' === $filename) {
2338 Wpfnl_functions::wpfnl_delete_transient();
2339 delete_option(WPFNL_TEMPLATES_OPTION_KEY . '_wc');
2340 delete_option(WPFNL_TEMPLATES_OPTION_KEY . '_lms');
2341 delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION);
2342 delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION);
2343 delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION);
2344 delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION);
2345 }
2346 }
2347
2348
2349
2350 /**
2351 * Get editor source data
2352 *
2353 * @return array
2354 * @since 1.0.0
2355 */
2356 private function get_editor_source()
2357 {
2358 // get product categories for email builder.
2359 $wc_categories = $this->get_formatted_wc_categories();
2360 $wp_categories = $this->get_formatted_wp_post_categories();
2361
2362 return apply_filters(
2363 'plugin_hook_name',
2364 array(
2365 'product_categories' => $wc_categories,
2366 'post_categories' => $wp_categories,
2367 'placeholder_image' => ''
2368 )
2369 );
2370 }
2371
2372
2373
2374 /**
2375 * Get the WooCommerce product categories
2376 *
2377 * @return array
2378 * @since 1.0.0
2379 */
2380 private function get_formatted_wc_categories()
2381 {
2382 $taxonomy = 'product_cat';
2383 $orderby = 'name';
2384 $show_count = 0;
2385 $pad_counts = 0;
2386 $hierarchical = 1;
2387 $title = '';
2388 $empty = 0;
2389
2390 $args = array(
2391 'taxonomy' => $taxonomy,
2392 'orderby' => $orderby,
2393 'show_count' => $show_count,
2394 'pad_counts' => $pad_counts,
2395 'hierarchical' => $hierarchical,
2396 'title_li' => $title,
2397 'hide_empty' => $empty,
2398 );
2399 $product_categories = get_categories($args);
2400 $wc_categories = array();
2401 foreach ($product_categories as $product_cat) {
2402 $wc_categories[] = array(
2403 'value' => $product_cat->term_id,
2404 'label' => $product_cat->name,
2405 );
2406 }
2407
2408 return $wc_categories;
2409 }
2410
2411
2412 /**
2413 * Get the WordPress post categories
2414 *
2415 * @return array
2416 * @since 1.0.0
2417 */
2418 private function get_formatted_wp_post_categories()
2419 {
2420 $taxonomy = 'category';
2421 $orderby = 'name';
2422 $show_count = 0;
2423 $pad_counts = 0;
2424 $hierarchical = 1;
2425 $title = '';
2426 $empty = 0;
2427
2428 $args = array(
2429 'taxonomy' => $taxonomy,
2430 'orderby' => $orderby,
2431 'show_count' => $show_count,
2432 'pad_counts' => $pad_counts,
2433 'hierarchical' => $hierarchical,
2434 'title_li' => $title,
2435 'hide_empty' => $empty,
2436 );
2437 $post_categories = get_categories($args);
2438 $categories = array();
2439 foreach ($post_categories as $post_cat) {
2440 $categories[] = array(
2441 'value' => $post_cat->term_id,
2442 'label' => $post_cat->name,
2443 );
2444 }
2445 return $categories;
2446 }
2447
2448
2449 /**
2450 * Add custom capabilities to administrator role
2451 *
2452 * @since 3.1.3
2453 */
2454 public function attach_admin_capabilities()
2455 {
2456 global $wp_roles;
2457 $capabilities = array(
2458 'wpf_manage_funnels',
2459 );
2460 foreach ($capabilities as $cap) {
2461 $wp_roles->add_cap('administrator', $cap);
2462 }
2463 }
2464
2465 /**
2466 * Checks if the current page slug matches the provided slug.
2467 *
2468 * @param $slug
2469 * @return bool
2470 * @since 3.5.0
2471 */
2472 public function is_current_page($slug)
2473 {
2474 if (empty($slug)) {
2475 return false;
2476 }
2477 $current_page_slug = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : '';
2478 if ($slug === $current_page_slug) {
2479 return true;
2480 }
2481 return false;
2482 }
2483
2484 /**
2485 * Add body class for WPFunnels admin pages.
2486 *
2487 * @param string $classes Current body classes.
2488 * @return string Modified body classes.
2489 * @since 3.9.5
2490 */
2491 public function add_wpfunnels_admin_body_class($classes)
2492 {
2493 // Check if we're on a WPFunnels admin page.
2494 $screen = get_current_screen();
2495
2496 if ($screen && (in_array($screen->id, $this->page_hooks) || 'wpfunnels_page_' . WPFNL_AI_FUNNEL_PAGE_SLUG === $screen->id)) {
2497 $classes .= ' wpfunnels-body';
2498 }
2499
2500 // The React admin styles the WordPress content column itself, so it gets
2501 // its own class instead of the legacy one.
2502 if ($screen && in_array($screen->id, $this->get_react_admin_hooks(), true)) {
2503 $classes .= ' rwpfnl-page';
2504 }
2505
2506 return $classes;
2507 }
2508
2509
2510 /**
2511 * Create contact on mailmint when user allows to track data
2512 *
2513 * @param array $data
2514 *
2515 * @since 3.3.1
2516 */
2517 public function wpfunnels_tracker_optin($data)
2518 {
2519 if (isset($data['admin_email'], $data['first_name'], $data['last_name'])) {
2520 $json_body_data = json_encode([
2521 'email' => $data['admin_email'],
2522 'first_name' => $data['first_name'],
2523 'last_name' => $data['last_name'],
2524 ]);
2525
2526 $webHookUrl = [
2527 'https://staging-useraccount.kinsta.cloud/?mailmint=1&route=webhook&topic=contact&hash=b258136d-6759-4e91-bbab-0b7397af6dc7'
2528 ];
2529
2530 try {
2531 if (!empty($webHookUrl)) {
2532 foreach ($webHookUrl as $url) {
2533 wp_remote_request($url, [
2534 'method' => 'POST',
2535 'headers' => [
2536 'Content-Type' => 'application/json',
2537 ],
2538 'body' => $json_body_data
2539 ]);
2540 }
2541 }
2542 }
2543 catch (\Exception $e) {
2544 }
2545 }
2546 }
2547
2548 /**
2549 * Creates a new MRM contact.
2550 *
2551 * @param array $data The data for the contact.
2552 * @return void
2553 */
2554 public function create_mrm_contact($data)
2555 {
2556 if (!isset($data['data']['user_email'])) {
2557 return;
2558 }
2559 // Get double opt-in settings.
2560 $default = \MRM\Common\MrmCommon::double_optin_default_configuration();
2561 $settings = get_option('_mrm_optin_settings', $default);
2562 $enable = isset($settings['enable']) ? $settings['enable'] : false;
2563
2564 $user_data = array(
2565 'email' => $data['data']['user_email'],
2566 'first_name' => isset($data['data']['first_name']) ? $data['data']['first_name'] : '',
2567 'last_name' => isset($data['data']['last_name']) ? $data['data']['last_name'] : '',
2568 'meta_fields' => array(
2569 'phone_number' => isset($data['data']['phone_number']) ? $data['data']['phone_number'] : '',
2570 ),
2571 'status' => $enable ? 'pending' : 'subscribed',
2572 'source' => 'WPFunnels',
2573 );
2574
2575 $mail_mint_object = new \WPFunnels\Integrations\MailMint($user_data);
2576 $mail_mint_object->create_or_update_contact();
2577 }
2578 }