PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.9
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.9
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / inc / admin / templates / kits / class-importer.php

class-importer.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.1.9, at inc/admin/templates/kits/class-importer.php

2,452 lines 92.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace MasterAddons\Inc\Admin\Templates\Kits;
4
5 use MasterAddons\Inc\Classes\Importer\JLTMA_Templates_Importer;
6 use MasterAddons\Inc\Classes\Helper;
7 use Elementor\Plugin;
8
9 defined('ABSPATH') || exit;
10
11 /**
12 * Template Kits Importer Class
13 *
14 * Handles all template kit import functionality including:
15 * - AJAX handlers for imports
16 * - Image imports to media library
17 * - Plugin installation/activation
18 * - Template kit uploads
19 */
20 class Importer
21 {
22 private static $_instance = null;
23
24 /**
25 * Track imported images to avoid duplicates
26 */
27 private $imported_images = [];
28
29 public function __construct()
30 {
31 $this->register_ajax_hooks();
32 $this->register_filters();
33 }
34
35 /**
36 * Register AJAX action hooks
37 */
38 private function register_ajax_hooks()
39 {
40 add_action('wp_ajax_jltma_activate_required_theme', [$this, 'activate_required_theme']);
41 add_action('wp_ajax_jltma_reset_previous_import', [$this, 'reset_previous_import']);
42 add_action('wp_ajax_jltma_import_templates_kit', [$this, 'import_templates_kit']);
43 add_action('wp_ajax_jltma_final_settings_setup', [$this, 'final_settings_setup']);
44 add_action('wp_ajax_jltma_search_query_results', [$this, 'search_query_results']);
45 add_action('wp_ajax_jltma_predownload_kit', [$this, 'predownload_kit']);
46 add_action('wp_ajax_jltma_download_all_kits', [$this, 'download_all_kits']);
47 add_action('wp_ajax_jltma_install_required_plugin', [$this, 'install_required_plugin']);
48 add_action('wp_ajax_jltma_activate_required_plugin', [$this, 'activate_required_plugin']);
49 add_action('wp_ajax_jltma_upload_template_kit', [$this, 'upload_template_kit']);
50 add_action('wp_ajax_jltma_cleanup_template_images', [$this, 'cleanup_template_images']);
51 add_action('wp_ajax_jltma_preimport_kit_images', [$this, 'preimport_kit_images']);
52 add_action('wp_ajax_jltma_precache_kit_images', [$this, 'precache_kit_images']);
53 add_action('init', [$this, 'disable_default_woo_pages_creation'], 2);
54 }
55
56 /**
57 * Register filters
58 */
59 private function register_filters()
60 {
61 // Set Image Timeout
62 if (version_compare(get_bloginfo('version'), '5.1.0', '>=')) {
63 add_filter('http_request_timeout', [$this, 'set_image_request_timeout'], 10, 2);
64 add_filter('wp_check_filetype_and_ext', [$this, 'real_mime_types_5_1_0'], 10, 5);
65 } else {
66 add_filter('wp_check_filetype_and_ext', [$this, 'real_mime_types'], 10, 4);
67 }
68 }
69
70 /**
71 * Get Theme Status
72 */
73 public function get_theme_status()
74 {
75 $theme = wp_get_theme();
76
77 // Theme installed and activate.
78 if ('Hello Elementor' === $theme->name || 'Hello Elementor' === $theme->parent_theme) {
79 return 'req-theme-active';
80 }
81
82 // Theme installed but not activate.
83 foreach ((array) wp_get_themes() as $theme_dir => $theme) {
84 if ('Hello Elementor' === $theme->name || 'Hello Elementor' === $theme->parent_theme) {
85 return 'req-theme-inactive';
86 }
87 }
88
89 return 'req-theme-not-installed';
90 }
91
92 /**
93 * Install/Activate Required Theme
94 */
95 public function activate_required_theme()
96 {
97 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
98
99 if (!wp_verify_nonce($nonce, 'jltma-template-kits-js') || !current_user_can('manage_options')) {
100 exit;
101 }
102
103 // Get Current Theme
104 $theme = get_option('stylesheet');
105
106 // No default theme activation - themes should be specified in manifest
107 }
108
109 /**
110 * Custom upload mimes for template kit imports
111 */
112 public function custom_upload_mimes($mimes)
113 {
114 // Allow SVG files.
115 $mimes['svg'] = 'image/svg+xml';
116 $mimes['svgz'] = 'image/svg+xml';
117
118 // Allow XML files.
119 $mimes['xml'] = 'text/xml';
120
121 // Allow JSON files.
122 $mimes['json'] = 'application/json';
123
124 return $mimes;
125 }
126
127 /**
128 * Sanitize SVG files on upload
129 */
130 public function sanitize_svg_on_upload($file)
131 {
132 if ($file['type'] === 'image/svg+xml') {
133 // Direct file ops on the PHP upload temp path, before WordPress moves it.
134 // WP_Filesystem (ftp/ssh methods) cannot reach the local upload temp file.
135 $file_content = file_get_contents($file['tmp_name']); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reading the PHP upload temp file for sanitization
136 $sanitized_content = $this->sanitize_svg($file_content);
137 file_put_contents($file['tmp_name'], $sanitized_content); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- writing back the sanitized SVG to the PHP upload temp file before WordPress moves it
138 }
139 return $file;
140 }
141
142 /**
143 * Sanitize SVG content
144 */
145 public function sanitize_svg($svg_content)
146 {
147 $dom = new \DOMDocument();
148 $dom->loadXML($svg_content, LIBXML_NOENT | LIBXML_DTDLOAD);
149
150 // Remove scripts
151 $scripts = $dom->getElementsByTagName('script');
152 while ($scripts->length > 0) {
153 $scripts->item(0)->parentNode->removeChild($scripts->item(0));
154 }
155
156 return $dom->saveXML();
157 }
158
159 /**
160 * Import Template Kit
161 */
162 public function import_templates_kit()
163 {
164 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
165 wp_send_json_error(['message' => 'Permission denied']);
166 return;
167 }
168
169 // Include the importer class if not already loaded
170 if (!class_exists('MasterAddons\Inc\Classes\Importer\JLTMA_Templates_Importer')) {
171 require_once \JLTMA_PATH . 'inc/classes/importer/class-jltma-templates-importer.php';
172 }
173
174 // Get cache manager instance
175 $cache_manager = null;
176 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
177 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
178 }
179
180 // Add upload mime types filter
181 add_filter('upload_mimes', [$this, 'custom_upload_mimes'], 99);
182 add_filter('wp_handle_upload_prefilter', [$this, 'sanitize_svg_on_upload']);
183
184 // Temp Define Importers
185 if (!defined('WP_LOAD_IMPORTERS')) {
186 define('WP_LOAD_IMPORTERS', true);
187 }
188
189 // Include if Class Does NOT Exist
190 if (!class_exists('WP_Import')) {
191 $class_wp_importer = \JLTMA_PATH . 'inc/classes/importer/class-wordpress-importer.php';
192 if (file_exists($class_wp_importer)) {
193 require $class_wp_importer;
194 }
195 }
196
197 // Get template kit ID
198 $kit = isset($_POST['jltma_templates_kit']) ? sanitize_file_name(wp_unslash($_POST['jltma_templates_kit'])) : '';
199 if (!$kit && isset($_POST['parentTemplate'])) {
200 $kit = sanitize_text_field( wp_unslash( $_POST['parentTemplate'] ) );
201 }
202
203 // Handle template import
204 if (isset($_POST['template'])) {
205 $this->handle_single_template_import($kit, $cache_manager);
206 return;
207 }
208
209 if (empty($kit)) {
210 wp_send_json_error(array(
211 'message' => 'Template kit ID is required.'
212 ));
213 return;
214 }
215
216 // Store kit ID temporarily
217 update_option('jltma-import-kit-id', $kit);
218
219 try {
220 // Try REST API with caching first, fallback to local JSON
221 $import_result = $this->import_kit_via_api($kit);
222
223 if (!$import_result) {
224 // Fallback to local JSON files
225 $importer = new JLTMA_Templates_Importer($kit);
226 $import_result = $importer->import_kit();
227 }
228
229 if ($import_result && $import_result['success']) {
230 // Return serialized result similar to Royal Addons
231 echo esc_html(serialize(array(
232 'success' => true,
233 'imported_pages' => $import_result['imported_pages'] ?? array(),
234 'message' => $import_result['message'] ?? 'Import completed successfully!'
235 )));
236 } else {
237 wp_send_json_error(array(
238 'message' => $import_result['message'] ?? 'Import failed.'
239 ));
240 }
241 } catch (\Exception $e) {
242 wp_send_json_error(array(
243 'message' => $e->getMessage()
244 ));
245 }
246 }
247
248 /**
249 * Handle single template import
250 */
251 private function handle_single_template_import($kit, $cache_manager)
252 {
253 $template_data = isset( $_POST['template'] ) ? sanitize_textarea_field( wp_unslash( $_POST['template'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
254 $decoded = json_decode($template_data, true);
255 $kit_id = $decoded['template_id'];
256 $kit_name = $decoded['title'];
257
258 // Duplicate detection: check if this template was already imported for this kit
259 $parent_kit = isset($_POST['parentTemplate']) ? sanitize_text_field( wp_unslash( $_POST['parentTemplate'] ) ) : $kit; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
260 $existing_page = $this->find_existing_imported_page($kit_id, $parent_kit, $kit_name);
261 if ($existing_page) {
262 $page_edit_url = admin_url('post.php?post=' . $existing_page->ID . '&action=elementor');
263 wp_send_json_success(array(
264 'message' => 'Template already imported, skipping duplicate.',
265 'page_created' => true,
266 'page_id' => $existing_page->ID,
267 'page_title' => $existing_page->post_title,
268 'page_edit_url' => $page_edit_url,
269 'edit_url' => $page_edit_url,
270 'view_url' => get_permalink($existing_page->ID),
271 'skipped' => true,
272 ));
273 exit;
274 }
275
276 // Check if we have both parentTemplate and template_id for cached templates
277 if (isset($_POST['parentTemplate']) && isset($decoded['template_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
278 $decoded = $this->load_template_from_cache($decoded, $kit_name, $cache_manager);
279 }
280
281 // Continue with the import process
282 $import_start_time = microtime(true);
283 $decoded['content'] = $this->import_images_to_media($decoded['content'], $kit_id, $kit_name);
284 $import_time = microtime(true) - $import_start_time;
285
286 $template_type = $decoded['type'] ?? 'page';
287
288 // Check if this is a global kit style template
289 $is_global_kit = false;
290 if (isset($decoded['metadata']['template_type']) && $decoded['metadata']['template_type'] === 'global-styles') {
291 $is_global_kit = true;
292 $template_type = 'kit';
293 }
294
295 // Prepare meta input
296 $meta_input = array(
297 '_elementor_edit_mode' => 'builder',
298 '_elementor_template_type' => $template_type,
299 '_elementor_version' => ELEMENTOR_VERSION,
300 '_elementor_data' => wp_slash(json_encode($decoded['content'])),
301 '_jltma_demo_import_item' => true,
302 '_jltma_import_kit_id' => $kit_id,
303 '_jltma_import_date' => current_time('mysql'),
304 '_jltma_original_template_id' => $kit_id,
305 '_thumbnail_id' => 0
306 );
307
308 $template_id = wp_insert_post(array(
309 'post_title' => $kit_name,
310 'post_type' => 'elementor_library',
311 'post_status' => 'publish',
312 'post_content' => '',
313 'meta_input' => $meta_input
314 ));
315
316 if (!is_wp_error($template_id) && $is_global_kit && isset($decoded['page_settings'])) {
317 update_post_meta($template_id, '_elementor_page_settings', $decoded['page_settings']);
318 }
319
320 // Handle global kit styles activation
321 if (!is_wp_error($template_id) && $is_global_kit) {
322 update_option('elementor_active_kit', $template_id);
323 if (class_exists('\Elementor\Plugin')) {
324 \Elementor\Plugin::$instance->files_manager->clear_cache();
325 }
326 } elseif (!is_wp_error($template_id) && $template_type === 'kit') {
327 $auto_activate = apply_filters('jltma_auto_activate_kit_template', true, $template_id, $kit_name, $decoded);
328 if ($auto_activate) {
329 update_option('elementor_active_kit', $template_id);
330 if (isset($decoded['settings']) && is_array($decoded['settings'])) {
331 $kit_instance = \Elementor\Plugin::$instance->documents->get($template_id);
332 if ($kit_instance) {
333 $kit_instance->save(['settings' => $decoded['settings']]);
334 }
335 }
336 if (class_exists('\Elementor\Plugin')) {
337 \Elementor\Plugin::$instance->files_manager->clear_cache();
338 }
339 }
340 }
341
342 if (!is_wp_error($template_id)) {
343 $response = $this->create_page_from_template($template_id, $template_type, $kit_name, $kit_id, $decoded, $is_global_kit);
344 wp_send_json_success($response);
345 exit;
346 } else {
347 wp_send_json_error(array(
348 'message' => 'Failed to save template: ' . $template_id->get_error_message()
349 ));
350 exit;
351 }
352 }
353
354 /**
355 * Load template from cache
356 */
357 private function load_template_from_cache($decoded, $kit_name, $cache_manager)
358 {
359 $parent_template = isset( $_POST['parentTemplate'] ) ? sanitize_text_field( wp_unslash( $_POST['parentTemplate'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
360 $template_id = sanitize_text_field($decoded['template_id']);
361
362 $upload_dir = wp_upload_dir();
363
364 // First check cached templates directory
365 $cached_base = $upload_dir['basedir'] . '/master_addons/templates_kits/kits/';
366 $manifest_path = $cached_base . $parent_template . '/manifest.json';
367
368 // Check if manifest exists in cached location
369 if (file_exists($manifest_path)) {
370 $manifest_content = file_get_contents($manifest_path);
371 $manifest = json_decode($manifest_content, true);
372
373 // Find the template in manifest
374 $template_found = false;
375 $template_source = null;
376 if (isset($manifest['templates']) && is_array($manifest['templates'])) {
377 foreach ($manifest['templates'] as $tpl) {
378 if ((isset($tpl['template_id']) && $tpl['template_id'] == $template_id) ||
379 (isset($tpl['name']) && $tpl['name'] == $kit_name)) {
380 $template_found = true;
381 $template_source = isset($tpl['source']) ? $tpl['source'] : null;
382 break;
383 }
384 }
385 }
386
387 if ($template_found && $template_source) {
388 $json_file_path = $cached_base . $parent_template . '/' . $template_source;
389 if (file_exists($json_file_path)) {
390 $json_content = file_get_contents($json_file_path);
391 $local_template = json_decode($json_content, true);
392 if ($local_template) {
393 $decoded = $local_template;
394 $decoded['template_id'] = $template_id;
395 $decoded['title'] = $kit_name;
396 }
397 }
398 } elseif ($kit_name === 'Global Kit Styles' || strpos(strtolower($kit_name), 'global') !== false) {
399 $global_path = $cached_base . $parent_template . '/templates/global.json';
400 if (file_exists($global_path)) {
401 $json_content = file_get_contents($global_path);
402 $local_template = json_decode($json_content, true);
403 if ($local_template) {
404 $decoded = $local_template;
405 if (!isset($decoded['template_id'])) {
406 $decoded['template_id'] = $template_id;
407 }
408 $decoded['title'] = $kit_name;
409 }
410 }
411 }
412 } elseif (is_null($decoded['content'])) {
413 $decoded = $this->load_template_from_purchased_kits($decoded, $parent_template, $kit_name, $template_id);
414 }
415
416 return $decoded;
417 }
418
419 /**
420 * Load template from purchased kits
421 */
422 private function load_template_from_purchased_kits($decoded, $parent_template, $kit_name, $template_id)
423 {
424 $upload_dir = wp_upload_dir();
425 $cache_base = $upload_dir['basedir'] . '/master_addons/purchased_kits/kits/';
426 $manifest_data = json_decode(file_get_contents($cache_base . 'kit_' . $parent_template . '/manifest.json'));
427
428 $thisTemplate = null;
429 foreach ($manifest_data->templates as $template_key => $template) {
430 if ($template->name === $kit_name) {
431 $thisTemplate = $template;
432 }
433 }
434
435 $template_slug = ($thisTemplate) ? $thisTemplate->source : str_replace(' ', '-', $kit_name) . '.json';
436 if ($thisTemplate) {
437 $json_file_path = $cache_base . 'kit_' . $parent_template . '/' . $template_slug;
438 } else {
439 $json_file_path = $cache_base . 'kit_' . $parent_template . '/templates/' . $template_slug;
440 }
441
442 // If direct path doesn't work, try to find the file
443 if (!file_exists($json_file_path)) {
444 $json_file_path = $this->find_template_file($cache_base, $parent_template, $template_slug, $kit_name);
445 }
446
447 // Read the JSON file
448 if (file_exists($json_file_path)) {
449 $json_content = file_get_contents($json_file_path);
450 $template_json = json_decode($json_content, true);
451
452 if ($template_json && isset($template_json['content'])) {
453 $decoded['content'] = $template_json['content'];
454 if (isset($template_json['type'])) {
455 if ($template_json['type'] == 'wp-post') {
456 $decoded['type'] = $template_json['metadata']['template_type'];
457 } else {
458 $decoded['type'] = $template_json['type'];
459 }
460 }
461 if (isset($template_json['version'])) {
462 $decoded['version'] = $template_json['version'];
463 }
464 if (isset($template_json['metadata'])) {
465 $decoded['metadata'] = $template_json['metadata'];
466 }
467 } else {
468 $decoded = array_merge($decoded, $template_json);
469 }
470 } else {
471 wp_send_json_error(array(
472 'message' => 'Template file not found: ' . $template_slug . ' in kit_' . $parent_template
473 ));
474 exit;
475 }
476
477 return $decoded;
478 }
479
480 /**
481 * Find template file with various naming conventions
482 */
483 private function find_template_file($cache_base, $parent_template, $template_slug, $kit_name)
484 {
485 $possible_names = [
486 $template_slug,
487 strtolower($template_slug),
488 str_replace('-', '_', $template_slug),
489 str_replace(' ', '_', $kit_name) . '.json',
490 str_replace([' ', '_'], '-', $kit_name) . '.json',
491 str_replace(['–', '—'], '-', $template_slug),
492 str_replace(['–', '—'], '-', str_replace(' ', '-', $kit_name)) . '.json',
493 str_replace(['\'', '\'', '\"', '\"'], '', $template_slug),
494 urlencode(str_replace(' ', '-', $kit_name)) . '.json',
495 preg_replace('/[^a-zA-Z0-9\-]/', '-', str_replace(' ', '-', $kit_name)) . '.json'
496 ];
497
498 foreach ($possible_names as $possible_name) {
499 $test_path = $cache_base . 'kit_' . $parent_template . '/templates/' . $possible_name;
500 if (file_exists($test_path)) {
501 return $test_path;
502 }
503 }
504
505 // Scan directory for matching file
506 $templates_dir = $cache_base . 'kit_' . $parent_template . '/templates/';
507 if (is_dir($templates_dir)) {
508 $files = scandir($templates_dir);
509 $normalized_kit_name = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $kit_name));
510
511 foreach ($files as $file) {
512 if (pathinfo($file, PATHINFO_EXTENSION) === 'json') {
513 $normalized_filename = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', pathinfo($file, PATHINFO_FILENAME)));
514 if ($normalized_filename === $normalized_kit_name || strpos($normalized_filename, $normalized_kit_name) !== false) {
515 return $templates_dir . $file;
516 }
517 }
518 }
519 }
520
521 return $cache_base . 'kit_' . $parent_template . '/templates/' . $template_slug;
522 }
523
524 /**
525 * Create page from template
526 */
527 private function create_page_from_template($template_id, $template_type, $kit_name, $kit_id, $decoded, $is_global_kit)
528 {
529 $created_page_id = null;
530 $page_edit_url = null;
531
532 $page_template_types = ['page', 'wp-page', 'single-page', 'landing-page', 'single-404', 'archive-blog'];
533 $library_template_types = ['section-header', 'section-footer', 'single-post'];
534
535 if (in_array($template_type, $page_template_types)) {
536 // Duplicate detection: skip if page already exists for this template
537 $existing = $this->find_existing_imported_page($kit_id, '', $kit_name);
538 if ($existing) {
539 $page_edit_url = admin_url('post.php?post=' . $existing->ID . '&action=elementor');
540 return array(
541 'message' => 'Template already imported, skipping duplicate.',
542 'template_id' => $template_id,
543 'template_edit_url' => admin_url('post.php?post=' . $template_id . '&action=elementor'),
544 'page_created' => true,
545 'page_id' => $existing->ID,
546 'page_title' => $existing->post_title,
547 'page_slug' => $existing->post_name,
548 'page_edit_url' => $page_edit_url,
549 'edit_url' => $page_edit_url,
550 'view_url' => get_permalink($existing->ID),
551 'skipped' => true,
552 );
553 }
554
555 $page_prefix = $this->get_page_prefix($kit_name, $decoded);
556 $separator = apply_filters('jltma_new_page_separator', ' - ', $page_prefix, $kit_name);
557
558 if (in_array($template_type, ['single-404', 'archive-blog'])) {
559 $page_title = $kit_name;
560 $page_slug = sanitize_title($kit_name);
561 } else {
562 $page_title = !empty($page_prefix) ? $page_prefix . $separator . $kit_name : $kit_name;
563 $page_slug = sanitize_title(!empty($page_prefix) ? $page_prefix . '-' . $kit_name : $kit_name);
564 }
565
566 $slug_check = 0;
567 $original_slug = $page_slug;
568 while (get_page_by_path($page_slug)) {
569 $slug_check++;
570 $page_slug = $original_slug . '-' . $slug_check;
571 }
572
573 $post_status = apply_filters('jltma_new_page_status', 'publish', $kit_name, $decoded);
574
575 $new_page_args = array(
576 'post_title' => $page_title,
577 'post_name' => $page_slug,
578 'post_type' => 'page',
579 'post_status' => $post_status,
580 'post_content' => '',
581 'meta_input' => array(
582 '_elementor_edit_mode' => 'builder',
583 '_elementor_template_type' => 'wp-page',
584 '_elementor_version' => ELEMENTOR_VERSION,
585 '_elementor_data' => wp_slash(json_encode($decoded['content'])),
586 '_jltma_demo_import_item' => true,
587 '_jltma_import_kit_id' => $kit_id,
588 '_jltma_import_date' => current_time('mysql'),
589 '_jltma_original_template_id' => $kit_id,
590 '_jltma_from_template_id' => $template_id
591 )
592 );
593
594 $created_page_id = wp_insert_post($new_page_args);
595
596 if (!is_wp_error($created_page_id)) {
597 update_post_meta($created_page_id, '_wp_page_template', 'elementor_canvas');
598 $this->handle_special_template_type($template_type, $created_page_id);
599 $page_edit_url = admin_url('post.php?post=' . $created_page_id . '&action=elementor');
600
601 // Generate Elementor CSS for the new page
602 if (class_exists('\Elementor\Core\Files\CSS\Post')) {
603 $css_file = new \Elementor\Core\Files\CSS\Post($created_page_id);
604 $css_file->update();
605 }
606 }
607 }
608
609 // Clear Elementor global cache
610 if (class_exists('\Elementor\Plugin')) {
611 \Elementor\Plugin::$instance->files_manager->clear_cache();
612 }
613
614 // Prepare response
615 $response = array(
616 'message' => 'Template imported successfully!',
617 'template_id' => $template_id,
618 'template_edit_url' => admin_url('post.php?post=' . $template_id . '&action=elementor')
619 );
620
621 if ($is_global_kit) {
622 $response['is_global_kit'] = true;
623 $response['message'] = 'Global kit styles imported and activated successfully!';
624 $response['edit_url'] = admin_url('admin.php?page=elementor#/kit-library');
625 $response['active_kit_id'] = $template_id;
626 } elseif ($created_page_id && !is_wp_error($created_page_id)) {
627 $response['page_created'] = true;
628 $response['page_id'] = $created_page_id;
629 $response['page_title'] = $page_title ?? $kit_name;
630 $response['page_slug'] = $page_slug ?? '';
631 $response['page_edit_url'] = $page_edit_url;
632 $response['edit_url'] = $page_edit_url;
633 $response['view_url'] = get_permalink($created_page_id);
634 $response['message'] = 'Template imported and page created successfully!';
635 } else {
636 $response['page_created'] = false;
637 $response['edit_url'] = $response['template_edit_url'];
638 $response['view_url'] = get_permalink($template_id);
639 }
640
641 return $response;
642 }
643
644 /**
645 * Check if a page with this template was already imported
646 */
647 private function find_existing_imported_page($template_id, $kit, $title)
648 {
649 global $wpdb;
650
651 // Check for existing page with same original_template_id
652 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for meta join lookup without core API equivalent
653 $existing = $wpdb->get_var($wpdb->prepare(
654 "SELECT p.ID FROM {$wpdb->posts} p
655 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
656 WHERE p.post_type = 'page'
657 AND p.post_status = 'publish'
658 AND pm.meta_key = '_jltma_original_template_id'
659 AND pm.meta_value = %s
660 LIMIT 1",
661 $template_id
662 ));
663
664 if ($existing) {
665 return get_post($existing);
666 }
667
668 return null;
669 }
670
671 /**
672 * Get page prefix from manifest
673 */
674 private function get_page_prefix($kit_name, $decoded)
675 {
676 $page_prefix = 'New';
677
678 // Nonce is verified upstream in import_templates_kit() before this private helper runs.
679 if (isset($_POST['parentTemplate'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
680 $parent_template = sanitize_text_field( wp_unslash( $_POST['parentTemplate'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
681 $upload_dir = wp_upload_dir();
682
683 $manifest_path = $upload_dir['basedir'] . '/master_addons/purchased_kits/kits/kit_' . $parent_template . '/manifest.json';
684
685 if (!file_exists($manifest_path)) {
686 $manifest_path = $upload_dir['basedir'] . '/master_addons/templates_kits/kits/kit_' . $parent_template . '.json';
687 if (!file_exists($manifest_path)) {
688 $manifest_path = $upload_dir['basedir'] . '/master_addons/templates_kits/kits/' . $parent_template . '.json';
689 }
690 }
691
692 if (file_exists($manifest_path)) {
693 $manifest_content = file_get_contents($manifest_path);
694 $manifest = json_decode($manifest_content, true);
695
696 if ($manifest && isset($manifest['title'])) {
697 $kit_title = $manifest['title'];
698 if (strpos($kit_title, '-') !== false) {
699 $parts = explode('-', $kit_title);
700 $page_prefix = trim($parts[0]);
701 } else {
702 $page_prefix = $kit_title;
703 }
704 } elseif ($manifest && isset($manifest['kit_name'])) {
705 $kit_title = $manifest['kit_name'];
706 if (strpos($kit_title, '-') !== false) {
707 $parts = explode('-', $kit_title);
708 $page_prefix = trim($parts[0]);
709 } else {
710 $page_prefix = $kit_title;
711 }
712 }
713 } else {
714 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
715 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
716 if (method_exists($cache_manager, 'get_kit_manifest')) {
717 $manifest = $cache_manager->get_kit_manifest($parent_template);
718 if ($manifest) {
719 $kit_title = $manifest['title'] ?? $manifest['kit_name'] ?? '';
720 if (!empty($kit_title)) {
721 if (strpos($kit_title, '-') !== false) {
722 $parts = explode('-', $kit_title);
723 $page_prefix = trim($parts[0]);
724 } else {
725 $page_prefix = $kit_title;
726 }
727 }
728 }
729 }
730 }
731 }
732 }
733
734 return apply_filters('jltma_new_page_prefix', $page_prefix, $kit_name, $decoded);
735 }
736
737 /**
738 * Handle special template types (landing page, 404, blog archive)
739 */
740 private function handle_special_template_type($template_type, $page_id)
741 {
742 if ($template_type === 'landing-page') {
743 update_option('show_on_front', 'page');
744 update_option('page_on_front', $page_id);
745 } elseif ($template_type === 'single-404') {
746 update_option('jltma_404_page_id', $page_id);
747 if (defined('ELEMENTOR_PRO_VERSION')) {
748 $kit_id = get_option('elementor_active_kit');
749 if ($kit_id) {
750 $kit_settings = get_post_meta($kit_id, '_elementor_page_settings', true);
751 if (!is_array($kit_settings)) {
752 $kit_settings = [];
753 }
754 $kit_settings['404_page_id'] = $page_id;
755 update_post_meta($kit_id, '_elementor_page_settings', $kit_settings);
756 }
757 }
758 } elseif ($template_type === 'archive-blog') {
759 update_option('show_on_front', 'page');
760 update_option('page_for_posts', $page_id);
761 }
762 }
763
764 /**
765 * Import Template Kit via REST API with caching
766 */
767 public function import_kit_via_api($kit)
768 {
769 $cache_manager = null;
770 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
771 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
772 }
773
774 if (!$cache_manager) {
775 return false;
776 }
777
778 $kit_data = $cache_manager->download_and_cache_kit($kit);
779
780 if (!$kit_data || !$kit_data['success']) {
781 return $this->import_kit_via_api_direct($kit);
782 }
783
784 $manifest_data = $kit_data['manifest'];
785 if (!$manifest_data || !isset($manifest_data['pages'])) {
786 $manifest_data = $cache_manager->get_kit_manifest($kit);
787 if (!$manifest_data || !isset($manifest_data['pages'])) {
788 return false;
789 }
790 }
791
792 $imported_pages = array();
793 foreach ($manifest_data['pages'] as $page_slug) {
794 $template_data = $cache_manager->get_kit_template($kit, $page_slug);
795
796 if ($template_data) {
797 $page_id = $this->import_template_from_data($template_data, $kit);
798 } else {
799 $page_id = $this->import_single_template_from_api($kit, $page_slug);
800 }
801
802 if ($page_id) {
803 $imported_pages[$page_slug] = $page_id;
804 }
805 }
806
807 if (isset($manifest_data['global_settings'])) {
808 $this->import_global_settings_from_api($manifest_data['global_settings']);
809 }
810
811 if (isset($imported_pages['home'])) {
812 update_option('show_on_front', 'page');
813 update_option('page_on_front', $imported_pages['home']);
814 }
815
816 return array(
817 'success' => true,
818 'imported_pages' => $imported_pages,
819 'message' => 'Template kit imported successfully!'
820 );
821 }
822
823 /**
824 * Fallback: Import Template Kit directly via API (without caching)
825 */
826 public function import_kit_via_api_direct($kit)
827 {
828 $config = null;
829 if (function_exists('MasterAddons\Inc\Admin\Templates\master_addons_templates')) {
830 $templates_instance = \MasterAddons\Inc\Admin\Templates\master_addons_templates();
831 if ($templates_instance && isset($templates_instance->config)) {
832 $config = $templates_instance->config->get('api');
833 }
834 }
835
836 $api_url = $config['base'] . $config['path'] . '/templates-kit/' . $kit . '/manifest.json';
837
838 if (Helper::jltma_premium()) {
839 $api_url = add_query_arg('pro_enabled', 'true', $api_url);
840 }
841
842 $response = wp_remote_get($api_url, [
843 'timeout' => 60,
844 'sslverify' => false,
845 'headers' => [
846 'User-Agent' => 'Master Addons Template Kit Importer/' . \JLTMA_VER
847 ]
848 ]);
849
850 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
851 return false;
852 }
853
854 $manifest_data = json_decode(wp_remote_retrieve_body($response), true);
855 if (!$manifest_data || !isset($manifest_data['pages'])) {
856 return false;
857 }
858
859 $imported_pages = array();
860 foreach ($manifest_data['pages'] as $page_slug) {
861 $page_id = $this->import_single_template_from_api($kit, $page_slug);
862 if ($page_id) {
863 $imported_pages[$page_slug] = $page_id;
864 }
865 }
866
867 if (isset($manifest_data['global_settings'])) {
868 $this->import_global_settings_from_api($manifest_data['global_settings']);
869 }
870
871 if (isset($imported_pages['home'])) {
872 update_option('show_on_front', 'page');
873 update_option('page_on_front', $imported_pages['home']);
874 }
875
876 return array(
877 'success' => true,
878 'imported_pages' => $imported_pages,
879 'message' => 'Template kit imported successfully via API!'
880 );
881 }
882
883 /**
884 * Import Single Template from API
885 */
886 public function import_single_template_from_api($kit, $template_slug)
887 {
888 $cache_manager = null;
889 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
890 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
891 }
892
893 if ($cache_manager) {
894 $template_data = $cache_manager->get_kit_template($kit, $template_slug);
895 if ($template_data) {
896 return $this->import_template_from_data($template_data, $kit);
897 }
898 }
899
900 $config = null;
901 if (function_exists('MasterAddons\Inc\Admin\Templates\master_addons_templates')) {
902 $templates_instance = \MasterAddons\Inc\Admin\Templates\master_addons_templates();
903 if ($templates_instance && isset($templates_instance->config)) {
904 $config = $templates_instance->config->get('api');
905 }
906 }
907
908 $template_url = $config['base'] . $config['path'] . '/templates-kit/' . $kit . '/' . $template_slug . '.json';
909
910 if (Helper::jltma_premium()) {
911 $template_url = add_query_arg('pro_enabled', 'true', $template_url);
912 }
913
914 $response = wp_remote_get($template_url, array(
915 'timeout' => 30,
916 'headers' => array(
917 'Accept' => 'application/json',
918 'User-Agent' => 'Master-Addons/' . \JLTMA_VER
919 )
920 ));
921
922 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
923 return false;
924 }
925
926 $template_data = json_decode(wp_remote_retrieve_body($response), true);
927 if (!$template_data) {
928 return false;
929 }
930
931 return $this->import_template_from_data($template_data, $kit);
932 }
933
934 /**
935 * Import template from data array
936 */
937 public function import_template_from_data($template_data, $kit)
938 {
939 if (!$template_data || !isset($template_data['page_settings'])) {
940 return false;
941 }
942
943 // Duplicate detection: check if page with same title and kit already exists
944 $post_title = $template_data['page_settings']['post_title'];
945 $existing = get_posts(array(
946 'post_type' => 'page',
947 'post_status' => 'publish',
948 'title' => $post_title,
949 'meta_query' => array(
950 array(
951 'key' => '_jltma_import_kit_id',
952 'value' => $kit,
953 ),
954 ),
955 'posts_per_page' => 1,
956 'fields' => 'ids',
957 ));
958 if (!empty($existing)) {
959 return $existing[0];
960 }
961
962 $post_type = $template_data['page_settings']['post_type'] ?? 'page';
963
964 $page_id = wp_insert_post(array(
965 'post_title' => $template_data['page_settings']['post_title'],
966 'post_type' => $post_type,
967 'post_status' => 'publish',
968 'post_content' => '',
969 'meta_input' => array(
970 '_elementor_edit_mode' => 'builder',
971 '_elementor_template_type' => 'wp-page',
972 '_elementor_version' => $template_data['version'],
973 '_elementor_data' => wp_slash(json_encode($template_data['content'])),
974 '_jltma_demo_import_item' => true,
975 '_jltma_import_kit_id' => $kit,
976 '_jltma_import_date' => current_time('mysql'),
977 '_wp_page_template' => 'elementor_canvas',
978 )
979 ));
980
981 if (is_wp_error($page_id)) {
982 return false;
983 }
984
985 // Generate Elementor CSS for the imported page
986 if (class_exists('\Elementor\Core\Files\CSS\Post')) {
987 $css_file = new \Elementor\Core\Files\CSS\Post($page_id);
988 $css_file->update();
989 }
990
991 return $page_id;
992 }
993
994 /**
995 * Import Global Settings from API
996 */
997 public function import_global_settings_from_api($global_settings)
998 {
999 if (isset($global_settings['colors'])) {
1000 $elementor_scheme = get_option('elementor_scheme_color', array());
1001
1002 $color_mapping = array(
1003 'primary' => '1',
1004 'secondary' => '2',
1005 'accent' => '3'
1006 );
1007
1008 foreach ($global_settings['colors'] as $color_name => $color_value) {
1009 if (isset($color_mapping[$color_name])) {
1010 $elementor_scheme[$color_mapping[$color_name]] = $color_value;
1011 }
1012 }
1013
1014 update_option('elementor_scheme_color', $elementor_scheme);
1015 }
1016
1017 if (isset($global_settings['typography'])) {
1018 $elementor_scheme = get_option('elementor_scheme_typography', array());
1019
1020 if (isset($global_settings['typography']['primary_font'])) {
1021 $elementor_scheme['1'] = array(
1022 'font_family' => $global_settings['typography']['primary_font'],
1023 'font_weight' => '400'
1024 );
1025 }
1026
1027 if (isset($global_settings['typography']['secondary_font'])) {
1028 $elementor_scheme['2'] = array(
1029 'font_family' => $global_settings['typography']['secondary_font'],
1030 'font_weight' => '400'
1031 );
1032 }
1033
1034 update_option('elementor_scheme_typography', $elementor_scheme);
1035 }
1036 }
1037
1038 /**
1039 * Reset Previous Import
1040 */
1041 public function reset_previous_import()
1042 {
1043 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
1044
1045 if (!wp_verify_nonce($nonce, 'jltma-template-kits-js') || !current_user_can('manage_options')) {
1046 exit;
1047 }
1048
1049 $args = [
1050 'post_type' => [
1051 'page',
1052 'post',
1053 'product',
1054 'elementor_library',
1055 'attachment',
1056 ],
1057 'post_status' => 'any',
1058 'posts_per_page' => '-1',
1059 'meta_key' => '_jltma_demo_import_item'
1060 ];
1061
1062 $imported_items = new \WP_Query($args);
1063
1064 if ($imported_items->have_posts()) {
1065 while ($imported_items->have_posts()) {
1066 $imported_items->the_post();
1067
1068 if ('Default Kit' == get_the_title()) {
1069 continue;
1070 }
1071
1072 wp_delete_post(get_the_ID(), true);
1073 }
1074
1075 wp_reset_query();
1076
1077 $imported_terms = get_terms([
1078 'meta_key' => '_jltma_demo_import_item',
1079 'posts_per_page' => -1,
1080 'hide_empty' => false,
1081 ]);
1082
1083 if (!empty($imported_terms)) {
1084 foreach ($imported_terms as $imported_term) {
1085 wp_delete_term($imported_term->term_id, $imported_term->taxonomy);
1086 }
1087 }
1088
1089 wp_send_json_success(esc_html__('Previous Import Files have been successfully Reset.', 'master-addons'));
1090 } else {
1091 wp_send_json_success(esc_html__('There is no Data for Reset.', 'master-addons'));
1092 }
1093 }
1094
1095 /**
1096 * Final Settings Setup
1097 */
1098 public function final_settings_setup()
1099 {
1100 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
1101
1102 if (!wp_verify_nonce($nonce, 'jltma-template-kits-js') || !current_user_can('manage_options')) {
1103 exit;
1104 }
1105
1106 $kit = !empty(get_option('jltma-import-kit-id')) ? esc_html(get_option('jltma-import-kit-id')) : '';
1107
1108 $this->import_elementor_site_settings($kit);
1109 $this->setup_templates($kit);
1110 $this->fix_elementor_images();
1111
1112 delete_option('jltma-import-kit-id');
1113
1114 $post = get_page_by_path('hello-world', OBJECT, 'post');
1115 if ($post) {
1116 wp_delete_post($post->ID, true);
1117 }
1118
1119 wp_send_json_success();
1120 }
1121
1122 /**
1123 * Activate Template Kit as Global Kit
1124 */
1125 public function activate_template_as_global_kit($template_id)
1126 {
1127 if (!$template_id || is_wp_error($template_id)) {
1128 return false;
1129 }
1130
1131 $template_type = get_post_meta($template_id, '_elementor_template_type', true);
1132 if ($template_type !== 'kit') {
1133 return false;
1134 }
1135
1136 update_option('elementor_active_kit', $template_id);
1137
1138 if (class_exists('\Elementor\Plugin')) {
1139 \Elementor\Plugin::$instance->files_manager->clear_cache();
1140 }
1141
1142 return true;
1143 }
1144
1145 /**
1146 * Import Elementor Site Settings
1147 */
1148 public function import_elementor_site_settings($kit)
1149 {
1150 update_option('elementor_experiment-e_local_google_fonts', 'inactive');
1151
1152 $response = wp_remote_get('https://master-addons.com/templates-kit/' . rawurlencode($kit) . '/site-settings.json', array('timeout' => 60));
1153
1154 if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
1155 $site_settings = json_decode(wp_remote_retrieve_body($response), true);
1156
1157 if (!empty($site_settings['settings'])) {
1158 $default_kit = \Elementor\Plugin::$instance->documents->get_doc_for_frontend(get_option('elementor_active_kit'));
1159
1160 $kit_settings = $default_kit->get_settings();
1161 $new_settings = $site_settings['settings'];
1162 $settings = array_merge($kit_settings, $new_settings);
1163
1164 $default_kit->save(['settings' => $settings]);
1165 }
1166 }
1167 }
1168
1169 /**
1170 * Setup Templates
1171 */
1172 public function setup_templates($kit)
1173 {
1174 $home_page = get_page_by_path('home-' . $kit);
1175 $blog_page = get_page_by_path('blog-' . $kit);
1176
1177 if ($home_page) {
1178 update_option('show_on_front', 'page');
1179 update_option('page_on_front', $home_page->ID);
1180
1181 if ($blog_page) {
1182 update_option('page_for_posts', $blog_page->ID);
1183 }
1184 }
1185 }
1186
1187 /**
1188 * Fix Elementor Images
1189 */
1190 public function fix_elementor_images()
1191 {
1192 $args = array(
1193 'post_type' => ['page', 'elementor_library'],
1194 'posts_per_page' => '-1',
1195 'meta_key' => '_elementor_version'
1196 );
1197 $elementor_pages = new \WP_Query($args);
1198
1199 if ($elementor_pages->have_posts()) {
1200 while ($elementor_pages->have_posts()) {
1201 $elementor_pages->the_post();
1202
1203 $site_url = get_site_url();
1204 $site_url = str_replace('/', '\/', $site_url);
1205 $demo_site_url = 'https://el.master-addons.com/' . get_option('jltma-import-kit-id');
1206 $demo_site_url = str_replace('/', '\/', $demo_site_url);
1207
1208 $data = get_post_meta(get_the_ID(), '_elementor_data', true);
1209
1210 if (!empty($data)) {
1211 if (is_string($data)) {
1212 $data = preg_replace('/\\\{1}\/sites\\\{1}\/\d+/', '', $data);
1213 $data = str_replace($demo_site_url, $site_url, $data);
1214 $data = json_decode($data, true);
1215 } elseif (is_array($data)) {
1216 $data_string = wp_json_encode($data);
1217 $data_string = preg_replace('/\\\{1}\/sites\\\{1}\/\d+/', '', $data_string);
1218 $data_string = str_replace($demo_site_url, $site_url, $data_string);
1219 $data = json_decode($data_string, true);
1220 }
1221 }
1222
1223 if (is_array($data)) {
1224 $data = $this->sanitize_elementor_data($data);
1225 }
1226
1227 update_metadata('post', get_the_ID(), '_elementor_data', $data);
1228
1229 $page_settings = get_post_meta(get_the_ID(), '_elementor_page_settings', true);
1230 $page_settings = json_encode($page_settings);
1231
1232 if (!empty($page_settings)) {
1233 $page_settings = preg_replace('/\\\{1}\/sites\\\{1}\/\d+/', '', $page_settings);
1234 $page_settings = str_replace($demo_site_url, $site_url, $page_settings);
1235 $page_settings = json_decode($page_settings, true);
1236 }
1237
1238 update_metadata('post', get_the_ID(), '_elementor_page_settings', $page_settings);
1239 }
1240 }
1241
1242 Plugin::$instance->files_manager->clear_cache();
1243 }
1244
1245 /**
1246 * Set Timeout for Image Request
1247 */
1248 public function set_image_request_timeout($timeout_value, $url)
1249 {
1250 if (strpos($url, 'https://master-addons.com/') === false) {
1251 return $timeout_value;
1252 }
1253
1254 $valid_ext = preg_match('/^((https?:\/\/)|(www\.))([a-z0-9-].?)+(:[0-9]+)?\/[\w\-\@]+\.(jpg|png|gif|jpeg|svg)\/?$/i', $url);
1255
1256 if ($valid_ext) {
1257 $timeout_value = 300;
1258 }
1259
1260 return $timeout_value;
1261 }
1262
1263 /**
1264 * Prevent WooCommerce creating default pages
1265 */
1266 public function disable_default_woo_pages_creation()
1267 {
1268 add_filter('woocommerce_create_pages', '__return_empty_array');
1269 }
1270
1271 /**
1272 * Sanitize Elementor Data to prevent warnings
1273 */
1274 public function sanitize_elementor_data($data)
1275 {
1276 if (!is_array($data)) {
1277 return $data;
1278 }
1279
1280 foreach ($data as &$element) {
1281 if (isset($element['elType']) && $element['elType'] === 'column') {
1282 if (!isset($element['settings']['_column_size'])) {
1283 $element['settings']['_column_size'] = 100;
1284 }
1285 }
1286
1287 if (isset($element['settings']) && is_array($element['settings'])) {
1288 $element['settings'] = $this->sanitize_element_settings($element['settings']);
1289 }
1290
1291 if (isset($element['elements']) && is_array($element['elements'])) {
1292 $element['elements'] = $this->sanitize_elementor_data($element['elements']);
1293 }
1294 }
1295
1296 return $data;
1297 }
1298
1299 /**
1300 * Sanitize Element Settings (handles images and other settings)
1301 */
1302 public function sanitize_element_settings($settings)
1303 {
1304 if (!is_array($settings)) {
1305 return $settings;
1306 }
1307
1308 $image_settings = [
1309 'image', 'background_image', 'hover_image', 'bg_image', 'icon_image',
1310 'gallery', 'images', 'slide_image', 'background_overlay_image',
1311 'testimonial_image', 'team_image', 'portfolio_image'
1312 ];
1313
1314 foreach ($settings as $key => &$value) {
1315 if (in_array($key, $image_settings) && is_array($value)) {
1316 if (!isset($value['id']) || empty($value['id'])) {
1317 $value['id'] = 0;
1318 }
1319 if (!isset($value['url'])) {
1320 $value['url'] = '';
1321 }
1322 }
1323
1324 if ($key === 'gallery' && is_array($value)) {
1325 foreach ($value as &$gallery_item) {
1326 if (is_array($gallery_item) && !isset($gallery_item['id'])) {
1327 $gallery_item['id'] = 0;
1328 }
1329 if (is_array($gallery_item) && !isset($gallery_item['url'])) {
1330 $gallery_item['url'] = '';
1331 }
1332 }
1333 }
1334
1335 if (is_array($value) && !in_array($key, $image_settings)) {
1336 $value = $this->sanitize_element_settings($value);
1337 }
1338 }
1339
1340 return $settings;
1341 }
1342
1343 /**
1344 * Search Query Results
1345 */
1346 public function search_query_results()
1347 {
1348 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
1349
1350 if (!wp_verify_nonce($nonce, 'jltma-template-kits-js') || !current_user_can('manage_options')) {
1351 exit;
1352 }
1353
1354 $search_query = isset($_POST['search_query']) ? sanitize_text_field(wp_unslash($_POST['search_query'])) : '';
1355 // Log search queries for analytics (optional)
1356 }
1357
1358 /**
1359 * Real mime types for WP 5.1.0+
1360 */
1361 public function real_mime_types_5_1_0($defaults, $file, $filename, $mimes, $real_mime)
1362 {
1363 return $this->real_mimes($defaults, $filename);
1364 }
1365
1366 /**
1367 * Real mime types for older WP
1368 */
1369 public function real_mime_types($defaults, $file, $filename, $mimes)
1370 {
1371 return $this->real_mimes($defaults, $filename);
1372 }
1373
1374 /**
1375 * Real mimes helper
1376 */
1377 public function real_mimes($defaults, $filename)
1378 {
1379 if (strpos($filename, 'main') !== false) {
1380 $defaults['ext'] = 'xml';
1381 $defaults['type'] = 'text/xml';
1382 }
1383
1384 return $defaults;
1385 }
1386
1387 /**
1388 * Import images to media library and replace URLs with attachment IDs
1389 */
1390 public function import_images_to_media($content, $kit_id, $kit_name = '')
1391 {
1392 if (is_string($content)) {
1393 $content = json_decode($content, true);
1394 }
1395
1396 if (!is_array($content)) {
1397 return $content;
1398 }
1399
1400 foreach ($content as &$element) {
1401 if (isset($element['settings'])) {
1402 $element['settings'] = $this->import_settings_images($element['settings'], $kit_id, $kit_name);
1403 }
1404
1405 if (isset($element['elements']) && is_array($element['elements'])) {
1406 $element['elements'] = $this->import_images_to_media($element['elements'], $kit_id, $kit_name);
1407 }
1408 }
1409
1410 return $content;
1411 }
1412
1413 /**
1414 * Replace image URLs with cached versions (deprecated - kept for compatibility)
1415 */
1416 public function replace_with_cached_images($content, $kit_id, $cache_manager)
1417 {
1418 return $this->import_images_to_media($content, $kit_id);
1419 }
1420
1421 /**
1422 * Import images in element settings to media library
1423 */
1424 public function import_settings_images($settings, $kit_id, $kit_name = '')
1425 {
1426 if (!is_array($settings)) {
1427 return $settings;
1428 }
1429
1430 $image_settings = [
1431 'image', 'background_image', 'hover_image', 'bg_image', 'icon_image',
1432 'gallery', 'images', 'slide_image', 'background_overlay_image',
1433 'testimonial_image', 'team_image', 'portfolio_image', 'logo_image',
1434 'before_image', 'after_image', 'author_image', 'product_image',
1435 'featured_image', 'fallback_image', 'mobile_image', 'tablet_image'
1436 ];
1437
1438 foreach ($settings as $key => &$value) {
1439 if (in_array($key, $image_settings) && is_array($value) && isset($value['url'])) {
1440 $attachment_data = $this->import_single_image($value['url'], $kit_id, $kit_name);
1441 if ($attachment_data) {
1442 $value['id'] = $attachment_data['id'];
1443 $value['url'] = $attachment_data['url'];
1444 }
1445 }
1446
1447 if ($key === 'gallery' && is_array($value)) {
1448 foreach ($value as &$gallery_item) {
1449 if (is_array($gallery_item) && isset($gallery_item['url'])) {
1450 $attachment_data = $this->import_single_image($gallery_item['url'], $kit_id, $kit_name);
1451 if ($attachment_data) {
1452 $gallery_item['id'] = $attachment_data['id'];
1453 $gallery_item['url'] = $attachment_data['url'];
1454 }
1455 }
1456 }
1457 }
1458
1459 if (is_array($value) && !in_array($key, $image_settings)) {
1460 foreach ($value as &$repeater_item) {
1461 if (is_array($repeater_item)) {
1462 $repeater_item = $this->import_settings_images($repeater_item, $kit_id, $kit_name);
1463 }
1464 }
1465 }
1466
1467 if (is_string($value) && (strpos($key, 'css') !== false || strpos($key, 'style') !== false)) {
1468 $value = $this->import_css_images($value, $kit_id, $kit_name);
1469 }
1470 }
1471
1472 return $settings;
1473 }
1474
1475 /**
1476 * Import images from CSS strings
1477 */
1478 public function import_css_images($css_string, $kit_id, $kit_name = '')
1479 {
1480 if (empty($css_string)) {
1481 return $css_string;
1482 }
1483
1484 preg_match_all('/url\([\'"]?([^\'")]+)[\'"]?\)/i', $css_string, $matches);
1485
1486 if (!empty($matches[1])) {
1487 foreach ($matches[1] as $url) {
1488 if (strpos($url, 'data:') === 0 || strpos($url, 'http') !== 0) {
1489 continue;
1490 }
1491
1492 if (preg_match('/\.(jpg|jpeg|png|gif|svg|webp)$/i', $url)) {
1493 $attachment_data = $this->import_single_image($url, $kit_id, $kit_name);
1494 if ($attachment_data) {
1495 $css_string = str_replace($url, $attachment_data['url'], $css_string);
1496 }
1497 }
1498 }
1499 }
1500
1501 return $css_string;
1502 }
1503
1504 /**
1505 * Import a single image to media library
1506 */
1507 public function import_single_image($image_url, $kit_id, $kit_name = '')
1508 {
1509 if (empty($image_url) || !filter_var($image_url, FILTER_VALIDATE_URL)) {
1510 return false;
1511 }
1512
1513 if (isset($this->imported_images[$image_url])) {
1514 return $this->imported_images[$image_url];
1515 }
1516
1517 $existing_attachment = $this->get_attachment_by_url($image_url);
1518 if ($existing_attachment) {
1519 $attachment_data = [
1520 'id' => $existing_attachment,
1521 'url' => wp_get_attachment_url($existing_attachment)
1522 ];
1523 $this->imported_images[$image_url] = $attachment_data;
1524 return $attachment_data;
1525 }
1526
1527 require_once(ABSPATH . 'wp-admin/includes/media.php');
1528 require_once(ABSPATH . 'wp-admin/includes/file.php');
1529 require_once(ABSPATH . 'wp-admin/includes/image.php');
1530
1531 $tmp = download_url($image_url, 300);
1532
1533 if (is_wp_error($tmp)) {
1534 $response = wp_remote_get($image_url, [
1535 'timeout' => 300,
1536 'sslverify' => false
1537 ]);
1538
1539 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1540 return false;
1541 }
1542
1543 $image_data = wp_remote_retrieve_body($response);
1544 $tmp = wp_tempnam();
1545 file_put_contents($tmp, $image_data); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- writing to a wp_tempnam() temp file that media_handle_sideload() then processes
1546
1547 if (!file_exists($tmp) || filesize($tmp) === 0) {
1548 wp_delete_file($tmp);
1549 return false;
1550 }
1551 }
1552
1553 $file_array = [];
1554 $file_array['name'] = basename($image_url);
1555
1556 if (!pathinfo($file_array['name'], PATHINFO_EXTENSION)) {
1557 $file_info = wp_check_filetype($tmp);
1558 if ($file_info['ext']) {
1559 $file_array['name'] = 'image-' . uniqid() . '.' . $file_info['ext'];
1560 }
1561 }
1562
1563 if (!empty($kit_name)) {
1564 $file_array['name'] = sanitize_title($kit_name) . '-' . $file_array['name'];
1565 }
1566
1567 $file_array['tmp_name'] = $tmp;
1568
1569 $attachment_id = media_handle_sideload($file_array, 0, null, [
1570 'post_title' => !empty($kit_name) ? $kit_name . ' - ' . pathinfo($file_array['name'], PATHINFO_FILENAME) : pathinfo($file_array['name'], PATHINFO_FILENAME),
1571 'post_content' => '',
1572 'post_status' => 'inherit'
1573 ]);
1574
1575 wp_delete_file($tmp);
1576
1577 if (is_wp_error($attachment_id)) {
1578 return false;
1579 }
1580
1581 update_post_meta($attachment_id, '_jltma_imported_from_kit', $kit_id);
1582 update_post_meta($attachment_id, '_jltma_original_url', $image_url);
1583 update_post_meta($attachment_id, '_jltma_import_date', current_time('mysql'));
1584 if (!empty($kit_name)) {
1585 update_post_meta($attachment_id, '_jltma_kit_name', $kit_name);
1586 }
1587
1588 $alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
1589 if (empty($alt_text)) {
1590 update_post_meta($attachment_id, '_wp_attachment_image_alt', $kit_name ?: 'Template Image');
1591 }
1592
1593 $attachment_data = [
1594 'id' => $attachment_id,
1595 'url' => wp_get_attachment_url($attachment_id)
1596 ];
1597
1598 $this->imported_images[$image_url] = $attachment_data;
1599
1600 return $attachment_data;
1601 }
1602
1603 /**
1604 * Get attachment ID by URL
1605 */
1606 public function get_attachment_by_url($url)
1607 {
1608 global $wpdb;
1609
1610 $clean_url = str_replace(['https://', 'http://'], '', $url);
1611
1612 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for attachment lookup by guid without core API equivalent
1613 $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type='attachment';", $url));
1614 if (!empty($attachment)) {
1615 return $attachment[0];
1616 }
1617
1618 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for attachment lookup by normalised guid without core API equivalent
1619 $attachment = $wpdb->get_col($wpdb->prepare(
1620 "SELECT ID FROM $wpdb->posts WHERE (REPLACE(REPLACE(guid, 'https://', ''), 'http://', '') = %s) AND post_type='attachment' LIMIT 1;",
1621 $clean_url
1622 ));
1623 if (!empty($attachment)) {
1624 return $attachment[0];
1625 }
1626
1627 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for attachment lookup by custom meta without core API equivalent
1628 $attachment = $wpdb->get_col($wpdb->prepare(
1629 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_jltma_original_url' AND meta_value=%s LIMIT 1;",
1630 $url
1631 ));
1632 if (!empty($attachment)) {
1633 return $attachment[0];
1634 }
1635
1636 return false;
1637 }
1638
1639 /**
1640 * Clean up orphaned template images
1641 */
1642 public function cleanup_template_images()
1643 {
1644 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1645 wp_send_json_error(['message' => 'Permission denied']);
1646 return;
1647 }
1648
1649 global $wpdb;
1650
1651 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for bulk meta key lookup without core API equivalent
1652 $template_attachments = $wpdb->get_col(
1653 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_jltma_imported_from_kit'"
1654 );
1655
1656 $deleted_count = 0;
1657 $kept_count = 0;
1658
1659 foreach ($template_attachments as $attachment_id) {
1660 $is_used = false;
1661
1662 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for bulk content LIKE search without core API equivalent
1663 $used_in_content = $wpdb->get_var($wpdb->prepare(
1664 "SELECT COUNT(*) FROM $wpdb->posts WHERE post_content LIKE %s OR post_content LIKE %s",
1665 '%' . $wpdb->esc_like( 'wp-image-' . $attachment_id ) . '%',
1666 '%' . $wpdb->esc_like( 'attachment_' . $attachment_id ) . '%'
1667 ));
1668
1669 if ($used_in_content > 0) {
1670 $is_used = true;
1671 }
1672
1673 if (!$is_used) {
1674 $attachment_url = wp_get_attachment_url($attachment_id);
1675 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for Elementor meta LIKE search without core API equivalent
1676 $used_in_elementor = $wpdb->get_var($wpdb->prepare(
1677 "SELECT COUNT(*) FROM $wpdb->postmeta WHERE meta_key='_elementor_data' AND meta_value LIKE %s",
1678 '%"id":' . $attachment_id . '%'
1679 ));
1680
1681 if ($used_in_elementor > 0) {
1682 $is_used = true;
1683 }
1684 }
1685
1686 if (!$is_used) {
1687 wp_delete_attachment($attachment_id, true);
1688 $deleted_count++;
1689 } else {
1690 $kept_count++;
1691 }
1692 }
1693
1694 wp_send_json_success([
1695 'message' => sprintf('Cleanup complete: %d orphaned images deleted, %d in use kept', $deleted_count, $kept_count),
1696 'deleted' => $deleted_count,
1697 'kept' => $kept_count
1698 ]);
1699 }
1700
1701 /**
1702 * Pre-import all images for a template kit to media library
1703 */
1704 public function preimport_kit_images()
1705 {
1706 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1707 wp_send_json_error(['message' => 'Permission denied']);
1708 return;
1709 }
1710
1711 $kit_id = isset($_POST['kit_id']) ? sanitize_text_field( wp_unslash( $_POST['kit_id'] ) ) : '';
1712 $template_data = isset($_POST['template_data']) ? sanitize_textarea_field( wp_unslash( $_POST['template_data'] ) ) : '';
1713
1714 if (empty($kit_id)) {
1715 wp_send_json_error(['message' => 'Kit ID is required']);
1716 return;
1717 }
1718
1719 if (!empty($template_data)) {
1720 $decoded = json_decode($template_data, true);
1721 if ($decoded && isset($decoded['content'])) {
1722 $image_urls = $this->extract_all_image_urls($decoded['content']);
1723 $imported_count = 0;
1724
1725 foreach ($image_urls as $url) {
1726 $result = $this->import_single_image($url, $kit_id, $decoded['title'] ?? '');
1727 if ($result) {
1728 $imported_count++;
1729 }
1730 }
1731
1732 wp_send_json_success([
1733 'message' => sprintf('Successfully imported %d images to media library', $imported_count),
1734 'image_count' => $imported_count,
1735 'imported_images' => $this->imported_images
1736 ]);
1737 }
1738 }
1739
1740 wp_send_json_error(['message' => 'Invalid template data']);
1741 }
1742
1743 /**
1744 * Extract all image URLs from Elementor content
1745 */
1746 public function extract_all_image_urls($content, &$urls = [])
1747 {
1748 if (is_string($content)) {
1749 $content = json_decode($content, true);
1750 }
1751
1752 if (!is_array($content)) {
1753 return $urls;
1754 }
1755
1756 foreach ($content as $element) {
1757 if (isset($element['settings']) && is_array($element['settings'])) {
1758 $this->extract_urls_from_settings($element['settings'], $urls);
1759 }
1760
1761 if (isset($element['elements']) && is_array($element['elements'])) {
1762 $this->extract_all_image_urls($element['elements'], $urls);
1763 }
1764 }
1765
1766 return array_unique($urls);
1767 }
1768
1769 /**
1770 * Extract image URLs from element settings
1771 */
1772 public function extract_urls_from_settings($settings, &$urls)
1773 {
1774 if (!is_array($settings)) {
1775 return;
1776 }
1777
1778 foreach ($settings as $key => $value) {
1779 if (is_array($value)) {
1780 if (isset($value['url']) && filter_var($value['url'], FILTER_VALIDATE_URL)) {
1781 $urls[] = $value['url'];
1782 }
1783 $this->extract_urls_from_settings($value, $urls);
1784 } elseif (is_string($value)) {
1785 if (preg_match_all('/(https?:\/\/[^\s"]+\.(?:jpg|jpeg|png|gif|svg|webp))/i', $value, $matches)) {
1786 $urls = array_merge($urls, $matches[1]);
1787 }
1788 }
1789 }
1790 }
1791
1792 /**
1793 * Pre-cache all images for a template kit (deprecated - kept for compatibility)
1794 */
1795 public function precache_kit_images()
1796 {
1797 $this->preimport_kit_images();
1798 }
1799
1800 /**
1801 * Pre-download template kit for faster import
1802 */
1803 public function predownload_kit()
1804 {
1805 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1806 wp_send_json_error(['message' => 'Permission denied']);
1807 return;
1808 }
1809
1810 $kit_id = isset($_POST['kit_id']) ? sanitize_text_field( wp_unslash( $_POST['kit_id'] ) ) : '';
1811
1812 if (empty($kit_id)) {
1813 wp_send_json_error(['message' => 'Kit ID is required']);
1814 return;
1815 }
1816
1817 $cache_manager = null;
1818 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
1819 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
1820 }
1821
1822 if (!$cache_manager) {
1823 wp_send_json_error(['message' => 'Cache manager not available']);
1824 return;
1825 }
1826
1827 $result = $cache_manager->download_and_cache_kit($kit_id);
1828
1829 if ($result && $result['success']) {
1830 $template_count = 0;
1831 if (isset($result['manifest']['pages']) && is_array($result['manifest']['pages'])) {
1832 $template_count = count($result['manifest']['pages']);
1833 }
1834
1835 wp_send_json_success([
1836 'message' => sprintf('Kit pre-downloaded successfully with %d templates', $template_count),
1837 'kit_id' => $kit_id,
1838 'template_count' => $template_count,
1839 'cached' => true
1840 ]);
1841 } else {
1842 wp_send_json_error(['message' => 'Failed to download kit']);
1843 }
1844 }
1845
1846 /**
1847 * Download all template kits for offline use
1848 */
1849 public function download_all_kits()
1850 {
1851 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1852 wp_send_json_error(['message' => 'Permission denied']);
1853 return;
1854 }
1855
1856 $cache_manager = null;
1857 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
1858 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
1859 }
1860
1861 if (!$cache_manager) {
1862 wp_send_json_error(['message' => 'Cache manager not available']);
1863 return;
1864 }
1865
1866 $stats = $cache_manager->download_all_kits();
1867
1868 wp_send_json_success([
1869 'message' => sprintf(
1870 'Download complete: %d downloaded, %d skipped, %d failed out of %d total kits',
1871 $stats['downloaded'],
1872 $stats['skipped'],
1873 $stats['failed'],
1874 $stats['total']
1875 ),
1876 'stats' => $stats
1877 ]);
1878 }
1879
1880 /**
1881 * Install required plugin for template kit
1882 */
1883 public function install_required_plugin()
1884 {
1885 $wpnonce_val = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
1886 $valid_nonce = wp_verify_nonce($wpnonce_val, 'jltma_template_kits_nonce_action') ||
1887 wp_verify_nonce($wpnonce_val, 'jltma_template_library_nonce');
1888
1889 if (!$valid_nonce || !current_user_can('install_plugins')) {
1890 wp_send_json_error(['message' => 'Permission denied']);
1891 return;
1892 }
1893
1894 $plugin_name = isset($_POST['plugin_name']) ? sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ) : '';
1895 $plugin_file = isset($_POST['plugin_file']) ? sanitize_text_field( wp_unslash( $_POST['plugin_file'] ) ) : '';
1896 $plugin_slug = $this->get_plugin_slug($plugin_file);
1897
1898 if (empty($plugin_slug)) {
1899 wp_send_json_error(['message' => 'Plugin slug is required']);
1900 return;
1901 }
1902
1903 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1904 require_once ABSPATH . 'wp-admin/includes/file.php';
1905 require_once ABSPATH . 'wp-admin/includes/misc.php';
1906 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1907 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
1908
1909 if (empty($plugin_file)) {
1910 $plugin_file = $plugin_slug . '/' . $plugin_slug . '.php';
1911 }
1912
1913 $installed_plugins = get_plugins();
1914 if (isset($installed_plugins[$plugin_file])) {
1915 $activate_result = activate_plugin($plugin_file);
1916
1917 if (is_wp_error($activate_result)) {
1918 wp_send_json_error([
1919 'message' => 'Plugin installed but activation failed: ' . $activate_result->get_error_message()
1920 ]);
1921 } else {
1922 wp_send_json_success([
1923 'message' => sprintf('%s has been activated successfully!', $plugin_name ?: $plugin_slug),
1924 'is_active' => true
1925 ]);
1926 }
1927 return;
1928 }
1929
1930 $api = plugins_api('plugin_information', [
1931 'slug' => $plugin_slug,
1932 'fields' => [
1933 'sections' => false,
1934 'tags' => false
1935 ]
1936 ]);
1937
1938 if (is_wp_error($api)) {
1939 if ($api->get_error_message() === "Plugin not found.") {
1940 $plugin_file_parts = explode('/', $plugin_file);
1941 if ($plugin_file_parts[0] !== $plugin_file_parts[1]) {
1942 $plugin_slug = $plugin_file_parts[0];
1943 }
1944 }
1945 $new_api = plugins_api('plugin_information', [
1946 'slug' => $plugin_slug,
1947 'fields' => [
1948 'sections' => false,
1949 'tags' => false
1950 ]
1951 ]);
1952 if (is_wp_error($new_api)) {
1953 wp_send_json_error([
1954 'message' => 'Failed to get plugin information: ',
1955 'details' => $api->get_error_message()
1956 ]);
1957 return;
1958 } else {
1959 $api = $new_api;
1960 }
1961 }
1962
1963 $skin = new \WP_Ajax_Upgrader_Skin();
1964 $upgrader = new \Plugin_Upgrader($skin);
1965
1966 $result = $upgrader->install($api->download_link);
1967
1968 if (is_wp_error($result)) {
1969 wp_send_json_error([
1970 'message' => 'Installation failed: ' . $result->get_error_message()
1971 ]);
1972 return;
1973 }
1974
1975 if (!$result) {
1976 wp_send_json_error([
1977 'message' => 'Installation failed for unknown reason'
1978 ]);
1979 return;
1980 }
1981
1982 $installed_plugins = get_plugins();
1983 $plugin_installed = false;
1984
1985 foreach ($installed_plugins as $file => $plugin) {
1986 if (strpos($file, $plugin_slug . '/') === 0 || $file === $plugin_file) {
1987 $plugin_file = $file;
1988 $plugin_installed = true;
1989 break;
1990 }
1991 }
1992
1993 if (!$plugin_installed) {
1994 wp_send_json_error([
1995 'message' => 'Plugin installed but could not be found'
1996 ]);
1997 return;
1998 }
1999
2000 $activate_result = activate_plugin($plugin_file);
2001
2002 if (is_wp_error($activate_result)) {
2003 wp_send_json_success([
2004 'message' => sprintf('%s has been installed successfully but needs manual activation.', $plugin_name ?: $plugin_slug),
2005 'is_active' => false
2006 ]);
2007 } else {
2008 wp_send_json_success([
2009 'message' => sprintf('%s has been installed and activated successfully!', $plugin_name ?: $plugin_slug),
2010 'is_active' => true
2011 ]);
2012 }
2013 }
2014
2015 /**
2016 * Activate required plugin for template kit
2017 */
2018 public function activate_required_plugin()
2019 {
2020 $wpnonce_activate = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
2021 $valid_nonce = wp_verify_nonce($wpnonce_activate, 'jltma_template_kits_nonce_action') ||
2022 wp_verify_nonce($wpnonce_activate, 'jltma_template_library_nonce');
2023
2024 if (!$valid_nonce || !current_user_can('activate_plugins')) {
2025 wp_send_json_error(['message' => 'Permission denied']);
2026 return;
2027 }
2028
2029 $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field( wp_unslash( $_POST['plugin_slug'] ) ) : '';
2030 $plugin_file = isset($_POST['plugin_file']) ? sanitize_text_field( wp_unslash( $_POST['plugin_file'] ) ) : '';
2031
2032 if (empty($plugin_slug) && empty($plugin_file)) {
2033 wp_send_json_error(['message' => 'Plugin slug is required']);
2034 return;
2035 }
2036
2037 if (!is_string($plugin_slug)) {
2038 $file_name = explode('/', $plugin_file)[1];
2039 $plugin_slug = explode('.', $file_name)[0];
2040 }
2041
2042 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2043
2044 if (empty($plugin_file) && $plugin_file !== 'theme') {
2045 $plugin_file = $plugin_slug . '/' . $plugin_slug . '.php';
2046 }
2047
2048 if ($plugin_file === 'theme') {
2049 $theme = wp_get_theme($plugin_slug);
2050
2051 if (!$theme->exists()) {
2052 wp_send_json_error(['message' => 'Theme not found. Please install it first.']);
2053 return;
2054 }
2055
2056 switch_theme($plugin_slug);
2057
2058 wp_send_json_success([
2059 'message' => ucfirst(str_replace('-', ' ', $plugin_slug)) . ' theme has been activated successfully!',
2060 'is_active' => true
2061 ]);
2062 return;
2063 }
2064
2065 $installed_plugins = get_plugins();
2066 if (!isset($installed_plugins[$plugin_file])) {
2067 $plugin_found = false;
2068 foreach ($installed_plugins as $file => $plugin) {
2069 if (strpos($file, $plugin_slug . '/') === 0) {
2070 $plugin_file = $file;
2071 $plugin_found = true;
2072 break;
2073 }
2074 }
2075
2076 if (!$plugin_found) {
2077 wp_send_json_error(['message' => 'Plugin not found. Please install it first.']);
2078 return;
2079 }
2080 }
2081
2082 if (is_plugin_active($plugin_file)) {
2083 wp_send_json_success([
2084 'message' => 'Plugin is already active!',
2085 'is_active' => true
2086 ]);
2087 return;
2088 }
2089
2090 $result = activate_plugin($plugin_file);
2091
2092 if (is_wp_error($result)) {
2093 wp_send_json_error([
2094 'message' => 'Activation failed: ' . $result->get_error_message()
2095 ]);
2096 } else {
2097 wp_send_json_success([
2098 'message' => 'Plugin activated successfully!',
2099 'is_active' => true
2100 ]);
2101 }
2102 }
2103
2104 /**
2105 * Get plugin slug from plugin file
2106 */
2107 public function get_plugin_slug($plugin_file)
2108 {
2109 $without_extension = pathinfo($plugin_file, PATHINFO_FILENAME);
2110 $parts = explode('/', $without_extension);
2111 $filename = end($parts);
2112
2113 return $filename;
2114 }
2115
2116 /**
2117 * Handle Template Kit Upload
2118 */
2119 public function upload_template_kit()
2120 {
2121 $wpnonce_upload = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
2122 $valid_nonce = wp_verify_nonce($wpnonce_upload, 'jltma_template_kits_nonce_action') ||
2123 wp_verify_nonce($wpnonce_upload, 'jltma_template_library_nonce');
2124 if (!$valid_nonce || !current_user_can('upload_files')) {
2125 wp_send_json_error(['message' => 'Permission denied']);
2126 return;
2127 }
2128
2129 if (!isset($_FILES['kit_file']) || $_FILES['kit_file']['error'] !== UPLOAD_ERR_OK) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- 'kit_file' existence verified by isset above
2130 wp_send_json_error(['message' => 'No file uploaded or upload failed']);
2131 return;
2132 }
2133
2134 // ── Resource headroom ────────────────────────────────────────────
2135 // Extracting a multi-MB zip and reading dozens of JSON template
2136 // files in one request can easily hit the default 30s / 128M
2137 // limits on shared hosting. Request generous limits up-front so
2138 // the "Reading kit contents" step isn't dragged out by throttled
2139 // resources. Only bumps the ceiling if the host allows it — a
2140 // disabled ini_set or a lower hard cap simply becomes the limit.
2141 if (function_exists('set_time_limit')) {
2142 @set_time_limit(600);
2143 }
2144 if (function_exists('wp_raise_memory_limit')) {
2145 wp_raise_memory_limit('admin');
2146 }
2147 @ini_set('memory_limit', '512M');
2148 @ignore_user_abort(true);
2149
2150 $uploaded_file = $_FILES['kit_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- file upload array; individual keys sanitized before use
2151
2152 $file_type = wp_check_filetype($uploaded_file['name']);
2153 if ($file_type['ext'] !== 'zip') {
2154 wp_send_json_error(['message' => 'Please upload a valid ZIP file']);
2155 return;
2156 }
2157
2158 $cache_manager = null;
2159 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
2160 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
2161 }
2162
2163 if (!$cache_manager) {
2164 wp_send_json_error(['message' => 'Cache manager not available']);
2165 return;
2166 }
2167
2168 $file_name_without_ext = pathinfo($uploaded_file['name'], PATHINFO_FILENAME);
2169
2170 if (preg_match('/^kit[_-]?(\d+)$/i', $file_name_without_ext, $matches)) {
2171 $kit_id = $matches[1];
2172 } else {
2173 $kit_id = (string)(9000 + time() % 1000);
2174 }
2175
2176 global $wp_filesystem;
2177 if (!function_exists('WP_Filesystem')) {
2178 require_once ABSPATH . 'wp-admin/includes/file.php';
2179 }
2180 WP_Filesystem();
2181
2182 $upload_dir = wp_upload_dir();
2183 $kits_dir = $upload_dir['basedir'] . '/master_addons/purchased_kits/kits/kit_' . $kit_id;
2184
2185 if (!$wp_filesystem->exists($kits_dir)) {
2186 $wp_filesystem->mkdir($kits_dir, 0755, true);
2187 }
2188
2189 // Native PHP's ZipArchive is significantly faster than WP's
2190 // unzip_file() because it avoids the pclzip pure-PHP fallback,
2191 // streams file entries directly to disk, and doesn't double-buffer
2192 // each file in memory. Falls back to unzip_file() when the ext
2193 // isn't available (extremely rare on modern PHP).
2194 $unzip_ok = false;
2195 if (class_exists('ZipArchive')) {
2196 $zip = new \ZipArchive();
2197 if ($zip->open($uploaded_file['tmp_name']) === true) {
2198 $unzip_ok = $zip->extractTo($kits_dir);
2199 $zip->close();
2200 }
2201 }
2202 if (!$unzip_ok) {
2203 $unzip_result = unzip_file($uploaded_file['tmp_name'], $kits_dir);
2204 if (is_wp_error($unzip_result)) {
2205 $wp_filesystem->delete($kits_dir, true);
2206 wp_send_json_error(['message' => 'Failed to extract ZIP file: ' . $unzip_result->get_error_message()]);
2207 return;
2208 }
2209 }
2210
2211 $manifest_path = $kits_dir . '/manifest.json';
2212 $manifest = null;
2213
2214 if ($wp_filesystem->exists($manifest_path)) {
2215 $manifest_content = $wp_filesystem->get_contents($manifest_path);
2216 $manifest = json_decode($manifest_content, true);
2217 } else {
2218 $files = $wp_filesystem->dirlist($kits_dir);
2219 foreach ($files as $file) {
2220 if ($file['type'] === 'd') {
2221 $sub_manifest = $kits_dir . '/' . $file['name'] . '/manifest.json';
2222 if ($wp_filesystem->exists($sub_manifest)) {
2223 $sub_dir = $kits_dir . '/' . $file['name'];
2224 $sub_files = $wp_filesystem->dirlist($sub_dir);
2225 foreach ($sub_files as $sub_file) {
2226 $wp_filesystem->move($sub_dir . '/' . $sub_file['name'], $kits_dir . '/' . $sub_file['name']);
2227 }
2228 $wp_filesystem->delete($sub_dir, true);
2229
2230 $manifest_content = $wp_filesystem->get_contents($manifest_path);
2231 $manifest = json_decode($manifest_content, true);
2232 break;
2233 }
2234 }
2235 }
2236 }
2237
2238 if (!$manifest) {
2239 $wp_filesystem->delete($kits_dir, true);
2240 wp_send_json_error(['message' => 'Invalid template kit: manifest.json not found']);
2241 return;
2242 }
2243
2244 $required_manifest_fields = ['manifest_version', 'title', 'kit_version', 'templates'];
2245 $missing_fields = [];
2246
2247 foreach ($required_manifest_fields as $field) {
2248 if (!isset($manifest[$field])) {
2249 $missing_fields[] = $field;
2250 }
2251 }
2252
2253 if (!empty($missing_fields)) {
2254 $wp_filesystem->delete($kits_dir, true);
2255 wp_send_json_error(['message' => 'Invalid manifest.json. Missing required fields: ' . implode(', ', $missing_fields)]);
2256 return;
2257 }
2258
2259 if (!is_array($manifest['templates']) || empty($manifest['templates'])) {
2260 $wp_filesystem->delete($kits_dir, true);
2261 wp_send_json_error(['message' => 'Invalid template kit: templates array is empty or invalid']);
2262 return;
2263 }
2264
2265 $screenshots_dir = $kits_dir . '/screenshots';
2266 $templates_dir = $kits_dir . '/templates';
2267
2268 $missing_folders = [];
2269
2270 if (!$wp_filesystem->exists($screenshots_dir)) {
2271 $missing_folders[] = 'screenshots';
2272 }
2273
2274 if (!$wp_filesystem->exists($templates_dir)) {
2275 $missing_folders[] = 'templates';
2276 }
2277
2278 if (!empty($missing_folders)) {
2279 $wp_filesystem->delete($kits_dir, true);
2280 wp_send_json_error(['message' => 'Invalid template kit structure. Missing required folders: ' . implode(', ', $missing_folders)]);
2281 return;
2282 }
2283
2284 $template_count = 0;
2285 $templates = [];
2286
2287 $manifest_templates = $manifest['templates'];
2288 $template_count = count($manifest_templates);
2289
2290 foreach ($manifest_templates as $page) {
2291 $slug = $page['slug'] ?? $page['fileName'] ?? $page['name'] ?? '';
2292
2293 $template_file = $templates_dir . '/' . $slug . '.json';
2294 if (!$wp_filesystem->exists($template_file)) {
2295 $template_file = $templates_dir . '/' . $slug;
2296 }
2297
2298 if ($wp_filesystem->exists($template_file)) {
2299 $template_content = $wp_filesystem->get_contents($template_file);
2300 $template_data = json_decode($template_content, true);
2301
2302 $thumbnail = '';
2303 if (isset($page['thumbnail']) && !empty($page['thumbnail'])) {
2304 if (strpos($page['thumbnail'], 'http') !== 0) {
2305 $thumbnail_file = $screenshots_dir . '/' . basename($page['thumbnail']);
2306 if ($wp_filesystem->exists($thumbnail_file)) {
2307 $thumbnail = 'screenshots/' . basename($page['thumbnail']);
2308 } else {
2309 $thumbnail = $page['thumbnail'];
2310 }
2311 } else {
2312 $thumbnail = $page['thumbnail'];
2313 }
2314 }
2315
2316 $templates[] = [
2317 'id' => $page['id'] ?? $page['slug'] ?? $slug,
2318 'slug' => $slug,
2319 'title' => $page['title'] ?? $page['name'] ?? $slug,
2320 'thumbnail' => $thumbnail,
2321 'content' => $template_data['content'] ?? $template_data,
2322 'page_settings' => $template_data['page_settings'] ?? [],
2323 'type' => $page['type'] ?? 'page',
2324 'template_id' => $page['template_id'] ?? $page['id'] ?? ''
2325 ];
2326 }
2327 }
2328
2329 $kit_thumbnail = '';
2330 if (isset($manifest['thumbnail_url']) && !empty($manifest['thumbnail_url'])) {
2331 $kit_thumbnail = $manifest['thumbnail_url'];
2332 } elseif (isset($manifest['thumbnail']) && !empty($manifest['thumbnail'])) {
2333 if (strpos($manifest['thumbnail'], 'http') !== 0) {
2334 $thumbnail_path = str_replace('screenshots/', '', $manifest['thumbnail']);
2335 if ($wp_filesystem->exists($kits_dir . '/' . $thumbnail_path)) {
2336 $kit_thumbnail = $manifest['thumbnail'];
2337 }
2338 } else {
2339 $kit_thumbnail = $manifest['thumbnail'];
2340 }
2341 } elseif (!empty($templates) && isset($templates[0]['thumbnail'])) {
2342 $kit_thumbnail = $templates[0]['thumbnail'];
2343 }
2344
2345 $kit_name = isset($manifest['title']) && !empty($manifest['title']) ? $manifest['title'] : (
2346 isset($manifest['name']) && !empty($manifest['name']) ? $manifest['name'] : 'Kit ' . $kit_id
2347 );
2348
2349 $categories = ['purchased'];
2350 if (isset($manifest['categories'])) {
2351 if (is_array($manifest['categories'])) {
2352 $categories = array_merge($categories, $manifest['categories']);
2353 } elseif (is_string($manifest['categories']) && !empty($manifest['categories'])) {
2354 if (strpos($manifest['categories'], ',') !== false) {
2355 $cats = array_map('trim', explode(',', $manifest['categories']));
2356 $categories = array_merge($categories, $cats);
2357 } else {
2358 $categories[] = trim($manifest['categories']);
2359 }
2360 }
2361 }
2362 $categories = array_unique($categories);
2363
2364 $keywords = [];
2365 if (isset($manifest['keywords'])) {
2366 if (is_array($manifest['keywords'])) {
2367 $keywords = $manifest['keywords'];
2368 } elseif (is_string($manifest['keywords']) && !empty($manifest['keywords'])) {
2369 $keywords = array_map('trim', explode(',', $manifest['keywords']));
2370 $keywords = array_filter($keywords);
2371 }
2372 }
2373
2374 $required_plugins = [];
2375 if (isset($manifest['required_plugins']) && is_array($manifest['required_plugins'])) {
2376 $required_plugins = $manifest['required_plugins'];
2377 } elseif (isset($manifest['requirements']) && is_array($manifest['requirements'])) {
2378 $required_plugins = $manifest['requirements'];
2379 }
2380
2381 $kit_data = [
2382 'kit_id' => $kit_id,
2383 'template_id' => $kit_id,
2384 'kit_name' => $kit_name,
2385 'title' => $kit_name,
2386 'descriptions' => isset($manifest['description']) ? $manifest['description'] : 'Uploaded template kit',
2387 'description' => isset($manifest['description']) ? $manifest['description'] : 'Uploaded template kit',
2388 'author' => isset($manifest['author']) ? $manifest['author'] : 'Unknown',
2389 'categories' => $categories,
2390 'keywords' => $keywords,
2391 'thumbnail' => $kit_thumbnail,
2392 'preview_url' => isset($manifest['preview_url']) ? $manifest['preview_url'] : '',
2393 'is_purchased' => true,
2394 'purchasable' => false,
2395 'downloadable' => true,
2396 'is_pro' => false,
2397 'downloads' => isset($manifest['downloads']) ? $manifest['downloads'] : 0,
2398 'purchase_url' => '',
2399 'uploaded_date' => current_time('mysql'),
2400 'template_count' => isset($manifest['template_count']) ? $manifest['template_count'] : $template_count,
2401 'templates' => $templates,
2402 'required_plugins' => $required_plugins,
2403 'manifest' => $manifest,
2404 'kit_path' => $kits_dir
2405 ];
2406
2407 $stored = $cache_manager->store_purchased_kit($kit_data);
2408 if (!$stored) {
2409 $wp_filesystem->delete($kits_dir, true);
2410 wp_send_json_error(['message' => 'Failed to store template kit']);
2411 return;
2412 }
2413
2414 $meta_data = [
2415 'kit_id' => $kit_id,
2416 'uploaded_date' => $kit_data['uploaded_date'],
2417 'original_filename' => $uploaded_file['name'],
2418 'is_purchased' => true
2419 ];
2420
2421 $wp_filesystem->put_contents(
2422 $kits_dir . '/meta.json',
2423 wp_json_encode($meta_data, JSON_PRETTY_PRINT)
2424 );
2425
2426 wp_send_json_success([
2427 'kit_id' => $kit_id,
2428 'name' => $kit_data['kit_name'],
2429 'template_count' => $template_count,
2430 'is_purchased' => true,
2431 'message' => sprintf('Template kit "%s" uploaded successfully with %d templates', $kit_data['kit_name'], $template_count),
2432 'templates' => $templates,
2433 'parentTemplate' => [
2434 'kit_id' => $kit_id,
2435 'kit_name' => $kit_data['kit_name'],
2436 'title' => $kit_data['kit_name'],
2437 'categories' => $kit_data['categories'],
2438 'required_plugins' => $required_plugins,
2439 'is_purchased' => true,
2440 ],
2441 ]);
2442 }
2443
2444 public static function get_instance()
2445 {
2446 if (is_null(self::$_instance)) {
2447 self::$_instance = new self();
2448 }
2449 return self::$_instance;
2450 }
2451 }
2452