PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.9
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.9
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / inc / admin / popup-builder / class-popup-frontend.php

class-popup-frontend.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.1.9, at inc/admin/popup-builder/class-popup-frontend.php

710 lines 29.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace MasterAddons\Inc\Admin\PopupBuilder;
3
4 use MasterAddons\Inc\Classes\Assets_Manager;
5
6 if (!defined('ABSPATH')) {
7 exit;
8 }
9
10 class Popup_Frontend {
11
12 private static $instance = null;
13 private $conditions_checker;
14 private $displayed_popups = [];
15
16 public static function get_instance() {
17 if (self::$instance === null) {
18 self::$instance = new self();
19 }
20 return self::$instance;
21 }
22
23 public function __construct() {
24 // Initialize conditions checker
25 add_action('init', function(){
26 $this->conditions_checker = new Popup_Conditions();
27 });
28 $this->init_hooks();
29 }
30
31 private function init_hooks() {
32 add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']);
33 add_action('wp_footer', [$this, 'render_popups']);
34 add_action('wp_ajax_jltma_popup_track_view', [$this, 'track_popup_view']);
35 add_action('wp_ajax_nopriv_jltma_popup_track_view', [$this, 'track_popup_view']);
36 add_action('wp_ajax_jltma_popup_track_conversion', [$this, 'track_popup_conversion']);
37 add_action('wp_ajax_nopriv_jltma_popup_track_conversion', [$this, 'track_popup_conversion']);
38 add_action('wp_ajax_jltma_popup_disable_expired', [$this, 'disable_expired_popup']);
39 add_action('wp_ajax_nopriv_jltma_popup_disable_expired', [$this, 'disable_expired_popup']);
40 add_action('wp_head', [$this, 'set_visitor_cookie']);
41 }
42
43 public function enqueue_frontend_assets() {
44 $popups = $this->get_active_popups_cpt();
45
46 // Enqueue popup builder frontend assets via Assets Manager
47 Assets_Manager::enqueue('popup-builder-frontend');
48
49 wp_localize_script('jltma-popup-builder-frontend', 'jltma_popup_frontend', [
50 'ajax_url' => admin_url('admin-ajax.php'),
51 'nonce' => wp_create_nonce('jltma_popup_frontend'),
52 'popups' => $this->prepare_popups_data($popups)
53 ]);
54
55 // Pro: enqueue additional popup frontend scripts (triggers, etc.)
56 do_action('master_addons/popup_builder/enqueue_frontend_scripts');
57 }
58
59 public function render_popups() {
60 // Get all published popup posts
61 $args = [
62 'post_type' => 'jltma_popup',
63 'post_status' => 'publish',
64 'posts_per_page' => -1,
65 'orderby' => 'date',
66 'order' => 'ASC',
67 ];
68
69 $popups = get_posts($args);
70
71 if (empty($popups) || !$popups) {
72 return;
73 }
74
75 foreach ($popups as $popup) {
76 // Check conditions before rendering
77 if ($this->should_display_popup($popup->ID)) {
78 $this->render_single_popup($popup->ID);
79 $this->displayed_popups[] = $popup->ID;
80 }
81 }
82 }
83
84 public function get_displayed_popups() {
85 return $this->displayed_popups;
86 }
87
88 private function should_display_popup($popup_id) {
89 // First check activation status
90 $activation_status = get_post_meta($popup_id, '_jltma_popup_activation', true);
91 if ($activation_status !== 'yes') {
92 return false;
93 }
94
95 // Check if popup has expired based on automatic disable settings
96 $elementor_settings = get_post_meta($popup_id, '_elementor_page_settings', true);
97
98 // Check if automatic disable is enabled and if the expiration date has passed
99 if (!empty($elementor_settings['popup_disable_automatic']) && $elementor_settings['popup_disable_automatic'] === 'yes') {
100 if (!empty($elementor_settings['popup_disable_after'])) {
101 $expiration_date = strtotime($elementor_settings['popup_disable_after']);
102 $current_time = current_time('timestamp');
103
104 // If current time is past the expiration date, don't display the popup
105 if ($current_time > $expiration_date) {
106 // Deactivate the popup and reset automatic disable settings
107 update_post_meta($popup_id, '_jltma_popup_activation', 'no');
108
109 // Reset the automatic disable settings in Elementor page settings
110 if (!empty($elementor_settings)) {
111 $elementor_settings['popup_disable_automatic'] = 'no';
112 update_post_meta($popup_id, '_elementor_page_settings', $elementor_settings);
113 }
114
115 return false;
116 }
117 }
118 }
119
120 // Get popup conditions from meta
121 $conditions_data = get_post_meta($popup_id, '_jltma_popup_conditions_data', true);
122
123 // If no conditions set, don't display
124 if (empty($conditions_data)) {
125 return false;
126 }
127
128 // Unserialize if needed
129 if (is_string($conditions_data)) {
130 $conditions_data = maybe_unserialize($conditions_data);
131 }
132
133 // Exclude conditions always win, regardless of their order relative to
134 // includes, so includes only resolve after the whole set is scanned.
135 $has_include = false;
136 $include_matched = false;
137
138 // Check each condition
139 foreach ($conditions_data as $condition) {
140 $type = $condition['type'] ?? 'include';
141 $rule = $condition['rule'] ?? '';
142 $specific = $condition['specific'] ?? '';
143 // Pro "Singular/Archive" schema stores target IDs in a separate `posts` array,
144 // with `specific` holding the sub-type (page/post/CPT slug).
145 $posts = isset($condition['posts']) ? (array) $condition['posts'] : [];
146
147 $condition_met = false;
148
149 // Check different rule types
150 switch ($rule) {
151 case 'entire_site':
152 $condition_met = true;
153 break;
154
155 // Pro condition schema: rule=singular, specific=<page|post|CPT>, posts=<IDs|empty=all>
156 case 'singular':
157 $target_ids = array_filter(array_map('intval', $posts));
158 if ($specific === 'page') {
159 $condition_met = empty($target_ids) ? is_page() : is_page($target_ids);
160 } elseif ($specific === 'post') {
161 $condition_met = empty($target_ids) ? is_single() : is_single($target_ids);
162 } elseif (!empty($specific)) {
163 $condition_met = empty($target_ids)
164 ? is_singular($specific)
165 : (is_singular($specific) && in_array((int) get_the_ID(), $target_ids, true));
166 } else {
167 $condition_met = is_singular();
168 }
169 break;
170
171 case 'archive':
172 $condition_met = is_archive();
173 break;
174
175 case 'search':
176 $condition_met = is_search();
177 break;
178
179 case '404':
180 $condition_met = is_404();
181 break;
182
183 case 'front_page':
184 $condition_met = is_front_page();
185 break;
186
187 case 'home_page':
188 $condition_met = is_home();
189 break;
190
191 case 'all_pages':
192 $condition_met = is_page();
193 break;
194
195 case 'all_posts':
196 $condition_met = is_single();
197 break;
198
199 case 'all_archives':
200 $condition_met = is_archive();
201 break;
202
203 case 'search_results':
204 $condition_met = is_search();
205 break;
206
207 case '404_page':
208 $condition_met = is_404();
209 break;
210
211 case 'specific_pages':
212 if (!empty($specific)) {
213 $page_ids = is_array($specific) ? $specific : explode(',', $specific);
214 $condition_met = is_page($page_ids);
215 }
216 break;
217
218 case 'specific_posts':
219 if (!empty($specific)) {
220 $post_ids = is_array($specific) ? $specific : explode(',', $specific);
221 $condition_met = is_single($post_ids);
222 }
223 break;
224
225 case 'post_categories':
226 if (!empty($specific)) {
227 $category_ids = is_array($specific) ? $specific : explode(',', $specific);
228 $condition_met = is_category($category_ids) || (is_single() && has_category($category_ids));
229 }
230 break;
231
232 case 'post_tags':
233 if (!empty($specific)) {
234 $tag_ids = is_array($specific) ? $specific : explode(',', $specific);
235 $condition_met = is_tag($tag_ids) || (is_single() && has_tag($tag_ids));
236 }
237 break;
238
239 case 'woo_shop':
240 if (class_exists('WooCommerce')) {
241 $condition_met = is_shop();
242 }
243 break;
244
245 case 'woo_product':
246 if (class_exists('WooCommerce')) {
247 $condition_met = is_product();
248 }
249 break;
250
251 case 'woo_cart':
252 if (class_exists('WooCommerce')) {
253 $condition_met = is_cart();
254 }
255 break;
256
257 case 'woo_checkout':
258 if (class_exists('WooCommerce')) {
259 $condition_met = is_checkout();
260 }
261 break;
262 }
263
264 // Handle include/exclude logic
265 if ($type === 'exclude') {
266 // An exclude match wins immediately and hides the popup.
267 if ($condition_met) {
268 return false;
269 }
270 } else {
271 // Defer the include decision until every exclude has been checked.
272 $has_include = true;
273 if ($condition_met) {
274 $include_matched = true;
275 }
276 }
277 }
278
279 // Only display if at least one include condition was defined and met.
280 return $has_include && $include_matched;
281 }
282
283 private function render_single_popup($popup_id) {
284 // Get Elementor content
285 $elementor_content = \Elementor\Plugin::instance()->frontend->get_builder_content($popup_id, false);
286
287 if (empty($elementor_content)) {
288 return;
289 }
290
291 // Get popup settings from Elementor page settings
292 $elementor_settings = get_post_meta($popup_id, '_elementor_page_settings', true);
293 if (empty($elementor_settings)) {
294 $elementor_settings = [];
295 }
296
297 // Prepare popup settings for JavaScript
298 $popup_settings = [
299 // Trigger settings
300 'popup_trigger' => $elementor_settings['popup_trigger'] ?? 'page-load',
301 'popup_load_delay' => $elementor_settings['popup_load_delay'] ?? 1,
302 'popup_scroll_progress' => isset($elementor_settings['popup_scroll_progress']['size']) ? $elementor_settings['popup_scroll_progress']['size'] : ($elementor_settings['popup_scroll_progress'] ?? 50),
303 'popup_element_scroll' => $elementor_settings['popup_element_scroll'] ?? '',
304 'popup_specific_date' => $elementor_settings['popup_specific_date'] ?? '',
305 'popup_inactivity_time' => $elementor_settings['popup_inactivity_time'] ?? 15,
306 'popup_custom_trigger' => $elementor_settings['popup_custom_trigger'] ?? '',
307 'popup_click_trigger' => $elementor_settings['popup_click_trigger'] ?? '',
308
309 // Display settings
310 'popup_display_as' => $elementor_settings['popup_display_as'] ?? 'modal',
311 'popup_position' => $elementor_settings['popup_position'] ?? 'center-center',
312 'popup_animation' => $elementor_settings['popup_animation'] ?? 'jltma-anim-fade-in',
313 'popup_animation_duration' => $elementor_settings['popup_animation_duration'] ?? 400,
314 'popup_custom_positioning' => $elementor_settings['popup_custom_positioning'] ?? '',
315
316 // Close settings
317 'popup_show_close_button' => $elementor_settings['popup_show_close_button'] ?? 'yes',
318 'popup_close_button_position' => $elementor_settings['popup_close_button_position'] ?? 'top-right',
319 'popup_close_button_display_delay' => $elementor_settings['popup_close_button_display_delay'] ?? 0,
320 'popup_automatic_close_switch' => $elementor_settings['popup_automatic_close_switch'] ?? 'no',
321 'popup_automatic_close_delay' => $elementor_settings['popup_automatic_close_delay'] ?? 10,
322 'popup_close_on_overlay' => $elementor_settings['popup_close_on_overlay'] ?? 'yes',
323 'popup_close_esc_key' => $elementor_settings['popup_close_esc_key'] ?? 'yes',
324 'popup_show_overlay' => $elementor_settings['popup_show_overlay'] ?? 'yes',
325
326 // Behavior settings
327 'popup_disable_page_scroll' => $elementor_settings['popup_disable_page_scroll'] ?? 'yes',
328 'popup_show_again_delay' => $elementor_settings['popup_show_again_delay'] ?? 'no-delay',
329 'popup_show_on_device' => $elementor_settings['popup_show_on_device'] ?? 'yes',
330 'popup_show_on_device_tablet' => $elementor_settings['popup_show_on_device_tablet'] ?? 'yes',
331 'popup_show_on_device_mobile' => $elementor_settings['popup_show_on_device_mobile'] ?? 'yes',
332
333 // Advanced settings
334 'popup_stop_after_date' => $elementor_settings['popup_stop_after_date'] ?? 'no',
335 'popup_stop_after_date_select' => $elementor_settings['popup_stop_after_date_select'] ?? '',
336 'popup_show_via_referral' => $elementor_settings['popup_show_via_referral'] ?? 'no',
337 'popup_referral_keyword' => $elementor_settings['popup_referral_keyword'] ?? '',
338
339 // Automatic disable settings
340 'popup_disable_automatic' => $elementor_settings['popup_disable_automatic'] ?? 'no',
341 'popup_disable_after' => $elementor_settings['popup_disable_after'] ?? '',
342 ];
343
344 // Free: restrict Pro features. Pro hooks to return original settings.
345 $free_settings = $popup_settings;
346 $free_settings['popup_trigger'] = 'page-load';
347 $free_settings['popup_close_esc_key'] = 'yes';
348 $free_settings['popup_disable_automatic'] = 'no';
349
350 // Restrict show again delay to free options only
351 $free_delay_options = ['no-delay', '1-minute', '3-minutes', '5-minutes'];
352 if (!in_array($free_settings['popup_show_again_delay'], $free_delay_options, true)) {
353 $free_settings['popup_show_again_delay'] = 'no-delay';
354 }
355
356 $popup_settings = apply_filters('master_addons/popup_builder/frontend_settings', $free_settings, $popup_settings);
357
358 // Add position class
359 $position_class = 'jltma-popup-position-' . str_replace('_', '-', $popup_settings['popup_position']);
360
361 // Add custom positioning class if enabled
362 if (!empty($elementor_settings['popup_custom_positioning']) && $elementor_settings['popup_custom_positioning'] === 'yes') {
363 $position_class .= ' jltma-popup-custom-position';
364 }
365
366 // Encode settings for data attribute
367 $encoded_settings = wp_json_encode($popup_settings);
368
369 // Don't render in preview mode (editor handles it differently)
370 if (\Elementor\Plugin::instance()->preview->is_preview_mode()) {
371 return;
372 }
373
374 // Render popup HTML structure (matching Royal Addons pattern with jltma- prefix)
375 ?>
376 <div id="jltma-popup-id-<?php echo esc_attr($popup_id); ?>" class="jltma-template-popup <?php echo esc_attr($position_class); ?>" data-settings='<?php echo esc_attr($encoded_settings); ?>'>
377 <div class="jltma-template-popup-inner">
378
379 <!-- Popup Overlay -->
380 <div class="jltma-popup-overlay"></div>
381
382 <!-- Template Container -->
383 <div class="jltma-popup-container">
384
385 <?php
386 // Show close button by default or when explicitly set to 'yes'
387 $show_close_button = !isset($elementor_settings['popup_show_close_button']) || $elementor_settings['popup_show_close_button'] === 'yes';
388 if ($show_close_button) :
389 ?>
390 <!-- Close Button -->
391 <div class="jltma-popup-close-btn jltma-popup-close-<?php echo esc_attr($elementor_settings['popup_close_button_position'] ?? 'top-right'); ?>">
392 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
393 </div>
394 <?php endif; ?>
395
396 <!-- Elementor Template Content -->
397 <div class="jltma-popup-container-inner">
398 <?php echo $elementor_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
399 </div>
400
401 </div>
402
403 </div>
404 </div>
405 <?php
406 }
407
408 private function get_active_popups_cpt() {
409 $args = [
410 'post_type' => 'jltma_popup',
411 'post_status' => 'publish',
412 'posts_per_page' => -1,
413 'meta_query' => [
414 [
415 'key' => '_jltma_popup_status',
416 'value' => 'active',
417 'compare' => '='
418 ]
419 ]
420 ];
421
422 $posts = get_posts($args);
423 $popups = [];
424
425 foreach ($posts as $post) {
426 $popups[] = [
427 'id' => $post->ID,
428 'name' => $post->post_title,
429 'type' => get_post_meta($post->ID, '_jltma_popup_type', true) ?: 'modal',
430 'settings' => get_post_meta($post->ID, '_jltma_popup_settings', true) ?: [],
431 'conditions' => get_post_meta($post->ID, '_jltma_popup_conditions', true) ?: []
432 ];
433 }
434
435 return $popups;
436 }
437
438 private function prepare_popups_data($popups) {
439 $data = [];
440
441 foreach ($popups as $popup) {
442 if (!$this->conditions_checker->check_conditions($popup['id'])) {
443 continue;
444 }
445
446 // Settings might be JSON string or array
447 $settings = is_string($popup['settings']) ? json_decode($popup['settings'], true) : $popup['settings'];
448 if (!$settings) {
449 $settings = [];
450 }
451
452 $data[] = [
453 'id' => $popup['id'],
454 'type' => $popup['type'],
455 'trigger' => 'load',
456 'trigger_delay' => 0,
457 'trigger_scroll_percent' => $settings['trigger_scroll_percent'] ?? 50,
458 'trigger_element' => $settings['trigger_element'] ?? '',
459 'show_frequency' => $settings['show_frequency'] ?? 'always',
460 'close_on_overlay' => $settings['close_on_overlay'] ?? true,
461 'close_on_esc' => $settings['close_on_esc'] ?? true,
462 'prevent_scroll' => $settings['prevent_scroll'] ?? true,
463 'auto_close' => $settings['popup_automatic_close_switch'] ?? false,
464 'auto_close_delay' => ($settings['popup_automatic_close_delay'] ?? 10) * 1000,
465 ];
466 }
467
468 return $data;
469 }
470
471 public function track_popup_view() {
472 check_ajax_referer('jltma_popup_frontend', 'nonce');
473
474 $popup_id = isset( $_POST['popup_id'] ) ? intval( $_POST['popup_id'] ) : 0;
475
476 if (!$popup_id) {
477 wp_send_json_error();
478 }
479
480 $popup = get_post($popup_id);
481 if (!$popup || $popup->post_type !== 'jltma_popup' || 'publish' !== $popup->post_status) {
482 wp_send_json_error(['message' => esc_html__('Invalid popup', 'master-addons')]);
483 }
484
485 // Update views count in post meta
486 $current_views = get_post_meta($popup_id, '_jltma_popup_views', true);
487 $current_views = $current_views ? intval($current_views) : 0;
488 update_post_meta($popup_id, '_jltma_popup_views', $current_views + 1);
489
490 // Set cookie to track frequency
491 $this->set_popup_cookie($popup_id);
492
493 // Track in analytics if enabled
494 do_action('jltma_popup_view_tracked', $popup_id);
495
496 wp_send_json_success();
497 }
498
499 public function track_popup_conversion() {
500 check_ajax_referer('jltma_popup_frontend', 'nonce');
501
502 $popup_id = isset( $_POST['popup_id'] ) ? intval( $_POST['popup_id'] ) : 0;
503
504 if (!$popup_id) {
505 wp_send_json_error();
506 }
507
508 $popup = get_post($popup_id);
509 if (!$popup || $popup->post_type !== 'jltma_popup' || 'publish' !== $popup->post_status) {
510 wp_send_json_error(['message' => esc_html__('Invalid popup', 'master-addons')]);
511 }
512
513 // Update conversions count in post meta
514 $current_conversions = get_post_meta($popup_id, '_jltma_popup_conversions', true);
515 $current_conversions = $current_conversions ? intval($current_conversions) : 0;
516 update_post_meta($popup_id, '_jltma_popup_conversions', $current_conversions + 1);
517
518 // Track in analytics if enabled
519 do_action('jltma_popup_conversion_tracked', $popup_id);
520
521 wp_send_json_success();
522 }
523
524 private function set_popup_cookie($popup_id) {
525 $popup = $this->get_popup_settings($popup_id);
526
527 if (!$popup) {
528 return;
529 }
530
531 // Settings might be JSON string or array
532 $settings = is_string($popup['settings']) ? json_decode($popup['settings'], true) : $popup['settings'];
533 $frequency = $settings['show_frequency'] ?? 'always';
534
535 $cookie_name = 'jltma_popup_shown_' . $popup_id;
536 $expiration = 0;
537
538 switch ($frequency) {
539 case 'once_day':
540 $expiration = time() + DAY_IN_SECONDS;
541 break;
542 case 'once_week':
543 $expiration = time() + WEEK_IN_SECONDS;
544 break;
545 case 'once':
546 $expiration = time() + (30 * DAY_IN_SECONDS);
547 break;
548 }
549
550 if ($expiration > 0) {
551 $max_age = $expiration - time();
552
553 $cookie_js = sprintf(
554 'document.cookie = "%1$s=%2$d; max-age=%3$d; path=/;";',
555 esc_js($cookie_name),
556 absint(time()),
557 (int) $max_age
558 );
559 $this->add_popup_inline_script($cookie_js);
560 }
561
562 if ($frequency === 'once_session') {
563 // Session cookie (no max-age/expires) — cleared automatically when the browser closes.
564 // Avoids PHP sessions, which break full-page caching on the frontend.
565 $session_js = sprintf(
566 'document.cookie = "%1$s=%2$d; path=/;";',
567 esc_js($cookie_name),
568 absint(time())
569 );
570 $this->add_popup_inline_script($session_js);
571 }
572 }
573
574 // public function set_visitor_cookie() {
575 // if (!isset($_COOKIE['jltma_popup_visitor'])) {
576 // setcookie('jltma_popup_visitor', '1', time() + (365 * DAY_IN_SECONDS), '/');
577 // }
578 // }
579
580 public function set_visitor_cookie() {
581 // Skip on admin, Elementor preview/editor, and if cookie already set
582 if ( is_feed() || is_admin() || isset($_COOKIE['jltma_popup_visitor']) || isset($_GET['elementor-preview']) || ( defined('ELEMENTOR_VERSION') && \Elementor\Plugin::$instance->preview->is_preview_mode() ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only existence check, no data processed
583 return;
584 }
585 $this->add_popup_inline_script('if (document.cookie.indexOf("jltma_popup_visitor=") === -1) { document.cookie = "jltma_popup_visitor=1; max-age=31536000; path=/;"; }');
586 }
587
588 /**
589 * Queue a small inline script through the enqueue API (printed in the footer)
590 * instead of echoing a hardcoded <script> tag. Multiple calls accumulate on the
591 * same inline-only handle.
592 *
593 * @param string $js
594 */
595 private function add_popup_inline_script($js) {
596 if (!wp_script_is('jltma-popup-inline', 'registered')) {
597 wp_register_script('jltma-popup-inline', false, array(), JLTMA_VER, true);
598 }
599 if (!wp_script_is('jltma-popup-inline', 'enqueued')) {
600 wp_enqueue_script('jltma-popup-inline');
601 }
602 wp_add_inline_script('jltma-popup-inline', $js);
603 }
604
605 private function get_popup_settings($popup_id) {
606 // Get popup post
607 $popup_post = get_post($popup_id);
608
609 if (!$popup_post || $popup_post->post_type !== 'jltma_popup') {
610 return false;
611 }
612
613 // Get settings from post meta
614 $popup = [
615 'id' => $popup_id,
616 'name' => $popup_post->post_title,
617 'type' => get_post_meta($popup_id, '_jltma_popup_type', true) ?: 'modal',
618 'status' => get_post_meta($popup_id, '_jltma_popup_status', true) ?: 'active',
619 'settings' => get_post_meta($popup_id, '_jltma_popup_settings', true) ?: [],
620 'conditions' => get_post_meta($popup_id, '_jltma_popup_conditions', true) ?: []
621 ];
622
623 return $popup;
624 }
625
626 // private function create_database_table() {
627 // global $wpdb;
628
629 // $table_name = $wpdb->prefix . 'jltma_popups';
630 // $charset_collate = $wpdb->get_charset_collate();
631
632 // $sql = "CREATE TABLE IF NOT EXISTS $table_name (
633 // id mediumint(9) NOT NULL AUTO_INCREMENT,
634 // name varchar(255) NOT NULL,
635 // type varchar(50) NOT NULL,
636 // content longtext NOT NULL,
637 // settings longtext,
638 // status varchar(20) DEFAULT 'active',
639 // views int DEFAULT 0,
640 // conversions int DEFAULT 0,
641 // created_at datetime DEFAULT CURRENT_TIMESTAMP,
642 // updated_at datetime DEFAULT CURRENT_TIMESTAMP,
643 // PRIMARY KEY (id)
644 // ) $charset_collate;";
645
646 // require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
647 // dbDelta($sql);
648 // }
649
650 public function disable_expired_popup() {
651 // Verify nonce for security
652 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'jltma_popup_frontend')) {
653 wp_die('Security check failed');
654 }
655
656 if (!isset($_POST['popup_id'])) {
657 wp_send_json_error('Popup ID not provided');
658 }
659
660 $popup_id = intval($_POST['popup_id']);
661
662 // Validate target is a real popup post. The publish status is intentionally
663 // not enforced here because this handler disables expired popups, which may
664 // already be in a non-publish state.
665 $popup = get_post($popup_id);
666 if (!$popup || $popup->post_type !== 'jltma_popup') {
667 wp_send_json_error(['message' => esc_html__('Invalid popup', 'master-addons')]);
668 }
669
670 $elementor_settings = get_post_meta($popup_id, '_elementor_page_settings', true);
671 if (!is_array($elementor_settings)) {
672 $elementor_settings = [];
673 }
674
675 // The frontend nonce is handed to every visitor, including logged-out ones,
676 // so it is a CSRF token and not an authorization boundary. Users who can edit
677 // the popup may disable it outright; for everyone else the expiration is
678 // re-verified server side, so an untrusted request can only trigger the same
679 // deactivation should_display_popup() would perform on its own. Without this
680 // check any visitor could disable an arbitrary popup by post ID.
681 if (!current_user_can('edit_post', $popup_id)) {
682 $auto_disable = isset($elementor_settings['popup_disable_automatic']) ? $elementor_settings['popup_disable_automatic'] : '';
683 $disable_after = isset($elementor_settings['popup_disable_after']) ? $elementor_settings['popup_disable_after'] : '';
684
685 if ('yes' !== $auto_disable || empty($disable_after)) {
686 wp_send_json_error(['message' => esc_html__('Popup is not scheduled for automatic expiration', 'master-addons')]);
687 }
688
689 $expiration_date = strtotime($disable_after);
690 if (false === $expiration_date || current_time('timestamp') <= $expiration_date) {
691 wp_send_json_error(['message' => esc_html__('Popup has not expired', 'master-addons')]);
692 }
693 }
694
695 // Deactivate the popup
696 update_post_meta($popup_id, '_jltma_popup_activation', 'no');
697
698 // Reset the automatic disable settings in Elementor page settings
699 if (!empty($elementor_settings)) {
700 $elementor_settings['popup_disable_automatic'] = 'no';
701 update_post_meta($popup_id, '_elementor_page_settings', $elementor_settings);
702 }
703
704 wp_send_json_success([
705 'message' => 'Popup has been disabled due to expiration',
706 'popup_id' => $popup_id
707 ]);
708 }
709 }
710