PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.2
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.2
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.2, at inc/admin/templates/kits/class-importer.php

2,445 lines 92.4 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 if (isset($_POST['parentTemplate'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in calling method
679 $parent_template = sanitize_text_field( wp_unslash( $_POST['parentTemplate'] ) );
680 $upload_dir = wp_upload_dir();
681
682 $manifest_path = $upload_dir['basedir'] . '/master_addons/purchased_kits/kits/kit_' . $parent_template . '/manifest.json';
683
684 if (!file_exists($manifest_path)) {
685 $manifest_path = $upload_dir['basedir'] . '/master_addons/templates_kits/kits/kit_' . $parent_template . '.json';
686 if (!file_exists($manifest_path)) {
687 $manifest_path = $upload_dir['basedir'] . '/master_addons/templates_kits/kits/' . $parent_template . '.json';
688 }
689 }
690
691 if (file_exists($manifest_path)) {
692 $manifest_content = file_get_contents($manifest_path);
693 $manifest = json_decode($manifest_content, true);
694
695 if ($manifest && isset($manifest['title'])) {
696 $kit_title = $manifest['title'];
697 if (strpos($kit_title, '-') !== false) {
698 $parts = explode('-', $kit_title);
699 $page_prefix = trim($parts[0]);
700 } else {
701 $page_prefix = $kit_title;
702 }
703 } elseif ($manifest && isset($manifest['kit_name'])) {
704 $kit_title = $manifest['kit_name'];
705 if (strpos($kit_title, '-') !== false) {
706 $parts = explode('-', $kit_title);
707 $page_prefix = trim($parts[0]);
708 } else {
709 $page_prefix = $kit_title;
710 }
711 }
712 } else {
713 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
714 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
715 if (method_exists($cache_manager, 'get_kit_manifest')) {
716 $manifest = $cache_manager->get_kit_manifest($parent_template);
717 if ($manifest) {
718 $kit_title = $manifest['title'] ?? $manifest['kit_name'] ?? '';
719 if (!empty($kit_title)) {
720 if (strpos($kit_title, '-') !== false) {
721 $parts = explode('-', $kit_title);
722 $page_prefix = trim($parts[0]);
723 } else {
724 $page_prefix = $kit_title;
725 }
726 }
727 }
728 }
729 }
730 }
731 }
732
733 return apply_filters('jltma_new_page_prefix', $page_prefix, $kit_name, $decoded);
734 }
735
736 /**
737 * Handle special template types (landing page, 404, blog archive)
738 */
739 private function handle_special_template_type($template_type, $page_id)
740 {
741 if ($template_type === 'landing-page') {
742 update_option('show_on_front', 'page');
743 update_option('page_on_front', $page_id);
744 } elseif ($template_type === 'single-404') {
745 update_option('jltma_404_page_id', $page_id);
746 if (defined('ELEMENTOR_PRO_VERSION')) {
747 $kit_id = get_option('elementor_active_kit');
748 if ($kit_id) {
749 $kit_settings = get_post_meta($kit_id, '_elementor_page_settings', true);
750 if (!is_array($kit_settings)) {
751 $kit_settings = [];
752 }
753 $kit_settings['404_page_id'] = $page_id;
754 update_post_meta($kit_id, '_elementor_page_settings', $kit_settings);
755 }
756 }
757 } elseif ($template_type === 'archive-blog') {
758 update_option('show_on_front', 'page');
759 update_option('page_for_posts', $page_id);
760 }
761 }
762
763 /**
764 * Import Template Kit via REST API with caching
765 */
766 public function import_kit_via_api($kit)
767 {
768 $cache_manager = null;
769 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
770 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
771 }
772
773 if (!$cache_manager) {
774 return false;
775 }
776
777 $kit_data = $cache_manager->download_and_cache_kit($kit);
778
779 if (!$kit_data || !$kit_data['success']) {
780 return $this->import_kit_via_api_direct($kit);
781 }
782
783 $manifest_data = $kit_data['manifest'];
784 if (!$manifest_data || !isset($manifest_data['pages'])) {
785 $manifest_data = $cache_manager->get_kit_manifest($kit);
786 if (!$manifest_data || !isset($manifest_data['pages'])) {
787 return false;
788 }
789 }
790
791 $imported_pages = array();
792 foreach ($manifest_data['pages'] as $page_slug) {
793 $template_data = $cache_manager->get_kit_template($kit, $page_slug);
794
795 if ($template_data) {
796 $page_id = $this->import_template_from_data($template_data, $kit);
797 } else {
798 $page_id = $this->import_single_template_from_api($kit, $page_slug);
799 }
800
801 if ($page_id) {
802 $imported_pages[$page_slug] = $page_id;
803 }
804 }
805
806 if (isset($manifest_data['global_settings'])) {
807 $this->import_global_settings_from_api($manifest_data['global_settings']);
808 }
809
810 if (isset($imported_pages['home'])) {
811 update_option('show_on_front', 'page');
812 update_option('page_on_front', $imported_pages['home']);
813 }
814
815 return array(
816 'success' => true,
817 'imported_pages' => $imported_pages,
818 'message' => 'Template kit imported successfully!'
819 );
820 }
821
822 /**
823 * Fallback: Import Template Kit directly via API (without caching)
824 */
825 public function import_kit_via_api_direct($kit)
826 {
827 $config = null;
828 if (function_exists('MasterAddons\Inc\Admin\Templates\master_addons_templates')) {
829 $templates_instance = \MasterAddons\Inc\Admin\Templates\master_addons_templates();
830 if ($templates_instance && isset($templates_instance->config)) {
831 $config = $templates_instance->config->get('api');
832 }
833 }
834
835 $api_url = $config['base'] . $config['path'] . '/templates-kit/' . $kit . '/manifest.json';
836
837 if (Helper::jltma_premium()) {
838 $api_url = add_query_arg('pro_enabled', 'true', $api_url);
839 }
840
841 $response = wp_remote_get($api_url, [
842 'timeout' => 60,
843 'sslverify' => false,
844 'headers' => [
845 'User-Agent' => 'Master Addons Template Kit Importer/' . \JLTMA_VER
846 ]
847 ]);
848
849 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
850 return false;
851 }
852
853 $manifest_data = json_decode(wp_remote_retrieve_body($response), true);
854 if (!$manifest_data || !isset($manifest_data['pages'])) {
855 return false;
856 }
857
858 $imported_pages = array();
859 foreach ($manifest_data['pages'] as $page_slug) {
860 $page_id = $this->import_single_template_from_api($kit, $page_slug);
861 if ($page_id) {
862 $imported_pages[$page_slug] = $page_id;
863 }
864 }
865
866 if (isset($manifest_data['global_settings'])) {
867 $this->import_global_settings_from_api($manifest_data['global_settings']);
868 }
869
870 if (isset($imported_pages['home'])) {
871 update_option('show_on_front', 'page');
872 update_option('page_on_front', $imported_pages['home']);
873 }
874
875 return array(
876 'success' => true,
877 'imported_pages' => $imported_pages,
878 'message' => 'Template kit imported successfully via API!'
879 );
880 }
881
882 /**
883 * Import Single Template from API
884 */
885 public function import_single_template_from_api($kit, $template_slug)
886 {
887 $cache_manager = null;
888 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
889 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
890 }
891
892 if ($cache_manager) {
893 $template_data = $cache_manager->get_kit_template($kit, $template_slug);
894 if ($template_data) {
895 return $this->import_template_from_data($template_data, $kit);
896 }
897 }
898
899 $config = null;
900 if (function_exists('MasterAddons\Inc\Admin\Templates\master_addons_templates')) {
901 $templates_instance = \MasterAddons\Inc\Admin\Templates\master_addons_templates();
902 if ($templates_instance && isset($templates_instance->config)) {
903 $config = $templates_instance->config->get('api');
904 }
905 }
906
907 $template_url = $config['base'] . $config['path'] . '/templates-kit/' . $kit . '/' . $template_slug . '.json';
908
909 if (Helper::jltma_premium()) {
910 $template_url = add_query_arg('pro_enabled', 'true', $template_url);
911 }
912
913 $response = wp_remote_get($template_url, array(
914 'timeout' => 30,
915 'headers' => array(
916 'Accept' => 'application/json',
917 'User-Agent' => 'Master-Addons/' . \JLTMA_VER
918 )
919 ));
920
921 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
922 return false;
923 }
924
925 $template_data = json_decode(wp_remote_retrieve_body($response), true);
926 if (!$template_data) {
927 return false;
928 }
929
930 return $this->import_template_from_data($template_data, $kit);
931 }
932
933 /**
934 * Import template from data array
935 */
936 public function import_template_from_data($template_data, $kit)
937 {
938 if (!$template_data || !isset($template_data['page_settings'])) {
939 return false;
940 }
941
942 // Duplicate detection: check if page with same title and kit already exists
943 $post_title = $template_data['page_settings']['post_title'];
944 $existing = get_posts(array(
945 'post_type' => 'page',
946 'post_status' => 'publish',
947 'title' => $post_title,
948 'meta_query' => array(
949 array(
950 'key' => '_jltma_import_kit_id',
951 'value' => $kit,
952 ),
953 ),
954 'posts_per_page' => 1,
955 'fields' => 'ids',
956 ));
957 if (!empty($existing)) {
958 return $existing[0];
959 }
960
961 $post_type = $template_data['page_settings']['post_type'] ?? 'page';
962
963 $page_id = wp_insert_post(array(
964 'post_title' => $template_data['page_settings']['post_title'],
965 'post_type' => $post_type,
966 'post_status' => 'publish',
967 'post_content' => '',
968 'meta_input' => array(
969 '_elementor_edit_mode' => 'builder',
970 '_elementor_template_type' => 'wp-page',
971 '_elementor_version' => $template_data['version'],
972 '_elementor_data' => wp_slash(json_encode($template_data['content'])),
973 '_jltma_demo_import_item' => true,
974 '_jltma_import_kit_id' => $kit,
975 '_jltma_import_date' => current_time('mysql'),
976 '_wp_page_template' => 'elementor_canvas',
977 )
978 ));
979
980 if (is_wp_error($page_id)) {
981 return false;
982 }
983
984 // Generate Elementor CSS for the imported page
985 if (class_exists('\Elementor\Core\Files\CSS\Post')) {
986 $css_file = new \Elementor\Core\Files\CSS\Post($page_id);
987 $css_file->update();
988 }
989
990 return $page_id;
991 }
992
993 /**
994 * Import Global Settings from API
995 */
996 public function import_global_settings_from_api($global_settings)
997 {
998 if (isset($global_settings['colors'])) {
999 $elementor_scheme = get_option('elementor_scheme_color', array());
1000
1001 $color_mapping = array(
1002 'primary' => '1',
1003 'secondary' => '2',
1004 'accent' => '3'
1005 );
1006
1007 foreach ($global_settings['colors'] as $color_name => $color_value) {
1008 if (isset($color_mapping[$color_name])) {
1009 $elementor_scheme[$color_mapping[$color_name]] = $color_value;
1010 }
1011 }
1012
1013 update_option('elementor_scheme_color', $elementor_scheme);
1014 }
1015
1016 if (isset($global_settings['typography'])) {
1017 $elementor_scheme = get_option('elementor_scheme_typography', array());
1018
1019 if (isset($global_settings['typography']['primary_font'])) {
1020 $elementor_scheme['1'] = array(
1021 'font_family' => $global_settings['typography']['primary_font'],
1022 'font_weight' => '400'
1023 );
1024 }
1025
1026 if (isset($global_settings['typography']['secondary_font'])) {
1027 $elementor_scheme['2'] = array(
1028 'font_family' => $global_settings['typography']['secondary_font'],
1029 'font_weight' => '400'
1030 );
1031 }
1032
1033 update_option('elementor_scheme_typography', $elementor_scheme);
1034 }
1035 }
1036
1037 /**
1038 * Reset Previous Import
1039 */
1040 public function reset_previous_import()
1041 {
1042 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
1043
1044 if (!wp_verify_nonce($nonce, 'jltma-template-kits-js') || !current_user_can('manage_options')) {
1045 exit;
1046 }
1047
1048 $args = [
1049 'post_type' => [
1050 'page',
1051 'post',
1052 'product',
1053 'elementor_library',
1054 'attachment',
1055 ],
1056 'post_status' => 'any',
1057 'posts_per_page' => '-1',
1058 'meta_key' => '_jltma_demo_import_item'
1059 ];
1060
1061 $imported_items = new \WP_Query($args);
1062
1063 if ($imported_items->have_posts()) {
1064 while ($imported_items->have_posts()) {
1065 $imported_items->the_post();
1066
1067 if ('Default Kit' == get_the_title()) {
1068 continue;
1069 }
1070
1071 wp_delete_post(get_the_ID(), true);
1072 }
1073
1074 wp_reset_query();
1075
1076 $imported_terms = get_terms([
1077 'meta_key' => '_jltma_demo_import_item',
1078 'posts_per_page' => -1,
1079 'hide_empty' => false,
1080 ]);
1081
1082 if (!empty($imported_terms)) {
1083 foreach ($imported_terms as $imported_term) {
1084 wp_delete_term($imported_term->term_id, $imported_term->taxonomy);
1085 }
1086 }
1087
1088 wp_send_json_success(esc_html__('Previous Import Files have been successfully Reset.', 'master-addons'));
1089 } else {
1090 wp_send_json_success(esc_html__('There is no Data for Reset.', 'master-addons'));
1091 }
1092 }
1093
1094 /**
1095 * Final Settings Setup
1096 */
1097 public function final_settings_setup()
1098 {
1099 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
1100
1101 if (!wp_verify_nonce($nonce, 'jltma-template-kits-js') || !current_user_can('manage_options')) {
1102 exit;
1103 }
1104
1105 $kit = !empty(get_option('jltma-import-kit-id')) ? esc_html(get_option('jltma-import-kit-id')) : '';
1106
1107 $this->import_elementor_site_settings($kit);
1108 $this->setup_templates($kit);
1109 $this->fix_elementor_images();
1110
1111 delete_option('jltma-import-kit-id');
1112
1113 $post = get_page_by_path('hello-world', OBJECT, 'post');
1114 if ($post) {
1115 wp_delete_post($post->ID, true);
1116 }
1117
1118 wp_send_json_success();
1119 }
1120
1121 /**
1122 * Activate Template Kit as Global Kit
1123 */
1124 public function activate_template_as_global_kit($template_id)
1125 {
1126 if (!$template_id || is_wp_error($template_id)) {
1127 return false;
1128 }
1129
1130 $template_type = get_post_meta($template_id, '_elementor_template_type', true);
1131 if ($template_type !== 'kit') {
1132 return false;
1133 }
1134
1135 update_option('elementor_active_kit', $template_id);
1136
1137 if (class_exists('\Elementor\Plugin')) {
1138 \Elementor\Plugin::$instance->files_manager->clear_cache();
1139 }
1140
1141 return true;
1142 }
1143
1144 /**
1145 * Import Elementor Site Settings
1146 */
1147 public function import_elementor_site_settings($kit)
1148 {
1149 update_option('elementor_experiment-e_local_google_fonts', 'inactive');
1150
1151 $response = wp_remote_get('https://master-addons.com/templates-kit/' . rawurlencode($kit) . '/site-settings.json', array('timeout' => 60));
1152
1153 if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
1154 $site_settings = json_decode(wp_remote_retrieve_body($response), true);
1155
1156 if (!empty($site_settings['settings'])) {
1157 $default_kit = \Elementor\Plugin::$instance->documents->get_doc_for_frontend(get_option('elementor_active_kit'));
1158
1159 $kit_settings = $default_kit->get_settings();
1160 $new_settings = $site_settings['settings'];
1161 $settings = array_merge($kit_settings, $new_settings);
1162
1163 $default_kit->save(['settings' => $settings]);
1164 }
1165 }
1166 }
1167
1168 /**
1169 * Setup Templates
1170 */
1171 public function setup_templates($kit)
1172 {
1173 $home_page = get_page_by_path('home-' . $kit);
1174 $blog_page = get_page_by_path('blog-' . $kit);
1175
1176 if ($home_page) {
1177 update_option('show_on_front', 'page');
1178 update_option('page_on_front', $home_page->ID);
1179
1180 if ($blog_page) {
1181 update_option('page_for_posts', $blog_page->ID);
1182 }
1183 }
1184 }
1185
1186 /**
1187 * Fix Elementor Images
1188 */
1189 public function fix_elementor_images()
1190 {
1191 $args = array(
1192 'post_type' => ['page', 'elementor_library'],
1193 'posts_per_page' => '-1',
1194 'meta_key' => '_elementor_version'
1195 );
1196 $elementor_pages = new \WP_Query($args);
1197
1198 if ($elementor_pages->have_posts()) {
1199 while ($elementor_pages->have_posts()) {
1200 $elementor_pages->the_post();
1201
1202 $site_url = get_site_url();
1203 $site_url = str_replace('/', '\/', $site_url);
1204 $demo_site_url = 'https://el.master-addons.com/' . get_option('jltma-import-kit-id');
1205 $demo_site_url = str_replace('/', '\/', $demo_site_url);
1206
1207 $data = get_post_meta(get_the_ID(), '_elementor_data', true);
1208
1209 if (!empty($data)) {
1210 if (is_string($data)) {
1211 $data = preg_replace('/\\\{1}\/sites\\\{1}\/\d+/', '', $data);
1212 $data = str_replace($demo_site_url, $site_url, $data);
1213 $data = json_decode($data, true);
1214 } elseif (is_array($data)) {
1215 $data_string = wp_json_encode($data);
1216 $data_string = preg_replace('/\\\{1}\/sites\\\{1}\/\d+/', '', $data_string);
1217 $data_string = str_replace($demo_site_url, $site_url, $data_string);
1218 $data = json_decode($data_string, true);
1219 }
1220 }
1221
1222 if (is_array($data)) {
1223 $data = $this->sanitize_elementor_data($data);
1224 }
1225
1226 update_metadata('post', get_the_ID(), '_elementor_data', $data);
1227
1228 $page_settings = get_post_meta(get_the_ID(), '_elementor_page_settings', true);
1229 $page_settings = json_encode($page_settings);
1230
1231 if (!empty($page_settings)) {
1232 $page_settings = preg_replace('/\\\{1}\/sites\\\{1}\/\d+/', '', $page_settings);
1233 $page_settings = str_replace($demo_site_url, $site_url, $page_settings);
1234 $page_settings = json_decode($page_settings, true);
1235 }
1236
1237 update_metadata('post', get_the_ID(), '_elementor_page_settings', $page_settings);
1238 }
1239 }
1240
1241 Plugin::$instance->files_manager->clear_cache();
1242 }
1243
1244 /**
1245 * Set Timeout for Image Request
1246 */
1247 public function set_image_request_timeout($timeout_value, $url)
1248 {
1249 if (strpos($url, 'https://master-addons.com/') === false) {
1250 return $timeout_value;
1251 }
1252
1253 $valid_ext = preg_match('/^((https?:\/\/)|(www\.))([a-z0-9-].?)+(:[0-9]+)?\/[\w\-\@]+\.(jpg|png|gif|jpeg|svg)\/?$/i', $url);
1254
1255 if ($valid_ext) {
1256 $timeout_value = 300;
1257 }
1258
1259 return $timeout_value;
1260 }
1261
1262 /**
1263 * Prevent WooCommerce creating default pages
1264 */
1265 public function disable_default_woo_pages_creation()
1266 {
1267 add_filter('woocommerce_create_pages', '__return_empty_array');
1268 }
1269
1270 /**
1271 * Sanitize Elementor Data to prevent warnings
1272 */
1273 public function sanitize_elementor_data($data)
1274 {
1275 if (!is_array($data)) {
1276 return $data;
1277 }
1278
1279 foreach ($data as &$element) {
1280 if (isset($element['elType']) && $element['elType'] === 'column') {
1281 if (!isset($element['settings']['_column_size'])) {
1282 $element['settings']['_column_size'] = 100;
1283 }
1284 }
1285
1286 if (isset($element['settings']) && is_array($element['settings'])) {
1287 $element['settings'] = $this->sanitize_element_settings($element['settings']);
1288 }
1289
1290 if (isset($element['elements']) && is_array($element['elements'])) {
1291 $element['elements'] = $this->sanitize_elementor_data($element['elements']);
1292 }
1293 }
1294
1295 return $data;
1296 }
1297
1298 /**
1299 * Sanitize Element Settings (handles images and other settings)
1300 */
1301 public function sanitize_element_settings($settings)
1302 {
1303 if (!is_array($settings)) {
1304 return $settings;
1305 }
1306
1307 $image_settings = [
1308 'image', 'background_image', 'hover_image', 'bg_image', 'icon_image',
1309 'gallery', 'images', 'slide_image', 'background_overlay_image',
1310 'testimonial_image', 'team_image', 'portfolio_image'
1311 ];
1312
1313 foreach ($settings as $key => &$value) {
1314 if (in_array($key, $image_settings) && is_array($value)) {
1315 if (!isset($value['id']) || empty($value['id'])) {
1316 $value['id'] = 0;
1317 }
1318 if (!isset($value['url'])) {
1319 $value['url'] = '';
1320 }
1321 }
1322
1323 if ($key === 'gallery' && is_array($value)) {
1324 foreach ($value as &$gallery_item) {
1325 if (is_array($gallery_item) && !isset($gallery_item['id'])) {
1326 $gallery_item['id'] = 0;
1327 }
1328 if (is_array($gallery_item) && !isset($gallery_item['url'])) {
1329 $gallery_item['url'] = '';
1330 }
1331 }
1332 }
1333
1334 if (is_array($value) && !in_array($key, $image_settings)) {
1335 $value = $this->sanitize_element_settings($value);
1336 }
1337 }
1338
1339 return $settings;
1340 }
1341
1342 /**
1343 * Search Query Results
1344 */
1345 public function search_query_results()
1346 {
1347 $search_query = isset($_POST['search_query']) ? sanitize_text_field(wp_unslash($_POST['search_query'])) : '';
1348 // Log search queries for analytics (optional)
1349 }
1350
1351 /**
1352 * Real mime types for WP 5.1.0+
1353 */
1354 public function real_mime_types_5_1_0($defaults, $file, $filename, $mimes, $real_mime)
1355 {
1356 return $this->real_mimes($defaults, $filename);
1357 }
1358
1359 /**
1360 * Real mime types for older WP
1361 */
1362 public function real_mime_types($defaults, $file, $filename, $mimes)
1363 {
1364 return $this->real_mimes($defaults, $filename);
1365 }
1366
1367 /**
1368 * Real mimes helper
1369 */
1370 public function real_mimes($defaults, $filename)
1371 {
1372 if (strpos($filename, 'main') !== false) {
1373 $defaults['ext'] = 'xml';
1374 $defaults['type'] = 'text/xml';
1375 }
1376
1377 return $defaults;
1378 }
1379
1380 /**
1381 * Import images to media library and replace URLs with attachment IDs
1382 */
1383 public function import_images_to_media($content, $kit_id, $kit_name = '')
1384 {
1385 if (is_string($content)) {
1386 $content = json_decode($content, true);
1387 }
1388
1389 if (!is_array($content)) {
1390 return $content;
1391 }
1392
1393 foreach ($content as &$element) {
1394 if (isset($element['settings'])) {
1395 $element['settings'] = $this->import_settings_images($element['settings'], $kit_id, $kit_name);
1396 }
1397
1398 if (isset($element['elements']) && is_array($element['elements'])) {
1399 $element['elements'] = $this->import_images_to_media($element['elements'], $kit_id, $kit_name);
1400 }
1401 }
1402
1403 return $content;
1404 }
1405
1406 /**
1407 * Replace image URLs with cached versions (deprecated - kept for compatibility)
1408 */
1409 public function replace_with_cached_images($content, $kit_id, $cache_manager)
1410 {
1411 return $this->import_images_to_media($content, $kit_id);
1412 }
1413
1414 /**
1415 * Import images in element settings to media library
1416 */
1417 public function import_settings_images($settings, $kit_id, $kit_name = '')
1418 {
1419 if (!is_array($settings)) {
1420 return $settings;
1421 }
1422
1423 $image_settings = [
1424 'image', 'background_image', 'hover_image', 'bg_image', 'icon_image',
1425 'gallery', 'images', 'slide_image', 'background_overlay_image',
1426 'testimonial_image', 'team_image', 'portfolio_image', 'logo_image',
1427 'before_image', 'after_image', 'author_image', 'product_image',
1428 'featured_image', 'fallback_image', 'mobile_image', 'tablet_image'
1429 ];
1430
1431 foreach ($settings as $key => &$value) {
1432 if (in_array($key, $image_settings) && is_array($value) && isset($value['url'])) {
1433 $attachment_data = $this->import_single_image($value['url'], $kit_id, $kit_name);
1434 if ($attachment_data) {
1435 $value['id'] = $attachment_data['id'];
1436 $value['url'] = $attachment_data['url'];
1437 }
1438 }
1439
1440 if ($key === 'gallery' && is_array($value)) {
1441 foreach ($value as &$gallery_item) {
1442 if (is_array($gallery_item) && isset($gallery_item['url'])) {
1443 $attachment_data = $this->import_single_image($gallery_item['url'], $kit_id, $kit_name);
1444 if ($attachment_data) {
1445 $gallery_item['id'] = $attachment_data['id'];
1446 $gallery_item['url'] = $attachment_data['url'];
1447 }
1448 }
1449 }
1450 }
1451
1452 if (is_array($value) && !in_array($key, $image_settings)) {
1453 foreach ($value as &$repeater_item) {
1454 if (is_array($repeater_item)) {
1455 $repeater_item = $this->import_settings_images($repeater_item, $kit_id, $kit_name);
1456 }
1457 }
1458 }
1459
1460 if (is_string($value) && (strpos($key, 'css') !== false || strpos($key, 'style') !== false)) {
1461 $value = $this->import_css_images($value, $kit_id, $kit_name);
1462 }
1463 }
1464
1465 return $settings;
1466 }
1467
1468 /**
1469 * Import images from CSS strings
1470 */
1471 public function import_css_images($css_string, $kit_id, $kit_name = '')
1472 {
1473 if (empty($css_string)) {
1474 return $css_string;
1475 }
1476
1477 preg_match_all('/url\([\'"]?([^\'")]+)[\'"]?\)/i', $css_string, $matches);
1478
1479 if (!empty($matches[1])) {
1480 foreach ($matches[1] as $url) {
1481 if (strpos($url, 'data:') === 0 || strpos($url, 'http') !== 0) {
1482 continue;
1483 }
1484
1485 if (preg_match('/\.(jpg|jpeg|png|gif|svg|webp)$/i', $url)) {
1486 $attachment_data = $this->import_single_image($url, $kit_id, $kit_name);
1487 if ($attachment_data) {
1488 $css_string = str_replace($url, $attachment_data['url'], $css_string);
1489 }
1490 }
1491 }
1492 }
1493
1494 return $css_string;
1495 }
1496
1497 /**
1498 * Import a single image to media library
1499 */
1500 public function import_single_image($image_url, $kit_id, $kit_name = '')
1501 {
1502 if (empty($image_url) || !filter_var($image_url, FILTER_VALIDATE_URL)) {
1503 return false;
1504 }
1505
1506 if (isset($this->imported_images[$image_url])) {
1507 return $this->imported_images[$image_url];
1508 }
1509
1510 $existing_attachment = $this->get_attachment_by_url($image_url);
1511 if ($existing_attachment) {
1512 $attachment_data = [
1513 'id' => $existing_attachment,
1514 'url' => wp_get_attachment_url($existing_attachment)
1515 ];
1516 $this->imported_images[$image_url] = $attachment_data;
1517 return $attachment_data;
1518 }
1519
1520 require_once(ABSPATH . 'wp-admin/includes/media.php');
1521 require_once(ABSPATH . 'wp-admin/includes/file.php');
1522 require_once(ABSPATH . 'wp-admin/includes/image.php');
1523
1524 $tmp = download_url($image_url, 300);
1525
1526 if (is_wp_error($tmp)) {
1527 $response = wp_remote_get($image_url, [
1528 'timeout' => 300,
1529 'sslverify' => false
1530 ]);
1531
1532 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1533 return false;
1534 }
1535
1536 $image_data = wp_remote_retrieve_body($response);
1537 $tmp = wp_tempnam();
1538 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
1539
1540 if (!file_exists($tmp) || filesize($tmp) === 0) {
1541 wp_delete_file($tmp);
1542 return false;
1543 }
1544 }
1545
1546 $file_array = [];
1547 $file_array['name'] = basename($image_url);
1548
1549 if (!pathinfo($file_array['name'], PATHINFO_EXTENSION)) {
1550 $file_info = wp_check_filetype($tmp);
1551 if ($file_info['ext']) {
1552 $file_array['name'] = 'image-' . uniqid() . '.' . $file_info['ext'];
1553 }
1554 }
1555
1556 if (!empty($kit_name)) {
1557 $file_array['name'] = sanitize_title($kit_name) . '-' . $file_array['name'];
1558 }
1559
1560 $file_array['tmp_name'] = $tmp;
1561
1562 $attachment_id = media_handle_sideload($file_array, 0, null, [
1563 'post_title' => !empty($kit_name) ? $kit_name . ' - ' . pathinfo($file_array['name'], PATHINFO_FILENAME) : pathinfo($file_array['name'], PATHINFO_FILENAME),
1564 'post_content' => '',
1565 'post_status' => 'inherit'
1566 ]);
1567
1568 wp_delete_file($tmp);
1569
1570 if (is_wp_error($attachment_id)) {
1571 return false;
1572 }
1573
1574 update_post_meta($attachment_id, '_jltma_imported_from_kit', $kit_id);
1575 update_post_meta($attachment_id, '_jltma_original_url', $image_url);
1576 update_post_meta($attachment_id, '_jltma_import_date', current_time('mysql'));
1577 if (!empty($kit_name)) {
1578 update_post_meta($attachment_id, '_jltma_kit_name', $kit_name);
1579 }
1580
1581 $alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
1582 if (empty($alt_text)) {
1583 update_post_meta($attachment_id, '_wp_attachment_image_alt', $kit_name ?: 'Template Image');
1584 }
1585
1586 $attachment_data = [
1587 'id' => $attachment_id,
1588 'url' => wp_get_attachment_url($attachment_id)
1589 ];
1590
1591 $this->imported_images[$image_url] = $attachment_data;
1592
1593 return $attachment_data;
1594 }
1595
1596 /**
1597 * Get attachment ID by URL
1598 */
1599 public function get_attachment_by_url($url)
1600 {
1601 global $wpdb;
1602
1603 $clean_url = str_replace(['https://', 'http://'], '', $url);
1604
1605 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for attachment lookup by guid without core API equivalent
1606 $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type='attachment';", $url));
1607 if (!empty($attachment)) {
1608 return $attachment[0];
1609 }
1610
1611 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for attachment lookup by normalised guid without core API equivalent
1612 $attachment = $wpdb->get_col($wpdb->prepare(
1613 "SELECT ID FROM $wpdb->posts WHERE (REPLACE(REPLACE(guid, 'https://', ''), 'http://', '') = %s) AND post_type='attachment' LIMIT 1;",
1614 $clean_url
1615 ));
1616 if (!empty($attachment)) {
1617 return $attachment[0];
1618 }
1619
1620 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for attachment lookup by custom meta without core API equivalent
1621 $attachment = $wpdb->get_col($wpdb->prepare(
1622 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_jltma_original_url' AND meta_value=%s LIMIT 1;",
1623 $url
1624 ));
1625 if (!empty($attachment)) {
1626 return $attachment[0];
1627 }
1628
1629 return false;
1630 }
1631
1632 /**
1633 * Clean up orphaned template images
1634 */
1635 public function cleanup_template_images()
1636 {
1637 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1638 wp_send_json_error(['message' => 'Permission denied']);
1639 return;
1640 }
1641
1642 global $wpdb;
1643
1644 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for bulk meta key lookup without core API equivalent
1645 $template_attachments = $wpdb->get_col(
1646 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_jltma_imported_from_kit'"
1647 );
1648
1649 $deleted_count = 0;
1650 $kept_count = 0;
1651
1652 foreach ($template_attachments as $attachment_id) {
1653 $is_used = false;
1654
1655 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for bulk content LIKE search without core API equivalent
1656 $used_in_content = $wpdb->get_var($wpdb->prepare(
1657 "SELECT COUNT(*) FROM $wpdb->posts WHERE post_content LIKE %s OR post_content LIKE %s",
1658 '%' . $wpdb->esc_like( 'wp-image-' . $attachment_id ) . '%',
1659 '%' . $wpdb->esc_like( 'attachment_' . $attachment_id ) . '%'
1660 ));
1661
1662 if ($used_in_content > 0) {
1663 $is_used = true;
1664 }
1665
1666 if (!$is_used) {
1667 $attachment_url = wp_get_attachment_url($attachment_id);
1668 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct query required for Elementor meta LIKE search without core API equivalent
1669 $used_in_elementor = $wpdb->get_var($wpdb->prepare(
1670 "SELECT COUNT(*) FROM $wpdb->postmeta WHERE meta_key='_elementor_data' AND meta_value LIKE %s",
1671 '%"id":' . $attachment_id . '%'
1672 ));
1673
1674 if ($used_in_elementor > 0) {
1675 $is_used = true;
1676 }
1677 }
1678
1679 if (!$is_used) {
1680 wp_delete_attachment($attachment_id, true);
1681 $deleted_count++;
1682 } else {
1683 $kept_count++;
1684 }
1685 }
1686
1687 wp_send_json_success([
1688 'message' => sprintf('Cleanup complete: %d orphaned images deleted, %d in use kept', $deleted_count, $kept_count),
1689 'deleted' => $deleted_count,
1690 'kept' => $kept_count
1691 ]);
1692 }
1693
1694 /**
1695 * Pre-import all images for a template kit to media library
1696 */
1697 public function preimport_kit_images()
1698 {
1699 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1700 wp_send_json_error(['message' => 'Permission denied']);
1701 return;
1702 }
1703
1704 $kit_id = isset($_POST['kit_id']) ? sanitize_text_field( wp_unslash( $_POST['kit_id'] ) ) : '';
1705 $template_data = isset($_POST['template_data']) ? sanitize_textarea_field( wp_unslash( $_POST['template_data'] ) ) : '';
1706
1707 if (empty($kit_id)) {
1708 wp_send_json_error(['message' => 'Kit ID is required']);
1709 return;
1710 }
1711
1712 if (!empty($template_data)) {
1713 $decoded = json_decode($template_data, true);
1714 if ($decoded && isset($decoded['content'])) {
1715 $image_urls = $this->extract_all_image_urls($decoded['content']);
1716 $imported_count = 0;
1717
1718 foreach ($image_urls as $url) {
1719 $result = $this->import_single_image($url, $kit_id, $decoded['title'] ?? '');
1720 if ($result) {
1721 $imported_count++;
1722 }
1723 }
1724
1725 wp_send_json_success([
1726 'message' => sprintf('Successfully imported %d images to media library', $imported_count),
1727 'image_count' => $imported_count,
1728 'imported_images' => $this->imported_images
1729 ]);
1730 }
1731 }
1732
1733 wp_send_json_error(['message' => 'Invalid template data']);
1734 }
1735
1736 /**
1737 * Extract all image URLs from Elementor content
1738 */
1739 public function extract_all_image_urls($content, &$urls = [])
1740 {
1741 if (is_string($content)) {
1742 $content = json_decode($content, true);
1743 }
1744
1745 if (!is_array($content)) {
1746 return $urls;
1747 }
1748
1749 foreach ($content as $element) {
1750 if (isset($element['settings']) && is_array($element['settings'])) {
1751 $this->extract_urls_from_settings($element['settings'], $urls);
1752 }
1753
1754 if (isset($element['elements']) && is_array($element['elements'])) {
1755 $this->extract_all_image_urls($element['elements'], $urls);
1756 }
1757 }
1758
1759 return array_unique($urls);
1760 }
1761
1762 /**
1763 * Extract image URLs from element settings
1764 */
1765 public function extract_urls_from_settings($settings, &$urls)
1766 {
1767 if (!is_array($settings)) {
1768 return;
1769 }
1770
1771 foreach ($settings as $key => $value) {
1772 if (is_array($value)) {
1773 if (isset($value['url']) && filter_var($value['url'], FILTER_VALIDATE_URL)) {
1774 $urls[] = $value['url'];
1775 }
1776 $this->extract_urls_from_settings($value, $urls);
1777 } elseif (is_string($value)) {
1778 if (preg_match_all('/(https?:\/\/[^\s"]+\.(?:jpg|jpeg|png|gif|svg|webp))/i', $value, $matches)) {
1779 $urls = array_merge($urls, $matches[1]);
1780 }
1781 }
1782 }
1783 }
1784
1785 /**
1786 * Pre-cache all images for a template kit (deprecated - kept for compatibility)
1787 */
1788 public function precache_kit_images()
1789 {
1790 $this->preimport_kit_images();
1791 }
1792
1793 /**
1794 * Pre-download template kit for faster import
1795 */
1796 public function predownload_kit()
1797 {
1798 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1799 wp_send_json_error(['message' => 'Permission denied']);
1800 return;
1801 }
1802
1803 $kit_id = isset($_POST['kit_id']) ? sanitize_text_field( wp_unslash( $_POST['kit_id'] ) ) : '';
1804
1805 if (empty($kit_id)) {
1806 wp_send_json_error(['message' => 'Kit ID is required']);
1807 return;
1808 }
1809
1810 $cache_manager = null;
1811 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
1812 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
1813 }
1814
1815 if (!$cache_manager) {
1816 wp_send_json_error(['message' => 'Cache manager not available']);
1817 return;
1818 }
1819
1820 $result = $cache_manager->download_and_cache_kit($kit_id);
1821
1822 if ($result && $result['success']) {
1823 $template_count = 0;
1824 if (isset($result['manifest']['pages']) && is_array($result['manifest']['pages'])) {
1825 $template_count = count($result['manifest']['pages']);
1826 }
1827
1828 wp_send_json_success([
1829 'message' => sprintf('Kit pre-downloaded successfully with %d templates', $template_count),
1830 'kit_id' => $kit_id,
1831 'template_count' => $template_count,
1832 'cached' => true
1833 ]);
1834 } else {
1835 wp_send_json_error(['message' => 'Failed to download kit']);
1836 }
1837 }
1838
1839 /**
1840 * Download all template kits for offline use
1841 */
1842 public function download_all_kits()
1843 {
1844 if (!wp_verify_nonce( isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '', 'jltma_template_kits_nonce_action') || !current_user_can('manage_options')) {
1845 wp_send_json_error(['message' => 'Permission denied']);
1846 return;
1847 }
1848
1849 $cache_manager = null;
1850 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
1851 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
1852 }
1853
1854 if (!$cache_manager) {
1855 wp_send_json_error(['message' => 'Cache manager not available']);
1856 return;
1857 }
1858
1859 $stats = $cache_manager->download_all_kits();
1860
1861 wp_send_json_success([
1862 'message' => sprintf(
1863 'Download complete: %d downloaded, %d skipped, %d failed out of %d total kits',
1864 $stats['downloaded'],
1865 $stats['skipped'],
1866 $stats['failed'],
1867 $stats['total']
1868 ),
1869 'stats' => $stats
1870 ]);
1871 }
1872
1873 /**
1874 * Install required plugin for template kit
1875 */
1876 public function install_required_plugin()
1877 {
1878 $wpnonce_val = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
1879 $valid_nonce = wp_verify_nonce($wpnonce_val, 'jltma_template_kits_nonce_action') ||
1880 wp_verify_nonce($wpnonce_val, 'jltma_template_library_nonce');
1881
1882 if (!$valid_nonce || !current_user_can('install_plugins')) {
1883 wp_send_json_error(['message' => 'Permission denied']);
1884 return;
1885 }
1886
1887 $plugin_name = isset($_POST['plugin_name']) ? sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ) : '';
1888 $plugin_file = isset($_POST['plugin_file']) ? sanitize_text_field( wp_unslash( $_POST['plugin_file'] ) ) : '';
1889 $plugin_slug = $this->get_plugin_slug($plugin_file);
1890
1891 if (empty($plugin_slug)) {
1892 wp_send_json_error(['message' => 'Plugin slug is required']);
1893 return;
1894 }
1895
1896 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1897 require_once ABSPATH . 'wp-admin/includes/file.php';
1898 require_once ABSPATH . 'wp-admin/includes/misc.php';
1899 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1900 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
1901
1902 if (empty($plugin_file)) {
1903 $plugin_file = $plugin_slug . '/' . $plugin_slug . '.php';
1904 }
1905
1906 $installed_plugins = get_plugins();
1907 if (isset($installed_plugins[$plugin_file])) {
1908 $activate_result = activate_plugin($plugin_file);
1909
1910 if (is_wp_error($activate_result)) {
1911 wp_send_json_error([
1912 'message' => 'Plugin installed but activation failed: ' . $activate_result->get_error_message()
1913 ]);
1914 } else {
1915 wp_send_json_success([
1916 'message' => sprintf('%s has been activated successfully!', $plugin_name ?: $plugin_slug),
1917 'is_active' => true
1918 ]);
1919 }
1920 return;
1921 }
1922
1923 $api = plugins_api('plugin_information', [
1924 'slug' => $plugin_slug,
1925 'fields' => [
1926 'sections' => false,
1927 'tags' => false
1928 ]
1929 ]);
1930
1931 if (is_wp_error($api)) {
1932 if ($api->get_error_message() === "Plugin not found.") {
1933 $plugin_file_parts = explode('/', $plugin_file);
1934 if ($plugin_file_parts[0] !== $plugin_file_parts[1]) {
1935 $plugin_slug = $plugin_file_parts[0];
1936 }
1937 }
1938 $new_api = plugins_api('plugin_information', [
1939 'slug' => $plugin_slug,
1940 'fields' => [
1941 'sections' => false,
1942 'tags' => false
1943 ]
1944 ]);
1945 if (is_wp_error($new_api)) {
1946 wp_send_json_error([
1947 'message' => 'Failed to get plugin information: ',
1948 'details' => $api->get_error_message()
1949 ]);
1950 return;
1951 } else {
1952 $api = $new_api;
1953 }
1954 }
1955
1956 $skin = new \WP_Ajax_Upgrader_Skin();
1957 $upgrader = new \Plugin_Upgrader($skin);
1958
1959 $result = $upgrader->install($api->download_link);
1960
1961 if (is_wp_error($result)) {
1962 wp_send_json_error([
1963 'message' => 'Installation failed: ' . $result->get_error_message()
1964 ]);
1965 return;
1966 }
1967
1968 if (!$result) {
1969 wp_send_json_error([
1970 'message' => 'Installation failed for unknown reason'
1971 ]);
1972 return;
1973 }
1974
1975 $installed_plugins = get_plugins();
1976 $plugin_installed = false;
1977
1978 foreach ($installed_plugins as $file => $plugin) {
1979 if (strpos($file, $plugin_slug . '/') === 0 || $file === $plugin_file) {
1980 $plugin_file = $file;
1981 $plugin_installed = true;
1982 break;
1983 }
1984 }
1985
1986 if (!$plugin_installed) {
1987 wp_send_json_error([
1988 'message' => 'Plugin installed but could not be found'
1989 ]);
1990 return;
1991 }
1992
1993 $activate_result = activate_plugin($plugin_file);
1994
1995 if (is_wp_error($activate_result)) {
1996 wp_send_json_success([
1997 'message' => sprintf('%s has been installed successfully but needs manual activation.', $plugin_name ?: $plugin_slug),
1998 'is_active' => false
1999 ]);
2000 } else {
2001 wp_send_json_success([
2002 'message' => sprintf('%s has been installed and activated successfully!', $plugin_name ?: $plugin_slug),
2003 'is_active' => true
2004 ]);
2005 }
2006 }
2007
2008 /**
2009 * Activate required plugin for template kit
2010 */
2011 public function activate_required_plugin()
2012 {
2013 $wpnonce_activate = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
2014 $valid_nonce = wp_verify_nonce($wpnonce_activate, 'jltma_template_kits_nonce_action') ||
2015 wp_verify_nonce($wpnonce_activate, 'jltma_template_library_nonce');
2016
2017 if (!$valid_nonce || !current_user_can('activate_plugins')) {
2018 wp_send_json_error(['message' => 'Permission denied']);
2019 return;
2020 }
2021
2022 $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field( wp_unslash( $_POST['plugin_slug'] ) ) : '';
2023 $plugin_file = isset($_POST['plugin_file']) ? sanitize_text_field( wp_unslash( $_POST['plugin_file'] ) ) : '';
2024
2025 if (empty($plugin_slug) && empty($plugin_file)) {
2026 wp_send_json_error(['message' => 'Plugin slug is required']);
2027 return;
2028 }
2029
2030 if (!is_string($plugin_slug)) {
2031 $file_name = explode('/', $plugin_file)[1];
2032 $plugin_slug = explode('.', $file_name)[0];
2033 }
2034
2035 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2036
2037 if (empty($plugin_file) && $plugin_file !== 'theme') {
2038 $plugin_file = $plugin_slug . '/' . $plugin_slug . '.php';
2039 }
2040
2041 if ($plugin_file === 'theme') {
2042 $theme = wp_get_theme($plugin_slug);
2043
2044 if (!$theme->exists()) {
2045 wp_send_json_error(['message' => 'Theme not found. Please install it first.']);
2046 return;
2047 }
2048
2049 switch_theme($plugin_slug);
2050
2051 wp_send_json_success([
2052 'message' => ucfirst(str_replace('-', ' ', $plugin_slug)) . ' theme has been activated successfully!',
2053 'is_active' => true
2054 ]);
2055 return;
2056 }
2057
2058 $installed_plugins = get_plugins();
2059 if (!isset($installed_plugins[$plugin_file])) {
2060 $plugin_found = false;
2061 foreach ($installed_plugins as $file => $plugin) {
2062 if (strpos($file, $plugin_slug . '/') === 0) {
2063 $plugin_file = $file;
2064 $plugin_found = true;
2065 break;
2066 }
2067 }
2068
2069 if (!$plugin_found) {
2070 wp_send_json_error(['message' => 'Plugin not found. Please install it first.']);
2071 return;
2072 }
2073 }
2074
2075 if (is_plugin_active($plugin_file)) {
2076 wp_send_json_success([
2077 'message' => 'Plugin is already active!',
2078 'is_active' => true
2079 ]);
2080 return;
2081 }
2082
2083 $result = activate_plugin($plugin_file);
2084
2085 if (is_wp_error($result)) {
2086 wp_send_json_error([
2087 'message' => 'Activation failed: ' . $result->get_error_message()
2088 ]);
2089 } else {
2090 wp_send_json_success([
2091 'message' => 'Plugin activated successfully!',
2092 'is_active' => true
2093 ]);
2094 }
2095 }
2096
2097 /**
2098 * Get plugin slug from plugin file
2099 */
2100 public function get_plugin_slug($plugin_file)
2101 {
2102 $without_extension = pathinfo($plugin_file, PATHINFO_FILENAME);
2103 $parts = explode('/', $without_extension);
2104 $filename = end($parts);
2105
2106 return $filename;
2107 }
2108
2109 /**
2110 * Handle Template Kit Upload
2111 */
2112 public function upload_template_kit()
2113 {
2114 $wpnonce_upload = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
2115 $valid_nonce = wp_verify_nonce($wpnonce_upload, 'jltma_template_kits_nonce_action') ||
2116 wp_verify_nonce($wpnonce_upload, 'jltma_template_library_nonce');
2117 if (!$valid_nonce || !current_user_can('upload_files')) {
2118 wp_send_json_error(['message' => 'Permission denied']);
2119 return;
2120 }
2121
2122 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
2123 wp_send_json_error(['message' => 'No file uploaded or upload failed']);
2124 return;
2125 }
2126
2127 // ── Resource headroom ────────────────────────────────────────────
2128 // Extracting a multi-MB zip and reading dozens of JSON template
2129 // files in one request can easily hit the default 30s / 128M
2130 // limits on shared hosting. Request generous limits up-front so
2131 // the "Reading kit contents" step isn't dragged out by throttled
2132 // resources. Only bumps the ceiling if the host allows it — a
2133 // disabled ini_set or a lower hard cap simply becomes the limit.
2134 if (function_exists('set_time_limit')) {
2135 @set_time_limit(600);
2136 }
2137 if (function_exists('wp_raise_memory_limit')) {
2138 wp_raise_memory_limit('admin');
2139 }
2140 @ini_set('memory_limit', '512M');
2141 @ignore_user_abort(true);
2142
2143 $uploaded_file = $_FILES['kit_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- file upload array; individual keys sanitized before use
2144
2145 $file_type = wp_check_filetype($uploaded_file['name']);
2146 if ($file_type['ext'] !== 'zip') {
2147 wp_send_json_error(['message' => 'Please upload a valid ZIP file']);
2148 return;
2149 }
2150
2151 $cache_manager = null;
2152 if (class_exists('MasterAddons\Inc\Classes\Template_Kit_Cache')) {
2153 $cache_manager = \MasterAddons\Inc\Classes\Template_Kit_Cache::get_instance();
2154 }
2155
2156 if (!$cache_manager) {
2157 wp_send_json_error(['message' => 'Cache manager not available']);
2158 return;
2159 }
2160
2161 $file_name_without_ext = pathinfo($uploaded_file['name'], PATHINFO_FILENAME);
2162
2163 if (preg_match('/^kit[_-]?(\d+)$/i', $file_name_without_ext, $matches)) {
2164 $kit_id = $matches[1];
2165 } else {
2166 $kit_id = (string)(9000 + time() % 1000);
2167 }
2168
2169 global $wp_filesystem;
2170 if (!function_exists('WP_Filesystem')) {
2171 require_once ABSPATH . 'wp-admin/includes/file.php';
2172 }
2173 WP_Filesystem();
2174
2175 $upload_dir = wp_upload_dir();
2176 $kits_dir = $upload_dir['basedir'] . '/master_addons/purchased_kits/kits/kit_' . $kit_id;
2177
2178 if (!$wp_filesystem->exists($kits_dir)) {
2179 $wp_filesystem->mkdir($kits_dir, 0755, true);
2180 }
2181
2182 // Native PHP's ZipArchive is significantly faster than WP's
2183 // unzip_file() because it avoids the pclzip pure-PHP fallback,
2184 // streams file entries directly to disk, and doesn't double-buffer
2185 // each file in memory. Falls back to unzip_file() when the ext
2186 // isn't available (extremely rare on modern PHP).
2187 $unzip_ok = false;
2188 if (class_exists('ZipArchive')) {
2189 $zip = new \ZipArchive();
2190 if ($zip->open($uploaded_file['tmp_name']) === true) {
2191 $unzip_ok = $zip->extractTo($kits_dir);
2192 $zip->close();
2193 }
2194 }
2195 if (!$unzip_ok) {
2196 $unzip_result = unzip_file($uploaded_file['tmp_name'], $kits_dir);
2197 if (is_wp_error($unzip_result)) {
2198 $wp_filesystem->delete($kits_dir, true);
2199 wp_send_json_error(['message' => 'Failed to extract ZIP file: ' . $unzip_result->get_error_message()]);
2200 return;
2201 }
2202 }
2203
2204 $manifest_path = $kits_dir . '/manifest.json';
2205 $manifest = null;
2206
2207 if ($wp_filesystem->exists($manifest_path)) {
2208 $manifest_content = $wp_filesystem->get_contents($manifest_path);
2209 $manifest = json_decode($manifest_content, true);
2210 } else {
2211 $files = $wp_filesystem->dirlist($kits_dir);
2212 foreach ($files as $file) {
2213 if ($file['type'] === 'd') {
2214 $sub_manifest = $kits_dir . '/' . $file['name'] . '/manifest.json';
2215 if ($wp_filesystem->exists($sub_manifest)) {
2216 $sub_dir = $kits_dir . '/' . $file['name'];
2217 $sub_files = $wp_filesystem->dirlist($sub_dir);
2218 foreach ($sub_files as $sub_file) {
2219 $wp_filesystem->move($sub_dir . '/' . $sub_file['name'], $kits_dir . '/' . $sub_file['name']);
2220 }
2221 $wp_filesystem->delete($sub_dir, true);
2222
2223 $manifest_content = $wp_filesystem->get_contents($manifest_path);
2224 $manifest = json_decode($manifest_content, true);
2225 break;
2226 }
2227 }
2228 }
2229 }
2230
2231 if (!$manifest) {
2232 $wp_filesystem->delete($kits_dir, true);
2233 wp_send_json_error(['message' => 'Invalid template kit: manifest.json not found']);
2234 return;
2235 }
2236
2237 $required_manifest_fields = ['manifest_version', 'title', 'kit_version', 'templates'];
2238 $missing_fields = [];
2239
2240 foreach ($required_manifest_fields as $field) {
2241 if (!isset($manifest[$field])) {
2242 $missing_fields[] = $field;
2243 }
2244 }
2245
2246 if (!empty($missing_fields)) {
2247 $wp_filesystem->delete($kits_dir, true);
2248 wp_send_json_error(['message' => 'Invalid manifest.json. Missing required fields: ' . implode(', ', $missing_fields)]);
2249 return;
2250 }
2251
2252 if (!is_array($manifest['templates']) || empty($manifest['templates'])) {
2253 $wp_filesystem->delete($kits_dir, true);
2254 wp_send_json_error(['message' => 'Invalid template kit: templates array is empty or invalid']);
2255 return;
2256 }
2257
2258 $screenshots_dir = $kits_dir . '/screenshots';
2259 $templates_dir = $kits_dir . '/templates';
2260
2261 $missing_folders = [];
2262
2263 if (!$wp_filesystem->exists($screenshots_dir)) {
2264 $missing_folders[] = 'screenshots';
2265 }
2266
2267 if (!$wp_filesystem->exists($templates_dir)) {
2268 $missing_folders[] = 'templates';
2269 }
2270
2271 if (!empty($missing_folders)) {
2272 $wp_filesystem->delete($kits_dir, true);
2273 wp_send_json_error(['message' => 'Invalid template kit structure. Missing required folders: ' . implode(', ', $missing_folders)]);
2274 return;
2275 }
2276
2277 $template_count = 0;
2278 $templates = [];
2279
2280 $manifest_templates = $manifest['templates'];
2281 $template_count = count($manifest_templates);
2282
2283 foreach ($manifest_templates as $page) {
2284 $slug = $page['slug'] ?? $page['fileName'] ?? $page['name'] ?? '';
2285
2286 $template_file = $templates_dir . '/' . $slug . '.json';
2287 if (!$wp_filesystem->exists($template_file)) {
2288 $template_file = $templates_dir . '/' . $slug;
2289 }
2290
2291 if ($wp_filesystem->exists($template_file)) {
2292 $template_content = $wp_filesystem->get_contents($template_file);
2293 $template_data = json_decode($template_content, true);
2294
2295 $thumbnail = '';
2296 if (isset($page['thumbnail']) && !empty($page['thumbnail'])) {
2297 if (strpos($page['thumbnail'], 'http') !== 0) {
2298 $thumbnail_file = $screenshots_dir . '/' . basename($page['thumbnail']);
2299 if ($wp_filesystem->exists($thumbnail_file)) {
2300 $thumbnail = 'screenshots/' . basename($page['thumbnail']);
2301 } else {
2302 $thumbnail = $page['thumbnail'];
2303 }
2304 } else {
2305 $thumbnail = $page['thumbnail'];
2306 }
2307 }
2308
2309 $templates[] = [
2310 'id' => $page['id'] ?? $page['slug'] ?? $slug,
2311 'slug' => $slug,
2312 'title' => $page['title'] ?? $page['name'] ?? $slug,
2313 'thumbnail' => $thumbnail,
2314 'content' => $template_data['content'] ?? $template_data,
2315 'page_settings' => $template_data['page_settings'] ?? [],
2316 'type' => $page['type'] ?? 'page',
2317 'template_id' => $page['template_id'] ?? $page['id'] ?? ''
2318 ];
2319 }
2320 }
2321
2322 $kit_thumbnail = '';
2323 if (isset($manifest['thumbnail_url']) && !empty($manifest['thumbnail_url'])) {
2324 $kit_thumbnail = $manifest['thumbnail_url'];
2325 } elseif (isset($manifest['thumbnail']) && !empty($manifest['thumbnail'])) {
2326 if (strpos($manifest['thumbnail'], 'http') !== 0) {
2327 $thumbnail_path = str_replace('screenshots/', '', $manifest['thumbnail']);
2328 if ($wp_filesystem->exists($kits_dir . '/' . $thumbnail_path)) {
2329 $kit_thumbnail = $manifest['thumbnail'];
2330 }
2331 } else {
2332 $kit_thumbnail = $manifest['thumbnail'];
2333 }
2334 } elseif (!empty($templates) && isset($templates[0]['thumbnail'])) {
2335 $kit_thumbnail = $templates[0]['thumbnail'];
2336 }
2337
2338 $kit_name = isset($manifest['title']) && !empty($manifest['title']) ? $manifest['title'] : (
2339 isset($manifest['name']) && !empty($manifest['name']) ? $manifest['name'] : 'Kit ' . $kit_id
2340 );
2341
2342 $categories = ['purchased'];
2343 if (isset($manifest['categories'])) {
2344 if (is_array($manifest['categories'])) {
2345 $categories = array_merge($categories, $manifest['categories']);
2346 } elseif (is_string($manifest['categories']) && !empty($manifest['categories'])) {
2347 if (strpos($manifest['categories'], ',') !== false) {
2348 $cats = array_map('trim', explode(',', $manifest['categories']));
2349 $categories = array_merge($categories, $cats);
2350 } else {
2351 $categories[] = trim($manifest['categories']);
2352 }
2353 }
2354 }
2355 $categories = array_unique($categories);
2356
2357 $keywords = [];
2358 if (isset($manifest['keywords'])) {
2359 if (is_array($manifest['keywords'])) {
2360 $keywords = $manifest['keywords'];
2361 } elseif (is_string($manifest['keywords']) && !empty($manifest['keywords'])) {
2362 $keywords = array_map('trim', explode(',', $manifest['keywords']));
2363 $keywords = array_filter($keywords);
2364 }
2365 }
2366
2367 $required_plugins = [];
2368 if (isset($manifest['required_plugins']) && is_array($manifest['required_plugins'])) {
2369 $required_plugins = $manifest['required_plugins'];
2370 } elseif (isset($manifest['requirements']) && is_array($manifest['requirements'])) {
2371 $required_plugins = $manifest['requirements'];
2372 }
2373
2374 $kit_data = [
2375 'kit_id' => $kit_id,
2376 'template_id' => $kit_id,
2377 'kit_name' => $kit_name,
2378 'title' => $kit_name,
2379 'descriptions' => isset($manifest['description']) ? $manifest['description'] : 'Uploaded template kit',
2380 'description' => isset($manifest['description']) ? $manifest['description'] : 'Uploaded template kit',
2381 'author' => isset($manifest['author']) ? $manifest['author'] : 'Unknown',
2382 'categories' => $categories,
2383 'keywords' => $keywords,
2384 'thumbnail' => $kit_thumbnail,
2385 'preview_url' => isset($manifest['preview_url']) ? $manifest['preview_url'] : '',
2386 'is_purchased' => true,
2387 'purchasable' => false,
2388 'downloadable' => true,
2389 'is_pro' => false,
2390 'downloads' => isset($manifest['downloads']) ? $manifest['downloads'] : 0,
2391 'purchase_url' => '',
2392 'uploaded_date' => current_time('mysql'),
2393 'template_count' => isset($manifest['template_count']) ? $manifest['template_count'] : $template_count,
2394 'templates' => $templates,
2395 'required_plugins' => $required_plugins,
2396 'manifest' => $manifest,
2397 'kit_path' => $kits_dir
2398 ];
2399
2400 $stored = $cache_manager->store_purchased_kit($kit_data);
2401 if (!$stored) {
2402 $wp_filesystem->delete($kits_dir, true);
2403 wp_send_json_error(['message' => 'Failed to store template kit']);
2404 return;
2405 }
2406
2407 $meta_data = [
2408 'kit_id' => $kit_id,
2409 'uploaded_date' => $kit_data['uploaded_date'],
2410 'original_filename' => $uploaded_file['name'],
2411 'is_purchased' => true
2412 ];
2413
2414 $wp_filesystem->put_contents(
2415 $kits_dir . '/meta.json',
2416 wp_json_encode($meta_data, JSON_PRETTY_PRINT)
2417 );
2418
2419 wp_send_json_success([
2420 'kit_id' => $kit_id,
2421 'name' => $kit_data['kit_name'],
2422 'template_count' => $template_count,
2423 'is_purchased' => true,
2424 'message' => sprintf('Template kit "%s" uploaded successfully with %d templates', $kit_data['kit_name'], $template_count),
2425 'templates' => $templates,
2426 'parentTemplate' => [
2427 'kit_id' => $kit_id,
2428 'kit_name' => $kit_data['kit_name'],
2429 'title' => $kit_data['kit_name'],
2430 'categories' => $kit_data['categories'],
2431 'required_plugins' => $required_plugins,
2432 'is_purchased' => true,
2433 ],
2434 ]);
2435 }
2436
2437 public static function get_instance()
2438 {
2439 if (is_null(self::$_instance)) {
2440 self::$_instance = new self();
2441 }
2442 return self::$_instance;
2443 }
2444 }
2445