PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.2
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.2
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 / widget-builder / class-widget-cpt.php

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

1,112 lines 40.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Master Addons Widget Builder Custom Post Type
4 *
5 * @package MasterAddons
6 * @subpackage WidgetBuilder
7 */
8
9 namespace MasterAddons\Inc\Admin\WidgetBuilder;
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 class Widget_CPT {
16
17 private static $instance = null;
18 private $post_type = 'jltma_widget';
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_widget_posts_columns', [$this, 'set_custom_columns']);
34 add_action('manage_jltma_widget_posts_custom_column', [$this, 'custom_column_content'], 10, 2);
35 add_filter('manage_edit-jltma_widget_sortable_columns', [$this, 'set_sortable_columns']);
36 add_action('save_post', [$this, 'save_meta_data']);
37 add_filter('post_row_actions', [$this, 'modify_row_actions'], 10, 2);
38 add_action('admin_head', [$this, 'admin_head_css']);
39 add_action('admin_footer', [$this, 'admin_footer_js']);
40 add_action('wp_ajax_jltma_widget_get_shortcode', [$this, 'get_shortcode_ajax']);
41 add_action('wp_ajax_jltma_add_widget_category', [$this, 'add_widget_category_ajax']);
42 add_action('template_redirect', [$this, 'handle_widget_preview']);
43 }
44
45 public function register_post_type() {
46 $labels = [
47 'name' => _x('Master Addons Widgets', 'Post Type General Name', 'master-addons'),
48 'singular_name' => _x('Widget', 'Post Type Singular Name', 'master-addons'),
49 'menu_name' => __('Widgets', 'master-addons'),
50 'name_admin_bar' => __('Widget', 'master-addons'),
51 'archives' => __('Widget Archives', 'master-addons'),
52 'attributes' => __('Widget Attributes', 'master-addons'),
53 'parent_item_colon' => __('Parent Widget:', 'master-addons'),
54 'all_items' => __('All Widgets', 'master-addons'),
55 'add_new_item' => __('Add New Widget', 'master-addons'),
56 'add_new' => __('Add New', 'master-addons'),
57 'new_item' => __('New Widget', 'master-addons'),
58 'edit_item' => __('Edit Widget', 'master-addons'),
59 'update_item' => __('Update Widget', 'master-addons'),
60 'view_item' => __('View Widget', 'master-addons'),
61 'view_items' => __('View Widgets', 'master-addons'),
62 'search_items' => __('Search Widget', 'master-addons'),
63 'not_found' => __('Not found', 'master-addons'),
64 'not_found_in_trash' => __('Not found in Trash', 'master-addons'),
65 'featured_image' => __('Featured Image', 'master-addons'),
66 'set_featured_image' => __('Set featured image', 'master-addons'),
67 'remove_featured_image' => __('Remove featured image', 'master-addons'),
68 'use_featured_image' => __('Use as featured image', 'master-addons'),
69 'insert_into_item' => __('Insert into widget', 'master-addons'),
70 'uploaded_to_this_item' => __('Uploaded to this widget', 'master-addons'),
71 'items_list' => __('Widgets list', 'master-addons'),
72 'items_list_navigation' => __('Widgets list navigation', 'master-addons'),
73 'filter_items_list' => __('Filter widgets list', 'master-addons'),
74 ];
75
76 $args = [
77 'label' => __('Widget', 'master-addons'),
78 'description' => __('Master Addons Custom Widgets', 'master-addons'),
79 'labels' => $labels,
80 'supports' => ['title', 'editor'],
81 'public' => false,
82 'show_ui' => true,
83 'show_in_menu' => false, // We'll add it to our custom menu
84 'menu_position' => 20,
85 'show_in_admin_bar' => true,
86 'show_in_nav_menus' => false,
87 'can_export' => true,
88 'has_archive' => false,
89 'exclude_from_search' => true,
90 'publicly_queryable' => false,
91 'capability_type' => 'post',
92 'show_in_rest' => true,
93 'rewrite' => false,
94 ];
95
96 register_post_type($this->post_type, $args);
97 }
98
99 public function set_custom_columns($columns) {
100 $new_columns = [];
101
102 $new_columns['cb'] = $columns['cb'];
103 $new_columns['title'] = __('Widget Name', 'master-addons');
104 $new_columns['jltma_widget_category'] = __('Widget Category', 'master-addons');
105 $new_columns['author'] = __('Author', 'master-addons');
106 $new_columns['jltma_widget_shortcode'] = __('Shortcode', 'master-addons');
107
108 return $new_columns;
109 }
110
111 public function custom_column_content($column, $post_id) {
112 switch ($column) {
113 case 'jltma_widget_category':
114 $this->render_category_column($post_id);
115 break;
116
117 case 'jltma_widget_shortcode':
118 $this->render_shortcode_column($post_id);
119 break;
120 }
121 }
122
123 private function render_category_column($post_id) {
124 $category = get_post_meta($post_id, '_jltma_widget_category', true);
125
126 if (empty($category)) {
127 $category = 'general';
128 }
129
130 $category_name = ucfirst(str_replace('_', ' ', $category));
131
132 echo '<span class="jltma-widget-category">' . esc_html($category_name) . '</span>';
133 echo '<br><a href="#" class="jltma-widget-edit-category" data-post-id="' . esc_attr( $post_id ) . '" data-category="' . esc_attr($category) . '">';
134 echo '<span class="dashicons dashicons-edit"></span> ' . esc_html__('Edit Category', 'master-addons');
135 echo '</a>';
136 }
137
138 private function render_shortcode_column($post_id) {
139 // Use the new shortcode format with widget ID
140 $shortcode = '[jltma_widget_' . $post_id . ']';
141
142 echo '<div style="position: relative; display: inline-block; max-width: 280px;">';
143 echo '<input type="text" readonly value="' . esc_attr($shortcode) . '" onclick="this.select()" style="width: 100%; font-family: monospace; font-size: 12px; padding: 4px 32px 4px 8px; border: 1px solid #ddd; border-radius: 3px; background: #f9f9f9; box-sizing: border-box;" title="' . esc_attr__('Click to select', 'master-addons') . '">';
144 echo '<button type="button" class="jltma-copy-shortcode-widget" data-shortcode="' . esc_attr($shortcode) . '" style="position: absolute; right: 2px; top: 2px; padding: 2px 4px; border: 1px solid #2271b1; border-radius: 3px; background: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; color: #2271b1;" title="' . esc_attr__('Copy to clipboard', 'master-addons') . '">';
145 echo '<span class="dashicons dashicons-clipboard" style="font-size: 14px; width: 14px; height: 14px;"></span>';
146 echo '</button>';
147 echo '</div>';
148 }
149
150 public function set_sortable_columns($columns) {
151 $columns['jltma_widget_category'] = 'jltma_widget_category';
152 return $columns;
153 }
154
155 public function modify_row_actions($actions, $post) {
156 if ($post->post_type === $this->post_type) {
157 // Remove default actions
158 unset($actions['inline hide-if-no-js']);
159 unset($actions['view']);
160
161 // Replace default edit with link to widget editor page
162 $editor_url = admin_url('admin.php?page=jltma-widget-editor&widget_id=' . $post->ID);
163 $actions['edit'] = sprintf(
164 '<a href="%s">%s</a>',
165 esc_url($editor_url),
166 __('Edit', 'master-addons')
167 );
168 }
169 return $actions;
170 }
171
172 public function save_meta_data($post_id) {
173 if (!isset($_POST['jltma_widget_meta_nonce_field']) ||
174 !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['jltma_widget_meta_nonce_field'] ) ), 'jltma_widget_meta_nonce')) {
175 return;
176 }
177
178 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
179 return;
180 }
181
182 if (!current_user_can('edit_post', $post_id)) {
183 return;
184 }
185
186 // Save widget name
187 if (isset($_POST['jltma_widget_name'])) {
188 $widget_name = sanitize_title( wp_unslash( $_POST['jltma_widget_name'] ) );
189 update_post_meta($post_id, '_jltma_widget_name', $widget_name);
190 }
191
192 // Save category
193 if (isset($_POST['jltma_widget_category'])) {
194 update_post_meta($post_id, '_jltma_widget_category', sanitize_text_field( wp_unslash( $_POST['jltma_widget_category'] ) ));
195 }
196 }
197
198 public function admin_head_css() {
199 global $current_screen;
200
201 if (!$current_screen || ($current_screen->post_type !== $this->post_type && $current_screen->id !== 'admin_page_jltma-widget-editor')) {
202 return;
203 }
204
205 ?>
206 <style>
207 .jltma-widget-category {
208 padding: 4px 8px;
209 border-radius: 3px;
210 font-size: 11px;
211 font-weight: 600;
212 background: #dcdcde;
213 color: #50575e;
214 }
215 .jltma-widget-edit-category {
216 color: #2271b1;
217 text-decoration: none;
218 font-size: 12px;
219 }
220 .jltma-widget-edit-category:hover {
221 color: #135e96;
222 }
223 /* Shortcode Column Styling */
224 .jltma-shortcode-box {
225 display: inline-flex;
226 align-items: center;
227 border: 1px solid #dcdcde;
228 border-radius: 4px;
229 background: #fff;
230 overflow: hidden;
231 max-width: 280px;
232 transition: border-color 0.2s ease;
233 }
234 .jltma-shortcode-box:hover {
235 border-color: #8c8f94;
236 }
237 .jltma-shortcode-input {
238 flex: 1;
239 padding: 6px 10px;
240 border: none;
241 background: transparent;
242 font-family: 'Courier New', Courier, monospace;
243 font-size: 13px;
244 color: #2c3338;
245 outline: none;
246 min-width: 0;
247 }
248 .jltma-copy-icon-btn {
249 display: inline-flex;
250 align-items: center;
251 justify-content: center;
252 padding: 6px 10px;
253 background: transparent;
254 border: none;
255 border-left: 1px solid #dcdcde;
256 cursor: pointer;
257 transition: all 0.2s ease;
258 color: #50575e;
259 }
260 .jltma-copy-icon-btn:hover {
261 background: #f0f0f1;
262 color: #2271b1;
263 }
264 .jltma-copy-icon-btn:active {
265 background: #dcdcde;
266 }
267 .jltma-copy-icon-btn svg {
268 width: 18px;
269 height: 18px;
270 display: block;
271 }
272 .jltma-copy-feedback {
273 display: inline-flex;
274 align-items: center;
275 gap: 4px;
276 margin-top: 6px;
277 color: #00a32a;
278 font-size: 12px;
279 font-weight: 500;
280 animation: fadeInOut 2.5s ease-in-out;
281 }
282 .jltma-copy-feedback .dashicons {
283 font-size: 16px;
284 width: 16px;
285 height: 16px;
286 }
287 @keyframes fadeInOut {
288 0%, 100% { opacity: 0; }
289 10%, 90% { opacity: 1; }
290 }
291
292 /* Widget Preview Styles */
293 .jltma-preview-notice {
294 padding: 20px;
295 background: #f0f6fc;
296 border: 1px solid #c3dafe;
297 border-radius: 4px;
298 color: #0c5460;
299 text-align: center;
300 }
301 .jltma-widget-preview-wrapper {
302 background: #f9f9f9;
303 border: 1px solid #ddd;
304 border-radius: 4px;
305 overflow: hidden;
306 }
307 .jltma-preview-toolbar {
308 background: #fff;
309 border-bottom: 1px solid #ddd;
310 padding: 12px 16px;
311 display: flex;
312 justify-content: space-between;
313 align-items: center;
314 }
315 .jltma-preview-actions {
316 display: flex;
317 align-items: center;
318 gap: 12px;
319 }
320 .jltma-refresh-preview {
321 display: inline-flex;
322 align-items: center;
323 gap: 6px;
324 padding: 6px 12px;
325 background: #2271b1;
326 color: #fff;
327 border: none;
328 border-radius: 3px;
329 cursor: pointer;
330 transition: all 0.2s ease;
331 }
332 .jltma-refresh-preview:hover {
333 background: #135e96;
334 }
335 .jltma-refresh-preview .dashicons {
336 font-size: 16px;
337 width: 16px;
338 height: 16px;
339 }
340 .jltma-preview-size-selector {
341 display: flex;
342 gap: 4px;
343 border: 1px solid #ddd;
344 border-radius: 3px;
345 overflow: hidden;
346 }
347 .jltma-preview-size {
348 padding: 6px 10px;
349 background: #fff;
350 border: none;
351 border-right: 1px solid #ddd;
352 cursor: pointer;
353 transition: all 0.2s ease;
354 }
355 .jltma-preview-size:last-child {
356 border-right: none;
357 }
358 .jltma-preview-size:hover {
359 background: #f0f0f1;
360 }
361 .jltma-preview-size.active {
362 background: #2271b1;
363 color: #fff;
364 }
365 .jltma-preview-size .dashicons {
366 font-size: 18px;
367 width: 18px;
368 height: 18px;
369 }
370 .jltma-preview-container {
371 position: relative;
372 background: #fff;
373 min-height: 400px;
374 display: flex;
375 justify-content: center;
376 align-items: center;
377 }
378 .jltma-preview-loader {
379 position: absolute;
380 top: 50%;
381 left: 50%;
382 transform: translate(-50%, -50%);
383 text-align: center;
384 z-index: 10;
385 }
386 .jltma-preview-loader.hidden {
387 display: none;
388 }
389 .jltma-spinner {
390 border: 3px solid #f3f3f3;
391 border-top: 3px solid #2271b1;
392 border-radius: 50%;
393 width: 40px;
394 height: 40px;
395 animation: spin 1s linear infinite;
396 margin: 0 auto 12px;
397 }
398 @keyframes spin {
399 0% { transform: rotate(0deg); }
400 100% { transform: rotate(360deg); }
401 }
402 .jltma-preview-loader p {
403 color: #666;
404 font-size: 14px;
405 margin: 0;
406 }
407 .jltma-preview-frame {
408 width: 100%;
409 min-height: 400px;
410 border: none;
411 background: #fff;
412 transition: all 0.3s ease;
413 }
414 .jltma-preview-frame.loading {
415 opacity: 0.3;
416 }
417 .jltma-preview-frame.desktop {
418 width: 100%;
419 }
420 .jltma-preview-frame.tablet {
421 width: 768px;
422 max-width: 100%;
423 margin: 20px auto;
424 box-shadow: 0 0 20px rgba(0,0,0,0.1);
425 }
426 .jltma-preview-frame.mobile {
427 width: 375px;
428 max-width: 100%;
429 margin: 20px auto;
430 box-shadow: 0 0 20px rgba(0,0,0,0.1);
431 }
432
433 /* Shortcode Copy Button */
434 .jltma-copy-shortcode-widget {
435 transition: all 0.2s ease;
436 }
437 .jltma-copy-shortcode-widget:hover {
438 background: #f0f6fc !important;
439 border-color: #0073aa !important;
440 }
441 .jltma-copy-shortcode-widget:hover .dashicons {
442 color: #0073aa !important;
443 }
444
445 /* Toast Notification */
446 .jltma-toast-notification {
447 position: fixed;
448 bottom: 30px;
449 left: 50%;
450 transform: translateX(-50%);
451 background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
452 color: #fff;
453 padding: 16px 24px;
454 border-radius: 8px;
455 box-shadow: 0 10px 40px rgba(0, 176, 155, 0.4);
456 display: flex;
457 align-items: center;
458 gap: 10px;
459 font-size: 14px;
460 font-weight: 500;
461 min-width: 300px;
462 max-width: 500px;
463 text-align: center;
464 z-index: 999999;
465 animation: jltmaSlideUp 0.3s ease-out;
466 overflow: hidden;
467 }
468 .jltma-toast-notification::before {
469 content: "";
470 position: absolute;
471 top: 0;
472 left: 0;
473 right: 0;
474 bottom: 0;
475 background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 100%);
476 pointer-events: none;
477 }
478 .jltma-toast-notification .dashicons {
479 font-size: 20px;
480 width: 20px;
481 height: 20px;
482 flex-shrink: 0;
483 position: relative;
484 z-index: 1;
485 }
486 .jltma-toast-notification span:not(.dashicons) {
487 position: relative;
488 z-index: 1;
489 }
490 @keyframes jltmaSlideUp {
491 from {
492 transform: translateX(-50%) translateY(100px);
493 opacity: 0;
494 }
495 to {
496 transform: translateX(-50%) translateY(0);
497 opacity: 1;
498 }
499 }
500
501 /* Add New Category Modal */
502 .jltma-category-modal-overlay {
503 display: none;
504 position: fixed;
505 top: 0;
506 left: 0;
507 right: 0;
508 bottom: 0;
509 background: rgba(0, 0, 0, 0.7);
510 z-index: 999999;
511 animation: fadeIn 0.2s ease-out;
512 }
513 .jltma-category-modal {
514 position: fixed;
515 top: 50%;
516 left: 50%;
517 transform: translate(-50%, -50%);
518 background: #2c2f36;
519 border-radius: 8px;
520 padding: 30px;
521 min-width: 500px;
522 max-width: 90%;
523 z-index: 1000000;
524 animation: slideDown 0.3s ease-out;
525 }
526 .jltma-category-modal h2 {
527 color: #ffffff;
528 font-size: 20px;
529 margin: 0 0 24px 0;
530 font-weight: 500;
531 }
532 .jltma-category-modal-input-wrapper {
533 margin-bottom: 20px;
534 }
535 .jltma-category-modal input[type="text"] {
536 width: 100%;
537 padding: 12px 16px;
538 background: #1e2126;
539 border: 1px solid #3e434a;
540 border-radius: 6px;
541 color: #ffffff;
542 font-size: 14px;
543 outline: none;
544 transition: border-color 0.2s ease;
545 }
546 .jltma-category-modal input[type="text"]:focus {
547 border-color: #5b6aff;
548 }
549 .jltma-category-modal input[type="text"]::placeholder {
550 color: #8a909a;
551 }
552 .jltma-category-modal-buttons {
553 display: flex;
554 justify-content: flex-end;
555 gap: 12px;
556 margin-top: 24px;
557 }
558 .jltma-category-modal-btn {
559 padding: 10px 24px;
560 border-radius: 6px;
561 font-size: 14px;
562 font-weight: 500;
563 cursor: pointer;
564 border: none;
565 transition: all 0.2s ease;
566 display: inline-flex;
567 align-items: center;
568 gap: 8px;
569 }
570 .jltma-category-modal-btn.primary {
571 background: #5b6aff;
572 color: #ffffff;
573 }
574 .jltma-category-modal-btn.primary:hover {
575 background: #4a59ee;
576 }
577 .jltma-category-modal-btn.primary:disabled {
578 background: #3e4a8a;
579 cursor: not-allowed;
580 opacity: 0.6;
581 }
582 .jltma-category-modal-btn.secondary {
583 background: transparent;
584 color: #ff4757;
585 border: 1px solid #ff4757;
586 }
587 .jltma-category-modal-btn.secondary:hover {
588 background: rgba(255, 71, 87, 0.1);
589 }
590 .jltma-category-modal-description {
591 color: #8a909a;
592 font-size: 13px;
593 font-style: italic;
594 margin-top: 12px;
595 }
596 @keyframes fadeIn {
597 from { opacity: 0; }
598 to { opacity: 1; }
599 }
600 @keyframes slideDown {
601 from {
602 transform: translate(-50%, -60%);
603 opacity: 0;
604 }
605 to {
606 transform: translate(-50%, -50%);
607 opacity: 1;
608 }
609 }
610 </style>
611 <?php
612 }
613
614 public function admin_footer_js() {
615 $screen = get_current_screen();
616
617 // Check if we're on widget listing or edit page
618 if (!$screen || !in_array($screen->id, ['edit-jltma_widget', 'jltma_widget', 'admin_page_jltma-widget-editor'])) {
619 return;
620 }
621
622 ?>
623 <!-- Add New Category Modal -->
624 <div class="jltma-category-modal-overlay">
625 <div class="jltma-category-modal">
626 <h2><?php esc_html_e('Add New Widget Category', 'master-addons'); ?></h2>
627 <div class="jltma-category-modal-input-wrapper">
628 <input type="text" id="jltma-new-category-name" placeholder="<?php esc_attr_e('Enter category name', 'master-addons'); ?>" />
629 <p class="jltma-category-modal-description"><?php esc_html_e('Category slug will be auto-generated from the name', 'master-addons'); ?></p>
630 </div>
631 <div class="jltma-category-modal-buttons">
632 <button type="button" class="jltma-category-modal-btn secondary" id="jltma-cancel-category">
633 <?php esc_html_e('Cancel', 'master-addons'); ?>
634 </button>
635 <button type="button" class="jltma-category-modal-btn primary" id="jltma-add-category" disabled>
636 <?php esc_html_e('Add Category', 'master-addons'); ?>
637 </button>
638 </div>
639 </div>
640 </div>
641
642 <script>
643
644 jQuery(document).ready(function($) {
645
646 // ===========================
647 // Add New Category Modal
648 // ===========================
649 var $categorySelect = $('#jltma_widget_category');
650 var $modalOverlay = $('.jltma-category-modal-overlay');
651 var $categoryInput = $('#jltma-new-category-name');
652 var $addButton = $('#jltma-add-category');
653 var $cancelButton = $('#jltma-cancel-category');
654 var previousCategoryValue = '';
655
656 // Handle "+ Add New Category" selection
657 $categorySelect.on('change', function() {
658 if ($(this).val() === 'add_new_category') {
659 previousCategoryValue = $(this).data('previous-value') || 'general';
660 openCategoryModal();
661 } else {
662 $(this).data('previous-value', $(this).val());
663 }
664 });
665
666 // Open modal
667 function openCategoryModal() {
668 $modalOverlay.fadeIn(200);
669 $categoryInput.val('').focus();
670 $addButton.prop('disabled', true);
671 }
672
673 // Close modal
674 function closeCategoryModal() {
675 $modalOverlay.fadeOut(200);
676 $categoryInput.val('');
677 $categorySelect.val(previousCategoryValue);
678 $addButton.prop('disabled', true);
679 }
680
681 // Close modal on overlay click
682 $modalOverlay.on('click', function(e) {
683 if (e.target === this) {
684 closeCategoryModal();
685 }
686 });
687
688 // Close modal on Cancel button
689 $cancelButton.on('click', function() {
690 closeCategoryModal();
691 });
692
693 // Enable/disable Add button based on input
694 $categoryInput.on('input', function() {
695 var value = $(this).val().trim();
696 $addButton.prop('disabled', value.length === 0);
697 });
698
699 // Handle Enter key in input
700 $categoryInput.on('keypress', function(e) {
701 if (e.which === 13) { // Enter key
702 e.preventDefault();
703 if (!$addButton.prop('disabled')) {
704 $addButton.click();
705 }
706 }
707 });
708
709 // Handle Escape key
710 $(document).on('keydown', function(e) {
711 if (e.key === 'Escape' && $modalOverlay.is(':visible')) {
712 closeCategoryModal();
713 }
714 });
715
716 // Handle Add Category button
717 $addButton.on('click', function() {
718 var categoryName = $categoryInput.val().trim();
719
720 if (categoryName.length === 0) {
721 return;
722 }
723
724 // Disable button and show loading state
725 $addButton.prop('disabled', true).text('<?php echo esc_js(__('Adding...', 'master-addons')); ?>');
726
727 // Send AJAX request
728 $.ajax({
729 url: ajaxurl,
730 type: 'POST',
731 data: {
732 action: 'jltma_add_widget_category',
733 category_name: categoryName,
734 _nonce: '<?php echo esc_js( wp_create_nonce('jltma_add_category_nonce') ); ?>'
735 },
736 success: function(response) {
737 if (response.success) {
738 // Add new option to select (before "+ Add New Category")
739 var newOption = '<option value="' + response.data.slug + '">' +
740 response.data.name + '</option>';
741 $categorySelect.find('option[value="add_new_category"]').before(newOption);
742
743 // Select the new category
744 $categorySelect.val(response.data.slug).data('previous-value', response.data.slug);
745
746 // Close modal
747 closeCategoryModal();
748
749 // Show success message
750 showToast(response.data.message || '<?php echo esc_js(__('Category added successfully!', 'master-addons')); ?>');
751 } else {
752 alert(response.data.message || '<?php echo esc_js(__('Failed to add category. Please try again.', 'master-addons')); ?>');
753 $addButton.prop('disabled', false).text('<?php echo esc_js(__('Add Category', 'master-addons')); ?>');
754 }
755 },
756 error: function() {
757 alert('<?php echo esc_js(__('An error occurred. Please try again.', 'master-addons')); ?>');
758 $addButton.prop('disabled', false).text('<?php echo esc_js(__('Add Category', 'master-addons')); ?>');
759 }
760 });
761 });
762
763 // Copy shortcode to clipboard (listing page - new style)
764 $(document).on('click', '.jltma-copy-shortcode-widget', function(e) {
765 e.preventDefault();
766 var button = $(this);
767 var shortcode = button.data('shortcode');
768 var originalIcon = button.find('.dashicons').attr('class');
769
770 if (!shortcode) {
771 return;
772 }
773
774 // Use modern clipboard API if available
775 if (navigator.clipboard && navigator.clipboard.writeText) {
776 navigator.clipboard.writeText(shortcode).then(function() {
777 showCopySuccess(button, originalIcon);
778 }).catch(function(err) {
779 fallbackCopyButton(shortcode, button, originalIcon);
780 });
781 } else {
782 fallbackCopyButton(shortcode, button, originalIcon);
783 }
784 });
785
786 function fallbackCopyButton(text, button, originalIcon) {
787 var temp = $('<textarea>');
788 $('body').append(temp);
789 temp.val(text).select();
790 document.execCommand('copy');
791 temp.remove();
792 showCopySuccess(button, originalIcon);
793 }
794
795 function showCopySuccess(button, originalIcon) {
796 // Change icon to checkmark
797 button.find('.dashicons').removeClass('dashicons-clipboard').addClass('dashicons-yes-alt');
798
799 // Use JLTMA Toaster if available, otherwise create custom toast
800 if (typeof JLTMA_Toaster !== 'undefined' && JLTMA_Toaster.success) {
801 JLTMA_Toaster.success('<?php echo esc_js(__('Shortcode copied to clipboard!', 'master-addons')); ?>', 2000);
802 } else {
803 showToast('<?php echo esc_js(__('Shortcode copied to clipboard!', 'master-addons')); ?>');
804 }
805
806 // Reset icon after delay
807 setTimeout(function() {
808 button.find('.dashicons').attr('class', originalIcon);
809 }, 2000);
810 }
811
812 function showToast(message) {
813 // Remove any existing toasts
814 $('.jltma-toast-notification').remove();
815
816 // Create toast
817 var toast = $('<div class="jltma-toast-notification">' +
818 '<span class="dashicons dashicons-yes"></span>' +
819 '<span>' + message + '</span>' +
820 '</div>');
821
822 $('body').append(toast);
823
824 // Auto-remove after 3 seconds
825 setTimeout(function() {
826 toast.fadeOut(300, function() {
827 $(this).remove();
828 });
829 }, 3000);
830 }
831
832 // Copy shortcode to clipboard (old style - if any remain)
833 $(document).on('click', '.jltma-copy-icon-btn', function(e) {
834 e.preventDefault();
835 var button = $(this);
836 var shortcode = button.data('shortcode');
837 var box = button.closest('.jltma-shortcode-box');
838 var feedback = box.next('.jltma-copy-feedback');
839
840 // Use modern clipboard API if available
841 if (navigator.clipboard && navigator.clipboard.writeText) {
842 navigator.clipboard.writeText(shortcode).then(function() {
843 showCopyFeedback(feedback);
844 }).catch(function() {
845 fallbackCopy(shortcode, feedback);
846 });
847 } else {
848 fallbackCopy(shortcode, feedback);
849 }
850 });
851
852 // Fallback copy method for older browsers
853 function fallbackCopy(text, feedback) {
854 var temp = $('<textarea>');
855 $('body').append(temp);
856 temp.val(text).select();
857 document.execCommand('copy');
858 temp.remove();
859 showCopyFeedback(feedback);
860 }
861
862 // Show copy feedback
863 function showCopyFeedback(feedback) {
864 feedback.show();
865 setTimeout(function() {
866 feedback.fadeOut(400);
867 }, 2000);
868 }
869
870 // Copy shortcode button (meta box)
871 $('.jltma-copy-shortcode').on('click', function(e) {
872 e.preventDefault();
873 var button = $(this);
874 var shortcode = button.data('shortcode');
875
876 if (navigator.clipboard && navigator.clipboard.writeText) {
877 navigator.clipboard.writeText(shortcode).then(function() {
878 showButtonFeedback(button);
879 }).catch(function() {
880 fallbackCopyButton(shortcode, button);
881 });
882 } else {
883 fallbackCopyButton(shortcode, button);
884 }
885 });
886
887 function fallbackCopyButton(text, button) {
888 var temp = $('<textarea>');
889 $('body').append(temp);
890 temp.val(text).select();
891 document.execCommand('copy');
892 temp.remove();
893 showButtonFeedback(button);
894 }
895
896 function showButtonFeedback(button) {
897 var originalText = button.text();
898 button.text('<?php echo esc_js(__('Copied!', 'master-addons')); ?>').css({
899 'background': '#00a32a',
900 'border-color': '#00a32a'
901 });
902 setTimeout(function() {
903 button.text(originalText).css({
904 'background': '',
905 'border-color': ''
906 });
907 }, 2000);
908 }
909
910 // Widget Preview Functionality
911 var $previewFrame = $('#jltma-widget-preview-frame');
912 var $previewLoader = $('.jltma-preview-loader');
913
914 if ($previewFrame.length) {
915 // Load preview on page load
916 function loadPreview() {
917 var widgetId = $previewFrame.data('widget-id');
918 var shortcode = $previewFrame.data('shortcode');
919
920 if (!widgetId) return;
921
922 // Show loader
923 $previewLoader.removeClass('hidden');
924 $previewFrame.addClass('loading');
925
926 // Create preview URL
927 var previewUrl = '<?php echo esc_js(add_query_arg(['jltma_widget_preview' => '1'], home_url('/'))); ?>';
928 previewUrl += '&widget_id=' + widgetId;
929 previewUrl += '&shortcode=' + encodeURIComponent(shortcode);
930 previewUrl += '&t=' + new Date().getTime(); // Cache buster
931
932 // Load iframe
933 $previewFrame.attr('src', previewUrl);
934 }
935
936 // Hide loader when iframe loads
937 $previewFrame.on('load', function() {
938 setTimeout(function() {
939 $previewLoader.addClass('hidden');
940 $previewFrame.removeClass('loading');
941 }, 300);
942 });
943
944 // Refresh button
945 $('.jltma-refresh-preview').on('click', function(e) {
946 e.preventDefault();
947 loadPreview();
948 });
949
950 // Preview size selector
951 $('.jltma-preview-size').on('click', function(e) {
952 e.preventDefault();
953 var size = $(this).data('size');
954
955 // Update active state
956 $('.jltma-preview-size').removeClass('active');
957 $(this).addClass('active');
958
959 // Update frame class
960 $previewFrame.removeClass('desktop tablet mobile').addClass(size);
961 });
962
963 // Initial load
964 loadPreview();
965 }
966 });
967 </script>
968 <?php
969 }
970
971 public function get_shortcode_ajax() {
972 if (!current_user_can('manage_options')) {
973 wp_send_json_error(['message' => 'You do not have permission to perform this action.'], 403);
974 }
975
976 check_ajax_referer('jltma_widget_nonce', '_nonce');
977
978 $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
979
980 // Use the new shortcode format with widget ID
981 $shortcode = '[jltma_widget_' . $post_id . ']';
982
983 wp_send_json_success(['shortcode' => $shortcode]);
984 }
985
986 public function add_widget_category_ajax() {
987 // Verify nonce
988 check_ajax_referer('jltma_add_category_nonce', '_nonce');
989
990 // Check user capabilities
991 if (!current_user_can('manage_options')) {
992 wp_send_json_error([
993 'message' => __('You do not have permission to add categories.', 'master-addons')
994 ]);
995 }
996
997 // Get category name
998 $category_name = isset($_POST['category_name']) ? sanitize_text_field( wp_unslash( $_POST['category_name'] ) ) : '';
999
1000 if (empty($category_name)) {
1001 wp_send_json_error([
1002 'message' => __('Category name is required.', 'master-addons')
1003 ]);
1004 }
1005
1006 // Generate slug from name
1007 $category_slug = sanitize_title($category_name);
1008
1009 // Get existing categories
1010 $categories = get_option('jltma_widget_categories', []);
1011
1012 // Check if category already exists
1013 if (isset($categories[$category_slug])) {
1014 wp_send_json_error([
1015 'message' => __('A category with this name already exists.', 'master-addons')
1016 ]);
1017 }
1018
1019 // Add new category
1020 $categories[$category_slug] = $category_name;
1021
1022 // Save categories
1023 update_option('jltma_widget_categories', $categories);
1024
1025 // Return success
1026 wp_send_json_success([
1027 /* translators: %s is the category name. */
1028 'message' => sprintf(__('Category "%s" added successfully!', 'master-addons'), $category_name),
1029 'slug' => $category_slug,
1030 'name' => $category_name
1031 ]);
1032 }
1033
1034 public function handle_widget_preview() {
1035 // Check if this is a preview request
1036 if (!isset($_GET['jltma_widget_preview']) || $_GET['jltma_widget_preview'] != '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only preview check, no nonce available
1037 return;
1038 }
1039
1040 // Verify user permissions
1041 if (!current_user_can('edit_posts')) {
1042 wp_die(esc_html__('You do not have permission to preview widgets.', 'master-addons'));
1043 }
1044
1045 $widget_id = isset($_GET['widget_id']) ? intval($_GET['widget_id']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce not available for preview URL
1046 $shortcode = isset($_GET['shortcode']) ? sanitize_text_field( wp_unslash( $_GET['shortcode'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce not available for preview URL
1047
1048 if (!$widget_id || !$shortcode) {
1049 wp_die(esc_html__('Invalid widget preview request.', 'master-addons'));
1050 }
1051
1052 // Check if widget exists
1053 $widget_post = get_post($widget_id);
1054 if (!$widget_post || $widget_post->post_type !== $this->post_type) {
1055 wp_die(esc_html__('Widget not found.', 'master-addons'));
1056 }
1057
1058 // Render preview template
1059 $this->render_preview_template($widget_id, $shortcode);
1060 exit;
1061 }
1062
1063 private function render_preview_template($widget_id, $shortcode) {
1064 ?>
1065 <!DOCTYPE html>
1066 <html <?php language_attributes(); ?>>
1067 <head>
1068 <meta charset="<?php bloginfo('charset'); ?>">
1069 <meta name="viewport" content="width=device-width, initial-scale=1">
1070 <title><?php esc_html_e('Widget Preview', 'master-addons'); ?></title>
1071 <?php wp_head(); ?>
1072 <style>
1073 body {
1074 margin: 0;
1075 padding: 20px;
1076 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
1077 background: #fff;
1078 }
1079 .jltma-preview-content {
1080 max-width: 100%;
1081 margin: 0 auto;
1082 }
1083 .jltma-preview-notice {
1084 background: #f0f6fc;
1085 border-left: 4px solid #2271b1;
1086 padding: 12px 16px;
1087 margin-bottom: 20px;
1088 font-size: 13px;
1089 color: #1d2327;
1090 }
1091 </style>
1092 </head>
1093 <body>
1094 <div class="jltma-preview-content">
1095 <div class="jltma-preview-notice">
1096 <?php esc_html_e('This is a live preview of your widget. Changes made to the widget will be reflected here after saving and refreshing.', 'master-addons'); ?>
1097 </div>
1098 <div class="jltma-widget-preview-output">
1099 <?php echo do_shortcode($shortcode); ?>
1100 </div>
1101 </div>
1102 <?php wp_footer(); ?>
1103 </body>
1104 </html>
1105 <?php
1106 }
1107
1108 public function get_post_type() {
1109 return $this->post_type;
1110 }
1111 }
1112