PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.2
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.2
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / admin / class-wpfnl-admin.php

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

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