PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
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
← All changes | includes/extensions/Templates/Templates.php +552 -31 51.1.36 → 51.1.86 View file →
@@ -4,8 +4,9 @@
4 4
5 5 namespace King_Addons;
6 6
7 7 use Exception;
8 +use King_Addons\SectionsMap;
8 9
9 10 if (!defined('ABSPATH')) {
10 11 exit;
11 12 }
@@ -26,8 +27,15 @@
26 27 {
27 28 $templates = TemplatesMap::getTemplatesMapArray();
28 29 $collections = CollectionsMap::getCollectionsMapArray();
29 30
31 + // Load Sections for counting
32 + if (!class_exists('King_Addons\\SectionsMap')) {
33 + require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
34 + }
35 + $sections_map = SectionsMap::getSectionsMapArray();
36 + $sections = $sections_map['sections'] ?? [];
37 +
30 38 uasort($collections, function ($a, $b) {
31 39 return strcasecmp($a, $b);
32 40 });
33 41
@@ -39,11 +47,14 @@
39 47 // Arrays for categories and tags
40 48 $categories = [];
41 49 $tags = [];
42 50 $category_counts = [];
51 + $collection_counts = [];
52 + $collection_images = [];
53 + $template_collection_map = [];
43 54
44 55 // Getting unique categories and tags
45 - foreach ($templates['templates'] as $template) {
56 + foreach ($templates['templates'] as $key => $template) {
46 57 if (!in_array($template['category'], $categories)) {
47 58 $categories[] = $template['category'];
48 59 }
49 60
@@ -54,10 +65,29 @@
54 65 }
55 66
56 67 $category = $template['category'];
57 68 $category_counts[$category] = isset($category_counts[$category]) ? $category_counts[$category] + 1 : 1;
69 +
70 + $collection_id = $template['collection'] ?? '';
71 + if ($collection_id !== '') {
72 + $collection_counts[$collection_id] = isset($collection_counts[$collection_id]) ? $collection_counts[$collection_id] + 1 : 1;
73 + if (!isset($collection_images[$collection_id])) {
74 + $collection_images[$collection_id] = $key;
75 + }
76 + $template_collection_map[$key] = $collection_id;
77 + }
58 78 }
59 79
80 + // Count sections per collection
81 + $collection_sections_counts = [];
82 + foreach ($sections as $section) {
83 + $parent = $section['parent_template'] ?? '';
84 + if ($parent && isset($template_collection_map[$parent])) {
85 + $col_id = $template_collection_map[$parent];
86 + $collection_sections_counts[$col_id] = isset($collection_sections_counts[$col_id]) ? $collection_sections_counts[$col_id] + 1 : 1;
87 + }
88 + }
89 +
60 90 sort($categories);
61 91
62 92 // Get filters from query
63 93 $search_query = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
@@ -104,9 +134,9 @@
104 134 </a>
105 135 </div>
106 136 <?php if (KING_ADDONS_EXT_HEADER_FOOTER_BUILDER): ?>
107 137 <div class="kng-nav-item kng-nav-item-current">
108 - <a href="<?php echo admin_url('edit.php?post_type=king-addons-el-hf'); ?>">
138 + <a href="<?php echo admin_url('admin.php?page=king-addons-el-hf'); ?>">
109 139 <div class="kng-nav-item-txt"><?php echo esc_html__('Free Header & Footer Builder', 'king-addons'); ?></div>
110 140 </a>
111 141 </div>
112 142 <?php endif; ?>
@@ -116,8 +146,13 @@
116 146 <div class="kng-nav-item-txt"><?php echo esc_html__('Free Popup Builder', 'king-addons'); ?></div>
117 147 </a>
118 148 </div>
119 149 <?php endif; ?>
150 + <div class="kng-nav-item kng-nav-item-current">
151 + <a href="https://www.youtube.com/@kingaddons/videos" target="_blank">
152 + <div class="kng-nav-item-txt"><?php echo esc_html__('YouTube Guides', 'king-addons'); ?></div>
153 + </a>
154 + </div>
120 155 <?php if (!king_addons_freemius()->can_use_premium_code()): ?>
121 156 <div class="kng-nav-item kng-nav-item-current kng-nav-activate-license">
122 157 <a id="activate-license-btn">
123 158 <img src="<?php echo esc_url(KING_ADDONS_URL) . 'includes/admin/img/up.svg'; ?>"
@@ -127,9 +162,9 @@
127 162 </div>
128 163 <?php endif; ?>
129 164 <?php if (!king_addons_freemius()->can_use_premium_code()): ?>
130 165 <div class="kng-promo-btn-wrap">
131 - <a href="https://kingaddons.com/pricing/?rel=king-addons-templates-catalog" target="_blank">
166 + <a href="https://kingaddons.com/pricing/?utm_source=king-addons-templates-catalog" target="_blank">
132 167 <div class="kng-promo-btn-txt">
133 168 <?php esc_html_e('Unlock Premium Features & 650+ Templates Today!', 'king-addons'); ?>
134 169 </div>
135 170 <img width="16px"
@@ -148,15 +183,18 @@
148 183 <i class="eicon-document-file"></i>
149 184 <?php esc_html_e('Templates', 'king-addons'); ?>
150 185 <span class="tab-count"><?php echo count($templates['templates']); ?></span>
151 186 </button>
152 - <?php if (KING_ADDONS_EXT_SECTIONS_CATALOG): ?>
153 187 <button class="king-addons-tab-button" data-tab="sections">
154 188 <i class="eicon-section"></i>
155 189 <?php esc_html_e('Sections', 'king-addons'); ?>
156 190 <span class="tab-count" id="sections-count">0</span>
157 191 </button>
158 - <?php endif; ?>
192 + <button class="king-addons-tab-button" data-tab="collections">
193 + <i class="eicon-folder"></i>
194 + <?php esc_html_e('Collections', 'king-addons'); ?>
195 + <span class="tab-count"><?php echo count($collections); ?></span>
196 + </button>
159 197 </div>
160 198
161 199 <!-- Templates Tab Content -->
162 200 <div id="templates-catalog" class="king-addons-tab-content active">
@@ -198,9 +236,9 @@
198 236 <?php if (!$is_premium_active): ?>
199 237 <div class="promo-wrapper">
200 238 <div class="promo-txt"><?php
201 239 esc_html_e('Unlock Premium Templates', 'king-addons');
202 - echo '<ul><li>$4.99/month</li>';
240 + echo '<ul><li>$6.99/month</li>';
203 241 echo '<li>Unlimited Downloads</li>';
204 242 echo '<li>Keep all templates forever</li></ul>';
205 243 ?></div>
206 244 <a class="purchase-btn"
@@ -233,9 +271,8 @@
233 271 </div>
234 272 </div>
235 273
236 274 <!-- Sections Tab Content -->
237 - <?php if (KING_ADDONS_EXT_SECTIONS_CATALOG): ?>
238 275 <div id="sections-catalog" class="king-addons-tab-content">
239 276 <div class="filters-wrapper">
240 277 <div class="filters">
241 278 <select id="sections-category">
@@ -265,10 +302,44 @@
265 302 </div>
266 303 <div class="sections-pagination"></div>
267 304 </div>
268 305 </div>
269 - <?php endif; ?>
270 306
307 + <!-- Collections Tab Content -->
308 + <div id="collections-catalog" class="king-addons-tab-content">
309 + <div class="collections-toolbar">
310 + <div class="collections-search">
311 + <input type="text" id="collections-search" placeholder="<?php esc_attr_e('Search collections...', 'king-addons'); ?>">
312 + </div>
313 + </div>
314 +
315 + <div class="collections-rows-wrapper">
316 + <div class="collections-rows-container">
317 + <div class="collections-loading">
318 + <?php esc_html_e('Loading collections...', 'king-addons'); ?>
319 + </div>
320 + </div>
321 + <div class="collections-rows-pagination"></div>
322 + </div>
323 +
324 + <!-- Collection Details View -->
325 + <div id="collection-details-view" style="display: none;">
326 + <div class="collection-details-header">
327 + <button type="button" class="ka-back-button">
328 + <i class="eicon-arrow-left"></i> <?php esc_html_e('Back to Collections', 'king-addons'); ?>
329 + </button>
330 + <div class="collection-details-search">
331 + <input type="text" id="collection-search" placeholder="<?php esc_attr_e('Search in collection...', 'king-addons'); ?>">
332 + </div>
333 + </div>
334 + <div class="collection-details-content">
335 + <div class="collection-loading">
336 + <?php esc_html_e('Loading collection...', 'king-addons'); ?>
337 + </div>
338 + </div>
339 + </div>
340 + </div>
341 +
271 342 <div id="template-preview-popup" style="display:none;">
272 343 <div class="popup-content">
273 344 <div class="popup-content-nav">
274 345 <button data-plan-active="<?php echo ($is_premium_active) ? 'premium' : 'free'; ?>"
@@ -295,8 +366,44 @@
295 366 </div>
296 367 <iframe id="template-preview-iframe" src="" frameborder="0"></iframe>
297 368 </div>
298 369 </div>
370 +
371 + <!-- Section Import Popup -->
372 + <div id="section-import-popup" class="ka-popup-overlay" style="display:none;">
373 + <div class="ka-popup-content">
374 + <button type="button" class="ka-popup-close" id="close-section-popup">
375 + <span class="dashicons dashicons-no-alt"></span>
376 + </button>
377 + <div class="ka-popup-body">
378 + <div class="ka-popup-icon">
379 + <i class="eicon-file-download"></i>
380 + </div>
381 + <h3 class="ka-popup-title"><?php esc_html_e('Import Section', 'king-addons'); ?></h3>
382 + <p class="ka-popup-message">
383 + <?php esc_html_e('Are you sure you want to import this section? It will be added to your current page.', 'king-addons'); ?>
384 + </p>
385 + <div class="ka-popup-details">
386 + <div class="ka-popup-detail-row">
387 + <span class="ka-label"><?php esc_html_e('Section:', 'king-addons'); ?></span>
388 + <span class="ka-value" id="popup-section-name"></span>
389 + </div>
390 + <div class="ka-popup-detail-row">
391 + <span class="ka-label"><?php esc_html_e('Plan:', 'king-addons'); ?></span>
392 + <span class="ka-value" id="popup-section-plan"></span>
393 + </div>
394 + </div>
395 + <div class="ka-popup-actions">
396 + <button type="button" class="ka-btn ka-btn-secondary" id="cancel-section-import">
397 + <?php esc_html_e('Cancel', 'king-addons'); ?>
398 + </button>
399 + <button type="button" class="ka-btn ka-btn-primary" id="confirm-section-import">
400 + <?php esc_html_e('Import Section', 'king-addons'); ?>
401 + </button>
402 + </div>
403 + </div>
404 + </div>
405 + </div>
299 406 <div id="template-installing-popup" style="display:none;">
300 407 <div class="popup-content">
301 408 <div id="progress-container">
302 409 <div id="progress-bar">
@@ -329,9 +436,9 @@
329 436
330 437 echo '<span class="pr-popup-title">Want This Premium Template?</span>';
331 438 echo '<br><span class="pr-popup-desc">';
332 439 echo 'Get <span class="pr-popup-desc-b">unlimited downloads</span> for just';
333 - echo ' <span class="pr-popup-desc-b">$4.99/month';
440 + echo ' <span class="pr-popup-desc-b">$6.99/month';
334 441 echo '</span> — keep them <span class="pr-popup-desc-b">even after</span> your subscription ends!';
335 442 echo '</span><span class="pr-popup-desc" style="font-size: 16px;opacity: 0.6;">Trusted by 20,000+ users</span>';
336 443 ?></div>
337 444 <a class="purchase-btn"
@@ -579,8 +686,10 @@
579 686 [$this, 'process_import_images']
580 687 );
581 688
582 689 add_action('wp_ajax_filter_templates', array($this, 'handle_filter_templates'));
690 + add_action('wp_ajax_king_addons_get_collection_details', array($this, 'handle_get_collection_details'));
691 + add_action('wp_ajax_king_addons_get_collections_rows', array($this, 'handle_get_collections_rows'));
583 692
584 693 // Sections catalog endpoints
585 694 add_action('wp_ajax_king_addons_get_sections_catalog', array($this, 'handle_get_sections_catalog'));
586 695 add_action('wp_ajax_king_addons_import_section_admin', array($this, 'handle_import_section_to_page'));
@@ -587,13 +696,15 @@
587 696
588 697 add_action('http_api_curl', array($this, 'set_custom_curl_options'), 10, 3);
589 698 }
590 699
591 - public function set_custom_curl_options($handle)
700 + public function set_custom_curl_options($handle, $r, $url)
592 701 {
593 - curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 60);
594 - curl_setopt($handle, CURLOPT_DNS_CACHE_TIMEOUT, 300);
595 - curl_setopt($handle, CURLOPT_TIMEOUT, 300);
702 + if (strpos($url, 'kingaddons.com') !== false) {
703 + curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 60);
704 + curl_setopt($handle, CURLOPT_DNS_CACHE_TIMEOUT, 300);
705 + curl_setopt($handle, CURLOPT_TIMEOUT, 300);
706 + }
596 707 }
597 708
598 709 function handle_filter_templates(): void
599 710 {
@@ -622,11 +733,25 @@
622 733
623 734 public function import_elementor_page_with_images(): void
624 735 {
625 736 if (!current_user_can('manage_options')) {
737 + wp_send_json_error('Insufficient permissions');
626 738 return;
627 739 }
628 740
741 + // Support both the legacy templates admin nonce and the Elementor editor
742 + // Template Catalog button nonce.
743 + $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
744 + $valid_nonce = $nonce && (
745 + wp_verify_nonce($nonce, 'kingAddonsNonce') ||
746 + wp_verify_nonce($nonce, 'king_addons_template_catalog')
747 + );
748 +
749 + if (!$valid_nonce) {
750 + wp_send_json_error('Invalid nonce');
751 + return;
752 + }
753 +
629 754 $import_data = json_decode(stripslashes($_POST['data']), true);
630 755
631 756 if (json_last_error() !== JSON_ERROR_NONE) {
632 757 wp_send_json_error('JSON decode error: ' . json_last_error_msg());
@@ -643,8 +768,13 @@
643 768 // Check if this is for existing page (from popup import)
644 769 $existing_page_id = isset($import_data['existing_page_id']) ? intval($import_data['existing_page_id']) : 0;
645 770 $create_new_page = isset($import_data['create_new_page']) ? (bool)$import_data['create_new_page'] : true;
646 771
772 + if ($existing_page_id && !current_user_can('edit_post', $existing_page_id)) {
773 + wp_send_json_error('Insufficient permissions');
774 + return;
775 + }
776 +
647 777 delete_transient('elementor_import_content');
648 778 delete_transient('elementor_import_images');
649 779 delete_transient('elementor_import_total_images');
650 780 delete_transient('elementor_import_images_processed');
@@ -663,9 +793,9 @@
663 793 set_transient('elementor_import_elementor_version', $elementor_version, 60 * 60);
664 794 set_transient('elementor_import_existing_page_id', $existing_page_id, 60 * 60);
665 795 set_transient('elementor_import_create_new_page', $create_new_page, 60 * 60);
666 796
667 - error_log('King Addons Import: Initialized ' . ($create_new_page ? 'new page' : 'existing page') . ' import. Existing page ID: ' . $existing_page_id);
797 + // error_log('King Addons Import: Initialized ' . ($create_new_page ? 'new page' : 'existing page') . ' import. Existing page ID: ' . $existing_page_id);
668 798
669 799 wp_send_json_success([
670 800 'message' => 'Import initialized.',
671 801 'images' => $image_data,
@@ -745,8 +875,21 @@
745 875 wp_send_json_error('The current user can not manage options and create pages. Please change it in the WordPress settings.');
746 876 return;
747 877 }
748 878
879 + // Support both the legacy templates admin nonce and the Elementor editor
880 + // Template Catalog button nonce.
881 + $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
882 + $valid_nonce = $nonce && (
883 + wp_verify_nonce($nonce, 'kingAddonsNonce') ||
884 + wp_verify_nonce($nonce, 'king_addons_template_catalog')
885 + );
886 +
887 + if (!$valid_nonce) {
888 + wp_send_json_error('Invalid nonce');
889 + return;
890 + }
891 +
749 892 $start_time = time();
750 893 $timeout = 30;
751 894
752 895 $content = get_transient('elementor_import_content');
@@ -853,9 +996,9 @@
853 996 // New behavior - for existing page, just signal completion
854 997 // Content and images are processed and stored in transients
855 998 // Will be merged by king_addons_merge_with_existing_page endpoint
856 999
857 - error_log('King Addons Import: Image processing completed for existing page. Content ready for merge.');
1000 + // error_log('King Addons Import: Image processing completed for existing page. Content ready for merge.');
858 1001
859 1002 wp_send_json_success([
860 1003 'message' => "Image processing completed! Content ready for merge.",
861 1004 'images_processed' => $images_processed,
@@ -872,8 +1015,12 @@
872 1015
873 1016 public function download_image_to_media_gallery($image_url, $image_retry_count)
874 1017 {
875 1018 try {
1019 + if (!$this->is_safe_remote_url($image_url)) {
1020 + throw new Exception('Blocked potentially unsafe image URL: ' . $image_url);
1021 + }
1022 +
876 1023 $response = wp_remote_get($image_url, ['timeout' => 30]);
877 1024
878 1025 if (is_wp_error($response)) {
879 1026 throw new Exception('HTTP request error: ' . $response->get_error_message());
@@ -957,14 +1104,46 @@
957 1104 }
958 1105
959 1106 return $attach_id;
960 1107 } catch (Exception $e) {
961 - error_log('[KING_ADDONS_ERROR] ' . $e->getMessage());
1108 + // error_log('[KING_ADDONS_ERROR] ' . $e->getMessage());
962 1109 return false;
963 1110 }
964 1111 }
965 1112
966 1113 /**
1114 + * Basic SSRF guard for remote URLs.
1115 + *
1116 + * @param string $url URL to validate.
1117 + *
1118 + * @return bool
1119 + */
1120 + private function is_safe_remote_url(string $url): bool
1121 + {
1122 + $parsed = parse_url($url);
1123 + if (!$parsed || empty($parsed['scheme']) || empty($parsed['host'])) {
1124 + return false;
1125 + }
1126 +
1127 + if (!in_array($parsed['scheme'], ['http', 'https'], true)) {
1128 + return false;
1129 + }
1130 +
1131 + $host = $parsed['host'];
1132 + if (filter_var($host, FILTER_VALIDATE_IP)) {
1133 + return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
1134 + }
1135 +
1136 + // Allow domain names; block obvious localhost patterns.
1137 + $lower = strtolower($host);
1138 + if ($lower === 'localhost' || str_ends_with($lower, '.local')) {
1139 + return false;
1140 + }
1141 +
1142 + return true;
1143 + }
1144 +
1145 + /**
967 1146 * AJAX handler for getting sections catalog data
968 1147 */
969 1148 public function handle_get_sections_catalog(): void
970 1149 {
@@ -973,16 +1152,15 @@
973 1152 return;
974 1153 }
975 1154
976 1155 // Check nonce - handle both admin area and popup
977 - if (isset($_POST['nonce'])) {
978 - $valid_nonce = wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce') ||
979 - wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog');
980 -
981 - if (!$valid_nonce) {
982 - wp_send_json_error('Invalid nonce');
983 - return;
984 - }
1156 + $nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : '';
1157 + $valid_nonce = $nonce && (wp_verify_nonce($nonce, 'kingAddonsNonce') ||
1158 + wp_verify_nonce($nonce, 'king_addons_template_catalog'));
1159 +
1160 + if (!$valid_nonce) {
1161 + wp_send_json_error('Invalid nonce');
1162 + return;
985 1163 }
986 1164
987 1165 if (!class_exists('King_Addons\\SectionsMap')) {
988 1166 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
@@ -987,9 +1165,12 @@
987 1165 if (!class_exists('King_Addons\\SectionsMap')) {
988 1166 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
989 1167 }
990 1168
991 - $sections_map = SectionsMap::getSectionsMapArray();
1169 + $sections_map_class = '\\King_Addons\\SectionsMap';
1170 + $sections_map = is_callable([$sections_map_class, 'getSectionsMapArray'])
1171 + ? call_user_func([$sections_map_class, 'getSectionsMapArray'])
1172 + : [];
992 1173 $sections = $sections_map['sections'] ?? [];
993 1174
994 1175 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
995 1176
@@ -1111,15 +1292,15 @@
1111 1292 } else {
1112 1293 $install_id = 0;
1113 1294 }
1114 1295
1115 - error_log('King Addons Premium Section: Using install_id: ' . $install_id . ' for premium section: ' . $section_key);
1296 + // error_log('King Addons Premium Section: Using install_id: ' . $install_id . ' for premium section: ' . $section_key);
1116 1297 } elseif ($section_plan === 'free') {
1117 1298 $api_url = 'https://api.kingaddons.com/get-section-free.php';
1118 1299 $install_id = 0;
1119 - error_log('King Addons Free Section: Fetching free section: ' . $section_key);
1300 + // error_log('King Addons Free Section: Fetching free section: ' . $section_key);
1120 1301 } else {
1121 - error_log('King Addons Section Error: Premium section requires premium license. Section: ' . $section_key . ', Plan: ' . $section_plan . ', Premium Active: ' . ($is_premium_active ? 'Yes' : 'No'));
1302 + // error_log('King Addons Section Error: Premium section requires premium license. Section: ' . $section_key . ', Plan: ' . $section_plan . ', Premium Active: ' . ($is_premium_active ? 'Yes' : 'No'));
1122 1303 wp_send_json_error('Premium section requires premium license');
1123 1304 return;
1124 1305 }
1125 1306
@@ -1140,12 +1321,12 @@
1140 1321
1141 1322 $body = wp_remote_retrieve_body($response);
1142 1323 $data = json_decode($body, true);
1143 1324
1144 - error_log('King Addons Section API Response: ' . substr($body, 0, 500) . (strlen($body) > 500 ? '...' : ''));
1325 + // error_log('King Addons Section API Response: ' . substr($body, 0, 500) . (strlen($body) > 500 ? '...' : ''));
1145 1326
1146 1327 if (!$data) {
1147 - error_log('King Addons Section Error: Failed to decode JSON response');
1328 + // error_log('King Addons Section Error: Failed to decode JSON response');
1148 1329 wp_send_json_error('Invalid JSON response from section API');
1149 1330 return;
1150 1331 }
1151 1332
@@ -1150,9 +1331,9 @@
1150 1331 }
1151 1332
1152 1333 if (!isset($data['success']) || !$data['success']) {
1153 1334 $error_message = isset($data['message']) ? $data['message'] : 'Unknown API error';
1154 - error_log('King Addons Section Error: API returned error: ' . $error_message);
1335 + // error_log('King Addons Section Error: API returned error: ' . $error_message);
1155 1336 wp_send_json_error('Section API error: ' . $error_message);
1156 1337 return;
1157 1338 }
1158 1339
@@ -1159,8 +1340,348 @@
1159 1340 // Return section data for frontend processing (adjust format from your API)
1160 1341 wp_send_json_success([
1161 1342 'section_data' => $data['section'], // Your API returns 'section' not 'landing'
1162 1343 'message' => 'Section data retrieved successfully'
1344 + ]);
1345 + }
1346 +
1347 + public function handle_get_collection_details()
1348 + {
1349 + // Verify nonce
1350 + if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce')) {
1351 + wp_send_json_error('Invalid nonce');
1352 + }
1353 +
1354 + $collection_id = isset($_POST['collection_id']) ? sanitize_text_field($_POST['collection_id']) : '';
1355 + if (empty($collection_id)) {
1356 + wp_send_json_error('Collection ID is required');
1357 + }
1358 +
1359 + $templates = TemplatesMap::getTemplatesMapArray();
1360 +
1361 + if (!class_exists('King_Addons\\SectionsMap')) {
1362 + require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
1363 + }
1364 + $sections_map = SectionsMap::getSectionsMapArray();
1365 + $sections = $sections_map['sections'] ?? [];
1366 +
1367 + // Filter Templates
1368 + $collection_templates = [];
1369 + $template_keys = [];
1370 + foreach ($templates['templates'] as $key => $template) {
1371 + if (isset($template['collection']) && (string)$template['collection'] === (string)$collection_id) {
1372 + $template['template_key'] = $key;
1373 + $collection_templates[] = $template;
1374 + $template_keys[] = $key;
1375 + }
1376 + }
1377 +
1378 + // Filter Sections
1379 + $collection_sections = [];
1380 + foreach ($sections as $key => $section) {
1381 + $parent = $section['parent_template'] ?? '';
1382 + if ($parent && in_array($parent, $template_keys)) {
1383 + $section['section_key'] = $key;
1384 + $collection_sections[] = $section;
1385 + }
1386 + }
1387 +
1388 + // Render Templates HTML
1389 + ob_start();
1390 + if (empty($collection_templates)) {
1391 + echo '<p class="templates-not-found">' . esc_html__('No templates found in this collection.', 'king-addons') . '</p>';
1392 + } else {
1393 + foreach ($collection_templates as $template) {
1394 + $attr_key = $template['template_key'];
1395 + ?>
1396 + <div class="template-item"
1397 + data-category="<?php echo esc_attr($template['category']); ?>"
1398 + data-tags="<?php echo esc_attr(implode(',', $template['tags'])); ?>"
1399 + data-template-key="<?php echo esc_attr($attr_key); ?>"
1400 + data-template-plan="<?php echo esc_attr($template['plan']); ?>">
1401 + <img class="kng-addons-template-thumbnail" loading="lazy"
1402 + src="<?php echo esc_url("https://thumbnails.kingaddons.com/$attr_key.png?v=4"); ?>"
1403 + alt="<?php echo esc_attr($template['title']); ?>">
1404 + <h3><?php echo esc_html($template['title']); ?></h3>
1405 + <div class="template-plan template-plan-<?php echo esc_html($template['plan']); ?>"><?php echo esc_html($template['plan']); ?></div>
1406 + </div>
1407 + <?php
1408 + }
1409 + }
1410 + $templates_html = ob_get_clean();
1411 +
1412 + // Render Sections HTML
1413 + ob_start();
1414 + if (empty($collection_sections)) {
1415 + echo '<p class="sections-not-found">' . esc_html__('No sections found in this collection.', 'king-addons') . '</p>';
1416 + } else {
1417 + foreach ($collection_sections as $section) {
1418 + $section_key = $section['section_key'];
1419 + $plan = $section['plan'];
1420 + $title = $section['title'];
1421 + $thumb_url = "https://thumbnails.kingaddons.com/sections/$plan/$section_key.png";
1422 + ?>
1423 + <div class="section-item"
1424 + data-section-key="<?php echo esc_attr($section_key); ?>"
1425 + data-section-plan="<?php echo esc_attr($plan); ?>">
1426 + <div class="section-preview">
1427 + <img src="<?php echo esc_url($thumb_url); ?>" alt="<?php echo esc_attr($title); ?>" loading="lazy">
1428 + <div class="section-actions">
1429 + <button class="import-section-btn" data-section-key="<?php echo esc_attr($section_key); ?>" data-section-plan="<?php echo esc_attr($plan); ?>">
1430 + <i class="eicon-file-download"></i> <?php esc_html_e('Import', 'king-addons'); ?>
1431 + </button>
1432 + <a href="<?php echo esc_url('https://sections.kingaddons.com/' . $section_key); ?>" class="live-preview-btn" target="_blank">
1433 + <i class="eicon-preview-medium"></i> <?php esc_html_e('Live Preview', 'king-addons'); ?>
1434 + </a>
1435 + </div>
1436 + <?php if ($plan === 'premium'): ?>
1437 + <span class="section-badge-pro">PRO</span>
1438 + <?php endif; ?>
1439 + </div>
1440 + <div class="section-info">
1441 + <h4><?php echo esc_html($title); ?></h4>
1442 + </div>
1443 + </div>
1444 + <?php
1445 + }
1446 + }
1447 + $sections_html = ob_get_clean();
1448 +
1449 + wp_send_json_success([
1450 + 'templates_html' => $templates_html,
1451 + 'sections_html' => $sections_html,
1452 + 'templates_count' => count($collection_templates),
1453 + 'sections_count' => count($collection_sections)
1454 + ]);
1455 + }
1456 +
1457 + public function handle_get_collections_rows()
1458 + {
1459 + if (!current_user_can('manage_options')) {
1460 + wp_send_json_error('Insufficient permissions');
1461 + }
1462 +
1463 + // Verify nonce
1464 + if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce')) {
1465 + wp_send_json_error('Invalid nonce');
1466 + }
1467 +
1468 + $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
1469 + $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
1470 + $per_page = 10; // Number of collections per page
1471 +
1472 + $templates = TemplatesMap::getTemplatesMapArray();
1473 + $collections_map = CollectionsMap::getCollectionsMapArray();
1474 +
1475 + // Load Sections to compute section counts per collection
1476 + if (!class_exists('King_Addons\\SectionsMap')) {
1477 + require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
1478 + }
1479 + $sections_map = SectionsMap::getSectionsMapArray();
1480 + $sections = $sections_map['sections'] ?? [];
1481 +
1482 + // Build templates grouped by collection + counts
1483 + // Search behavior mirrors template search: title + tags (and direct key).
1484 + $search_terms = array_values(array_filter(preg_split('/\s+/', (string) $search)));
1485 + $has_search = !empty($search_terms);
1486 +
1487 + $templates_by_collection = [];
1488 + $templates_by_collection_title = [];
1489 + $templates_by_collection_tags = [];
1490 +
1491 + $template_counts = [];
1492 + $template_key_to_collection = [];
1493 + $template_matches_search = [];
1494 + $collections_matching_by_name = [];
1495 +
1496 + $contains_ci = static function ($haystack, $needle): bool {
1497 + if ($needle === '') {
1498 + return true;
1499 + }
1500 + if (function_exists('mb_stripos')) {
1501 + return mb_stripos((string) $haystack, (string) $needle) !== false;
1502 + }
1503 + return stripos((string) $haystack, (string) $needle) !== false;
1504 + };
1505 +
1506 + if ($has_search) {
1507 + foreach ($collections_map as $collection_id => $collection_name) {
1508 + foreach ($search_terms as $term) {
1509 + if ($term !== '' && $contains_ci($collection_name, $term)) {
1510 + $collections_matching_by_name[$collection_id] = true;
1511 + break;
1512 + }
1513 + }
1514 + }
1515 + }
1516 +
1517 + foreach (($templates['templates'] ?? []) as $key => $template) {
1518 + $collection_id = $template['collection'] ?? '';
1519 + if ($collection_id === '' || !isset($collections_map[$collection_id])) {
1520 + continue;
1521 + }
1522 +
1523 + $template_key_to_collection[$key] = $collection_id;
1524 +
1525 + if (!isset($template_counts[$collection_id])) {
1526 + $template_counts[$collection_id] = 0;
1527 + }
1528 + $template_counts[$collection_id]++;
1529 +
1530 + $title = (string) ($template['title'] ?? '');
1531 + $tags = is_array($template['tags'] ?? null) ? $template['tags'] : [];
1532 + $matches_by_title = false;
1533 + $matches_by_tags = false;
1534 + $matches_by_key = ($search !== '' && $key === $search);
1535 +
1536 + if ($has_search) {
1537 + foreach ($search_terms as $term) {
1538 + if ($term === '') {
1539 + continue;
1540 + }
1541 +
1542 + if ($contains_ci($title, $term)) {
1543 + $matches_by_title = true;
1544 + continue;
1545 + }
1546 +
1547 + if ($contains_ci($key, $term)) {
1548 + $matches_by_key = true;
1549 + continue;
1550 + }
1551 +
1552 + foreach ($tags as $tag) {
1553 + if ($contains_ci((string) $tag, $term)) {
1554 + $matches_by_tags = true;
1555 + break;
1556 + }
1557 + }
1558 + }
1559 + }
1560 +
1561 + $collection_name_match = $has_search && isset($collections_matching_by_name[$collection_id]);
1562 + $template_is_relevant = !$has_search || $collection_name_match || $matches_by_key || $matches_by_title || $matches_by_tags;
1563 + $template_matches_search[$key] = (bool) ($matches_by_key || $matches_by_title || $matches_by_tags);
1564 +
1565 + if (!$template_is_relevant) {
1566 + continue;
1567 + }
1568 +
1569 + $item = [
1570 + 'key' => $key,
1571 + 'title' => $title,
1572 + 'plan' => $template['plan'],
1573 + 'category' => $template['category'],
1574 + ];
1575 +
1576 + if (!isset($templates_by_collection_title[$collection_id])) {
1577 + $templates_by_collection_title[$collection_id] = [];
1578 + }
1579 + if (!isset($templates_by_collection_tags[$collection_id])) {
1580 + $templates_by_collection_tags[$collection_id] = [];
1581 + }
1582 +
1583 + // No search: keep natural order (default Collections landing should not be empty).
1584 + if (!$has_search) {
1585 + if (!isset($templates_by_collection[$collection_id])) {
1586 + $templates_by_collection[$collection_id] = [];
1587 + }
1588 + $templates_by_collection[$collection_id][] = $item;
1589 + continue;
1590 + }
1591 +
1592 + // If the collection name matches, keep natural order; otherwise prioritize title matches.
1593 + if ($collection_name_match) {
1594 + if (!isset($templates_by_collection[$collection_id])) {
1595 + $templates_by_collection[$collection_id] = [];
1596 + }
1597 + $templates_by_collection[$collection_id][] = $item;
1598 + } else {
1599 + if ($matches_by_key || $matches_by_title) {
1600 + $templates_by_collection_title[$collection_id][] = $item;
1601 + } elseif ($matches_by_tags) {
1602 + $templates_by_collection_tags[$collection_id][] = $item;
1603 + }
1604 + }
1605 + }
1606 +
1607 + // Merge per-collection buckets and apply soft limit
1608 + $collection_ids_all = array_keys($template_counts);
1609 + foreach ($collection_ids_all as $collection_id) {
1610 + if (!isset($templates_by_collection[$collection_id])) {
1611 + $templates_by_collection[$collection_id] = [];
1612 + }
1613 +
1614 + if ($has_search && !isset($collections_matching_by_name[$collection_id])) {
1615 + $merged = array_merge(
1616 + $templates_by_collection_title[$collection_id] ?? [],
1617 + $templates_by_collection_tags[$collection_id] ?? []
1618 + );
1619 + $templates_by_collection[$collection_id] = $merged;
1620 + }
1621 +
1622 + if (count($templates_by_collection[$collection_id]) > 20) {
1623 + $templates_by_collection[$collection_id] = array_slice($templates_by_collection[$collection_id], 0, 20);
1624 + }
1625 + }
1626 +
1627 + // Compute sections count per collection using parent_template -> template -> collection mapping.
1628 + // When searching, count only sections whose parent template matches search (unless collection name matches).
1629 + $section_counts = [];
1630 + foreach ($sections as $section) {
1631 + $parent = $section['parent_template'] ?? '';
1632 + if (!$parent || !isset($template_key_to_collection[$parent])) {
1633 + continue;
1634 + }
1635 +
1636 + $collection_id = $template_key_to_collection[$parent];
1637 + $collection_name_match = $has_search && isset($collections_matching_by_name[$collection_id]);
1638 + if ($has_search && !$collection_name_match) {
1639 + if (!isset($template_matches_search[$parent]) || !$template_matches_search[$parent]) {
1640 + continue;
1641 + }
1642 + }
1643 +
1644 + if (!isset($section_counts[$collection_id])) {
1645 + $section_counts[$collection_id] = 0;
1646 + }
1647 + $section_counts[$collection_id]++;
1648 + }
1649 +
1650 + // Only collections that actually have templates in the (possibly filtered) result
1651 + $collection_ids = array_keys(array_filter($templates_by_collection, static function ($items) {
1652 + return !empty($items);
1653 + }));
1654 +
1655 + // Sort collections by name to match the main Collections view
1656 + usort($collection_ids, function ($a, $b) use ($collections_map) {
1657 + return strcasecmp((string) ($collections_map[$a] ?? ''), (string) ($collections_map[$b] ?? ''));
1658 + });
1659 +
1660 + $total_collections = count($collection_ids);
1661 + $total_pages = (int) ceil($total_collections / $per_page);
1662 + $page = min($page, max(1, $total_pages));
1663 + $offset = ($page - 1) * $per_page;
1664 + $paged_collection_ids = array_slice($collection_ids, $offset, $per_page);
1665 +
1666 + $result_collections = [];
1667 + foreach ($paged_collection_ids as $id) {
1668 + $result_collections[] = [
1669 + 'id' => $id,
1670 + 'name' => $collections_map[$id],
1671 + 'templates' => $templates_by_collection[$id],
1672 + 'templates_count' => (int) ($template_counts[$id] ?? 0),
1673 + 'sections_count' => (int) ($section_counts[$id] ?? 0)
1674 + ];
1675 + }
1676 +
1677 + wp_send_json_success([
1678 + 'collections' => $result_collections,
1679 + 'pagination' => [
1680 + 'total' => $total_collections,
1681 + 'pages' => $total_pages,
1682 + 'current' => $page
1683 + ]
1163 1684 ]);
1164 1685 }
1165 1686 }
1166 1687