PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.38
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.38
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / extensions / Template_Catalog_Button / Template_Catalog_Button.php

Template_Catalog_Button.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.38, at includes/extensions/Template_Catalog_Button/Template_Catalog_Button.php

992 lines 36.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Template Catalog Button Extension
5 *
6 * Adds a button to Elementor editor panel that opens King Addons template catalog in popup
7 */
8
9 namespace King_Addons;
10
11 if (!defined('ABSPATH')) {
12 exit; // Exit if accessed directly.
13 }
14
15 class Template_Catalog_Button
16 {
17 /**
18 * Instance
19 *
20 * @var Template_Catalog_Button|null The single instance of the class.
21 */
22 private static ?Template_Catalog_Button $_instance = null;
23
24 /**
25 * Instance
26 *
27 * Ensures only one instance of the class is loaded or can be loaded.
28 *
29 * @return Template_Catalog_Button An instance of the class.
30 */
31 public static function instance(): Template_Catalog_Button
32 {
33 if (is_null(self::$_instance)) {
34 self::$_instance = new self();
35 }
36 return self::$_instance;
37 }
38
39 /**
40 * Constructor
41 */
42 public function __construct()
43 {
44 // Only load if templates catalog is enabled
45 if (!KING_ADDONS_EXT_TEMPLATES_CATALOG) {
46 return;
47 }
48
49 // Check if template catalog button is disabled by premium user
50 if ($this->is_template_catalog_disabled()) {
51 return;
52 }
53
54 // Hook into Elementor editor
55 add_action('elementor/editor/before_enqueue_scripts', [$this, 'enqueue_editor_scripts'], 10);
56 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueue_editor_styles'], 10);
57
58 // AJAX endpoints for template catalog in editor
59 add_action('wp_ajax_king_addons_get_template_catalog', [$this, 'get_template_catalog']);
60 add_action('wp_ajax_king_addons_import_template_to_page', [$this, 'import_template_to_page']);
61 add_action('wp_ajax_king_addons_import_template_content', [$this, 'import_template_content']);
62
63 // New endpoint for merging with existing page
64 add_action('wp_ajax_king_addons_merge_with_existing_page', [$this, 'merge_with_existing_page']);
65
66 // Sections catalog endpoint
67 add_action('wp_ajax_king_addons_get_sections_catalog', [$this, 'get_sections_catalog']);
68
69 // Section import endpoints
70 add_action('wp_ajax_king_addons_import_section_to_page', [$this, 'import_section_to_page']);
71 }
72
73 /**
74 * Enqueue scripts for Elementor editor
75 */
76 public function enqueue_editor_scripts(): void
77 {
78 wp_enqueue_script(
79 'king-addons-template-catalog-button',
80 KING_ADDONS_URL . 'includes/extensions/Template_Catalog_Button/assets/template-catalog-button.js',
81 ['jquery', 'elementor-editor'],
82 KING_ADDONS_VERSION,
83 true
84 );
85
86 // Get current post ID if available
87 $current_post_id = 0;
88 if (isset($_GET['post'])) {
89 $current_post_id = intval($_GET['post']);
90 } elseif (isset($_GET['post_id'])) {
91 $current_post_id = intval($_GET['post_id']);
92 }
93
94 // Localize script with template catalog data
95 wp_localize_script(
96 'king-addons-template-catalog-button',
97 'kingAddonsTemplateCatalog',
98 [
99 'templateCatalogUrl' => admin_url('admin.php?page=king-addons-templates'),
100 'templatesEnabled' => KING_ADDONS_EXT_TEMPLATES_CATALOG,
101 'buttonEnabled' => !$this->is_template_catalog_disabled(), // If script loaded, button is enabled
102 'buttonText' => $this->get_button_text(),
103 'nonce' => wp_create_nonce('king_addons_template_catalog'),
104 'ajaxUrl' => admin_url('admin-ajax.php'),
105 'isPremium' => function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code(),
106 'currentPostId' => $current_post_id,
107 'adminUrl' => admin_url(),
108 'pluginUrl' => KING_ADDONS_URL,
109 ]
110 );
111 }
112
113 /**
114 * Enqueue styles for Elementor editor
115 */
116 public function enqueue_editor_styles(): void
117 {
118 wp_enqueue_style(
119 'king-addons-template-catalog-popup',
120 KING_ADDONS_URL . 'includes/extensions/Template_Catalog_Button/assets/template-catalog-popup.css',
121 [],
122 KING_ADDONS_VERSION
123 );
124 }
125
126 /**
127 * Check if template catalog button is disabled by premium user
128 */
129 private function is_template_catalog_disabled(): bool
130 {
131 // Only premium users can disable the template catalog button
132 if (!function_exists('king_addons_freemius') || !king_addons_freemius()->can_use_premium_code()) {
133 return false;
134 }
135
136 // Check if setting exists and is enabled (1 = disabled)
137 $disabled = get_option('king_addons_disable_template_catalog_button', '0');
138 return $disabled === '1';
139 }
140
141 /**
142 * Get button text based on user's subscription level
143 */
144 private function get_button_text(): string
145 {
146 if (function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code()) {
147 return esc_html__('Templates Pro', 'king-addons');
148 }
149
150 return esc_html__('Free Templates', 'king-addons');
151 }
152
153 /**
154 * AJAX handler for getting template catalog data
155 */
156 public function get_template_catalog(): void
157 {
158 if (!current_user_can('edit_posts')) {
159 wp_send_json_error('Insufficient permissions');
160 return;
161 }
162
163 if (!wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog')) {
164 wp_send_json_error('Invalid nonce');
165 return;
166 }
167
168 $templates = \King_Addons\TemplatesMap::getTemplatesMapArray();
169 $collections = \King_Addons\CollectionsMap::getCollectionsMapArray();
170
171 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
172
173 // Get filters from request
174 $search_query = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
175 $selected_category = isset($_POST['category']) ? sanitize_text_field($_POST['category']) : '';
176 $selected_collection = isset($_POST['collection']) ? sanitize_text_field($_POST['collection']) : '';
177 $current_page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
178
179 // Get categories and tags
180 $categories = [];
181 $tags = [];
182 $category_counts = [];
183
184 foreach ($templates['templates'] as $template) {
185 if (!in_array($template['category'], $categories)) {
186 $categories[] = $template['category'];
187 }
188
189 foreach ($template['tags'] as $tag) {
190 if (!in_array($tag, $tags)) {
191 $tags[] = $tag;
192 }
193 }
194
195 $category = $template['category'];
196 $category_counts[$category] = isset($category_counts[$category]) ? $category_counts[$category] + 1 : 1;
197 }
198
199 sort($categories);
200
201 // Filter templates
202 $filtered_templates = $templates['templates'];
203
204 // Apply filters
205 if (!empty($search_query)) {
206 $matched_by_title = [];
207 $matched_by_tags = [];
208
209 foreach ($filtered_templates as $template_key => $template) {
210 $found_in_title = stripos($template['title'], $search_query) !== false;
211 $found_in_tags = false;
212
213 foreach ($template['tags'] as $tag) {
214 if (stripos($tag, $search_query) !== false) {
215 $found_in_tags = true;
216 break;
217 }
218 }
219
220 if ($found_in_title) {
221 $template['template_key'] = $template_key;
222 $matched_by_title[] = $template;
223 } elseif ($found_in_tags) {
224 $template['template_key'] = $template_key;
225 $matched_by_tags[] = $template;
226 }
227 }
228
229 $filtered_templates = array_merge($matched_by_title, $matched_by_tags);
230 } else {
231 // Add template keys for non-search results
232 $temp_templates = [];
233 foreach ($filtered_templates as $key => $template) {
234 $template['template_key'] = $key;
235 $temp_templates[] = $template;
236 }
237 $filtered_templates = $temp_templates;
238 }
239
240 if (!empty($selected_category)) {
241 $filtered_templates = array_filter($filtered_templates, function($template) use ($selected_category) {
242 return $template['category'] === $selected_category;
243 });
244 }
245
246 if (!empty($selected_collection)) {
247 $filtered_templates = array_filter($filtered_templates, function($template) use ($selected_collection) {
248 return $template['collection'] == $selected_collection;
249 });
250 }
251
252 // Pagination
253 $items_per_page = 20;
254 $total_templates = count($filtered_templates);
255 $total_pages = ceil($total_templates / $items_per_page);
256 $offset = ($current_page - 1) * $items_per_page;
257 $paged_templates = array_slice($filtered_templates, $offset, $items_per_page);
258
259 wp_send_json_success([
260 'templates' => $paged_templates,
261 'categories' => $categories,
262 'collections' => $collections,
263 'category_counts' => $category_counts,
264 'pagination' => [
265 'current_page' => $current_page,
266 'total_pages' => $total_pages,
267 'total_templates' => $total_templates,
268 'items_per_page' => $items_per_page
269 ],
270 'is_premium_active' => $is_premium_active
271 ]);
272 }
273
274 /**
275 * AJAX handler for importing template to current page
276 */
277 public function import_template_to_page(): void
278 {
279 if (!current_user_can('edit_posts')) {
280 wp_send_json_error('Insufficient permissions');
281 return;
282 }
283
284 if (!wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog')) {
285 wp_send_json_error('Invalid nonce');
286 return;
287 }
288
289 $template_key = sanitize_text_field($_POST['template_key']);
290 $template_plan = sanitize_text_field($_POST['template_plan']);
291 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
292
293 // Determine API URL and install ID
294 if ($is_premium_active && $template_plan === 'premium') {
295 $api_url = 'https://api.kingaddons.com/get-template.php';
296
297 // Use the same method as original templates catalog
298 if (function_exists('king_addons_freemius')) {
299 $freemius_site = king_addons_freemius()->get_site();
300 $install_id = $freemius_site ? $freemius_site->id : 0;
301 } else {
302 $install_id = 0;
303 }
304
305 error_log('King Addons Premium Template: Using install_id: ' . $install_id . ' for premium template: ' . $template_key);
306 } elseif ($template_plan === 'free') {
307 $api_url = 'https://api.kingaddons.com/get-template-free.php';
308 $install_id = 0;
309 error_log('King Addons Free Template: Fetching free template: ' . $template_key);
310 } else {
311 error_log('King Addons Template Error: Premium template requires premium license. Template: ' . $template_key . ', Plan: ' . $template_plan . ', Premium Active: ' . ($is_premium_active ? 'Yes' : 'No'));
312 wp_send_json_error('Premium template requires premium license');
313 return;
314 }
315
316 // Get template data from API
317 $response = wp_remote_post($api_url, [
318 'headers' => ['Content-Type' => 'application/json'],
319 'body' => json_encode([
320 'key' => $template_key,
321 'install' => $install_id,
322 ]),
323 'timeout' => 60
324 ]);
325
326 if (is_wp_error($response)) {
327 wp_send_json_error('Failed to fetch template: ' . $response->get_error_message());
328 return;
329 }
330
331 $body = wp_remote_retrieve_body($response);
332 $data = json_decode($body, true);
333
334 error_log('King Addons API Response: ' . substr($body, 0, 500) . (strlen($body) > 500 ? '...' : ''));
335
336 if (!$data) {
337 error_log('King Addons Template Error: Failed to decode JSON response');
338 wp_send_json_error('Invalid JSON response from template API');
339 return;
340 }
341
342 if (!isset($data['success']) || !$data['success']) {
343 $error_message = isset($data['message']) ? $data['message'] : 'Unknown API error';
344 error_log('King Addons Template Error: API returned error: ' . $error_message);
345 wp_send_json_error('Template API error: ' . $error_message);
346 return;
347 }
348
349 // Return template data for frontend processing
350 wp_send_json_success([
351 'template_data' => $data['landing'],
352 'message' => 'Template data retrieved successfully'
353 ]);
354 }
355
356 /**
357 * AJAX handler for importing template content directly into current page
358 */
359 public function import_template_content(): void
360 {
361 if (!current_user_can('edit_posts')) {
362 wp_send_json_error('Insufficient permissions');
363 return;
364 }
365
366 if (!wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog')) {
367 wp_send_json_error('Invalid nonce');
368 return;
369 }
370
371 // Security fix: Sanitize template data input
372 $raw_template_data = sanitize_textarea_field(stripslashes($_POST['template_data'] ?? ''));
373 $template_data = json_decode($raw_template_data, true);
374 $page_id = intval($_POST['page_id'] ?? 0);
375
376 error_log('King Addons Template Import: Starting import for page ID: ' . $page_id);
377 error_log('King Addons Template Import: Template data keys: ' . json_encode(array_keys($template_data ?: [])));
378
379 if (!$template_data || !$page_id) {
380 error_log('King Addons Template Import: Invalid data - template_data: ' . (!empty($template_data) ? 'valid' : 'invalid') . ', page_id: ' . $page_id);
381 wp_send_json_error('Invalid template data or page ID');
382 return;
383 }
384
385 // Get current page Elementor data
386 $current_data = get_post_meta($page_id, '_elementor_data', true);
387 $current_elements = json_decode($current_data, true);
388
389 if (!is_array($current_elements)) {
390 $current_elements = [];
391 }
392
393 // Parse template content
394 $template_content = isset($template_data['content']) ? $template_data['content'] : null;
395 if (!$template_content) {
396 wp_send_json_error('No template content found');
397 return;
398 }
399
400 // If template_content is a string, decode it
401 if (is_string($template_content)) {
402 $template_content = json_decode($template_content, true);
403 }
404
405 if (!is_array($template_content)) {
406 wp_send_json_error('Invalid template content format');
407 return;
408 }
409
410 // Process images in template content
411 $image_map = [];
412 $images_processed = 0;
413 $images_failed = 0;
414
415 if (isset($template_data['images']) && is_array($template_data['images'])) {
416 error_log('King Addons Template Import: Processing ' . count($template_data['images']) . ' images');
417
418 foreach ($template_data['images'] as $image) {
419 // Download and import image
420 $new_image_id = $this->download_and_import_image($image['url']);
421 if ($new_image_id) {
422 $image_map[$image['id']] = $new_image_id;
423 $images_processed++;
424 error_log('King Addons Template Import: Successfully imported image ' . $image['url'] . ' as ID ' . $new_image_id);
425 } else {
426 $images_failed++;
427 error_log('King Addons Template Import: Failed to import image ' . $image['url']);
428 }
429 }
430
431 error_log('King Addons Template Import: Images summary - processed: ' . $images_processed . ', failed: ' . $images_failed);
432 } else {
433 error_log('King Addons Template Import: No images to process');
434 }
435
436 // Replace image IDs in template content
437 $template_content = $this->replace_image_ids($template_content, $image_map);
438
439 // Merge template content with current page content
440 $current_count = count($current_elements);
441 $new_count = count($template_content);
442 $merged_elements = array_merge($current_elements, $template_content);
443 $total_count = count($merged_elements);
444
445 error_log('King Addons Template Import: Merging content - current: ' . $current_count . ', new: ' . $new_count . ', total: ' . $total_count);
446
447 // Update page meta
448 $update_result = update_post_meta($page_id, '_elementor_data', wp_slash(json_encode($merged_elements)));
449 update_post_meta($page_id, '_elementor_edit_mode', 'builder');
450
451 error_log('King Addons Template Import: Page meta updated - result: ' . ($update_result ? 'success' : 'failed'));
452
453 // Clear Elementor cache
454 if (class_exists('\Elementor\Plugin')) {
455 \Elementor\Plugin::$instance->files_manager->clear_cache();
456 error_log('King Addons Template Import: Elementor cache cleared');
457 } else {
458 error_log('King Addons Template Import: Elementor Plugin class not found, cache not cleared');
459 }
460
461 error_log('King Addons Template Import: Import completed successfully');
462
463 wp_send_json_success([
464 'message' => 'Template imported successfully',
465 'imported_elements' => $new_count,
466 'images_processed' => $images_processed,
467 'images_failed' => $images_failed,
468 'page_id' => $page_id,
469 'current_elements_before' => $current_count,
470 'total_elements_after' => $total_count
471 ]);
472 }
473
474 /**
475 * Download and import image to WordPress media library
476 */
477 private function download_and_import_image($image_url): ?int
478 {
479 try {
480 // Security fix: Validate URL to prevent SSRF attacks
481 if (!$this->is_safe_image_url($image_url)) {
482 error_log('King Addons Security: Blocked unsafe image URL: ' . $image_url);
483 return null;
484 }
485
486 $response = wp_remote_get($image_url, [
487 'timeout' => 30,
488 'user-agent' => 'King Addons Template Import/1.0',
489 'redirection' => 2 // Limit redirects
490 ]);
491
492 if (is_wp_error($response)) {
493 return null;
494 }
495
496 $status_code = wp_remote_retrieve_response_code($response);
497 if ($status_code !== 200) {
498 return null;
499 }
500
501 $image_data = wp_remote_retrieve_body($response);
502 if (empty($image_data)) {
503 return null;
504 }
505
506 // Security fix: Sanitize filename components
507 $image_name = sanitize_file_name(pathinfo(basename($image_url), PATHINFO_FILENAME));
508 $image_extension = sanitize_file_name(pathinfo(basename($image_url), PATHINFO_EXTENSION));
509
510 // Validate file extension
511 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
512 if (!in_array(strtolower($image_extension), $allowed_extensions, true)) {
513 error_log('King Addons Security: Invalid image extension: ' . $image_extension);
514 return null;
515 }
516
517 $unique_image_name = $image_name . '-' . time() . '.' . $image_extension;
518
519 $upload_dir = wp_upload_dir();
520 if (!file_exists($upload_dir['path'])) {
521 wp_mkdir_p($upload_dir['path']);
522 }
523 $image_file = $upload_dir['path'] . '/' . $unique_image_name;
524
525 if (file_put_contents($image_file, $image_data) === false) {
526 return null;
527 }
528
529 $wp_filetype = wp_check_filetype($unique_image_name);
530 $attachment = [
531 'post_mime_type' => $wp_filetype['type'],
532 'post_title' => sanitize_file_name($unique_image_name),
533 'post_content' => '',
534 'post_status' => 'inherit',
535 ];
536
537 $attach_id = wp_insert_attachment($attachment, $image_file);
538
539 require_once(ABSPATH . 'wp-admin/includes/image.php');
540 $attach_data = wp_generate_attachment_metadata($attach_id, $image_file);
541 wp_update_attachment_metadata($attach_id, $attach_data);
542
543 return $attach_id;
544
545 } catch (\Exception $e) {
546 return null;
547 }
548 }
549
550 /**
551 * Replace image IDs in template content
552 */
553 private function replace_image_ids($content, $image_map): array
554 {
555 if (!is_array($content)) {
556 return $content;
557 }
558
559 foreach ($content as &$element) {
560 if (isset($element['settings'])) {
561 $element['settings'] = $this->replace_image_ids_in_settings($element['settings'], $image_map);
562 }
563
564 if (isset($element['elements']) && is_array($element['elements'])) {
565 $element['elements'] = $this->replace_image_ids($element['elements'], $image_map);
566 }
567 }
568
569 return $content;
570 }
571
572 /**
573 * Generate new unique IDs for all elements to avoid conflicts on repeated imports
574 */
575 private function regenerate_element_ids($content): array
576 {
577 if (!is_array($content)) {
578 return $content;
579 }
580
581 foreach ($content as &$element) {
582 // Generate new unique ID for this element
583 if (isset($element['id'])) {
584 $element['id'] = $this->generate_unique_elementor_id();
585 }
586
587 // Process nested elements recursively
588 if (isset($element['elements']) && is_array($element['elements'])) {
589 $element['elements'] = $this->regenerate_element_ids($element['elements']);
590 }
591 }
592
593 return $content;
594 }
595
596 /**
597 * Generate a unique Elementor-style ID
598 */
599 private function generate_unique_elementor_id(): string
600 {
601 // Elementor uses 7-character alphanumeric IDs
602 $chars = '0123456789abcdef';
603 $id = '';
604 for ($i = 0; $i < 7; $i++) {
605 $id .= $chars[rand(0, strlen($chars) - 1)];
606 }
607 return $id;
608 }
609
610 /**
611 * Replace image IDs in element settings
612 */
613 private function replace_image_ids_in_settings($settings, $image_map): array
614 {
615 if (!is_array($settings)) {
616 return $settings;
617 }
618
619 foreach ($settings as $key => &$value) {
620 if (is_array($value)) {
621 $value = $this->replace_image_ids_in_settings($value, $image_map);
622 } elseif (isset($image_map[$value])) {
623 // Replace image ID
624 $value = $image_map[$value];
625 }
626 }
627
628 return $settings;
629 }
630
631 /**
632 * Merge processed template content with existing page
633 */
634 public function merge_with_existing_page(): void
635 {
636 if (!current_user_can('edit_posts')) {
637 wp_send_json_error('Insufficient permissions');
638 return;
639 }
640
641 if (!wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog')) {
642 wp_send_json_error('Invalid nonce');
643 return;
644 }
645
646 $page_id = intval($_POST['page_id']);
647
648 if (!$page_id) {
649 wp_send_json_error('Invalid page ID');
650 return;
651 }
652
653 // Get processed content from the original import system
654 $content = get_transient('elementor_import_content');
655 $page_title = get_transient('elementor_import_page_title');
656
657 if (!$content) {
658 wp_send_json_error('No processed content found. Import may have expired.');
659 return;
660 }
661
662 error_log('King Addons Import: Merging processed content with existing page ' . $page_id);
663
664 // Get current page Elementor data
665 $current_data = get_post_meta($page_id, '_elementor_data', true);
666 $current_elements = json_decode($current_data, true);
667
668 if (!is_array($current_elements)) {
669 $current_elements = [];
670 }
671
672 // The content is already processed by the original system (images replaced)
673 $template_content = $content;
674
675 if (!is_array($template_content)) {
676 wp_send_json_error('Invalid processed content format');
677 return;
678 }
679
680 // Generate new unique IDs for all elements to avoid conflicts
681 $template_content = $this->regenerate_element_ids($template_content);
682
683 // Merge template content with current page content
684 $current_count = count($current_elements);
685 $new_count = count($template_content);
686 $merged_elements = array_merge($current_elements, $template_content);
687 $total_count = count($merged_elements);
688
689 error_log('King Addons Import: Merging content - current: ' . $current_count . ', new: ' . $new_count . ', total: ' . $total_count);
690
691 // Update page meta with merged content
692 $update_result = update_post_meta($page_id, '_elementor_data', wp_slash(json_encode($merged_elements)));
693 update_post_meta($page_id, '_elementor_edit_mode', 'builder');
694
695 // Force update _elementor_version to trigger cache clear
696 if (defined('ELEMENTOR_VERSION')) {
697 update_post_meta($page_id, '_elementor_version', ELEMENTOR_VERSION);
698 }
699
700 // Update page modification time to force Elementor refresh
701 wp_update_post(['ID' => $page_id, 'post_modified' => current_time('mysql'), 'post_modified_gmt' => current_time('mysql', 1)]);
702
703 error_log('King Addons Import: Page meta updated - result: ' . ($update_result ? 'success' : 'failed'));
704
705 // Clear all Elementor caches
706 if (class_exists('\Elementor\Plugin')) {
707 \Elementor\Plugin::$instance->files_manager->clear_cache();
708 error_log('King Addons Import: Elementor cache cleared');
709 }
710
711 // Clean up transients
712 delete_transient('elementor_import_content');
713 delete_transient('elementor_import_images');
714 delete_transient('elementor_import_total_images');
715 delete_transient('elementor_import_images_processed');
716 delete_transient('elementor_import_image_retry_count');
717 delete_transient('elementor_import_page_title');
718 delete_transient('elementor_import_elementor_version');
719 delete_transient('elementor_import_existing_page_id');
720 delete_transient('elementor_import_create_new_page');
721
722 error_log('King Addons Import: Merge completed successfully');
723
724 wp_send_json_success([
725 'message' => 'Template merged successfully',
726 'imported_elements' => $new_count,
727 'page_id' => $page_id,
728 'current_elements_before' => $current_count,
729 'total_elements_after' => $total_count
730 ]);
731 }
732
733 /**
734 * AJAX handler for getting sections catalog data for popup
735 */
736 public function get_sections_catalog(): void
737 {
738 if (!current_user_can('edit_posts')) {
739 wp_send_json_error('Insufficient permissions');
740 return;
741 }
742
743 if (!wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog')) {
744 wp_send_json_error('Invalid nonce');
745 return;
746 }
747
748 if (!class_exists('King_Addons\\SectionsMap')) {
749 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
750 }
751
752 $sections_map = SectionsMap::getSectionsMapArray();
753 $sections = $sections_map['sections'] ?? [];
754
755 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
756
757 // Get filters from request
758 $search_query = sanitize_text_field($_POST['search'] ?? '');
759 $selected_category = sanitize_text_field($_POST['category'] ?? '');
760 $selected_type = sanitize_text_field($_POST['section_type'] ?? '');
761 $selected_plan = sanitize_text_field($_POST['plan'] ?? '');
762 $current_page = max(1, intval($_POST['page'] ?? 1));
763
764 // Get categories and section types for filters
765 $categories = [];
766 $section_types = [];
767
768 foreach ($sections as $section_key => $section) {
769 if (!in_array($section['category'], $categories)) {
770 $categories[] = $section['category'];
771 }
772 if (!in_array($section['section_type'], $section_types)) {
773 $section_types[] = $section['section_type'];
774 }
775 }
776
777 sort($categories);
778 sort($section_types);
779
780 // Filter sections
781 $filtered_sections = [];
782
783 foreach ($sections as $section_key => $section) {
784 // Add section key for frontend
785 $section['section_key'] = $section_key;
786
787 // Skip premium sections if user doesn't have premium license
788 if ($section['plan'] === 'premium' && !$is_premium_active) {
789 continue;
790 }
791
792 // Apply search filter
793 if (!empty($search_query)) {
794 $found_in_title = stripos($section['title'], $search_query) !== false;
795 $found_in_tags = false;
796
797 foreach ($section['tags'] ?? [] as $tag) {
798 if (stripos($tag, $search_query) !== false) {
799 $found_in_tags = true;
800 break;
801 }
802 }
803
804 if (!$found_in_title && !$found_in_tags) {
805 continue;
806 }
807 }
808
809 // Apply category filter
810 if (!empty($selected_category) && $section['category'] !== $selected_category) {
811 continue;
812 }
813
814 // Apply section type filter
815 if (!empty($selected_type) && $section['section_type'] !== $selected_type) {
816 continue;
817 }
818
819 // Apply plan filter
820 if (!empty($selected_plan) && $section['plan'] !== $selected_plan) {
821 continue;
822 }
823
824 $filtered_sections[] = $section;
825 }
826
827 // Pagination
828 $items_per_page = 20;
829 $total_sections = count($filtered_sections);
830 $total_pages = ceil($total_sections / $items_per_page);
831 $offset = ($current_page - 1) * $items_per_page;
832 $paged_sections = array_slice($filtered_sections, $offset, $items_per_page);
833
834 wp_send_json_success([
835 'sections' => $paged_sections,
836 'categories' => $categories,
837 'section_types' => $section_types,
838 'pagination' => [
839 'current_page' => $current_page,
840 'total_pages' => $total_pages,
841 'total_sections' => $total_sections,
842 'items_per_page' => $items_per_page
843 ],
844 'is_premium_active' => $is_premium_active
845 ]);
846 }
847
848 /**
849 * AJAX handler for importing section to current page
850 */
851 public function import_section_to_page(): void
852 {
853 if (!current_user_can('edit_posts')) {
854 wp_send_json_error('Insufficient permissions');
855 return;
856 }
857
858 if (!wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog')) {
859 wp_send_json_error('Invalid nonce');
860 return;
861 }
862
863 $section_key = sanitize_text_field($_POST['section_key']);
864 $section_plan = sanitize_text_field($_POST['section_plan']);
865 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
866
867 // Determine API URL and install ID (same logic as templates)
868 if ($is_premium_active && $section_plan === 'premium') {
869 $api_url = 'https://api.kingaddons.com/get-section.php';
870
871 // Use the same method as original templates catalog
872 if (function_exists('king_addons_freemius')) {
873 $freemius_site = king_addons_freemius()->get_site();
874 $install_id = $freemius_site ? $freemius_site->id : 0;
875 } else {
876 $install_id = 0;
877 }
878
879 error_log('King Addons Premium Section: Using install_id: ' . $install_id . ' for premium section: ' . $section_key);
880 } elseif ($section_plan === 'free') {
881 $api_url = 'https://api.kingaddons.com/get-section-free.php';
882 $install_id = 0;
883 error_log('King Addons Free Section: Fetching free section: ' . $section_key);
884 } else {
885 error_log('King Addons Section Error: Premium section requires premium license. Section: ' . $section_key . ', Plan: ' . $section_plan . ', Premium Active: ' . ($is_premium_active ? 'Yes' : 'No'));
886 wp_send_json_error('Premium section requires premium license');
887 return;
888 }
889
890 // Get section data from API (same as templates)
891 $response = wp_remote_post($api_url, [
892 'headers' => ['Content-Type' => 'application/json'],
893 'body' => json_encode([
894 'key' => $section_key,
895 'install' => $install_id,
896 ]),
897 'timeout' => 60
898 ]);
899
900 if (is_wp_error($response)) {
901 wp_send_json_error('Failed to fetch section: ' . $response->get_error_message());
902 return;
903 }
904
905 $body = wp_remote_retrieve_body($response);
906 $data = json_decode($body, true);
907
908 error_log('King Addons Section API Response: ' . substr($body, 0, 500) . (strlen($body) > 500 ? '...' : ''));
909
910 if (!$data) {
911 error_log('King Addons Section Error: Failed to decode JSON response');
912 wp_send_json_error('Invalid JSON response from section API');
913 return;
914 }
915
916 if (!isset($data['success']) || !$data['success']) {
917 $error_message = isset($data['message']) ? $data['message'] : 'Unknown API error';
918 error_log('King Addons Section Error: API returned error: ' . $error_message);
919 wp_send_json_error('Section API error: ' . $error_message);
920 return;
921 }
922
923 // Return section data for frontend processing (adjust format from your API)
924 wp_send_json_success([
925 'section_data' => $data['section'], // Your API returns 'section' not 'landing'
926 'message' => 'Section data retrieved successfully'
927 ]);
928 }
929
930 /**
931 * Validate image URL for security (prevent SSRF attacks)
932 * @param string $url The URL to validate
933 * @return bool True if URL is safe, false otherwise
934 */
935 private function is_safe_image_url(string $url): bool
936 {
937 // Parse URL
938 $parsed_url = parse_url($url);
939 if (!$parsed_url || !isset($parsed_url['scheme']) || !isset($parsed_url['host'])) {
940 return false;
941 }
942
943 // Only allow HTTP/HTTPS
944 if (!in_array($parsed_url['scheme'], ['http', 'https'], true)) {
945 return false;
946 }
947
948 // Block local/private IP addresses to prevent SSRF
949 $host = $parsed_url['host'];
950
951 // Check if it's an IP address
952 if (filter_var($host, FILTER_VALIDATE_IP)) {
953 // Block private/reserved IP ranges
954 if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
955 return false;
956 }
957 }
958
959 // Block localhost and common local domains
960 $blocked_hosts = [
961 'localhost',
962 '127.0.0.1',
963 '::1',
964 'metadata.google.internal',
965 '169.254.169.254', // AWS metadata
966 ];
967
968 if (in_array(strtolower($host), $blocked_hosts, true)) {
969 return false;
970 }
971
972 // Only allow images from trusted domains (King Addons CDN)
973 $allowed_domains = [
974 'api.kingaddons.com',
975 'cdn.kingaddons.com',
976 'templates.kingaddons.com',
977 'images.kingaddons.com'
978 ];
979
980 $is_allowed_domain = false;
981 foreach ($allowed_domains as $allowed_domain) {
982 if (strtolower($host) === strtolower($allowed_domain) ||
983 str_ends_with(strtolower($host), '.' . strtolower($allowed_domain))) {
984 $is_allowed_domain = true;
985 break;
986 }
987 }
988
989 return $is_allowed_domain;
990 }
991 }
992