PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.83
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 +547 -31 51.1.39 → 51.1.83 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; ?>
@@ -132,9 +162,9 @@
132 162 </div>
133 163 <?php endif; ?>
134 164 <?php if (!king_addons_freemius()->can_use_premium_code()): ?>
135 165 <div class="kng-promo-btn-wrap">
136 - <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">
137 167 <div class="kng-promo-btn-txt">
138 168 <?php esc_html_e('Unlock Premium Features & 650+ Templates Today!', 'king-addons'); ?>
139 169 </div>
140 170 <img width="16px"
@@ -153,15 +183,18 @@
153 183 <i class="eicon-document-file"></i>
154 184 <?php esc_html_e('Templates', 'king-addons'); ?>
155 185 <span class="tab-count"><?php echo count($templates['templates']); ?></span>
156 186 </button>
157 - <?php if (KING_ADDONS_EXT_SECTIONS_CATALOG): ?>
158 187 <button class="king-addons-tab-button" data-tab="sections">
159 188 <i class="eicon-section"></i>
160 189 <?php esc_html_e('Sections', 'king-addons'); ?>
161 190 <span class="tab-count" id="sections-count">0</span>
162 191 </button>
163 - <?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>
164 197 </div>
165 198
166 199 <!-- Templates Tab Content -->
167 200 <div id="templates-catalog" class="king-addons-tab-content active">
@@ -203,9 +236,9 @@
203 236 <?php if (!$is_premium_active): ?>
204 237 <div class="promo-wrapper">
205 238 <div class="promo-txt"><?php
206 239 esc_html_e('Unlock Premium Templates', 'king-addons');
207 - echo '<ul><li>$4.99/month</li>';
240 + echo '<ul><li>$6.99/month</li>';
208 241 echo '<li>Unlimited Downloads</li>';
209 242 echo '<li>Keep all templates forever</li></ul>';
210 243 ?></div>
211 244 <a class="purchase-btn"
@@ -238,9 +271,8 @@
238 271 </div>
239 272 </div>
240 273
241 274 <!-- Sections Tab Content -->
242 - <?php if (KING_ADDONS_EXT_SECTIONS_CATALOG): ?>
243 275 <div id="sections-catalog" class="king-addons-tab-content">
244 276 <div class="filters-wrapper">
245 277 <div class="filters">
246 278 <select id="sections-category">
@@ -270,10 +302,44 @@
270 302 </div>
271 303 <div class="sections-pagination"></div>
272 304 </div>
273 305 </div>
274 - <?php endif; ?>
275 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 +
276 342 <div id="template-preview-popup" style="display:none;">
277 343 <div class="popup-content">
278 344 <div class="popup-content-nav">
279 345 <button data-plan-active="<?php echo ($is_premium_active) ? 'premium' : 'free'; ?>"
@@ -300,8 +366,44 @@
300 366 </div>
301 367 <iframe id="template-preview-iframe" src="" frameborder="0"></iframe>
302 368 </div>
303 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>
304 406 <div id="template-installing-popup" style="display:none;">
305 407 <div class="popup-content">
306 408 <div id="progress-container">
307 409 <div id="progress-bar">
@@ -334,9 +436,9 @@
334 436
335 437 echo '<span class="pr-popup-title">Want This Premium Template?</span>';
336 438 echo '<br><span class="pr-popup-desc">';
337 439 echo 'Get <span class="pr-popup-desc-b">unlimited downloads</span> for just';
338 - echo ' <span class="pr-popup-desc-b">$4.99/month';
440 + echo ' <span class="pr-popup-desc-b">$6.99/month';
339 441 echo '</span> — keep them <span class="pr-popup-desc-b">even after</span> your subscription ends!';
340 442 echo '</span><span class="pr-popup-desc" style="font-size: 16px;opacity: 0.6;">Trusted by 20,000+ users</span>';
341 443 ?></div>
342 444 <a class="purchase-btn"
@@ -584,8 +686,10 @@
584 686 [$this, 'process_import_images']
585 687 );
586 688
587 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'));
588 692
589 693 // Sections catalog endpoints
590 694 add_action('wp_ajax_king_addons_get_sections_catalog', array($this, 'handle_get_sections_catalog'));
591 695 add_action('wp_ajax_king_addons_import_section_admin', array($this, 'handle_import_section_to_page'));
@@ -592,13 +696,15 @@
592 696
593 697 add_action('http_api_curl', array($this, 'set_custom_curl_options'), 10, 3);
594 698 }
595 699
596 - public function set_custom_curl_options($handle)
700 + public function set_custom_curl_options($handle, $r, $url)
597 701 {
598 - curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 60);
599 - curl_setopt($handle, CURLOPT_DNS_CACHE_TIMEOUT, 300);
600 - 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 + }
601 707 }
602 708
603 709 function handle_filter_templates(): void
604 710 {
@@ -627,11 +733,25 @@
627 733
628 734 public function import_elementor_page_with_images(): void
629 735 {
630 736 if (!current_user_can('manage_options')) {
737 + wp_send_json_error('Insufficient permissions');
631 738 return;
632 739 }
633 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 +
634 754 $import_data = json_decode(stripslashes($_POST['data']), true);
635 755
636 756 if (json_last_error() !== JSON_ERROR_NONE) {
637 757 wp_send_json_error('JSON decode error: ' . json_last_error_msg());
@@ -648,8 +768,13 @@
648 768 // Check if this is for existing page (from popup import)
649 769 $existing_page_id = isset($import_data['existing_page_id']) ? intval($import_data['existing_page_id']) : 0;
650 770 $create_new_page = isset($import_data['create_new_page']) ? (bool)$import_data['create_new_page'] : true;
651 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 +
652 777 delete_transient('elementor_import_content');
653 778 delete_transient('elementor_import_images');
654 779 delete_transient('elementor_import_total_images');
655 780 delete_transient('elementor_import_images_processed');
@@ -668,9 +793,9 @@
668 793 set_transient('elementor_import_elementor_version', $elementor_version, 60 * 60);
669 794 set_transient('elementor_import_existing_page_id', $existing_page_id, 60 * 60);
670 795 set_transient('elementor_import_create_new_page', $create_new_page, 60 * 60);
671 796
672 - 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);
673 798
674 799 wp_send_json_success([
675 800 'message' => 'Import initialized.',
676 801 'images' => $image_data,
@@ -750,8 +875,21 @@
750 875 wp_send_json_error('The current user can not manage options and create pages. Please change it in the WordPress settings.');
751 876 return;
752 877 }
753 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 +
754 892 $start_time = time();
755 893 $timeout = 30;
756 894
757 895 $content = get_transient('elementor_import_content');
@@ -858,9 +996,9 @@
858 996 // New behavior - for existing page, just signal completion
859 997 // Content and images are processed and stored in transients
860 998 // Will be merged by king_addons_merge_with_existing_page endpoint
861 999
862 - 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.');
863 1001
864 1002 wp_send_json_success([
865 1003 'message' => "Image processing completed! Content ready for merge.",
866 1004 'images_processed' => $images_processed,
@@ -877,8 +1015,12 @@
877 1015
878 1016 public function download_image_to_media_gallery($image_url, $image_retry_count)
879 1017 {
880 1018 try {
1019 + if (!$this->is_safe_remote_url($image_url)) {
1020 + throw new Exception('Blocked potentially unsafe image URL: ' . $image_url);
1021 + }
1022 +
881 1023 $response = wp_remote_get($image_url, ['timeout' => 30]);
882 1024
883 1025 if (is_wp_error($response)) {
884 1026 throw new Exception('HTTP request error: ' . $response->get_error_message());
@@ -962,14 +1104,46 @@
962 1104 }
963 1105
964 1106 return $attach_id;
965 1107 } catch (Exception $e) {
966 - error_log('[KING_ADDONS_ERROR] ' . $e->getMessage());
1108 + // error_log('[KING_ADDONS_ERROR] ' . $e->getMessage());
967 1109 return false;
968 1110 }
969 1111 }
970 1112
971 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 + /**
972 1146 * AJAX handler for getting sections catalog data
973 1147 */
974 1148 public function handle_get_sections_catalog(): void
975 1149 {
@@ -978,16 +1152,15 @@
978 1152 return;
979 1153 }
980 1154
981 1155 // Check nonce - handle both admin area and popup
982 - if (isset($_POST['nonce'])) {
983 - $valid_nonce = wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce') ||
984 - wp_verify_nonce($_POST['nonce'], 'king_addons_template_catalog');
985 -
986 - if (!$valid_nonce) {
987 - wp_send_json_error('Invalid nonce');
988 - return;
989 - }
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;
990 1163 }
991 1164
992 1165 if (!class_exists('King_Addons\\SectionsMap')) {
993 1166 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
@@ -992,9 +1165,12 @@
992 1165 if (!class_exists('King_Addons\\SectionsMap')) {
993 1166 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
994 1167 }
995 1168
996 - $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 + : [];
997 1173 $sections = $sections_map['sections'] ?? [];
998 1174
999 1175 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
1000 1176
@@ -1116,15 +1292,15 @@
1116 1292 } else {
1117 1293 $install_id = 0;
1118 1294 }
1119 1295
1120 - 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);
1121 1297 } elseif ($section_plan === 'free') {
1122 1298 $api_url = 'https://api.kingaddons.com/get-section-free.php';
1123 1299 $install_id = 0;
1124 - error_log('King Addons Free Section: Fetching free section: ' . $section_key);
1300 + // error_log('King Addons Free Section: Fetching free section: ' . $section_key);
1125 1301 } else {
1126 - 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'));
1127 1303 wp_send_json_error('Premium section requires premium license');
1128 1304 return;
1129 1305 }
1130 1306
@@ -1145,12 +1321,12 @@
1145 1321
1146 1322 $body = wp_remote_retrieve_body($response);
1147 1323 $data = json_decode($body, true);
1148 1324
1149 - 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 ? '...' : ''));
1150 1326
1151 1327 if (!$data) {
1152 - error_log('King Addons Section Error: Failed to decode JSON response');
1328 + // error_log('King Addons Section Error: Failed to decode JSON response');
1153 1329 wp_send_json_error('Invalid JSON response from section API');
1154 1330 return;
1155 1331 }
1156 1332
@@ -1155,9 +1331,9 @@
1155 1331 }
1156 1332
1157 1333 if (!isset($data['success']) || !$data['success']) {
1158 1334 $error_message = isset($data['message']) ? $data['message'] : 'Unknown API error';
1159 - error_log('King Addons Section Error: API returned error: ' . $error_message);
1335 + // error_log('King Addons Section Error: API returned error: ' . $error_message);
1160 1336 wp_send_json_error('Section API error: ' . $error_message);
1161 1337 return;
1162 1338 }
1163 1339
@@ -1164,8 +1340,348 @@
1164 1340 // Return section data for frontend processing (adjust format from your API)
1165 1341 wp_send_json_success([
1166 1342 'section_data' => $data['section'], // Your API returns 'section' not 'landing'
1167 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 + ]
1168 1684 ]);
1169 1685 }
1170 1686 }
1171 1687