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

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

1,689 lines 77.4 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('admin.php?page=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 if ($existing_page_id && !current_user_can('edit_post', $existing_page_id)) {
773 wp_send_json_error('Insufficient permissions');
774 return;
775 }
776
777 delete_transient('elementor_import_content');
778 delete_transient('elementor_import_images');
779 delete_transient('elementor_import_total_images');
780 delete_transient('elementor_import_images_processed');
781 delete_transient('elementor_import_image_retry_count');
782 delete_transient('elementor_import_page_title');
783 delete_transient('elementor_import_elementor_version');
784 delete_transient('elementor_import_existing_page_id');
785 delete_transient('elementor_import_create_new_page');
786
787 set_transient('elementor_import_content', $content, 60 * 60);
788 set_transient('elementor_import_images', $image_data, 60 * 60);
789 set_transient('elementor_import_total_images', count($image_data), 60 * 60);
790 set_transient('elementor_import_images_processed', 0, 60 * 60);
791 set_transient('elementor_import_image_retry_count', [], 60 * 60);
792 set_transient('elementor_import_page_title', $page_title, 60 * 60);
793 set_transient('elementor_import_elementor_version', $elementor_version, 60 * 60);
794 set_transient('elementor_import_existing_page_id', $existing_page_id, 60 * 60);
795 set_transient('elementor_import_create_new_page', $create_new_page, 60 * 60);
796
797 // error_log('King Addons Import: Initialized ' . ($create_new_page ? 'new page' : 'existing page') . ' import. Existing page ID: ' . $existing_page_id);
798
799 wp_send_json_success([
800 'message' => 'Import initialized.',
801 'images' => $image_data,
802 'existing_page_id' => $existing_page_id,
803 'create_new_page' => $create_new_page
804 ]);
805 } else {
806 wp_send_json_error('Invalid import data.');
807 }
808 }
809
810 private function replace_image_data(array &$array, $old_url, $new_url, $old_id, $new_id)
811 {
812 foreach ($array as $key => &$value) {
813 if (is_array($value)) {
814 // Standard structure with both 'url' and 'id'
815 if (
816 isset($value['url'], $value['id']) &&
817 $value['url'] === $old_url &&
818 $value['id'] === $old_id
819 ) {
820 $value['url'] = $new_url;
821 $value['id'] = $new_id;
822 }
823
824 // Background image structure (common in Elementor)
825 if (
826 isset($value['url']) &&
827 $value['url'] === $old_url &&
828 (!isset($value['id']) || $value['id'] === $old_id)
829 ) {
830 $value['url'] = $new_url;
831 if (isset($value['id'])) {
832 $value['id'] = $new_id;
833 }
834 }
835
836 // Recursively check deeper nested arrays
837 $this->replace_image_data($value, $old_url, $new_url, $old_id, $new_id);
838 } elseif (is_string($value)) {
839 // Direct URL string replacement
840 if ($value === $old_url) {
841 $array[$key] = $new_url;
842 }
843
844 // CSS background-image style replacement
845 if (strpos($value, 'background-image:') !== false && strpos($value, $old_url) !== false) {
846 $array[$key] = str_replace($old_url, $new_url, $value);
847 }
848
849 // URL() function replacement in CSS
850 if (strpos($value, 'url(') !== false && strpos($value, $old_url) !== false) {
851 $array[$key] = str_replace($old_url, $new_url, $value);
852 }
853
854 // JSON string containing URLs (for widget settings)
855 if (strpos($value, '{') === 0 && strpos($value, $old_url) !== false) {
856 $decoded = json_decode($value, true);
857 if (is_array($decoded)) {
858 $this->replace_image_data($decoded, $old_url, $new_url, $old_id, $new_id);
859 $array[$key] = json_encode($decoded);
860 }
861 }
862 }
863 }
864 }
865
866 public function process_import_images(): void
867 {
868 // Conditionally increase execution time limit based on setting
869 $improve_import = get_option('king_addons_improve_import_performance', '1');
870 if ($improve_import === '1') {
871 @set_time_limit(300);
872 }
873
874 if (!current_user_can('manage_options')) {
875 wp_send_json_error('The current user can not manage options and create pages. Please change it in the WordPress settings.');
876 return;
877 }
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
892 $start_time = time();
893 $timeout = 30;
894
895 $content = get_transient('elementor_import_content');
896 $image_data = get_transient('elementor_import_images');
897 $total_images = get_transient('elementor_import_total_images');
898 $images_processed = get_transient('elementor_import_images_processed');
899 $image_retry_count = get_transient('elementor_import_image_retry_count');
900 $page_title = get_transient('elementor_import_page_title');
901 $elementor_version = get_transient('elementor_import_elementor_version');
902 $existing_page_id = get_transient('elementor_import_existing_page_id');
903 $create_new_page = get_transient('elementor_import_create_new_page');
904
905 if (!is_array($image_retry_count)) {
906 $image_retry_count = [];
907 }
908
909 if ($images_processed < $total_images) {
910 $current_image = $image_data[$images_processed];
911 $url = $current_image['url'];
912 if (!isset($image_retry_count[$url])) {
913 $image_retry_count[$url] = 0;
914 }
915
916 $new_image_id = $this->download_image_to_media_gallery($url, $image_retry_count[$url]);
917 if ($new_image_id === false) {
918 $image_retry_count[$url]++;
919 set_transient('elementor_import_image_retry_count', $image_retry_count, 60 * 60);
920
921 if ($image_retry_count[$url] > 3) {
922 $images_processed++;
923 set_transient('elementor_import_images_processed', $images_processed, 60 * 60);
924 $progress = round(($images_processed / $total_images) * 90) + 10;
925
926 wp_send_json_success([
927 'progress' => $progress,
928 'message' => "Processed $images_processed of $total_images images.",
929 'image_url' => $url,
930 'images_processed' => $images_processed,
931 'new_image_id' => 'SKIPPED'
932 ]);
933 } else {
934 wp_send_json_error([
935 'retry' => true,
936 'image_url' => $url
937 ]);
938 }
939 } else {
940 $new_url = wp_get_attachment_url($new_image_id);
941 $images_processed++;
942 set_transient('elementor_import_images_processed', $images_processed, 60 * 60);
943 $progress = round(($images_processed / $total_images) * 90) + 10;
944
945 $this->replace_image_data($content, $url, $new_url, $current_image['id'], $new_image_id);
946
947 set_transient('elementor_import_content', $content, 60 * 60);
948
949 wp_send_json_success([
950 'progress' => $progress,
951 'message' => "Processed $images_processed of $total_images images.",
952 'image_url' => $url,
953 'new_image_url' => $new_url
954 ]);
955 }
956 } else {
957 // All images processed
958 if ($create_new_page) {
959 // Original behavior - create new page
960 $new_post_id = wp_insert_post([
961 'post_title' => $page_title,
962 'post_content' => '',
963 'post_status' => 'publish',
964 'post_type' => 'page',
965 ]);
966
967 if ($new_post_id) {
968 update_post_meta($new_post_id, '_elementor_data', wp_slash(json_encode($content)));
969 update_post_meta($new_post_id, '_elementor_edit_mode', 'builder');
970 update_post_meta($new_post_id, '_elementor_template_type', 'page');
971 update_post_meta($new_post_id, '_elementor_version', $elementor_version);
972
973 update_post_meta($new_post_id, '_wp_page_template', 'elementor_canvas');
974
975 update_post_meta($new_post_id, '_wp_gutenberg_disable', '1');
976 update_post_meta($new_post_id, '_wp_gutenberg_enabled', '0');
977
978 delete_transient('elementor_import_content');
979 delete_transient('elementor_import_images');
980 delete_transient('elementor_import_total_images');
981 delete_transient('elementor_import_images_processed');
982 delete_transient('elementor_import_image_retry_count');
983 delete_transient('elementor_import_page_title');
984 delete_transient('elementor_import_elementor_version');
985 delete_transient('elementor_import_existing_page_id');
986 delete_transient('elementor_import_create_new_page');
987
988 wp_send_json_success([
989 'message' => "Page imported successfully!",
990 'page_url' => get_permalink($new_post_id),
991 ]);
992 } else {
993 wp_send_json_error('Failed to import page.');
994 }
995 } else {
996 // New behavior - for existing page, just signal completion
997 // Content and images are processed and stored in transients
998 // Will be merged by king_addons_merge_with_existing_page endpoint
999
1000 // error_log('King Addons Import: Image processing completed for existing page. Content ready for merge.');
1001
1002 wp_send_json_success([
1003 'message' => "Image processing completed! Content ready for merge.",
1004 'images_processed' => $images_processed,
1005 'existing_page_id' => $existing_page_id,
1006 'processing_complete' => true
1007 ]);
1008 }
1009 }
1010
1011 if (time() >= $start_time + $timeout) {
1012 wp_send_json_error('Process timeout, please resume the import.');
1013 }
1014 }
1015
1016 public function download_image_to_media_gallery($image_url, $image_retry_count)
1017 {
1018 try {
1019 if (!$this->is_safe_remote_url($image_url)) {
1020 throw new Exception('Blocked potentially unsafe image URL: ' . $image_url);
1021 }
1022
1023 $response = wp_remote_get($image_url, ['timeout' => 30]);
1024
1025 if (is_wp_error($response)) {
1026 throw new Exception('HTTP request error: ' . $response->get_error_message());
1027 }
1028
1029 $status_code = wp_remote_retrieve_response_code($response);
1030 if ($status_code !== 200) {
1031 throw new Exception(
1032 'HTTP status code ' . $status_code . ' when trying to download: ' . $image_url
1033 );
1034 }
1035
1036 $image_data = wp_remote_retrieve_body($response);
1037 if (empty($image_data)) {
1038 throw new Exception('Failed to retrieve image data from URL: ' . $image_url);
1039 }
1040
1041 $image_name = pathinfo(basename($image_url), PATHINFO_FILENAME);
1042 $image_extension = pathinfo(basename($image_url), PATHINFO_EXTENSION);
1043 $unique_image_name = $image_name . '-' . time() . '.' . $image_extension;
1044
1045 $upload_dir = wp_upload_dir();
1046 if (! file_exists($upload_dir['path'])) {
1047 wp_mkdir_p($upload_dir['path']);
1048 }
1049 $image_file = $upload_dir['path'] . '/' . $unique_image_name;
1050
1051 if (file_put_contents($image_file, $image_data) === false) {
1052 throw new Exception('Failed to write image data to file: ' . $image_file);
1053 }
1054
1055 $wp_filetype = wp_check_filetype($unique_image_name);
1056 $attachment = [
1057 'post_mime_type' => $wp_filetype['type'],
1058 'post_title' => sanitize_file_name($unique_image_name),
1059 'post_content' => '',
1060 'post_status' => 'inherit',
1061 ];
1062
1063 $attach_id = wp_insert_attachment($attachment, $image_file);
1064
1065 // Conditionally disable intermediate image sizes based on setting
1066 $improve_import = get_option('king_addons_improve_import_performance', '1');
1067 if ($improve_import === '1') {
1068 // Disable ALL extra sizes and big-image scaling
1069 add_filter('intermediate_image_sizes', '__return_empty_array', 999);
1070 add_filter('big_image_size_threshold', '__return_false', 999);
1071
1072 /* Save only the original file info — no thumbnails, no EXIF */
1073 // Get image dimensions
1074 $image_size = getimagesize($image_file);
1075 $width = $image_size ? $image_size[0] : 0;
1076 $height = $image_size ? $image_size[1] : 0;
1077
1078 $meta = [
1079 'file' => wp_basename($image_file),
1080 'filesize' => filesize($image_file),
1081 'width' => $width,
1082 'height' => $height,
1083 'sizes' => [],
1084 'image_meta' => [],
1085 ];
1086 wp_update_attachment_metadata($attach_id, $meta);
1087
1088 // Clean up filters
1089 remove_filter('intermediate_image_sizes', '__return_empty_array', 999);
1090 remove_filter('big_image_size_threshold', '__return_false', 999);
1091 } else {
1092 if ($image_retry_count > 1) {
1093 add_filter('intermediate_image_sizes', '__return_empty_array', 999);
1094 }
1095
1096 // Generate default metadata if optimization is off
1097 require_once(ABSPATH . 'wp-admin/includes/image.php');
1098 $attach_data = wp_generate_attachment_metadata($attach_id, $image_file);
1099 wp_update_attachment_metadata($attach_id, $attach_data);
1100
1101 if ($image_retry_count > 1) {
1102 remove_filter('intermediate_image_sizes', '__return_empty_array', 999);
1103 }
1104 }
1105
1106 return $attach_id;
1107 } catch (Exception $e) {
1108 // error_log('[KING_ADDONS_ERROR] ' . $e->getMessage());
1109 return false;
1110 }
1111 }
1112
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 /**
1146 * AJAX handler for getting sections catalog data
1147 */
1148 public function handle_get_sections_catalog(): void
1149 {
1150 if (!current_user_can('manage_options')) {
1151 wp_send_json_error('Insufficient permissions');
1152 return;
1153 }
1154
1155 // Check nonce - handle both admin area and popup
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;
1163 }
1164
1165 if (!class_exists('King_Addons\\SectionsMap')) {
1166 require_once KING_ADDONS_PATH . 'includes/SectionsMap.php';
1167 }
1168
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 : [];
1173 $sections = $sections_map['sections'] ?? [];
1174
1175 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
1176
1177 // Get filters from request
1178 $search_query = sanitize_text_field($_POST['search'] ?? '');
1179 $selected_category = sanitize_text_field($_POST['category'] ?? '');
1180 $selected_type = sanitize_text_field($_POST['section_type'] ?? '');
1181 $selected_plan = sanitize_text_field($_POST['plan'] ?? '');
1182 $current_page = max(1, intval($_POST['page'] ?? 1));
1183
1184 // Get categories and section types for filters
1185 $categories = [];
1186 $section_types = [];
1187
1188 foreach ($sections as $section_key => $section) {
1189 if (!in_array($section['category'], $categories)) {
1190 $categories[] = $section['category'];
1191 }
1192 if (!in_array($section['section_type'], $section_types)) {
1193 $section_types[] = $section['section_type'];
1194 }
1195 }
1196
1197 sort($categories);
1198 sort($section_types);
1199
1200 // Filter sections
1201 $filtered_sections = [];
1202
1203 foreach ($sections as $section_key => $section) {
1204 // Add section key for frontend
1205 $section['section_key'] = $section_key;
1206
1207 // Note: Premium sections are shown but restricted for import if no premium license
1208
1209 // Apply search filter
1210 if (!empty($search_query)) {
1211 $found_in_title = stripos($section['title'], $search_query) !== false;
1212 $found_in_tags = false;
1213
1214 foreach ($section['tags'] ?? [] as $tag) {
1215 if (stripos($tag, $search_query) !== false) {
1216 $found_in_tags = true;
1217 break;
1218 }
1219 }
1220
1221 if (!$found_in_title && !$found_in_tags) {
1222 continue;
1223 }
1224 }
1225
1226 // Apply category filter
1227 if (!empty($selected_category) && $section['category'] !== $selected_category) {
1228 continue;
1229 }
1230
1231 // Apply section type filter
1232 if (!empty($selected_type) && $section['section_type'] !== $selected_type) {
1233 continue;
1234 }
1235
1236 // Apply plan filter
1237 if (!empty($selected_plan) && $section['plan'] !== $selected_plan) {
1238 continue;
1239 }
1240
1241 $filtered_sections[] = $section;
1242 }
1243
1244 // Pagination
1245 $items_per_page = 20;
1246 $total_sections = count($filtered_sections);
1247 $total_pages = ceil($total_sections / $items_per_page);
1248 $offset = ($current_page - 1) * $items_per_page;
1249 $paged_sections = array_slice($filtered_sections, $offset, $items_per_page);
1250
1251 wp_send_json_success([
1252 'sections' => $paged_sections,
1253 'categories' => $categories,
1254 'section_types' => $section_types,
1255 'pagination' => [
1256 'current_page' => $current_page,
1257 'total_pages' => $total_pages,
1258 'total_sections' => $total_sections,
1259 'items_per_page' => $items_per_page
1260 ],
1261 'is_premium_active' => $is_premium_active
1262 ]);
1263 }
1264
1265 /**
1266 * AJAX handler for importing section to current page (from main admin catalog)
1267 */
1268 public function handle_import_section_to_page(): void
1269 {
1270 if (!current_user_can('edit_posts')) {
1271 wp_send_json_error('Insufficient permissions');
1272 return;
1273 }
1274
1275 if (!wp_verify_nonce($_POST['nonce'], 'kingAddonsNonce')) {
1276 wp_send_json_error('Invalid nonce');
1277 return;
1278 }
1279
1280 $section_key = sanitize_text_field($_POST['section_key']);
1281 $section_plan = sanitize_text_field($_POST['section_plan']);
1282 $is_premium_active = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code();
1283
1284 // Determine API URL and install ID (same logic as templates)
1285 if ($is_premium_active && $section_plan === 'premium') {
1286 $api_url = 'https://api.kingaddons.com/get-section.php';
1287
1288 // Use the same method as original templates catalog
1289 if (function_exists('king_addons_freemius')) {
1290 $freemius_site = king_addons_freemius()->get_site();
1291 $install_id = $freemius_site ? $freemius_site->id : 0;
1292 } else {
1293 $install_id = 0;
1294 }
1295
1296 // error_log('King Addons Premium Section: Using install_id: ' . $install_id . ' for premium section: ' . $section_key);
1297 } elseif ($section_plan === 'free') {
1298 $api_url = 'https://api.kingaddons.com/get-section-free.php';
1299 $install_id = 0;
1300 // error_log('King Addons Free Section: Fetching free section: ' . $section_key);
1301 } else {
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'));
1303 wp_send_json_error('Premium section requires premium license');
1304 return;
1305 }
1306
1307 // Get section data from API (same as templates)
1308 $response = wp_remote_post($api_url, [
1309 'headers' => ['Content-Type' => 'application/json'],
1310 'body' => json_encode([
1311 'key' => $section_key,
1312 'install' => $install_id,
1313 ]),
1314 'timeout' => 60
1315 ]);
1316
1317 if (is_wp_error($response)) {
1318 wp_send_json_error('Failed to fetch section: ' . $response->get_error_message());
1319 return;
1320 }
1321
1322 $body = wp_remote_retrieve_body($response);
1323 $data = json_decode($body, true);
1324
1325 // error_log('King Addons Section API Response: ' . substr($body, 0, 500) . (strlen($body) > 500 ? '...' : ''));
1326
1327 if (!$data) {
1328 // error_log('King Addons Section Error: Failed to decode JSON response');
1329 wp_send_json_error('Invalid JSON response from section API');
1330 return;
1331 }
1332
1333 if (!isset($data['success']) || !$data['success']) {
1334 $error_message = isset($data['message']) ? $data['message'] : 'Unknown API error';
1335 // error_log('King Addons Section Error: API returned error: ' . $error_message);
1336 wp_send_json_error('Section API error: ' . $error_message);
1337 return;
1338 }
1339
1340 // Return section data for frontend processing (adjust format from your API)
1341 wp_send_json_success([
1342 'section_data' => $data['section'], // Your API returns 'section' not 'landing'
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 ]
1684 ]);
1685 }
1686 }
1687
1688 Templates::instance();
1689