PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.83
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 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Template_Catalog_Button / Template_Catalog_Button.php

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

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