PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.76
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.76
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 / Templates / Templates.php

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

1,684 lines 77.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /** @noinspection SpellCheckingInspection, DuplicatedCode */
4
5 namespace King_Addons;
6
7 use Exception;
8 use King_Addons\SectionsMap;
9
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 final class Templates
15 {
16 private static ?Templates $instance = null;
17
18 public static function instance(): ?Templates
19 {
20 if (is_null(self::$instance)) {
21 self::$instance = new self();
22 }
23 return self::$instance;
24 }
25
26 public function render_template_catalog_page(): void
27 {
28 $templates = TemplatesMap::getTemplatesMapArray();
29 $collections = CollectionsMap::getCollectionsMapArray();
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
38 uasort($collections, function ($a, $b) {
39 return strcasecmp($a, $b);
40 });
41
42 $is_premium_active = king_addons_freemius()->can_use_premium_code();
43
44 // TODO: TEST: For UI testing, it doesn't enable the real premium
45 // $is_premium_active = false;
46
47 // Arrays for categories and tags
48 $categories = [];
49 $tags = [];
50 $category_counts = [];
51 $collection_counts = [];
52 $collection_images = [];
53 $template_collection_map = [];
54
55 // Getting unique categories and tags
56 foreach ($templates['templates'] as $key => $template) {
57 if (!in_array($template['category'], $categories)) {
58 $categories[] = $template['category'];
59 }
60
61 foreach ($template['tags'] as $tag) {
62 if (!in_array($tag, $tags)) {
63 $tags[] = $tag;
64 }
65 }
66
67 $category = $template['category'];
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 }
78 }
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
90 sort($categories);
91
92 // Get filters from query
93 $search_query = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
94 $selected_category = isset($_GET['category']) ? sanitize_text_field($_GET['category']) : '';
95 $selected_collection = isset($_GET['collection']) ? sanitize_text_field($_GET['collection']) : '';
96 $selected_tags = isset($_GET['tags']) ? array_filter(explode(',', sanitize_text_field($_GET['tags']))) : [];
97 $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
98
99 // Use the common function to get filtered templates and pagination
100 $result = $this->get_filtered_templates($templates, $search_query, $selected_category, $selected_tags, $selected_collection, $current_page);
101
102 if (isset($_GET['ajax']) && $_GET['ajax']) {
103 wp_send_json_success(['grid_html' => $result['grid_html'], 'pagination_html' => $result['pagination_html']]);
104 }
105 if ($is_premium_active) {
106 ?>
107 <script type="text/javascript">
108 (function() {
109 window.kingAddons = window.kingAddons || {};
110 window.kingAddons.installId = <?php
111 echo json_encode(king_addons_freemius()->get_site()->id);
112 ?>;
113 })();
114 </script>
115 <?php
116 }
117 ?>
118 <div id="king-addons-templates-top"></div>
119 <div id="king-addons-templates" class="king-addons-templates">
120 <div class="kng-intro">
121 <div class="kng-intro-wrap">
122 <div class="kng-intro-wrap-1">
123 <h1 class="kng-intro-title"><?php echo esc_html__('King Addons for Elementor', 'king-addons'); ?></h1>
124 <?php if ($is_premium_active): ?>
125 <span class="premium-active-txt"><?php echo esc_html__('PREMIUM', 'king-addons'); ?></h1></span>
126 <?php endif; ?>
127 <h2 class="kng-intro-subtitle"><?php echo esc_html__('Discover professionally designed, attention-grabbing, and SEO-optimized templates perfect for any site', 'king-addons'); ?></h2>
128 </div>
129 <div class="kng-intro-wrap-2">
130 <div class="kng-navigation">
131 <div class="kng-nav-item kng-nav-item-current">
132 <a href="<?php echo admin_url('admin.php?page=king-addons'); ?>">
133 <div class="kng-nav-item-txt"><?php echo esc_html__('Free Widgets & Features', 'king-addons'); ?></div>
134 </a>
135 </div>
136 <?php if (KING_ADDONS_EXT_HEADER_FOOTER_BUILDER): ?>
137 <div class="kng-nav-item kng-nav-item-current">
138 <a href="<?php echo admin_url('edit.php?post_type=king-addons-el-hf'); ?>">
139 <div class="kng-nav-item-txt"><?php echo esc_html__('Free Header & Footer Builder', 'king-addons'); ?></div>
140 </a>
141 </div>
142 <?php endif; ?>
143 <?php if (KING_ADDONS_EXT_POPUP_BUILDER): ?>
144 <div class="kng-nav-item kng-nav-item-current">
145 <a href="<?php echo admin_url('admin.php?page=king-addons-popup-builder'); ?>">
146 <div class="kng-nav-item-txt"><?php echo esc_html__('Free Popup Builder', 'king-addons'); ?></div>
147 </a>
148 </div>
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>
155 <?php if (!king_addons_freemius()->can_use_premium_code()): ?>
156 <div class="kng-nav-item kng-nav-item-current kng-nav-activate-license">
157 <a id="activate-license-btn">
158 <img src="<?php echo esc_url(KING_ADDONS_URL) . 'includes/admin/img/up.svg'; ?>"
159 alt="<?php echo esc_html__('Activate License', 'king-addons'); ?>">
160 <div class="kng-nav-item-txt"><?php echo esc_html__('Activate License', 'king-addons'); ?></div>
161 </a>
162 </div>
163 <?php endif; ?>
164 <?php if (!king_addons_freemius()->can_use_premium_code()): ?>
165 <div class="kng-promo-btn-wrap">
166 <a href="https://kingaddons.com/pricing/?utm_source=king-addons-templates-catalog" target="_blank">
167 <div class="kng-promo-btn-txt">
168 <?php esc_html_e('Unlock Premium Features & 650+ Templates Today!', 'king-addons'); ?>
169 </div>
170 <img width="16px"
171 src="<?php echo esc_url(KING_ADDONS_URL) . 'includes/admin/img/share-v2.svg'; ?>"
172 alt="<?php echo esc_html__('Open link in the new tab', 'king-addons'); ?>">
173 </a>
174 </div>
175 <?php endif; ?>
176 </div>
177 </div>
178 </div>
179 </div>
180 <!-- Catalog Tabs -->
181 <div class="king-addons-catalog-tabs">
182 <button class="king-addons-tab-button active" data-tab="templates">
183 <i class="eicon-document-file"></i>
184 <?php esc_html_e('Templates', 'king-addons'); ?>
185 <span class="tab-count"><?php echo count($templates['templates']); ?></span>
186 </button>
187 <button class="king-addons-tab-button" data-tab="sections">
188 <i class="eicon-section"></i>
189 <?php esc_html_e('Sections', 'king-addons'); ?>
190 <span class="tab-count" id="sections-count">0</span>
191 </button>
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>
197 </div>
198
199 <!-- Templates Tab Content -->
200 <div id="templates-catalog" class="king-addons-tab-content active">
201 <div class="filters-wrapper">
202 <div class="filters">
203 <select id="template-category">
204 <option value=""><?php esc_html_e('All Categories', 'king-addons'); ?>
205 (<?php echo count($templates['templates']); ?>)
206 </option>
207 <?php foreach ($categories as $category): ?>
208 <option value="<?php echo esc_attr($category); ?>" <?php selected($selected_category, $category); ?>>
209 <?php echo esc_html(ucwords(str_replace('-', ' ', $category))); ?>
210 (<?php echo $category_counts[$category]; ?>)
211 </option>
212 <?php endforeach; ?>
213 </select>
214 <select id="template-collection">
215 <option value=""><?php esc_html_e('All Collections', 'king-addons'); ?>
216 (<?php echo count($collections); ?>)
217 </option>
218 <?php foreach ($collections as $id => $name): ?>
219 <option value="<?php echo esc_attr($id); ?>" <?php selected($selected_collection, $id); ?>>
220 <?php echo esc_html($name); ?>
221 </option>
222 <?php endforeach; ?>
223 </select>
224 <div id="template-tags">
225 <?php
226 shuffle($tags); ?>
227 <div class="tags-header">Tags</div>
228 <?php foreach ($tags as $tag): ?>
229 <label>
230 <input type="checkbox"
231 value="<?php echo esc_attr($tag); ?>" <?php echo in_array($tag, $selected_tags) ? 'checked' : ''; ?>> <?php echo esc_html(ucwords(str_replace('-', ' ', $tag))); ?>
232 </label>
233 <?php endforeach; ?>
234 </div>
235 <button id="reset-filters"><?php esc_html_e('Reset Search & Filters', 'king-addons'); ?></button>
236 <?php if (!$is_premium_active): ?>
237 <div class="promo-wrapper">
238 <div class="promo-txt"><?php
239 esc_html_e('Unlock Premium Templates', 'king-addons');
240 echo '<ul><li>$6.99/month</li>';
241 echo '<li>Unlimited Downloads</li>';
242 echo '<li>Keep all templates forever</li></ul>';
243 ?></div>
244 <a class="purchase-btn"
245 href="https://kingaddons.com/pricing/?utm_source=kng-templates-banner-side&utm_medium=plugin&utm_campaign=kng"
246 target="_blank">
247 <button class="promo-btn purchase-btn"
248 style="display: flex;align-items: center;">
249 <img src="<?php echo esc_url(KING_ADDONS_URL) . 'includes/admin/img/icon-for-admin.svg'; ?>"
250 style="margin-right: 5px;width: 14px;height: 14px;"
251 alt="<?php echo esc_html__('Upgrade Now', 'king-addons'); ?>"><?php esc_html_e('Upgrade Now', 'king-addons'); ?>
252 </button>
253 </a>
254 </div>
255 <?php endif; ?>
256 </div>
257 </div>
258 <div class="templates-grid-wrapper">
259 <div class="search-wrapper">
260 <input type="text" id="template-search" value="<?php echo esc_attr($search_query); ?>"
261 placeholder="<?php esc_attr_e('Search templates...', 'king-addons'); ?>">
262 </div>
263 <div class="templates-grid">
264 <?php echo $result['grid_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
265 ?>
266 </div>
267 <div class="pagination">
268 <?php echo $result['pagination_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
269 ?>
270 </div>
271 </div>
272 </div>
273
274 <!-- Sections Tab Content -->
275 <div id="sections-catalog" class="king-addons-tab-content">
276 <div class="filters-wrapper">
277 <div class="filters">
278 <select id="sections-category">
279 <option value=""><?php esc_html_e('All Categories', 'king-addons'); ?></option>
280 </select>
281 <select id="sections-type">
282 <option value=""><?php esc_html_e('All Types', 'king-addons'); ?></option>
283 </select>
284 <select id="sections-plan">
285 <option value=""><?php esc_html_e('All Plans', 'king-addons'); ?></option>
286 <option value="free"><?php esc_html_e('Free', 'king-addons'); ?></option>
287 <?php if ($is_premium_active): ?>
288 <option value="premium"><?php esc_html_e('Premium', 'king-addons'); ?></option>
289 <?php endif; ?>
290 </select>
291 <button id="sections-reset-filters"><?php esc_html_e('Reset Search & Filters', 'king-addons'); ?></button>
292 </div>
293 </div>
294 <div class="sections-grid-wrapper">
295 <div class="search-wrapper">
296 <input type="text" id="sections-search" placeholder="<?php esc_attr_e('Search sections...', 'king-addons'); ?>">
297 </div>
298 <div class="sections-grid">
299 <div class="sections-loading">
300 <?php esc_html_e('Loading sections...', 'king-addons'); ?>
301 </div>
302 </div>
303 <div class="sections-pagination"></div>
304 </div>
305 </div>
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
342 <div id="template-preview-popup" style="display:none;">
343 <div class="popup-content">
344 <div class="popup-content-nav">
345 <button data-plan-active="<?php echo ($is_premium_active) ? 'premium' : 'free'; ?>"
346 id="install-template">
347 <?php esc_html_e('Import Template', 'king-addons'); ?>
348 </button>
349 <a href="#" id="template-preview-link" target="_blank">
350 <?php esc_html_e('Live Preview', 'king-addons'); ?>
351 </a>
352 <div class="preview-mode-switcher">
353 <button data-mode="desktop" class="active" id="preview-desktop">
354 <span class="dashicons dashicons-desktop"></span>
355 </button>
356 <button data-mode="tablet" id="preview-tablet">
357 <span class="dashicons dashicons-tablet"></span>
358 </button>
359 <button data-mode="mobile" id="preview-mobile">
360 <span class="dashicons dashicons-smartphone"></span>
361 </button>
362 </div>
363 <button id="close-popup">
364 <?php esc_html_e('Close Preview X', 'king-addons'); ?>
365 </button>
366 </div>
367 <iframe id="template-preview-iframe" src="" frameborder="0"></iframe>
368 </div>
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>
406 <div id="template-installing-popup" style="display:none;">
407 <div class="popup-content">
408 <div id="progress-container">
409 <div id="progress-bar">
410 0%
411 </div>
412 </div>
413 <div id="progress"></div>
414 <div id="image-list"></div>
415 <div id="final_response"></div>
416 <button id="close-installing-popup"
417 style="display:none;"><?php esc_html_e('Close', 'king-addons'); ?></button>
418 <a href="#" id="go-to-imported-page"
419 style="display:none;"><?php esc_html_e('Go to imported page', 'king-addons'); ?></a>
420 </div>
421 </div>
422 <div id="license-activating-popup" style="display:none;">
423 <div class="license-activating-popup-content">
424 <div class="license-activating-popup-txt"><?php esc_html_e('1. Download and install the premium version of the plugin - King Addons Pro. You can find the link in the email received after the license purchase.', 'king-addons'); ?></div>
425 <div class="license-activating-popup-txt"><?php esc_html_e('2. Go to the Plugins page.', 'king-addons'); ?></div>
426 <div class="license-activating-popup-txt"><?php esc_html_e('3. Find the King Addons Pro plugin.', 'king-addons'); ?></div>
427 <div class="license-activating-popup-txt"><?php esc_html_e('4. Click on Activate License link.', 'king-addons'); ?></div>
428 <div class="license-activating-popup-txt"><?php esc_html_e('5. Enter the License Key provided in the email. Done!', 'king-addons'); ?></div>
429 <button id="close-license-activating-popup"><?php esc_html_e('Close', 'king-addons'); ?></button>
430 </div>
431 </div>
432 <div id="premium-promo-popup" style="display:none;">
433 <div class="premium-promo-popup-content">
434 <div class="premium-promo-popup-wrapper">
435 <div class="premium-promo-popup-txt"><?php
436
437 echo '<span class="pr-popup-title">Want This Premium Template?</span>';
438 echo '<br><span class="pr-popup-desc">';
439 echo 'Get <span class="pr-popup-desc-b">unlimited downloads</span> for just';
440 echo ' <span class="pr-popup-desc-b">$6.99/month';
441 echo '</span> — keep them <span class="pr-popup-desc-b">even after</span> your subscription ends!';
442 echo '</span><span class="pr-popup-desc" style="font-size: 16px;opacity: 0.6;">Trusted by 20,000+ users</span>';
443 ?></div>
444 <a class="purchase-btn"
445 href="https://kingaddons.com/pricing/?utm_source=kng-templates-banner-pro&utm_medium=plugin&utm_campaign=kng"
446 target="_blank">
447 <button class="premium-promo-popup-purchase-btn purchase-btn">
448 <img src="<?php echo esc_url(KING_ADDONS_URL) . 'includes/admin/img/icon-for-admin.svg'; ?>"
449 style="margin-right: 7px;width: 16px;height: 16px;"
450 alt="<?php echo esc_html__('Unlock All Templates', 'king-addons'); ?>"><?php esc_html_e('Unlock All Templates', 'king-addons'); ?>
451 </button>
452 </a>
453 <button id="close-premium-promo-popup"><?php esc_html_e('Close', 'king-addons'); ?></button>
454 </div>
455 </div>
456 </div>
457 </div>
458 <?php
459 }
460
461 private function get_filtered_templates($templates, $search_query, $selected_category, $selected_tags, $selected_collection, $current_page): array
462 {
463 $search_terms = array_filter(explode(' ', $search_query));
464 $has_search = !empty($search_terms);
465 // Custom: direct key search
466 $search_by_key = false;
467 $found_by_key = [];
468 if (!empty($search_query) && isset($templates['templates'][$search_query])) {
469 $found_by_key[] = $templates['templates'][$search_query];
470 $found_by_key[0]['template_key'] = $search_query;
471 $search_by_key = true;
472 }
473
474 // Filter templates based on search and selected filters
475 if ($search_by_key) {
476 $filtered_templates = $found_by_key;
477 } elseif (!$has_search) {
478 $filtered_templates = array_filter($templates['templates'], function ($template) use ($search_terms, $selected_category, $selected_tags, $selected_collection) {
479
480 foreach ($search_terms as $term) {
481 $found_in_title = stripos($template['title'], $term) !== false;
482 $found_in_tags = false;
483
484 foreach ($template['tags'] as $tag) {
485 if (stripos($tag, $term) !== false) {
486 $found_in_tags = true;
487 break;
488 }
489 }
490
491 if (!$found_in_title && !$found_in_tags) {
492 return false;
493 }
494 }
495
496 if ($selected_category && $template['category'] !== $selected_category) {
497 return false;
498 }
499
500 if ($selected_tags) {
501 $template_tags = $template['tags'];
502 foreach ($selected_tags as $tag) {
503 if (!in_array($tag, $template_tags)) {
504 return false;
505 }
506 }
507 }
508
509 if ($selected_collection && $template['collection'] != $selected_collection) {
510 return false;
511 }
512
513 return true;
514 });
515
516 // Shuffle templates
517 // shuffle($filtered_templates);
518
519 } else {
520
521 $filtered_templates = $templates['templates'];
522
523 if (!empty($search_terms) || !empty($selected_category) || !empty($selected_tags) || !empty($selected_collection)) {
524 $matched_by_title = [];
525 $matched_by_tags = [];
526
527 foreach ($filtered_templates as $key => $template) {
528
529 $found_in_title = false;
530 $found_in_tags = false;
531
532 foreach ($search_terms as $term) {
533 if ($term && stripos($template['title'], $term) !== false) {
534 $found_in_title = true;
535 }
536
537 foreach ($template['tags'] as $tag) {
538 if (stripos($tag, $term) !== false) {
539 $found_in_tags = true;
540 break;
541 }
542 }
543 // if (!$found_in_title && !$found_in_tags) {
544 // continue;
545 // }
546 }
547
548 if ($selected_category && $template['category'] !== $selected_category) {
549 continue;
550 }
551
552 if ($selected_tags) {
553 $template_tags = $template['tags'];
554 foreach ($selected_tags as $tag) {
555 if (!in_array($tag, $template_tags)) {
556 continue 2;
557 }
558 }
559 }
560
561 if ($selected_collection && $template['collection'] != $selected_collection) {
562 continue;
563 }
564
565 // Attach the template key for later use
566 $template['template_key'] = $key;
567
568 if ($found_in_title) {
569 $matched_by_title[] = $template;
570 } elseif ($found_in_tags) {
571 $matched_by_tags[] = $template;
572 }
573 }
574
575 // Combine the arrays, so templates matching by title come first
576 $filtered_templates = array_merge($matched_by_title, $matched_by_tags);
577 }
578 }
579
580 // Pagination setup
581 $items_per_page = 20;
582 $total_templates = count($filtered_templates);
583 $offset = ($current_page - 1) * $items_per_page;
584 $paged_templates = array_slice($filtered_templates, $offset, $items_per_page);
585
586 ob_start();
587 if (empty($paged_templates)) {
588 echo '<p class="templates-not-found">' . esc_html__('No templates found.', 'king-addons') . '</p>';
589 } else {
590 foreach ($paged_templates as $key => $template) {
591 $attr_key = ($has_search) ? $template['template_key'] : $key;
592 ?>
593 <div class="template-item"
594 data-category="<?php echo esc_attr($template['category']); ?>"
595 data-tags="<?php echo esc_attr(implode(',', $template['tags'])); ?>"
596 data-template-key="<?php echo esc_attr($attr_key); ?>"
597 data-template-plan="<?php echo esc_attr($template['plan']); ?>">
598 <img class="kng-addons-template-thumbnail" loading="lazy"
599 src="<?php echo esc_url("https://thumbnails.kingaddons.com/$attr_key.png?v=4"); ?>"
600 alt="<?php echo esc_attr($template['title']); ?>">
601 <h3><?php echo esc_html($template['title']); ?></h3>
602 <div class="template-plan template-plan-<?php echo esc_html($template['plan']); ?>"><?php echo esc_html($template['plan']); ?></div>
603 </div>
604 <?php
605 }
606 }
607 $grid_html = ob_get_clean();
608
609 ob_start();
610
611 $pages = paginate_links(array(
612 'base' => add_query_arg(array(
613 'paged' => '%#%',
614 's' => $search_query,
615 'category' => $selected_category,
616 'collection' => $selected_collection,
617 'tags' => implode(',', $selected_tags),
618 )),
619 'format' => '?paged=%#%',
620 'current' => $current_page,
621 'total' => ceil($total_templates / $items_per_page),
622 'prev_text' => __('&larr; Previous', 'king-addons'),
623 'next_text' => __('Next &rarr;', 'king-addons'),
624 'end_size' => 9,
625 'mid_size' => 3,
626 ));
627 if ($pages) {
628 echo '<div id="king-addons-pagination-inner-wrap" class="pagination-inner-wrap"><div class="pagination-inner">';
629 echo $pages; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
630 echo '</div></div>';
631 }
632
633 $pagination_html = ob_get_clean();
634
635 return ['grid_html' => $grid_html, 'pagination_html' => $pagination_html];
636 }
637
638 public function king_addons_enqueue_scripts(): void
639 {
640 $screen = get_current_screen();
641 if ($screen->id === 'toplevel_page_king-addons-templates') {
642 wp_enqueue_style('king-addons-templates-style', KING_ADDONS_URL . 'includes/admin/css/templates.css', '', KING_ADDONS_VERSION);
643
644 if (!wp_script_is(KING_ADDONS_ASSETS_UNIQUE_KEY . '-' . 'jquery' . '-' . 'jquery')) {
645 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-' . 'jquery' . '-' . 'jquery', '', '', KING_ADDONS_VERSION);
646 }
647
648 wp_enqueue_script('king-addons-templates-script', KING_ADDONS_URL . 'includes/admin/js/templates.js', '', KING_ADDONS_VERSION, true);
649
650 wp_localize_script('king-addons-templates-script', 'kingAddonsData', array(
651 'adminUrl' => admin_url('admin-post.php'),
652 'ajaxUrl' => admin_url('admin-ajax.php'),
653 'nonce' => wp_create_nonce('kingAddonsNonce'),
654 ));
655 }
656 // if ($screen->id === 'king-addons_page_king-addons-account') {
657 // wp_enqueue_style('king-addons-account-style', KING_ADDONS_URL . 'includes/admin/css/account.css', '', KING_ADDONS_VERSION);
658 // }
659 }
660
661 public function __construct()
662 {
663 add_action('admin_enqueue_scripts', array($this, 'king_addons_enqueue_scripts'));
664
665 // Get the performance setting
666 $improve_import = get_option('king_addons_improve_import_performance', '1');
667
668 // Conditionally add time limit adjustments based on setting
669 if ($improve_import === '1') {
670 add_action('wp_ajax_import_elementor_page_with_images', function () {
671 @set_time_limit(300);
672 }, 0);
673
674 add_action('wp_ajax_process_import_images', function () {
675 @set_time_limit(300);
676 }, 0);
677 }
678
679 add_action(
680 'wp_ajax_import_elementor_page_with_images',
681 [$this, 'import_elementor_page_with_images']
682 );
683
684 add_action(
685 'wp_ajax_process_import_images',
686 [$this, 'process_import_images']
687 );
688
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'));
692
693 // Sections catalog endpoints
694 add_action('wp_ajax_king_addons_get_sections_catalog', array($this, 'handle_get_sections_catalog'));
695 add_action('wp_ajax_king_addons_import_section_admin', array($this, 'handle_import_section_to_page'));
696
697 add_action('http_api_curl', array($this, 'set_custom_curl_options'), 10, 3);
698 }
699
700 public function set_custom_curl_options($handle, $r, $url)
701 {
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 }
707 }
708
709 function handle_filter_templates(): void
710 {
711 if (!current_user_can('manage_options')) {
712 return;
713 }
714
715 if (!isset($_POST['action']) || $_POST['action'] !== 'filter_templates') {
716 wp_send_json_error('Invalid request');
717 return;
718 }
719
720 $search_query = isset($_POST['s']) ? sanitize_text_field($_POST['s']) : '';
721 $selected_category = isset($_POST['category']) ? sanitize_text_field($_POST['category']) : '';
722 $selected_collection = isset($_POST['collection']) ? sanitize_text_field($_POST['collection']) : '';
723 $selected_tags = isset($_POST['tags']) ? array_filter(explode(',', sanitize_text_field($_POST['tags']))) : [];
724 $current_page = isset($_POST['paged']) ? max(1, intval($_POST['paged'])) : 1;
725
726 $templates = TemplatesMap::getTemplatesMapArray();
727
728 // Use the common function to get filtered templates and pagination
729 $result = $this->get_filtered_templates($templates, $search_query, $selected_category, $selected_tags, $selected_collection, $current_page);
730
731 wp_send_json_success(['grid_html' => $result['grid_html'], 'pagination_html' => $result['pagination_html']]);
732 }
733
734 public function import_elementor_page_with_images(): void
735 {
736 if (!current_user_can('manage_options')) {
737 wp_send_json_error('Insufficient permissions');
738 return;
739 }
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
754 $import_data = json_decode(stripslashes($_POST['data']), true);
755
756 if (json_last_error() !== JSON_ERROR_NONE) {
757 wp_send_json_error('JSON decode error: ' . json_last_error_msg());
758 return;
759 }
760
761 if (isset($import_data['content']) && isset($import_data['images']) && isset($import_data['title'])) {
762 $content = $import_data['content'];
763 $image_data = $import_data['images'];
764
765 $page_title = sanitize_text_field($import_data['title']);
766 $elementor_version = sanitize_text_field($import_data['elementor_version']);
767
768 // Check if this is for existing page (from popup import)
769 $existing_page_id = isset($import_data['existing_page_id']) ? intval($import_data['existing_page_id']) : 0;
770 $create_new_page = isset($import_data['create_new_page']) ? (bool)$import_data['create_new_page'] : true;
771
772 delete_transient('elementor_import_content');
773 delete_transient('elementor_import_images');
774 delete_transient('elementor_import_total_images');
775 delete_transient('elementor_import_images_processed');
776 delete_transient('elementor_import_image_retry_count');
777 delete_transient('elementor_import_page_title');
778 delete_transient('elementor_import_elementor_version');
779 delete_transient('elementor_import_existing_page_id');
780 delete_transient('elementor_import_create_new_page');
781
782 set_transient('elementor_import_content', $content, 60 * 60);
783 set_transient('elementor_import_images', $image_data, 60 * 60);
784 set_transient('elementor_import_total_images', count($image_data), 60 * 60);
785 set_transient('elementor_import_images_processed', 0, 60 * 60);
786 set_transient('elementor_import_image_retry_count', [], 60 * 60);
787 set_transient('elementor_import_page_title', $page_title, 60 * 60);
788 set_transient('elementor_import_elementor_version', $elementor_version, 60 * 60);
789 set_transient('elementor_import_existing_page_id', $existing_page_id, 60 * 60);
790 set_transient('elementor_import_create_new_page', $create_new_page, 60 * 60);
791
792 error_log('King Addons Import: Initialized ' . ($create_new_page ? 'new page' : 'existing page') . ' import. Existing page ID: ' . $existing_page_id);
793
794 wp_send_json_success([
795 'message' => 'Import initialized.',
796 'images' => $image_data,
797 'existing_page_id' => $existing_page_id,
798 'create_new_page' => $create_new_page
799 ]);
800 } else {
801 wp_send_json_error('Invalid import data.');
802 }
803 }
804
805 private function replace_image_data(array &$array, $old_url, $new_url, $old_id, $new_id)
806 {
807 foreach ($array as $key => &$value) {
808 if (is_array($value)) {
809 // Standard structure with both 'url' and 'id'
810 if (
811 isset($value['url'], $value['id']) &&
812 $value['url'] === $old_url &&
813 $value['id'] === $old_id
814 ) {
815 $value['url'] = $new_url;
816 $value['id'] = $new_id;
817 }
818
819 // Background image structure (common in Elementor)
820 if (
821 isset($value['url']) &&
822 $value['url'] === $old_url &&
823 (!isset($value['id']) || $value['id'] === $old_id)
824 ) {
825 $value['url'] = $new_url;
826 if (isset($value['id'])) {
827 $value['id'] = $new_id;
828 }
829 }
830
831 // Recursively check deeper nested arrays
832 $this->replace_image_data($value, $old_url, $new_url, $old_id, $new_id);
833 } elseif (is_string($value)) {
834 // Direct URL string replacement
835 if ($value === $old_url) {
836 $array[$key] = $new_url;
837 }
838
839 // CSS background-image style replacement
840 if (strpos($value, 'background-image:') !== false && strpos($value, $old_url) !== false) {
841 $array[$key] = str_replace($old_url, $new_url, $value);
842 }
843
844 // URL() function replacement in CSS
845 if (strpos($value, 'url(') !== false && strpos($value, $old_url) !== false) {
846 $array[$key] = str_replace($old_url, $new_url, $value);
847 }
848
849 // JSON string containing URLs (for widget settings)
850 if (strpos($value, '{') === 0 && strpos($value, $old_url) !== false) {
851 $decoded = json_decode($value, true);
852 if (is_array($decoded)) {
853 $this->replace_image_data($decoded, $old_url, $new_url, $old_id, $new_id);
854 $array[$key] = json_encode($decoded);
855 }
856 }
857 }
858 }
859 }
860
861 public function process_import_images(): void
862 {
863 // Conditionally increase execution time limit based on setting
864 $improve_import = get_option('king_addons_improve_import_performance', '1');
865 if ($improve_import === '1') {
866 @set_time_limit(300);
867 }
868
869 if (!current_user_can('manage_options')) {
870 wp_send_json_error('The current user can not manage options and create pages. Please change it in the WordPress settings.');
871 return;
872 }
873
874 // Support both the legacy templates admin nonce and the Elementor editor
875 // Template Catalog button nonce.
876 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
877 $valid_nonce = $nonce && (
878 wp_verify_nonce($nonce, 'kingAddonsNonce') ||
879 wp_verify_nonce($nonce, 'king_addons_template_catalog')
880 );
881
882 if (!$valid_nonce) {
883 wp_send_json_error('Invalid nonce');
884 return;
885 }
886
887 $start_time = time();
888 $timeout = 30;
889
890 $content = get_transient('elementor_import_content');
891 $image_data = get_transient('elementor_import_images');
892 $total_images = get_transient('elementor_import_total_images');
893 $images_processed = get_transient('elementor_import_images_processed');
894 $image_retry_count = get_transient('elementor_import_image_retry_count');
895 $page_title = get_transient('elementor_import_page_title');
896 $elementor_version = get_transient('elementor_import_elementor_version');
897 $existing_page_id = get_transient('elementor_import_existing_page_id');
898 $create_new_page = get_transient('elementor_import_create_new_page');
899
900 if (!is_array($image_retry_count)) {
901 $image_retry_count = [];
902 }
903
904 if ($images_processed < $total_images) {
905 $current_image = $image_data[$images_processed];
906 $url = $current_image['url'];
907 if (!isset($image_retry_count[$url])) {
908 $image_retry_count[$url] = 0;
909 }
910
911 $new_image_id = $this->download_image_to_media_gallery($url, $image_retry_count[$url]);
912 if ($new_image_id === false) {
913 $image_retry_count[$url]++;
914 set_transient('elementor_import_image_retry_count', $image_retry_count, 60 * 60);
915
916 if ($image_retry_count[$url] > 3) {
917 $images_processed++;
918 set_transient('elementor_import_images_processed', $images_processed, 60 * 60);
919 $progress = round(($images_processed / $total_images) * 90) + 10;
920
921 wp_send_json_success([
922 'progress' => $progress,
923 'message' => "Processed $images_processed of $total_images images.",
924 'image_url' => $url,
925 'images_processed' => $images_processed,
926 'new_image_id' => 'SKIPPED'
927 ]);
928 } else {
929 wp_send_json_error([
930 'retry' => true,
931 'image_url' => $url
932 ]);
933 }
934 } else {
935 $new_url = wp_get_attachment_url($new_image_id);
936 $images_processed++;
937 set_transient('elementor_import_images_processed', $images_processed, 60 * 60);
938 $progress = round(($images_processed / $total_images) * 90) + 10;
939
940 $this->replace_image_data($content, $url, $new_url, $current_image['id'], $new_image_id);
941
942 set_transient('elementor_import_content', $content, 60 * 60);
943
944 wp_send_json_success([
945 'progress' => $progress,
946 'message' => "Processed $images_processed of $total_images images.",
947 'image_url' => $url,
948 'new_image_url' => $new_url
949 ]);
950 }
951 } else {
952 // All images processed
953 if ($create_new_page) {
954 // Original behavior - create new page
955 $new_post_id = wp_insert_post([
956 'post_title' => $page_title,
957 'post_content' => '',
958 'post_status' => 'publish',
959 'post_type' => 'page',
960 ]);
961
962 if ($new_post_id) {
963 update_post_meta($new_post_id, '_elementor_data', wp_slash(json_encode($content)));
964 update_post_meta($new_post_id, '_elementor_edit_mode', 'builder');
965 update_post_meta($new_post_id, '_elementor_template_type', 'page');
966 update_post_meta($new_post_id, '_elementor_version', $elementor_version);
967
968 update_post_meta($new_post_id, '_wp_page_template', 'elementor_canvas');
969
970 update_post_meta($new_post_id, '_wp_gutenberg_disable', '1');
971 update_post_meta($new_post_id, '_wp_gutenberg_enabled', '0');
972
973 delete_transient('elementor_import_content');
974 delete_transient('elementor_import_images');
975 delete_transient('elementor_import_total_images');
976 delete_transient('elementor_import_images_processed');
977 delete_transient('elementor_import_image_retry_count');
978 delete_transient('elementor_import_page_title');
979 delete_transient('elementor_import_elementor_version');
980 delete_transient('elementor_import_existing_page_id');
981 delete_transient('elementor_import_create_new_page');
982
983 wp_send_json_success([
984 'message' => "Page imported successfully!",
985 'page_url' => get_permalink($new_post_id),
986 ]);
987 } else {
988 wp_send_json_error('Failed to import page.');
989 }
990 } else {
991 // New behavior - for existing page, just signal completion
992 // Content and images are processed and stored in transients
993 // Will be merged by king_addons_merge_with_existing_page endpoint
994
995 error_log('King Addons Import: Image processing completed for existing page. Content ready for merge.');
996
997 wp_send_json_success([
998 'message' => "Image processing completed! Content ready for merge.",
999 'images_processed' => $images_processed,
1000 'existing_page_id' => $existing_page_id,
1001 'processing_complete' => true
1002 ]);
1003 }
1004 }
1005
1006 if (time() >= $start_time + $timeout) {
1007 wp_send_json_error('Process timeout, please resume the import.');
1008 }
1009 }
1010
1011 public function download_image_to_media_gallery($image_url, $image_retry_count)
1012 {
1013 try {
1014 if (!$this->is_safe_remote_url($image_url)) {
1015 throw new Exception('Blocked potentially unsafe image URL: ' . $image_url);
1016 }
1017
1018 $response = wp_remote_get($image_url, ['timeout' => 30]);
1019
1020 if (is_wp_error($response)) {
1021 throw new Exception('HTTP request error: ' . $response->get_error_message());
1022 }
1023
1024 $status_code = wp_remote_retrieve_response_code($response);
1025 if ($status_code !== 200) {
1026 throw new Exception(
1027 'HTTP status code ' . $status_code . ' when trying to download: ' . $image_url
1028 );
1029 }
1030
1031 $image_data = wp_remote_retrieve_body($response);
1032 if (empty($image_data)) {
1033 throw new Exception('Failed to retrieve image data from URL: ' . $image_url);
1034 }
1035
1036 $image_name = pathinfo(basename($image_url), PATHINFO_FILENAME);
1037 $image_extension = pathinfo(basename($image_url), PATHINFO_EXTENSION);
1038 $unique_image_name = $image_name . '-' . time() . '.' . $image_extension;
1039
1040 $upload_dir = wp_upload_dir();
1041 if (! file_exists($upload_dir['path'])) {
1042 wp_mkdir_p($upload_dir['path']);
1043 }
1044 $image_file = $upload_dir['path'] . '/' . $unique_image_name;
1045
1046 if (file_put_contents($image_file, $image_data) === false) {
1047 throw new Exception('Failed to write image data to file: ' . $image_file);
1048 }
1049
1050 $wp_filetype = wp_check_filetype($unique_image_name);
1051 $attachment = [
1052 'post_mime_type' => $wp_filetype['type'],
1053 'post_title' => sanitize_file_name($unique_image_name),
1054 'post_content' => '',
1055 'post_status' => 'inherit',
1056 ];
1057
1058 $attach_id = wp_insert_attachment($attachment, $image_file);
1059
1060 // Conditionally disable intermediate image sizes based on setting
1061 $improve_import = get_option('king_addons_improve_import_performance', '1');
1062 if ($improve_import === '1') {
1063 // Disable ALL extra sizes and big-image scaling
1064 add_filter('intermediate_image_sizes', '__return_empty_array', 999);
1065 add_filter('big_image_size_threshold', '__return_false', 999);
1066
1067 /* Save only the original file info — no thumbnails, no EXIF */
1068 // Get image dimensions
1069 $image_size = getimagesize($image_file);
1070 $width = $image_size ? $image_size[0] : 0;
1071 $height = $image_size ? $image_size[1] : 0;
1072
1073 $meta = [
1074 'file' => wp_basename($image_file),
1075 'filesize' => filesize($image_file),
1076 'width' => $width,
1077 'height' => $height,
1078 'sizes' => [],
1079 'image_meta' => [],
1080 ];
1081 wp_update_attachment_metadata($attach_id, $meta);
1082
1083 // Clean up filters
1084 remove_filter('intermediate_image_sizes', '__return_empty_array', 999);
1085 remove_filter('big_image_size_threshold', '__return_false', 999);
1086 } else {
1087 if ($image_retry_count > 1) {
1088 add_filter('intermediate_image_sizes', '__return_empty_array', 999);
1089 }
1090
1091 // Generate default metadata if optimization is off
1092 require_once(ABSPATH . 'wp-admin/includes/image.php');
1093 $attach_data = wp_generate_attachment_metadata($attach_id, $image_file);
1094 wp_update_attachment_metadata($attach_id, $attach_data);
1095
1096 if ($image_retry_count > 1) {
1097 remove_filter('intermediate_image_sizes', '__return_empty_array', 999);
1098 }
1099 }
1100
1101 return $attach_id;
1102 } catch (Exception $e) {
1103 error_log('[KING_ADDONS_ERROR] ' . $e->getMessage());
1104 return false;
1105 }
1106 }
1107
1108 /**
1109 * Basic SSRF guard for remote URLs.
1110 *
1111 * @param string $url URL to validate.
1112 *
1113 * @return bool
1114 */
1115 private function is_safe_remote_url(string $url): bool
1116 {
1117 $parsed = parse_url($url);
1118 if (!$parsed || empty($parsed['scheme']) || empty($parsed['host'])) {
1119 return false;
1120 }
1121
1122 if (!in_array($parsed['scheme'], ['http', 'https'], true)) {
1123 return false;
1124 }
1125
1126 $host = $parsed['host'];
1127 if (filter_var($host, FILTER_VALIDATE_IP)) {
1128 return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
1129 }
1130
1131 // Allow domain names; block obvious localhost patterns.
1132 $lower = strtolower($host);
1133 if ($lower === 'localhost' || str_ends_with($lower, '.local')) {
1134 return false;
1135 }
1136
1137 return true;
1138 }
1139
1140 /**
1141 * AJAX handler for getting sections catalog data
1142 */
1143 public function handle_get_sections_catalog(): void
1144 {
1145 if (!current_user_can('manage_options')) {
1146 wp_send_json_error('Insufficient permissions');
1147 return;
1148 }
1149
1150 // Check nonce - handle both admin area and popup
1151 $nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : '';
1152 $valid_nonce = $nonce && (wp_verify_nonce($nonce, 'kingAddonsNonce') ||
1153 wp_verify_nonce($nonce, 'king_addons_template_catalog'));
1154
1155 if (!$valid_nonce) {
1156 wp_send_json_error('Invalid nonce');
1157 return;
1158 }
1159
1160 if (!class_exists('King_Addons\\SectionsMap')) {
1161 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
1162 }
1163
1164 $sections_map_class = '\\King_Addons\\SectionsMap';
1165 $sections_map = is_callable([$sections_map_class, 'getSectionsMapArray'])
1166 ? call_user_func([$sections_map_class, 'getSectionsMapArray'])
1167 : [];
1168 $sections = $sections_map['sections'] ?? [];
1169
1170 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
1171
1172 // Get filters from request
1173 $search_query = sanitize_text_field($_POST['search'] ?? '');
1174 $selected_category = sanitize_text_field($_POST['category'] ?? '');
1175 $selected_type = sanitize_text_field($_POST['section_type'] ?? '');
1176 $selected_plan = sanitize_text_field($_POST['plan'] ?? '');
1177 $current_page = max(1, intval($_POST['page'] ?? 1));
1178
1179 // Get categories and section types for filters
1180 $categories = [];
1181 $section_types = [];
1182
1183 foreach ($sections as $section_key => $section) {
1184 if (!in_array($section['category'], $categories)) {
1185 $categories[] = $section['category'];
1186 }
1187 if (!in_array($section['section_type'], $section_types)) {
1188 $section_types[] = $section['section_type'];
1189 }
1190 }
1191
1192 sort($categories);
1193 sort($section_types);
1194
1195 // Filter sections
1196 $filtered_sections = [];
1197
1198 foreach ($sections as $section_key => $section) {
1199 // Add section key for frontend
1200 $section['section_key'] = $section_key;
1201
1202 // Note: Premium sections are shown but restricted for import if no premium license
1203
1204 // Apply search filter
1205 if (!empty($search_query)) {
1206 $found_in_title = stripos($section['title'], $search_query) !== false;
1207 $found_in_tags = false;
1208
1209 foreach ($section['tags'] ?? [] as $tag) {
1210 if (stripos($tag, $search_query) !== false) {
1211 $found_in_tags = true;
1212 break;
1213 }
1214 }
1215
1216 if (!$found_in_title && !$found_in_tags) {
1217 continue;
1218 }
1219 }
1220
1221 // Apply category filter
1222 if (!empty($selected_category) && $section['category'] !== $selected_category) {
1223 continue;
1224 }
1225
1226 // Apply section type filter
1227 if (!empty($selected_type) && $section['section_type'] !== $selected_type) {
1228 continue;
1229 }
1230
1231 // Apply plan filter
1232 if (!empty($selected_plan) && $section['plan'] !== $selected_plan) {
1233 continue;
1234 }
1235
1236 $filtered_sections[] = $section;
1237 }
1238
1239 // Pagination
1240 $items_per_page = 20;
1241 $total_sections = count($filtered_sections);
1242 $total_pages = ceil($total_sections / $items_per_page);
1243 $offset = ($current_page - 1) * $items_per_page;
1244 $paged_sections = array_slice($filtered_sections, $offset, $items_per_page);
1245
1246 wp_send_json_success([
1247 'sections' => $paged_sections,
1248 'categories' => $categories,
1249 'section_types' => $section_types,
1250 'pagination' => [
1251 'current_page' => $current_page,
1252 'total_pages' => $total_pages,
1253 'total_sections' => $total_sections,
1254 'items_per_page' => $items_per_page
1255 ],
1256 'is_premium_active' => $is_premium_active
1257 ]);
1258 }
1259
1260 /**
1261 * AJAX handler for importing section to current page (from main admin catalog)
1262 */
1263 public function handle_import_section_to_page(): void
1264 {
1265 if (!current_user_can('edit_posts')) {
1266 wp_send_json_error('Insufficient permissions');
1267 return;
1268 }
1269
1270 if (!wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce')) {
1271 wp_send_json_error('Invalid nonce');
1272 return;
1273 }
1274
1275 $section_key = sanitize_text_field($_POST['section_key']);
1276 $section_plan = sanitize_text_field($_POST['section_plan']);
1277 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
1278
1279 // Determine API URL and install ID (same logic as templates)
1280 if ($is_premium_active && $section_plan === 'premium') {
1281 $api_url = 'https://api.kingaddons.com/get-section.php';
1282
1283 // Use the same method as original templates catalog
1284 if (function_exists('king_addons_freemius')) {
1285 $freemius_site = king_addons_freemius()->get_site();
1286 $install_id = $freemius_site ? $freemius_site->id : 0;
1287 } else {
1288 $install_id = 0;
1289 }
1290
1291 error_log('King Addons Premium Section: Using install_id: ' . $install_id . ' for premium section: ' . $section_key);
1292 } elseif ($section_plan === 'free') {
1293 $api_url = 'https://api.kingaddons.com/get-section-free.php';
1294 $install_id = 0;
1295 error_log('King Addons Free Section: Fetching free section: ' . $section_key);
1296 } else {
1297 error_log('King Addons Section Error: Premium section requires premium license. Section: ' . $section_key . ', Plan: ' . $section_plan . ', Premium Active: ' . ($is_premium_active ? 'Yes' : 'No'));
1298 wp_send_json_error('Premium section requires premium license');
1299 return;
1300 }
1301
1302 // Get section data from API (same as templates)
1303 $response = wp_remote_post($api_url, [
1304 'headers' => ['Content-Type' => 'application/json'],
1305 'body' => json_encode([
1306 'key' => $section_key,
1307 'install' => $install_id,
1308 ]),
1309 'timeout' => 60
1310 ]);
1311
1312 if (is_wp_error($response)) {
1313 wp_send_json_error('Failed to fetch section: ' . $response->get_error_message());
1314 return;
1315 }
1316
1317 $body = wp_remote_retrieve_body($response);
1318 $data = json_decode($body, true);
1319
1320 error_log('King Addons Section API Response: ' . substr($body, 0, 500) . (strlen($body) > 500 ? '...' : ''));
1321
1322 if (!$data) {
1323 error_log('King Addons Section Error: Failed to decode JSON response');
1324 wp_send_json_error('Invalid JSON response from section API');
1325 return;
1326 }
1327
1328 if (!isset($data['success']) || !$data['success']) {
1329 $error_message = isset($data['message']) ? $data['message'] : 'Unknown API error';
1330 error_log('King Addons Section Error: API returned error: ' . $error_message);
1331 wp_send_json_error('Section API error: ' . $error_message);
1332 return;
1333 }
1334
1335 // Return section data for frontend processing (adjust format from your API)
1336 wp_send_json_success([
1337 'section_data' => $data['section'], // Your API returns 'section' not 'landing'
1338 'message' => 'Section data retrieved successfully'
1339 ]);
1340 }
1341
1342 public function handle_get_collection_details()
1343 {
1344 // Verify nonce
1345 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce')) {
1346 wp_send_json_error('Invalid nonce');
1347 }
1348
1349 $collection_id = isset($_POST['collection_id']) ? sanitize_text_field($_POST['collection_id']) : '';
1350 if (empty($collection_id)) {
1351 wp_send_json_error('Collection ID is required');
1352 }
1353
1354 $templates = TemplatesMap::getTemplatesMapArray();
1355
1356 if (!class_exists('King_Addons\\SectionsMap')) {
1357 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
1358 }
1359 $sections_map = SectionsMap::getSectionsMapArray();
1360 $sections = $sections_map['sections'] ?? [];
1361
1362 // Filter Templates
1363 $collection_templates = [];
1364 $template_keys = [];
1365 foreach ($templates['templates'] as $key => $template) {
1366 if (isset($template['collection']) && (string)$template['collection'] === (string)$collection_id) {
1367 $template['template_key'] = $key;
1368 $collection_templates[] = $template;
1369 $template_keys[] = $key;
1370 }
1371 }
1372
1373 // Filter Sections
1374 $collection_sections = [];
1375 foreach ($sections as $key => $section) {
1376 $parent = $section['parent_template'] ?? '';
1377 if ($parent && in_array($parent, $template_keys)) {
1378 $section['section_key'] = $key;
1379 $collection_sections[] = $section;
1380 }
1381 }
1382
1383 // Render Templates HTML
1384 ob_start();
1385 if (empty($collection_templates)) {
1386 echo '<p class="templates-not-found">' . esc_html__('No templates found in this collection.', 'king-addons') . '</p>';
1387 } else {
1388 foreach ($collection_templates as $template) {
1389 $attr_key = $template['template_key'];
1390 ?>
1391 <div class="template-item"
1392 data-category="<?php echo esc_attr($template['category']); ?>"
1393 data-tags="<?php echo esc_attr(implode(',', $template['tags'])); ?>"
1394 data-template-key="<?php echo esc_attr($attr_key); ?>"
1395 data-template-plan="<?php echo esc_attr($template['plan']); ?>">
1396 <img class="kng-addons-template-thumbnail" loading="lazy"
1397 src="<?php echo esc_url("https://thumbnails.kingaddons.com/$attr_key.png?v=4"); ?>"
1398 alt="<?php echo esc_attr($template['title']); ?>">
1399 <h3><?php echo esc_html($template['title']); ?></h3>
1400 <div class="template-plan template-plan-<?php echo esc_html($template['plan']); ?>"><?php echo esc_html($template['plan']); ?></div>
1401 </div>
1402 <?php
1403 }
1404 }
1405 $templates_html = ob_get_clean();
1406
1407 // Render Sections HTML
1408 ob_start();
1409 if (empty($collection_sections)) {
1410 echo '<p class="sections-not-found">' . esc_html__('No sections found in this collection.', 'king-addons') . '</p>';
1411 } else {
1412 foreach ($collection_sections as $section) {
1413 $section_key = $section['section_key'];
1414 $plan = $section['plan'];
1415 $title = $section['title'];
1416 $thumb_url = "https://thumbnails.kingaddons.com/sections/$plan/$section_key.png";
1417 ?>
1418 <div class="section-item"
1419 data-section-key="<?php echo esc_attr($section_key); ?>"
1420 data-section-plan="<?php echo esc_attr($plan); ?>">
1421 <div class="section-preview">
1422 <img src="<?php echo esc_url($thumb_url); ?>" alt="<?php echo esc_attr($title); ?>" loading="lazy">
1423 <div class="section-actions">
1424 <button class="import-section-btn" data-section-key="<?php echo esc_attr($section_key); ?>" data-section-plan="<?php echo esc_attr($plan); ?>">
1425 <i class="eicon-file-download"></i> <?php esc_html_e('Import', 'king-addons'); ?>
1426 </button>
1427 <a href="<?php echo esc_url('https://sections.kingaddons.com/' . $section_key); ?>" class="live-preview-btn" target="_blank">
1428 <i class="eicon-preview-medium"></i> <?php esc_html_e('Live Preview', 'king-addons'); ?>
1429 </a>
1430 </div>
1431 <?php if ($plan === 'premium'): ?>
1432 <span class="section-badge-pro">PRO</span>
1433 <?php endif; ?>
1434 </div>
1435 <div class="section-info">
1436 <h4><?php echo esc_html($title); ?></h4>
1437 </div>
1438 </div>
1439 <?php
1440 }
1441 }
1442 $sections_html = ob_get_clean();
1443
1444 wp_send_json_success([
1445 'templates_html' => $templates_html,
1446 'sections_html' => $sections_html,
1447 'templates_count' => count($collection_templates),
1448 'sections_count' => count($collection_sections)
1449 ]);
1450 }
1451
1452 public function handle_get_collections_rows()
1453 {
1454 if (!current_user_can('manage_options')) {
1455 wp_send_json_error('Insufficient permissions');
1456 }
1457
1458 // Verify nonce
1459 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce')) {
1460 wp_send_json_error('Invalid nonce');
1461 }
1462
1463 $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
1464 $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
1465 $per_page = 10; // Number of collections per page
1466
1467 $templates = TemplatesMap::getTemplatesMapArray();
1468 $collections_map = CollectionsMap::getCollectionsMapArray();
1469
1470 // Load Sections to compute section counts per collection
1471 if (!class_exists('King_Addons\\SectionsMap')) {
1472 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
1473 }
1474 $sections_map = SectionsMap::getSectionsMapArray();
1475 $sections = $sections_map['sections'] ?? [];
1476
1477 // Build templates grouped by collection + counts
1478 // Search behavior mirrors template search: title + tags (and direct key).
1479 $search_terms = array_values(array_filter(preg_split('/\s+/', (string) $search)));
1480 $has_search = !empty($search_terms);
1481
1482 $templates_by_collection = [];
1483 $templates_by_collection_title = [];
1484 $templates_by_collection_tags = [];
1485
1486 $template_counts = [];
1487 $template_key_to_collection = [];
1488 $template_matches_search = [];
1489 $collections_matching_by_name = [];
1490
1491 $contains_ci = static function ($haystack, $needle): bool {
1492 if ($needle === '') {
1493 return true;
1494 }
1495 if (function_exists('mb_stripos')) {
1496 return mb_stripos((string) $haystack, (string) $needle) !== false;
1497 }
1498 return stripos((string) $haystack, (string) $needle) !== false;
1499 };
1500
1501 if ($has_search) {
1502 foreach ($collections_map as $collection_id => $collection_name) {
1503 foreach ($search_terms as $term) {
1504 if ($term !== '' && $contains_ci($collection_name, $term)) {
1505 $collections_matching_by_name[$collection_id] = true;
1506 break;
1507 }
1508 }
1509 }
1510 }
1511
1512 foreach (($templates['templates'] ?? []) as $key => $template) {
1513 $collection_id = $template['collection'] ?? '';
1514 if ($collection_id === '' || !isset($collections_map[$collection_id])) {
1515 continue;
1516 }
1517
1518 $template_key_to_collection[$key] = $collection_id;
1519
1520 if (!isset($template_counts[$collection_id])) {
1521 $template_counts[$collection_id] = 0;
1522 }
1523 $template_counts[$collection_id]++;
1524
1525 $title = (string) ($template['title'] ?? '');
1526 $tags = is_array($template['tags'] ?? null) ? $template['tags'] : [];
1527 $matches_by_title = false;
1528 $matches_by_tags = false;
1529 $matches_by_key = ($search !== '' && $key === $search);
1530
1531 if ($has_search) {
1532 foreach ($search_terms as $term) {
1533 if ($term === '') {
1534 continue;
1535 }
1536
1537 if ($contains_ci($title, $term)) {
1538 $matches_by_title = true;
1539 continue;
1540 }
1541
1542 if ($contains_ci($key, $term)) {
1543 $matches_by_key = true;
1544 continue;
1545 }
1546
1547 foreach ($tags as $tag) {
1548 if ($contains_ci((string) $tag, $term)) {
1549 $matches_by_tags = true;
1550 break;
1551 }
1552 }
1553 }
1554 }
1555
1556 $collection_name_match = $has_search && isset($collections_matching_by_name[$collection_id]);
1557 $template_is_relevant = !$has_search || $collection_name_match || $matches_by_key || $matches_by_title || $matches_by_tags;
1558 $template_matches_search[$key] = (bool) ($matches_by_key || $matches_by_title || $matches_by_tags);
1559
1560 if (!$template_is_relevant) {
1561 continue;
1562 }
1563
1564 $item = [
1565 'key' => $key,
1566 'title' => $title,
1567 'plan' => $template['plan'],
1568 'category' => $template['category'],
1569 ];
1570
1571 if (!isset($templates_by_collection_title[$collection_id])) {
1572 $templates_by_collection_title[$collection_id] = [];
1573 }
1574 if (!isset($templates_by_collection_tags[$collection_id])) {
1575 $templates_by_collection_tags[$collection_id] = [];
1576 }
1577
1578 // No search: keep natural order (default Collections landing should not be empty).
1579 if (!$has_search) {
1580 if (!isset($templates_by_collection[$collection_id])) {
1581 $templates_by_collection[$collection_id] = [];
1582 }
1583 $templates_by_collection[$collection_id][] = $item;
1584 continue;
1585 }
1586
1587 // If the collection name matches, keep natural order; otherwise prioritize title matches.
1588 if ($collection_name_match) {
1589 if (!isset($templates_by_collection[$collection_id])) {
1590 $templates_by_collection[$collection_id] = [];
1591 }
1592 $templates_by_collection[$collection_id][] = $item;
1593 } else {
1594 if ($matches_by_key || $matches_by_title) {
1595 $templates_by_collection_title[$collection_id][] = $item;
1596 } elseif ($matches_by_tags) {
1597 $templates_by_collection_tags[$collection_id][] = $item;
1598 }
1599 }
1600 }
1601
1602 // Merge per-collection buckets and apply soft limit
1603 $collection_ids_all = array_keys($template_counts);
1604 foreach ($collection_ids_all as $collection_id) {
1605 if (!isset($templates_by_collection[$collection_id])) {
1606 $templates_by_collection[$collection_id] = [];
1607 }
1608
1609 if ($has_search && !isset($collections_matching_by_name[$collection_id])) {
1610 $merged = array_merge(
1611 $templates_by_collection_title[$collection_id] ?? [],
1612 $templates_by_collection_tags[$collection_id] ?? []
1613 );
1614 $templates_by_collection[$collection_id] = $merged;
1615 }
1616
1617 if (count($templates_by_collection[$collection_id]) > 20) {
1618 $templates_by_collection[$collection_id] = array_slice($templates_by_collection[$collection_id], 0, 20);
1619 }
1620 }
1621
1622 // Compute sections count per collection using parent_template -> template -> collection mapping.
1623 // When searching, count only sections whose parent template matches search (unless collection name matches).
1624 $section_counts = [];
1625 foreach ($sections as $section) {
1626 $parent = $section['parent_template'] ?? '';
1627 if (!$parent || !isset($template_key_to_collection[$parent])) {
1628 continue;
1629 }
1630
1631 $collection_id = $template_key_to_collection[$parent];
1632 $collection_name_match = $has_search && isset($collections_matching_by_name[$collection_id]);
1633 if ($has_search && !$collection_name_match) {
1634 if (!isset($template_matches_search[$parent]) || !$template_matches_search[$parent]) {
1635 continue;
1636 }
1637 }
1638
1639 if (!isset($section_counts[$collection_id])) {
1640 $section_counts[$collection_id] = 0;
1641 }
1642 $section_counts[$collection_id]++;
1643 }
1644
1645 // Only collections that actually have templates in the (possibly filtered) result
1646 $collection_ids = array_keys(array_filter($templates_by_collection, static function ($items) {
1647 return !empty($items);
1648 }));
1649
1650 // Sort collections by name to match the main Collections view
1651 usort($collection_ids, function ($a, $b) use ($collections_map) {
1652 return strcasecmp((string) ($collections_map[$a] ?? ''), (string) ($collections_map[$b] ?? ''));
1653 });
1654
1655 $total_collections = count($collection_ids);
1656 $total_pages = (int) ceil($total_collections / $per_page);
1657 $page = min($page, max(1, $total_pages));
1658 $offset = ($page - 1) * $per_page;
1659 $paged_collection_ids = array_slice($collection_ids, $offset, $per_page);
1660
1661 $result_collections = [];
1662 foreach ($paged_collection_ids as $id) {
1663 $result_collections[] = [
1664 'id' => $id,
1665 'name' => $collections_map[$id],
1666 'templates' => $templates_by_collection[$id],
1667 'templates_count' => (int) ($template_counts[$id] ?? 0),
1668 'sections_count' => (int) ($section_counts[$id] ?? 0)
1669 ];
1670 }
1671
1672 wp_send_json_success([
1673 'collections' => $result_collections,
1674 'pagination' => [
1675 'total' => $total_collections,
1676 'pages' => $total_pages,
1677 'current' => $page
1678 ]
1679 ]);
1680 }
1681 }
1682
1683 Templates::instance();
1684