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-cpt.php

class-popup-cpt.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-cpt.php

572 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Master Addons Popup Custom Post Type
4 *
5 * @package MasterAddons
6 * @subpackage PopupBuilder
7 */
8
9 namespace MasterAddons\Inc\Admin\PopupBuilder;
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 class Popup_CPT {
16
17 private static $instance = null;
18 private $post_type = 'jltma_popup';
19
20 public static function get_instance() {
21 if (self::$instance === null) {
22 self::$instance = new self();
23 }
24 return self::$instance;
25 }
26
27 public function __construct() {
28 $this->init_hooks();
29 }
30
31 private function init_hooks() {
32 add_action('init', [$this, 'register_post_type']);
33 add_filter('manage_jltma_popup_posts_columns', [$this, 'set_custom_columns']);
34 add_action('manage_jltma_popup_posts_custom_column', [$this, 'custom_column_content'], 10, 2);
35 add_filter('manage_edit-jltma_popup_sortable_columns', [$this, 'set_sortable_columns']);
36 add_action('add_meta_boxes', [$this, 'add_meta_boxes']);
37 add_action('save_post', [$this, 'save_meta_data']);
38 add_filter('post_row_actions', [$this, 'modify_row_actions'], 10, 2);
39 add_action('admin_head', [$this, 'admin_head_css']);
40 add_action('wp_ajax_jltma_popup_get_shortcode', [$this, 'get_shortcode_ajax']);
41 // Don't override single_template - let Elementor handle it
42 // add_filter('single_template', [$this, 'load_canvas_template']);
43
44 // Add Elementor support
45 add_filter('elementor/utils/get_public_post_types', [$this, 'add_elementor_cpt_support']);
46 add_action('elementor/init', [$this, 'register_with_elementor']);
47
48 // Prevent redirect when editing with Elementor
49 add_action('admin_init', [$this, 'prevent_elementor_redirect'], 1);
50 add_filter('user_has_cap', [$this, 'grant_elementor_edit_cap'], 10, 4);
51 }
52
53 public function register_post_type() {
54 $labels = [
55 'name' => _x('Master Addons Popups', 'Post Type General Name', 'master-addons'),
56 'singular_name' => _x('Popup', 'Post Type Singular Name', 'master-addons'),
57 'menu_name' => __('Popups', 'master-addons'),
58 'name_admin_bar' => __('Popup', 'master-addons'),
59 'archives' => __('Popup Archives', 'master-addons'),
60 'attributes' => __('Popup Attributes', 'master-addons'),
61 'parent_item_colon' => __('Parent Popup:', 'master-addons'),
62 'all_items' => __('All Popups', 'master-addons'),
63 'add_new_item' => __('Add New Popup', 'master-addons'),
64 'add_new' => __('Add New', 'master-addons'),
65 'new_item' => __('New Popup', 'master-addons'),
66 'edit_item' => __('Edit Popup', 'master-addons'),
67 'update_item' => __('Update Popup', 'master-addons'),
68 'view_item' => __('View Popup', 'master-addons'),
69 'view_items' => __('View Popups', 'master-addons'),
70 'search_items' => __('Search Popup', 'master-addons'),
71 'not_found' => __('Not found', 'master-addons'),
72 'not_found_in_trash' => __('Not found in Trash', 'master-addons'),
73 'featured_image' => __('Featured Image', 'master-addons'),
74 'set_featured_image' => __('Set featured image', 'master-addons'),
75 'remove_featured_image' => __('Remove featured image', 'master-addons'),
76 'use_featured_image' => __('Use as featured image', 'master-addons'),
77 'insert_into_item' => __('Insert into popup', 'master-addons'),
78 'uploaded_to_this_item' => __('Uploaded to this popup', 'master-addons'),
79 'items_list' => __('Popups list', 'master-addons'),
80 'items_list_navigation' => __('Popups list navigation', 'master-addons'),
81 'filter_items_list' => __('Filter popups list', 'master-addons'),
82 ];
83
84 $args = [
85 'label' => __('Popup', 'master-addons'),
86 'description' => __('Master Addons Popup Templates', 'master-addons'),
87 'labels' => $labels,
88 'supports' => ['title', 'editor', 'elementor'],
89 'public' => true,
90 'show_ui' => true,
91 'show_in_menu' => false, // We'll add it to our custom menu
92 'menu_position' => 20,
93 'show_in_admin_bar' => true, // Enable this so Elementor can detect it
94 'show_in_nav_menus' => false,
95 'can_export' => true,
96 'has_archive' => false,
97 'exclude_from_search' => true,
98 'publicly_queryable' => true,
99 'capability_type' => 'post',
100 'show_in_rest' => true,
101 'rewrite' => false, // Disable rewrite since we use canvas template
102 ];
103
104 register_post_type($this->post_type, $args);
105
106 // Add Elementor support explicitly for this post type
107 if (defined('ELEMENTOR_VERSION')) {
108 add_post_type_support($this->post_type, 'elementor');
109
110 // Update Elementor's CPT support option
111 $cpt_support = get_option('elementor_cpt_support', ['page', 'post']);
112 if (!in_array($this->post_type, $cpt_support)) {
113 $cpt_support[] = $this->post_type;
114 update_option('elementor_cpt_support', $cpt_support);
115 }
116 }
117 }
118
119 /**
120 * Load Elementor canvas template for popup frontend display
121 * Don't override when editing in Elementor
122 */
123 public function load_canvas_template($single_template) {
124 global $post;
125
126 // Don't override template when editing with Elementor
127 if (is_admin() || (isset($_GET['action']) && $_GET['action'] === 'elementor')) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only template check, no form submission
128 return $single_template;
129 }
130
131 if ($post && $post->post_type === $this->post_type) {
132 // Check if Elementor is installed
133 if (defined('ELEMENTOR_PATH')) {
134 $elementor_2_0_canvas = ELEMENTOR_PATH . '/modules/page-templates/templates/canvas.php';
135
136 if (file_exists($elementor_2_0_canvas)) {
137 return $elementor_2_0_canvas;
138 } else {
139 return ELEMENTOR_PATH . '/includes/page-templates/canvas.php';
140 }
141 }
142 }
143
144 return $single_template;
145 }
146
147 public function set_custom_columns($columns) {
148 $new_columns = [];
149
150 $new_columns['cb'] = $columns['cb'];
151 $new_columns['title'] = __('Popup Name', 'master-addons');
152 $new_columns['jltma_popup_conditions'] = __('Conditions', 'master-addons');
153 $new_columns['jltma_popup_status'] = __('Status', 'master-addons');
154 $new_columns['jltma_popup_shortcode'] = __('Shortcode', 'master-addons');
155 // $new_columns['date'] = $columns['date'];
156
157 return $new_columns;
158 }
159
160 public function custom_column_content($column, $post_id) {
161 switch ($column) {
162 case 'jltma_popup_conditions':
163 $this->render_conditions_column($post_id);
164 break;
165
166 case 'jltma_popup_shortcode':
167 $this->render_shortcode_column($post_id);
168 break;
169
170 case 'jltma_popup_status':
171 $this->render_status_column($post_id);
172 break;
173 }
174 }
175
176 private function render_conditions_column($post_id) {
177 $conditions_data = get_post_meta($post_id, '_jltma_popup_conditions_data', true);
178 $cond_parts = [];
179
180 if (!empty($conditions_data) && is_array($conditions_data)) {
181 foreach ($conditions_data as $condition) {
182 $type_label = isset($condition['type']) ? ucfirst($condition['type']) : 'Include';
183 $rule_label = isset($condition['rule']) ? ucwords(str_replace('_', ' ', $condition['rule'])) : '';
184
185 $cond_text = $type_label . ' &gt; ' . $rule_label;
186
187 // Handle singular conditions with post types
188 if (isset($condition['rule']) && $condition['rule'] === 'singular') {
189 if (!empty($condition['specific'])) {
190 $post_type_obj = get_post_type_object($condition['specific']);
191 $post_type_label = $post_type_obj ? $post_type_obj->label : $condition['specific'];
192
193 // Check if specific posts are selected
194 if (!empty($condition['posts']) && is_array($condition['posts'])) {
195 $post_titles = [];
196 foreach ($condition['posts'] as $condition_post_id) {
197 if ($condition_post_id && is_numeric($condition_post_id)) {
198 $post_title = get_the_title($condition_post_id);
199 if ($post_title) {
200 $post_titles[] = $post_title;
201 }
202 }
203 }
204
205 if (!empty($post_titles)) {
206 $cond_text .= ' &gt; ' . $post_type_label . ' &gt; ' . implode(', ', $post_titles);
207 } else {
208 $cond_text .= ' &gt; All ' . $post_type_label;
209 }
210 } else {
211 $cond_text .= ' &gt; All ' . $post_type_label;
212 }
213 } else {
214 $cond_text .= ' &gt; All';
215 }
216 }
217 // Handle archive conditions
218 elseif (isset($condition['rule']) && $condition['rule'] === 'archive') {
219 if (!empty($condition['specific'])) {
220 // Format archive type labels
221 $archive_label = $condition['specific'];
222 if ($condition['specific'] === 'category') {
223 $archive_label = 'Category';
224 } elseif ($condition['specific'] === 'post_tag') {
225 $archive_label = 'Tag';
226 } elseif ($condition['specific'] === 'author') {
227 $archive_label = 'Author';
228 } elseif ($condition['specific'] === 'date') {
229 $archive_label = 'Date';
230 } else {
231 // For custom taxonomies
232 $taxonomy = get_taxonomy($condition['specific']);
233 if ($taxonomy) {
234 $archive_label = $taxonomy->labels->singular_name;
235 }
236 }
237 $cond_text .= ' &gt; ' . $archive_label;
238 } else {
239 $cond_text .= ' &gt; All';
240 }
241 }
242 // Other condition types remain as is (404, search, front_page, entire_site)
243
244 $cond_parts[] = $cond_text;
245 }
246 }
247
248 if (empty($cond_parts)) {
249 $cond_parts[] = 'Include &gt; Entire Site';
250 }
251
252 $condition_text = implode('<br>', $cond_parts);
253 echo wp_kses_post( $condition_text );
254 $edit_conditions_html = '<br><a href="#" class="jltma-popup-edit-conditions" data-popup-id="' . $post_id . '">'
255 . '<span class="dashicons dashicons-edit"></span> ' . __('Edit Conditions', 'master-addons')
256 . '</a>';
257 echo wp_kses_post( apply_filters('master_addons/popup_builder/edit_conditions_link', $edit_conditions_html, $post_id) );
258 }
259
260 private function render_shortcode_column($post_id) {
261 $shortcode = '[jltma_popup id="' . $post_id . '"]';
262 echo '<div style="display: flex; gap: 4px; position: relative;">';
263 echo '<input type="text" readonly value="' . \esc_attr($shortcode) . '" onclick="this.select()" style="flex: 1; font-family: monospace; font-size: 12px; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; background: #f9f9f9;" title="' . \esc_attr__('Click to select', 'master-addons') . '">';
264 echo '<button type="button" class="jltma-copy-shortcode" data-shortcode="' . \esc_attr($shortcode) . '" style="padding: 4px 6px; border: 1px solid #ddd; border-radius: 3px; background: #f9f9f9; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; position: absolute; right: 4px; top: 50%; transform: translateY(-50%);" title="' . \esc_attr__('Copy to clipboard', 'master-addons') . '">';
265 echo '<span class="dashicons dashicons-clipboard" style="font-size: 14px; width: 14px; height: 14px;"></span>';
266 echo '</button>';
267 echo '</div>';
268 }
269
270 private function render_status_column($post_id) {
271 $status = get_post_meta($post_id, '_jltma_popup_activation', true);
272 $is_active = ($status === 'yes');
273
274 // Check if popup has automatic expiration and has expired
275 if ($is_active) {
276 $elementor_settings = get_post_meta($post_id, '_elementor_page_settings', true);
277
278 if (!empty($elementor_settings['popup_disable_automatic']) && $elementor_settings['popup_disable_automatic'] === 'yes') {
279 if (!empty($elementor_settings['popup_disable_after'])) {
280 $expiration_date = strtotime($elementor_settings['popup_disable_after']);
281 $current_time = current_time('timestamp');
282
283 // If expired, update the status
284 if ($current_time > $expiration_date) {
285 $is_active = false;
286 // Update the activation status in database
287 update_post_meta($post_id, '_jltma_popup_activation', 'no');
288
289 // Reset the automatic disable settings
290 $elementor_settings['popup_disable_automatic'] = 'no';
291 update_post_meta($post_id, '_elementor_page_settings', $elementor_settings);
292 }
293 }
294 }
295 }
296
297 $status_class = $is_active ? 'jltma-popup-status-active' : 'jltma-popup-status-inactive';
298 $status_text = $is_active ? 'Active' : 'Inactive';
299
300 echo '<span class="jltma-popup-status ' . esc_attr( $status_class ) . '">' . esc_html($status_text) . '</span>';
301 }
302
303 public function set_sortable_columns($columns) {
304 $columns['jltma_popup_status'] = 'jltma_popup_status';
305 return $columns;
306 }
307
308 public function modify_row_actions($actions, $post) {
309 if ($post->post_type === $this->post_type) {
310 // Remove default actions
311 unset($actions['inline hide-if-no-js']);
312 unset($actions['view']);
313
314 // Replace default edit with modal opener
315 $actions['edit'] = '<a href="#" class="jltma-edit-popup" data-popup-id="' . $post->ID . '">' . __('Edit', 'master-addons') . '</a>';
316 }
317
318 return $actions;
319 }
320
321 public function add_meta_boxes() {
322 add_meta_box(
323 'jltma_popup_settings',
324 __('Popup Settings', 'master-addons'),
325 [$this, 'render_popup_settings_meta_box'],
326 $this->post_type,
327 'side',
328 'high'
329 );
330
331 add_meta_box(
332 'jltma_popup_conditions',
333 __('Display Conditions', 'master-addons'),
334 [$this, 'render_popup_conditions_meta_box'],
335 $this->post_type,
336 'normal',
337 'high'
338 );
339 }
340
341 public function render_popup_settings_meta_box($post) {
342 wp_nonce_field('jltma_popup_meta_nonce', 'jltma_popup_meta_nonce_field');
343
344 $activation = get_post_meta($post->ID, '_jltma_popup_activation', true);
345
346 ?>
347 <table class="form-table">
348 <tr>
349 <td>
350 <label>
351 <input type="checkbox" name="jltma_popup_activation" value="yes" <?php checked($activation, 'yes'); ?>>
352 <?php esc_html_e('Active', 'master-addons'); ?>
353 </label>
354 </td>
355 </tr>
356 </table>
357 <?php
358 }
359
360 public function render_popup_conditions_meta_box($post) {
361 $conditions_data = get_post_meta($post->ID, '_jltma_popup_conditions_data', true);
362
363 ?>
364 <div class="jltma-popup-conditions-meta">
365 <p><?php esc_html_e('Configure where this popup should be displayed. Use the "Edit Conditions" button in the popup list for advanced condition management.', 'master-addons'); ?></p>
366 <button type="button" class="button jltma-popup-edit-conditions" data-popup-id="<?php echo absint( $post->ID ); ?>">
367 <?php esc_html_e('Edit Conditions', 'master-addons'); ?>
368 </button>
369 </div>
370 <?php
371 }
372
373 public function save_meta_data($post_id) {
374 if (!isset($_POST['jltma_popup_meta_nonce_field']) ||
375 !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['jltma_popup_meta_nonce_field'] ) ), 'jltma_popup_meta_nonce')) {
376 return;
377 }
378
379 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
380 return;
381 }
382
383 if (!current_user_can('edit_post', $post_id)) {
384 return;
385 }
386
387 // Save activation status
388 $activation = isset($_POST['jltma_popup_activation']) ? 'yes' : 'no';
389 update_post_meta($post_id, '_jltma_popup_activation', $activation);
390 }
391
392 public function admin_head_css() {
393 global $current_screen;
394
395 if (!$current_screen || $current_screen->post_type !== $this->post_type) {
396 return;
397 }
398
399 ?>
400 <style>
401 .jltma-popup-status {
402 display: inline-block;
403 margin-left: 5px;
404 padding: 3px 10px;
405 border-radius: 50px;
406 font-size: 11px;
407 line-height: 1.4;
408 font-weight: 600;
409 text-transform: capitalize;
410 letter-spacing: 0.02em;
411 background-color: #fef2f2;
412 color: #dc2626;
413 border: 1px solid #fecaca;
414 }
415 .jltma-popup-status-active {
416 background-color: #ecfdf5;
417 color: #059669;
418 border: 1px solid #a7f3d0;
419 }
420 .jltma-popup-edit-conditions {
421 color: #2271b1;
422 text-decoration: none;
423 font-size: 12px;
424 }
425 .jltma-copy-shortcode {
426 color: #2271b1;
427 text-decoration: none;
428 font-size: 12px;
429 }
430 .jltma-copy-shortcode:hover {
431 color: #135e96;
432 }
433 .jltma-shortcode-display {
434 background: #f0f0f1;
435 padding: 2px 4px;
436 border-radius: 2px;
437 font-size: 11px;
438 }
439 .jltma-popup-conditions {
440 font-size: 12px;
441 color: #50575e;
442 }
443 </style>
444 <?php
445 }
446
447 public function get_shortcode_ajax() {
448 check_ajax_referer('jltma_popup_nonce', '_nonce');
449
450 if ( ! current_user_can( 'edit_posts' ) ) {
451 wp_send_json_error( [ 'message' => __( 'Insufficient permissions', 'master-addons' ) ] );
452 }
453
454 $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
455 $shortcode = '[jltma_popup id="' . $post_id . '"]';
456
457 wp_send_json_success(['shortcode' => $shortcode]);
458 }
459
460 public function get_post_type() {
461 return $this->post_type;
462 }
463
464 /**
465 * Add popup post type to Elementor supported post types
466 */
467 public function add_elementor_cpt_support($post_types) {
468 $post_types[$this->post_type] = $this->post_type;
469 return $post_types;
470 }
471
472 /**
473 * Register popup post type with Elementor on init
474 */
475 public function register_with_elementor() {
476 if (!defined('ELEMENTOR_VERSION')) {
477 return;
478 }
479
480 // Ensure the post type is in Elementor's CPT support list
481 $cpt_support = get_option('elementor_cpt_support', ['page', 'post']);
482 if (!in_array($this->post_type, $cpt_support)) {
483 $cpt_support[] = $this->post_type;
484 update_option('elementor_cpt_support', $cpt_support);
485 }
486 }
487
488 /**
489 * Prevent redirect when editing popup with Elementor
490 * Also ensures proper document type is set
491 */
492 public function prevent_elementor_redirect() {
493 // Check if we're trying to edit a popup with Elementor
494 if (!isset($_GET['action']) || $_GET['action'] !== 'elementor') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only redirect check, no form submission
495 return;
496 }
497
498 if (!isset($_GET['post'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only redirect check, no form submission
499 return;
500 }
501
502 $post_id = intval($_GET['post']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only redirect check, no form submission
503
504 if (!current_user_can('edit_post', $post_id)) {
505 return;
506 }
507
508 $post = get_post($post_id);
509
510 if (!$post || $post->post_type !== $this->post_type) {
511 return;
512 }
513
514 // Force add to Elementor's CPT support before any checks
515 if (defined('ELEMENTOR_VERSION')) {
516 $cpt_support = get_option('elementor_cpt_support', ['page', 'post']);
517 if (!in_array($this->post_type, $cpt_support)) {
518 $cpt_support[] = $this->post_type;
519 update_option('elementor_cpt_support', $cpt_support);
520 }
521
522 // Force add post type support
523 add_post_type_support($this->post_type, 'elementor');
524 }
525
526 // CRITICAL: Ensure Elementor mode is set
527 update_post_meta($post_id, '_elementor_edit_mode', 'builder');
528
529 // ALWAYS force our custom document type
530 update_post_meta($post_id, '_elementor_template_type', 'jltma_popup');
531
532 // Ensure page template is set to canvas for proper rendering
533 update_post_meta($post_id, '_wp_page_template', 'elementor_canvas');
534 }
535
536 /**
537 * Grant edit_posts capability for popup post type
538 * This prevents WordPress from redirecting when Elementor checks capabilities
539 */
540 public function grant_elementor_edit_cap($allcaps, $caps, $args, $user) {
541 // Only apply when editing with Elementor
542 if (!isset($_GET['action']) || $_GET['action'] !== 'elementor' || !isset($_GET['post'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only capability check, no form submission
543 return $allcaps;
544 }
545
546 $post_id = intval($_GET['post']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only capability check, no form submission
547 $post = get_post($post_id);
548
549 if (!$post || $post->post_type !== $this->post_type) {
550 return $allcaps;
551 }
552
553 // Security: only grant the cap if the user already has a base editing
554 // capability through their role. Without this guard, any logged-in user
555 // (incl. subscribers) visiting the Elementor edit URL for a popup would be
556 // granted edit_post for that request — a privilege escalation. With
557 // capability_type='post' on the CPT, authors+ already have edit_posts
558 // natively, so the workaround stays a no-op for them and is fully off for
559 // lower-privileged users.
560 if (empty($user->allcaps['edit_posts']) && empty($user->allcaps['edit_pages'])) {
561 return $allcaps;
562 }
563
564 // Grant edit capability for this specific post type
565 if (isset($caps[0]) && in_array($caps[0], ['edit_post', 'edit_posts'])) {
566 $allcaps[$caps[0]] = true;
567 }
568
569 return $allcaps;
570 }
571 }
572