PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.1.2
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.1.2
3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.10 All 111 releases
← All changes | includes/Core/Importer/FullSiteImport.php +424 -1540 3.6.13.1.2 View file →
@@ -1,32 +1,9 @@
1 1 <?php
2 2
3 -/**
4 - * remove 'action' => 'continue',
5 - * way to retry
6 - * way to skip if failed multiple times
7 - *
8 - * @todo: on runner check for timeout and retry
9 - * @todo: use ErrorException on runner to skip item when error occurs: not useful
10 - *
11 - */
12 -
13 -
14 3 namespace Templately\Core\Importer;
15 4
16 -use Elementor\Plugin;
17 -use Error;
18 5 use Exception;
19 -use Templately\Core\Importer\Exception\NonRetirableErrorException;
20 -use Templately\Core\Importer\Exception\RetryableErrorException;
21 -use Templately\Core\Importer\Exception\UnknownErrorException;
22 -use Templately\Core\Importer\Runners\Finalizer;
23 -use Templately\Core\Importer\Utils\LogHandler;
24 -use Templately\Core\Importer\Utils\Utils;
25 -use Templately\Core\Importer\Utils\SessionData;
26 -use Templately\Core\Importer\Utils\AIUtils;
27 -use Templately\Core\Importer\Utils\ElementorSettingsMerger;
28 -use Templately\Core\Importer\Utils\GutenbergSettingsMerger;
29 6 use Templately\Utils\Base;
30 7 use Templately\Utils\Helper;
31 8 use Templately\Utils\Installer;
32 9 use Templately\Utils\Options;
@@ -39,86 +16,46 @@
39 16 protected $export;
40 17
41 18 private $version = '1.0.0';
42 19
20 + public $session_id;
43 21 public $download_key;
22 + public $dir_path;
23 + protected $filePath;
24 + protected $tmp_dir = null;
44 25 protected $dev_mode = false;
45 26 protected $api_key = '';
46 - protected $session_id = '';
27 + public $request_params = [];
47 28 protected $documents_data = [];
29 + protected $dependency_data = [];
48 30 private $is_import_status_handled = false;
49 31
50 - public $dir_path;
51 - protected $filePath;
52 - protected $tmp_dir = null;
53 - public $request_params = [];
54 -
55 - // Polling-specific property for ai_poll_template()
56 - private $polling_is_last_part = null;
57 -
58 32 public function __construct() {
59 - $this->dev_mode = defined('TEMPLATELY_DEV') && TEMPLATELY_DEV;
60 - $this->api_key = Options::get_instance()->get('api_key');
33 + $this->dev_mode = defined( 'TEMPLATELY_DEV' ) && TEMPLATELY_DEV;
34 + $this->api_key = Options::get_instance()->get( 'api_key' );
61 35
62 - $this->add_ajax_action('import_settings', $this);
63 - $this->add_ajax_action('create_session_and_download', $this);
64 - $this->add_ajax_action('import_status', $this);
65 - $this->add_ajax_action('import', $this);
66 - $this->add_ajax_action('import_revert', $this);
67 - $this->add_ajax_action('import_info', $this);
68 - $this->add_ajax_action('import_global_settings', $this);
69 - $this->add_ajax_action('import_close_feedback_modal', $this);
70 - $this->add_ajax_action('feedback_form', $this);
71 - $this->add_ajax_action('google_font', $this);
72 - $this->add_ajax_action('ai_get_json', $this);
73 - $this->add_ajax_action('ai_poll_template', $this);
74 - $this->add_ajax_action('merge_settings_with_template', $this);
36 + add_action( 'wp_ajax_templately_import_settings', [ $this, 'import_settings' ] );
37 + add_action( 'wp_ajax_templately_pack_import_status', [ $this, 'import_status' ] );
38 + add_action( 'wp_ajax_templately_pack_import', [ $this, 'import' ] );
39 + add_action( 'wp_ajax_templately_pack_import_info', [ $this, 'import_info' ] );
40 + add_action( 'admin_init', [ $this, 'admin_init' ] );
75 41
76 - add_action('admin_init', [$this, 'admin_init']);
77 - // add_action('admin_notices', [$this, 'add_revert_button']);
78 -
79 - if(isset($_GET['action']) && ($_GET['action'] == 'templately_pack_import' || $_GET['action'] == 'templately_pack_import_status')) {
42 + if(isset($_GET['action']) && $_GET['action'] == 'templately_pack_import') {
80 43 add_filter('wp_redirect', '__return_false', 999);
81 44 }
82 45
83 - if ($this->dev_mode) {
84 - add_filter('http_request_host_is_external', '__return_true');
85 - add_filter('http_request_args', function ($args) {
46 + if ( $this->dev_mode ) {
47 + add_filter( 'http_request_host_is_external', '__return_true' );
48 + add_filter( 'http_request_args', function ( $args ) {
86 49 $args['sslverify'] = false;
87 50
88 51 return $args;
89 - });
52 + } );
90 53 }
91 54 }
92 55
93 - public function add_ajax_action($action, $object) {
94 - add_action("wp_ajax_templately_pack_$action", function() use ($action, $object) {
95 - // Check nonce
96 - $nonce = null;
97 - if(isset($_POST['nonce'])){
98 - $nonce = $_POST['nonce'];
99 - }
100 - if(isset($_GET['nonce'])){
101 - $nonce = $_GET['nonce'];
102 - }
103 - if (!$nonce || !wp_verify_nonce($nonce, 'templately_nonce')) {
104 - wp_send_json_error(['message' => __('Invalid nonce', 'templately')]);
105 - wp_die();
106 - }
107 -
108 - // Check user capability
109 - if (!current_user_can('install_plugins') || !current_user_can('install_themes')) {
110 - wp_send_json_error(['message' => __('Insufficient permissions', 'templately')]);
111 - wp_die();
112 - }
113 -
114 - // Call the actual handler method
115 - call_user_func([$this, $action]);
116 - });
117 - }
118 -
119 - public function admin_init() {
120 - if (get_option('templately_flush_rewrite_rules', false)) {
56 + public function admin_init(){
57 + if(get_option('templately_flush_rewrite_rules', false)){
121 58 flush_rewrite_rules();
122 59 delete_option('templately_flush_rewrite_rules');
123 60 }
124 61 }
@@ -123,569 +60,224 @@
123 60 }
124 61 }
125 62
126 63 public function import_settings() {
127 - $user = Options::get_instance()->get('user');
128 - if (!empty($user['is_disconnected'])) {
129 - wp_send_json_error(['message' => __('Your site connection is disconnected. Please migrate your connection first.', 'templately')]);
130 - return;
64 + if( ! current_user_can( 'edit_posts' ) ) {
65 + // @todo show error in ui. Currently getting stack.
66 + wp_send_json_error( __( 'Unauthorized action.', 'templately' ) );
131 67 }
132 68
133 - $data = wp_unslash($_POST);
69 + $data = wp_unslash( $_POST );
134 70
135 - $upload_dir = wp_upload_dir();
71 + update_option( self::SESSION_OPTION_KEY, $data );
136 72
137 - if(!empty($data['session_id'])){
138 - $session_id = $data['session_id'];
139 - // Security: Sanitize session_id from user input
140 - $session_id = AIUtils::sanitize_path_component($data['session_id'], 'session_id');
141 - if (is_wp_error($session_id)) {
142 - wp_send_json_error(['message' => $session_id->get_error_message()]);
143 - return;
144 - }
145 - $session_data = SessionData::get_data($session_id);
146 - $data = array_merge($session_data, $data);
147 - }
148 - else {
149 - $session_id = uniqid();
150 - }
73 + delete_transient('templately_fsi_log');
151 74
152 - $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
153 - $prv_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview' . DIRECTORY_SEPARATOR;
154 -
155 - $this->session_id = $session_id;
156 - $data['session_id'] = $session_id;
157 -
158 - $data['root_dir'] = $tmp_dir;
159 - $data['prv_dir'] = $prv_dir;
160 - $data['dir_path'] = $tmp_dir . $session_id . DIRECTORY_SEPARATOR;
161 - $data['zip_path'] = $tmp_dir . "{$session_id}.zip";
162 -
163 -
164 - if ( is_array( $data ) && ! empty( $data ) ) {
165 - foreach ( $data as $key => $value ) {
166 - $json = is_string($value) ? json_decode( $value, true ) : null;
167 - $data[ $key ] = $json !== null ? $json : $value;
168 - }
169 - }
170 -
171 - SessionData::save($session_id, $data);
172 -
173 -
174 - //clear previous revert backup
175 - $options = Utils::get_backup_options();
176 - foreach ($options as $key => $value) {
177 - delete_option("__templately_$key");
178 - }
179 - delete_option('templately_fsi_imported_list');
180 - delete_option('templately_fsi_log');
181 -
182 - wp_send_json_success([
75 + wp_send_json_success( [
183 76 'is_lightspeed' => !Helper::should_flush(),
184 - 'session_id' => $session_id,
185 - ]);
77 + ] );
186 78 }
187 79
188 - public function import_ai_settings() {
189 - $data = wp_unslash($_POST);
80 + private function update_session_data( $data ): bool {
81 + $old_data = get_option( self::SESSION_OPTION_KEY, [] );
190 82
191 - $upload_dir = wp_upload_dir();
83 + return update_option( self::SESSION_OPTION_KEY, wp_parse_args( $data, $old_data ) );
84 + }
192 85
193 - // Security: Sanitize session_id from user input
194 - $session_id = AIUtils::sanitize_path_component($data['session_id'], 'session_id');
195 - if (is_wp_error($session_id)) {
196 - wp_send_json_error(['message' => $session_id->get_error_message()]);
197 - return;
198 - }
86 + public function get_session_data(): array {
87 + $data = get_option( self::SESSION_OPTION_KEY, [] );
199 88
200 - $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
201 - $prv_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview' . DIRECTORY_SEPARATOR;
89 + $options = [];
202 90
203 - $this->session_id = $session_id;
204 - $data['root_dir'] = $tmp_dir;
205 - $data['prv_dir'] = $prv_dir;
206 - $data['dir_path'] = $tmp_dir . $session_id . DIRECTORY_SEPARATOR;
207 - $data['zip_path'] = $tmp_dir . "{$session_id}.zip";
208 -
209 - // Handle isLocalSite flag conversion
210 - if (isset($data['isLocalSite'])) {
211 - $data['isLocalSite'] = filter_var($data['isLocalSite'], FILTER_VALIDATE_BOOLEAN);
212 - }
213 -
214 91 if ( is_array( $data ) && ! empty( $data ) ) {
215 92 foreach ( $data as $key => $value ) {
216 - $json = is_string($value) ? json_decode( $value, true ) : null;
217 - $data[ $key ] = $json !== null ? $json : $value;
93 + $json = json_decode( $value, true );
94 + $options[ $key ] = $json !== null ? $json : $value;
218 95 }
219 96 }
220 97
221 - SessionData::save($session_id, $data);
222 -
223 -
224 - return $data;
98 + return $options;
225 99 }
226 100
227 - public function create_session_and_download() {
228 - if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
229 - exit;
230 - }
231 -
232 - Utils::add_gd_editor_filter();
233 -
234 - define('TEMPLATELY_START_TIME', microtime(true));
235 -
236 - register_shutdown_function( [ $this, 'register_shutdown' ] );
237 -
238 - // $this->finishRequestHeaders();
239 -
240 - try {
241 - // Get session data from AJAX request
242 - $session_data = $this->import_ai_settings();
243 -
244 - $this->request_params = $session_data;
245 - $this->initialize_props();
246 - $this->add_revert_hooks();
247 - $progress = $this->request_params['progress'] ?? [];
248 -
249 - if(empty($progress['create_log_dir'])){
250 - // Create Log Directory and if fail then chose option method
251 - LogHandler::create_log_dir();
252 -
253 - SessionData::mark_step_complete($this->session_id, 'create_log_dir');
254 - }
255 -
256 - $_id = isset($this->request_params['id']) ? (int) $this->request_params['id'] : null;
257 -
258 - if ($_id === null) {
259 - $this->throw(__('Invalid Pack ID.', 'templately'));
260 - }
261 -
262 - $this->check_writing_permission();
263 -
264 -
265 - if(empty($progress['download_zip'])){
266 -
267 - /**
268 - * Download the zip
269 - */
270 - $this->download_zip( $_id, true );
271 -
272 - SessionData::mark_step_complete($this->session_id, 'download_zip');
273 - }
274 -
275 - /**
276 - * Reading Manifest File
277 - */
278 - $this->manifest = $this->read_manifest($this->request_params['dir_path']);
279 -
280 - /**
281 - * Version Check
282 - */
283 - if ( ! empty( $this->manifest['version'] ) && version_compare( $this->manifest['version'], $this->version, '>' ) ) {
284 - $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
285 - }
286 -
287 - $platform = $this->manifest['platform'] ?? '';
288 - if($platform === 'elementor') {
289 - Helper::enable_elementor_container();
290 - }
291 -
292 - update_option('templately_import_platform', $platform);
293 -
294 - // Return success response for AJAX
295 - wp_send_json_success([
296 - 'session_id' => $this->session_id,
297 - 'pack_downloaded' => true,
298 - 'platform' => $platform,
299 - 'message' => __('Session created and pack downloaded successfully', 'templately')
300 - ]);
301 -
302 - } catch ( Exception $e ) {
303 - $should_retry = $e instanceof RetryableErrorException;
304 -
305 - wp_send_json_error([
306 - 'message' => $e->getMessage(),
307 - 'should_retry' => $should_retry
308 - ]);
309 - }
310 - }
311 -
312 - public function import_close_feedback_modal() {
313 - $return = null;
314 - if(isset($_GET['closeAction']) && $_GET['closeAction']){
315 - $review_email = isset($_POST['review-email']) ? sanitize_email($_POST['review-email']) : '';
316 - $pack_id = get_user_meta(get_current_user_id(), 'templately_fsi_pack_id', true);
317 -
318 - // Prepare the body of the request
319 - $body = json_encode([
320 - 'action' => $_GET['closeAction'],
321 - 'email' => $review_email,
322 - 'pack_id' => (int) $pack_id,
323 - ]);
324 -
325 - // Send the request to the API
326 - $response = Helper::make_api_post_request('v2/feedback/close', json_decode($body, true), [], 30);
327 - $body = wp_remote_retrieve_body($response);
328 - $return = json_decode($body, true);
329 - }
330 - update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done');
331 - wp_send_json_success($return);
332 - }
333 - public function feedback_form() {
334 - // Get data from $_POST
335 - $review_description = isset($_POST['review-description']) ? sanitize_textarea_field($_POST['review-description']) : '';
336 - $review_email = isset($_POST['review-email']) ? sanitize_email($_POST['review-email']) : '';
337 - $rating = isset($_POST['rating']) ? sanitize_text_field($_POST['rating']) : '';
338 - $pack_id = get_user_meta(get_current_user_id(), 'templately_fsi_pack_id', true);
339 -
340 - // Prepare the body of the request
341 - $body = json_encode([
342 - 'description' => $review_description,
343 - 'email' => $review_email,
344 - 'rating' => (int) $rating,
345 - 'pack_id' => (int) $pack_id,
346 - ]);
347 -
348 - // Send the request to the API
349 - $response = Helper::make_api_post_request('v2/feedback/store', json_decode($body, true), [], 30);
350 -
351 - if (is_wp_error($response)) {
352 - wp_send_json_error($response->get_error_message());
353 - }
354 -
355 - if (wp_remote_retrieve_response_code($response) != 200) {
356 - $error_message = $this->extract_error_from_response($response);
357 - wp_send_json_error($error_message, wp_remote_retrieve_response_code($response));
358 - }
359 -
360 - $body = wp_remote_retrieve_body($response);
361 - $data = json_decode($body, true);
362 -
363 - if (!isset($data['status']) || $data['status'] !== 'success') {
364 - wp_send_json_error('API response indicates failure.');
365 - }
366 -
367 - if (!isset($data['message'])) {
368 - wp_send_json_error('API response missing data.');
369 - }
370 -
371 - $result = $data['message'];
372 -
373 - wp_send_json_success($result);
374 - }
375 -
376 - // Modified get_session_data to use SessionData
377 - public function get_session_data() {
378 - if ($session_id = SessionData::get_session_id()) {
379 - return SessionData::get_data($session_id);
380 - }
381 - return [];
382 - }
383 -
384 - // Modified update_session_data to use SessionData
385 - public function update_session_data($data) {
386 - if ($session_id = SessionData::get_session_id()) {
387 - return SessionData::save($session_id, array_merge($this->get_session_data(), $data));
388 - }
389 - return false;
390 - }
391 -
392 101 public function initialize_props() {
393 102 $data = $this->get_session_data();
394 - if (isset($data['session_id'])) {
103 + if(isset($data['session_id'])){
395 104 $this->session_id = $data['session_id'];
396 105 }
397 - if (isset($data['dir_path'])) {
106 + if(isset($data['dir_path'])){
398 107 $this->dir_path = $data['dir_path'];
399 108 }
400 - if (isset($data['zip_path'])) {
401 - $this->filePath = $data['zip_path'];
402 - }
403 - if (isset($data['download_key'])) {
109 + if(isset($data['download_key'])){
404 110 $this->download_key = $data['download_key'];
405 111 }
406 - if (isset($data['is_import_status_handled'])) {
407 - $this->is_import_status_handled = $data['is_import_status_handled'];
112 + if(isset($data['dependency_data'])){
113 + $this->dependency_data = $data['dependency_data'];
408 114 }
115 +
409 116 }
410 117
411 - public function clear_session_data(): bool {
412 - return delete_site_option(self::SESSION_OPTION_KEY);
118 + private function clear_session_data(): bool {
119 + return delete_site_option( self::SESSION_OPTION_KEY );
413 120 }
414 121
415 - private function finishRequestHeaders() {
416 - if(Helper::should_flush()) {
417 - // Disable output buffering and compression
418 - @ini_set('output_buffering', 'Off');
419 - @ini_set('zlib.output_compression', 'Off');
420 - @ini_set('implicit_flush', 1);
122 + public function import() {
123 + if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
124 + exit;
125 + }
421 126
422 - // Time to run the import! Set no limit
423 - set_time_limit(0);
127 + delete_transient( 'templately_fsi_log' );
424 128
425 -
426 - // Set headers to prevent caching and buffering
427 - header('Content-Type: text/event-stream, charset=UTF-8');
428 - header('Cache-Control: no-cache, must-revalidate');
429 - header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
430 - header('Connection: Keep-Alive');
431 - header('Pragma: no-cache');
432 -
433 - if (!empty($GLOBALS['is_nginx'])) {
434 - header('X-Accel-Buffering: no');
435 - header('Content-Encoding: none');
436 - }
437 -
438 - flush();
439 - ob_flush();
440 - wp_ob_end_flush_all();
441 - } else {
442 - header("Cache-Control: no-store, no-cache");
443 - // header( 'Content-Type: text/event-stream, charset=UTF-8' );
444 - // header( "Connection: Keep-Alive" );
445 -
446 - // Ignore user aborts and allow the script to run forever
447 - // (Use with caution, consider progress updates or timeouts)
448 - ignore_user_abort(true);
449 -
450 - // Time to run the import! Set no limit
451 - set_time_limit(0);
452 -
453 -
454 - if (!empty($GLOBALS['is_nginx'])) {
455 - header('X-Accel-Buffering: no');
456 - header('Content-Encoding: none');
457 - }
458 -
459 - // Send output as soon as possible during long-running process
460 - if (function_exists('fastcgi_finish_request')) {
461 - fastcgi_finish_request();
462 - } elseif (function_exists('litespeed_finish_request')) {
463 - litespeed_finish_request();
464 - } else {
465 - wp_ob_end_flush_all();
466 - }
129 + if(Helper::should_flush()){
130 + header( "Cache-Control: no-store, no-cache" );
131 + header( 'Content-Type: text/event-stream, charset=UTF-8' );
132 + header( "Connection: Keep-Alive" );
467 133 }
468 - }
469 134
470 - public function import() {
471 - if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
472 - exit;
135 + if ( $GLOBALS['is_nginx'] ) {
136 + header( 'X-Accel-Buffering: no' );
137 + header( 'Content-Encoding: none' );
473 138 }
474 139
475 - Utils::add_gd_editor_filter();
140 + register_shutdown_function( [ $this, 'register_shutdown' ] );
476 141
142 + // Ignore user aborts and allow the script
143 + // to run forever
144 + ignore_user_abort(true);
477 145
478 - define('TEMPLATELY_START_TIME', microtime(true));
146 + // Time to run the import!
147 + set_time_limit( 0 );
479 148
480 - // delete_option( 'templately_fsi_log' );
481 149
482 - register_shutdown_function( [ $this, 'register_shutdown' ] );
483 150
484 - $this->finishRequestHeaders();
151 + // // Don't run cron until the request finishes, if possible.
152 + // if ( PHP_VERSION_ID >= 70016 && function_exists( 'fastcgi_finish_request' ) ) {
153 + // fastcgi_finish_request();
154 + // } elseif ( function_exists( 'litespeed_finish_request' ) ) {
155 + // litespeed_finish_request();
156 + // }
157 + if(Helper::should_flush()){
158 + flush();
159 + wp_ob_end_flush_all();
160 + }
485 161
486 162 try {
487 - $user = Options::get_instance()->get('user');
488 - if (!empty($user['is_disconnected'])) {
489 - $this->throw(__('Your site connection is disconnected. Please migrate your connection first.', 'templately'));
490 - }
163 + // TODO: Need to check if user is connected or not
491 164
492 - if(!empty($_GET['session_id'])){
493 - // Security: Sanitize session_id from user input
494 - $session_id = AIUtils::sanitize_path_component(sanitize_text_field($_GET['session_id']), 'session_id');
495 - if (is_wp_error($session_id)) {
496 - $this->throw($session_id->get_error_message());
497 - }
498 - $this->session_id = $session_id;
499 - }
500 - else {
501 - $this->throw(__('Invalid Session ID.', 'templately'));
502 - }
503 -
504 -
505 165 $this->request_params = $this->get_session_data();
506 - $this->initialize_props();
507 - $this->add_revert_hooks();
508 - $progress = $this->request_params['progress'] ?? [];
509 166
510 - // Trigger action hook for network admin multisite handling
511 - do_action( 'templately_fsi_before_import', $this, $this->request_params );
167 + $_id = isset( $this->request_params['id'] ) ? (int) $this->request_params['id'] : null;
512 168
513 - // Filter Child Type for Elementor Pro Promotion Widget
514 - if ( class_exists( '\Elementor\Plugin' ) ) {
515 - add_filter( 'elementor/element/get_child_type', [ '\Templately\Core\Platform\Elementor', 'filter_child_type' ], 10, 3 );
169 + if ( $_id === null ) {
170 + $this->throw( __( 'Invalid Pack ID.', 'templately' ) );
516 171 }
517 172
518 - // Refresh progress after potential multisite creation
519 - $progress = $this->request_params['progress'] ?? [];
520 -
521 - if(empty($progress['create_log_dir'])){
522 - // Create Log Directory and if fail then chose option method
523 - LogHandler::create_log_dir();
524 -
525 - SessionData::mark_step_complete($this->session_id, 'create_log_dir');
526 - $this->sse_message( [
527 - 'type' => 'eventLog',
528 - 'action' => 'eventLog',
529 - 'info' => 'create_log_dir',
530 - 'results' => __METHOD__ . '::' . __LINE__,
531 - ] );
173 + if(!isset($_GET['part'])){
174 + $this->throw( __( 'Invalid request.', 'templately' ) );
532 175 }
533 176
534 - $_id = isset($this->request_params['id']) ? (int) $this->request_params['id'] : null;
177 + switch ($_GET['part']) {
178 + case 'download':
179 + /**
180 + * Check Writing Permission
181 + */
182 + $this->check_writing_permission();
535 183
536 - if ($_id === null) {
537 - $this->throw(__('Invalid Pack ID.', 'templately'));
538 - }
184 + /**
185 + * Download the zip
186 + */
187 + $this->download_zip( $_id );
539 188
540 - $this->sse_message( [
541 - 'type' => 'start',
542 - 'action' => 'eventLog',
543 - 'results' => __METHOD__ . '::' . __LINE__,
544 - ] );
189 + /**
190 + * Reading Manifest File
191 + */
192 + $this->read_manifest();
545 193
546 - if(empty($progress['check_writing_permission'])){
547 - /**
548 - * Check Writing Permission
549 - */
550 - $this->check_writing_permission();
194 + /**
195 + * Version Check
196 + */
197 + if ( ! empty( $this->manifest['version'] ) && version_compare( $this->manifest['version'], $this->version, '>' ) ) {
198 + /**
199 + * FIXME: The message should be re-written (by content/support team).
200 + */
201 + $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
202 + }
551 203
552 - SessionData::mark_step_complete($this->session_id, 'check_writing_permission');
553 - }
204 + /**
205 + * Checking & Installing Plugin Dependencies
206 + */
207 + $this->install_dependencies();
554 208
555 - if(empty($progress['download_zip'])){
209 + $this->sse_message( [
210 + 'type' => 'complete',
211 + 'action' => 'downloadComplete',
212 + 'results' => '',
213 + ] );
214 + break;
215 + case 'import':
216 + /**
217 + * Should Revert Old Data
218 + */
219 + $this->revert();
556 220
557 - /**
558 - * Download the zip
559 - */
560 - $this->download_zip( $_id );
561 221
562 - SessionData::mark_step_complete($this->session_id, 'download_zip');
563 - $this->sse_message( [
564 - 'type' => 'continue',
565 - 'action' => 'continue',
566 - 'info' => 'download_zip',
567 - 'results' => __METHOD__ . '::' . __LINE__,
568 - ] );
569 - exit;
570 - }
222 + $this->initialize_props();
571 223
224 + /**
225 + * Reading Manifest File
226 + */
227 + $this->read_manifest();
572 228
573 -
574 -
575 - /**
576 - * Reading Manifest File
577 - */
578 - $this->manifest = $this->read_manifest($this->request_params['dir_path']);
579 -
580 - /**
581 - * Version Check
582 - */
583 - if ( ! empty( $this->manifest['version'] ) && version_compare( $this->manifest['version'], $this->version, '>' ) ) {
584 - /**
585 - * FIXME: The message should be re-written (by content/support team).
586 - */
587 - $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
229 + /**
230 + * Platform Based Templates Import
231 + */
232 + $this->start_content_import();
233 + break;
234 + default:
235 + $this->throw( __( 'Invalid request.', 'templately' ) );
236 + break;
588 237 }
589 238
590 - $platform = $this->manifest['platform'] ?? '';
591 - if($platform === 'elementor') {
592 - Helper::enable_elementor_container();
593 - }
594 -
595 -
596 -
597 - update_option('templately_import_platform', $platform);
598 -
599 -
600 - /**
601 - * Should Revert Old Data
602 - */
603 - // $this->revert();
604 -
605 - /**
606 - * Platform Based Templates Import
607 - */
608 - $this->start_content_import();
609 -
610 239 } catch ( Exception $e ) {
611 - $should_retry = $e instanceof RetryableErrorException;
612 240 $this->handle_import_status('failed', $e->getMessage());
613 -
614 - $this->sse_message([
615 - 'action' => 'error',
616 - 'status' => 'error',
617 - 'type' => "error",
618 - 'retry' => $should_retry,
619 - 'title' => __("Oops!", "templately"),
620 - 'message' => $e->getMessage(),
621 - 'trace' => $e->getTraceAsString(),
622 - ]);
241 + $this->sse_message( [
242 + 'action' => 'error',
243 + 'status' => 'error',
244 + 'type' => "error",
245 + 'title' => __("Oops!", "templately"),
246 + 'message' => $e->getMessage()
247 + ] );
623 248 }
624 249
625 - // if($_GET['part'] === 'import'){
250 + if($_GET['part'] === 'import'){
626 251 // TODO: cleanup
627 - // $this->clear_session_data();
628 - // }
252 + $this->clear_session_data();
253 + }
629 254 }
630 255
256 + public function import_status(){
257 + $log = get_transient( 'templately_fsi_log' );
258 + if(!empty($log) && is_array($log)){
259 + $lastLogIndex = isset($_GET['lastLogIndex']) ? (int) $_GET['lastLogIndex'] : 0;
260 + $log = array_slice($log, $lastLogIndex);
631 261
632 - // Updated import_status method
633 - public function import_status() {
634 - $request_params = $this->get_session_data();
635 -
636 - if (isset($request_params['log_type']) && $request_params['log_type'] == 'file') {
637 - $log_index = isset($_GET['lastLogIndex']) ? (int) $_GET['lastLogIndex'] : 0;
638 - $log = LogHandler::read_log_file($log_index);
639 -
640 - wp_send_json(['count' => count($log), 'log' => $log]);
641 - } else {
642 - $log = get_option('templately_fsi_log');
643 -
644 - if (!empty($log) && is_array($log) && isset($_GET['lastLogIndex'])) {
645 - $lastLogIndex = (int) $_GET['lastLogIndex'];
646 - $log = array_slice($log, $lastLogIndex);
262 + // delete log after complete
263 + $data = end($log);
264 + if(isset($data, $data['action']) && ($data['action'] === 'complete' || $data['action'] === 'downloadComplete' || $data['action'] === 'error')){
265 + delete_transient( 'templately_fsi_log' );
647 266 }
648 - wp_send_json(['count' => $log ? count($log) : 0, 'log' => $log]);
649 267 }
268 + wp_send_json( ['log' => $log] );
650 269 }
651 270
652 271 /**
653 272 * @throws Exception
654 273 */
655 - private function throw($message, $code = 0) {
656 - if ($this->dev_mode) {
657 - error_log(print_r($message, 1));
274 + private function throw( $message, $code = 0 ) {
275 + if ( $this->dev_mode ) {
276 + error_log( print_r( $message, 1 ) );
658 277 }
659 - throw new Exception($message);
278 + throw new Exception( $message );
660 279 }
661 - /**
662 - * @throws Exception
663 - */
664 - private function throw_non_retryable($message, $code = 0) {
665 - if ($this->dev_mode) {
666 - error_log(print_r($message, 1));
667 - }
668 - throw new NonRetirableErrorException($message);
669 - }
670 - /**
671 - * @throws Exception
672 - */
673 - private function throw_retryable($message, $code = 0) {
674 - if ($this->dev_mode) {
675 - error_log(print_r($message, 1));
676 - }
677 - throw new RetryableErrorException($message);
678 - }
679 - /**
680 - * @throws Exception
681 - */
682 - private function throw_unknown($message, $code = 0) {
683 - if ($this->dev_mode) {
684 - error_log(print_r($message, 1));
685 - }
686 - throw new UnknownErrorException($message);
687 - }
688 280
689 281 /**
690 282 * @throws Exception
691 283 */
@@ -691,80 +283,84 @@
691 283 */
692 284 private function check_writing_permission() {
693 285 $upload_dir = wp_upload_dir();
694 286
695 - if (!is_writable($upload_dir['basedir'])) {
696 - $this->throw(__('Upload directory is not writable.', 'templately'));
287 + if ( ! is_writable( $upload_dir['basedir'] ) ) {
288 + $this->throw( __( 'Upload directory is not writable.', 'templately' ) );
697 289 }
698 290
699 - $this->tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
291 + $this->tmp_dir = trailingslashit( $upload_dir['basedir'] ) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
700 292
701 - if (!is_dir($this->tmp_dir)) {
702 - wp_mkdir_p($this->tmp_dir);
293 + if ( ! is_dir( $this->tmp_dir ) ) {
294 + wp_mkdir_p( $this->tmp_dir );
703 295 }
704 296
705 - $this->sse_log('writing_permission_check', __('Permission Passed', 'templately'), 100);
297 + $this->sse_log( 'writing_permission_check', __( 'Permission Passed', 'templately' ), 100 );
706 298 }
707 299
300 + private function get_api_url( $version, $end_point ): string {
301 + return $this->dev_mode ? "https://app.templately.dev/api/$version/" . $end_point : "https://app.templately.com/api/$version/" . $end_point;
302 + }
303 +
304 + private function info_get_api_url( $id ): string {
305 + return $this->dev_mode ? 'https://app.templately.dev/api/v1/import/info/pack/' . $id : 'https://app.templately.com/api/v1/import/info/pack/' . $id;
306 + }
307 +
708 308 /**
709 309 * @throws Exception
710 310 */
711 - private function download_zip( $id, $is_ai = false ) {
712 - $this->sse_log( 'download', __( 'Downloading Template Pack', 'templately' ), 1 );
713 - $extra_headers = [
714 - 'x-templately-is-ai' => $is_ai,
715 - 'x-templately-session-id' => $this->session_id,
716 - 'x-templately-requested-platform' => $this->request_params["requested_platform"] ?? 'templately',
717 - ];
718 - $response = Helper::make_api_get_request("v2/import/pack/$id", [], $extra_headers, 90);
311 + private function download_zip( $id ) {
312 + // $this->sse_log( 'download', __( 'Downloading Template Pack', 'templately' ), 1 );
313 + $response = wp_remote_get( $this->get_api_url( "v2", "import/pack/$id" ), [
314 + 'timeout' => 30,
315 + 'headers' => [
316 + 'Content-Type' => 'application/json',
317 + 'Authorization' => 'Bearer ' . $this->api_key,
318 + 'x-templately-ip' => Helper::get_ip(),
319 + 'x-templately-url' => home_url( '/' ),
320 + 'x-templately-version' => TEMPLATELY_VERSION,
321 + ]
322 + ] );
719 323
720 - $response_code = wp_remote_retrieve_response_code($response);
721 - $content_type = wp_remote_retrieve_header($response, 'content-type');
722 - $this->download_key = wp_remote_retrieve_header($response, 'download-key');
324 + $response_code = wp_remote_retrieve_response_code( $response );
325 + $content_type = wp_remote_retrieve_header( $response, 'content-type' );
326 + $this->download_key = wp_remote_retrieve_header( $response, 'download-key' );
723 327
724 - if (is_wp_error($response)) {
725 - $this->throw_retryable(__('Template pack download failed', 'templately') . $response->get_error_message());
726 - } else if ($response_code != 200) {
328 + if ( is_wp_error( $response ) ) {
329 + $this->throw( __( 'Template pack download failed', 'templately' ) . $response->get_error_message() );
330 + }
331 + else if ($response_code != 200) {
727 332 if (strpos($content_type, 'application/json') !== false) {
728 333 // Retrieve Data from Response Body.
729 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
334 + $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
730 335
731 336 // If the response body is JSON and it contains an error, throw an exception with the error message
732 337 if (isset($response_body['status']) && $response_body['status'] === 'error') {
733 - $support_message = '';
734 - if(strpos($response_body['message'], 'https://templately.com/?support=open') === false){
735 - $support_message = sprintf(__(" Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://templately.com/?support=open');
736 - }
737 - $this->throw_non_retryable($response_body['message'] . $support_message);
338 + $this->throw( $response_body['message'] );
738 339 }
739 340 }
740 - $this->throw_unknown(__('Template pack download failed with response code: ', 'templately') . $response_code);
341 + $this->throw( __( 'Template pack download failed with response code: ', 'templately' ) . $response_code );
741 342 }
742 343
743 - $this->sse_log('download', __('Downloading Template Pack', 'templately'), 57);
344 + $this->sse_log( 'download', __( 'Template is getting ready', 'templately' ), 57 );
744 345
745 - SessionData::set($this->session_id, 'download_key', $this->download_key);
346 + $session_id = uniqid();
347 + $this->dir_path = $this->tmp_dir . $session_id . DIRECTORY_SEPARATOR;
348 + $this->filePath = $this->tmp_dir . "{$session_id}.zip";
349 + $this->session_id = $session_id;
746 350
747 - // Security: Validate file path is within WordPress upload directory before writing
748 - $validation = AIUtils::validate_file_path($this->filePath);
749 - if (is_wp_error($validation)) {
750 - $this->throw($validation->get_error_message());
751 - }
351 + $this->update_session_data( [
352 + 'session_id' => $this->session_id,
353 + 'dir_path' => $this->dir_path,
354 + 'download_key' => $this->download_key,
355 + ] );
752 356
753 - // Security: Validate file path is within WordPress upload directory before writing
754 - $validation = AIUtils::validate_file_path($this->filePath);
755 - if (is_wp_error($validation)) {
756 - $this->throw($validation->get_error_message());
757 - }
357 + if ( file_put_contents( $this->filePath, $response['body'] ) ) { // phpcs:ignore
358 + $this->sse_log( 'download', __( 'Template is getting ready', 'templately' ), 100 );
758 359
759 - wp_mkdir_p(dirname($this->filePath));
760 -
761 - if (file_put_contents($this->filePath, $response['body'])) { // phpcs:ignore
762 - $this->sse_log('download', __('Downloading Template Pack', 'templately'), 100);
763 -
764 360 $this->unzip();
765 361 } else {
766 - $this->throw_retryable(__('Downloading Failed. Please try again', 'templately'));
362 + $this->throw( __( 'Downloading Failed. Please try again', 'templately' ) );
767 363 }
768 364 }
769 365
770 366 /**
@@ -770,102 +366,35 @@
770 366 /**
771 367 * @throws Exception
772 368 */
773 369 protected function unzip() {
774 - if (!WP_Filesystem()) {
775 - $this->throw(__('WP_Filesystem cannot be initialized', 'templately'));
370 + if ( ! WP_Filesystem() ) {
371 + $this->throw( __( 'WP_Filesystem cannot be initialized', 'templately' ) );
776 372 }
777 - $unzip = unzip_file($this->filePath, $this->dir_path);
778 - if (is_wp_error($unzip)) {
779 - $unzip = $this->unzip_file($this->filePath, $this->dir_path);
780 - }
781 373
782 - $manifest_file = $this->dir_path . 'manifest.json';
783 -
784 - // If manifest.json is missing, but any subdirectory contains manifest.json, move all its contents up and remove the subdirectory.
785 - if ( ! file_exists( $manifest_file ) ) {
786 - $entries = array_diff( scandir( $this->dir_path ), [ '.', '..' ] );
787 - $dirs = array_filter( $entries, fn($e) => is_dir( $this->dir_path . $e ) );
788 - $files = array_filter( $entries, fn($e) => is_file( $this->dir_path . $e ) );
789 - foreach ($dirs as $subdir) {
790 - $subdir_path = $this->dir_path . $subdir . DIRECTORY_SEPARATOR;
791 - if ( file_exists( $subdir_path . 'manifest.json' ) ) {
792 - copy($subdir_path . 'manifest.json', $manifest_file);
793 -
794 - foreach ( array_diff( scandir( $subdir_path ), [ '.', '..' ] ) as $item ) {
795 - $src = $subdir_path . $item;
796 - $dst = $this->dir_path . $item;
797 - if (is_dir($src)) {
798 - if (!file_exists($dst)) {
799 - wp_mkdir_p($dst);
800 - }
801 - // Recursively copy directory
802 - $this->copyDirectory($src, $dst);
803 - } else {
804 - copy($src, $dst);
805 - }
806 - }
807 - // Remove the subdirectory and its contents
808 - $this->removeDirectory($subdir_path);
809 - break; // Only process the first subdir with manifest.json
810 - }
811 - }
374 + $unzip = unzip_file( $this->filePath, $this->dir_path );
375 + if ( is_wp_error( $unzip ) ) {
376 + $unzip = $this->unzip_file( $this->filePath, $this->dir_path );
812 377 }
813 378
814 - if (is_wp_error($unzip)) {
379 + if ( is_wp_error( $unzip ) ) {
815 380 $error = $unzip->get_error_message();
816 - if (empty($error)) {
381 + if(empty($error)){
817 382 // Generic error message
818 - Helper::log($unzip);
819 - $error_message = sprintf(__("It seems we're experiencing technical difficulties. Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://templately.com/?support=open');
820 - $this->throw($error_message);
821 - } else {
822 - $this->throw($unzip->get_error_message());
383 + Helper::log( $unzip );
384 + $error_message = sprintf(__("It seems we're experiencing technical difficulties. Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://wpdeveloper.com/support');
385 + $this->throw( $error_message );
823 386 }
824 - }
825 -
826 - if ($unzip) {
827 - unlink($this->filePath);
828 - }
829 - }
830 -
831 - /**
832 - * Recursively copy a directory
833 - */
834 - private function copyDirectory($src, $dst) {
835 - $dir = opendir($src);
836 - wp_mkdir_p($dst);
837 - while(false !== ($file = readdir($dir))) {
838 - if (($file != '.') && ($file != '..')) {
839 - if (is_dir($src . DIRECTORY_SEPARATOR . $file)) {
840 - $this->copyDirectory($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file);
841 - } else {
842 - copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file);
843 - }
387 + else{
388 + $this->throw( $unzip->get_error_message() );
844 389 }
845 390 }
846 - closedir($dir);
847 - }
848 391
849 - /**
850 - * Recursively remove a directory
851 - */
852 - private function removeDirectory($dir) {
853 - if (!file_exists($dir)) return;
854 - $items = array_diff(scandir($dir), ['.', '..']);
855 - foreach ($items as $item) {
856 - $path = $dir . DIRECTORY_SEPARATOR . $item;
857 - if (is_dir($path)) {
858 - $this->removeDirectory($path);
859 - } else {
860 - unlink($path);
861 - }
392 + if ( $unzip ) {
393 + unlink( $this->filePath );
862 394 }
863 - rmdir($dir);
864 395 }
865 396
866 -
867 -
868 397 /**
869 398 * Unzip a specified ZIP file to a location on the Filesystem.
870 399 *
871 400 * @param string $file Full path and filename of ZIP archive.
@@ -896,18 +425,17 @@
896 425
897 426 /**
898 427 * @throws Exception
899 428 */
900 - private function read_manifest($dir_path) {
901 - $manifest_content = file_get_contents($dir_path . 'manifest.json');
902 - if (empty($manifest_content)) {
903 - $this->throw(__('Cannot be imported, as the manifest file is corrupted', 'templately'));
429 + private function read_manifest() {
430 + $manifest_content = file_get_contents( $this->dir_path . DIRECTORY_SEPARATOR . 'manifest.json' );
431 + if ( empty( $manifest_content ) ) {
432 + $this->throw( __( 'Cannot be imported, as the manifest file is corrupted', 'templately' ) );
904 433 }
905 434
906 - $manifest_content = json_decode($manifest_content, true);
907 - $this->removeLog('temp');
435 + $this->manifest = json_decode( $manifest_content, true );
436 + $this->removeLog( 'temp' );
908 437
909 - return $manifest_content;
910 438 // TODO: Read & Broadcast the LOG for waiting list
911 439 // $this->sse_log( 'plugin', 'Installing required plugins', '--', 'updateLog', 'processing' );
912 440 // // $this->sse_log( 'extra-content', 'Import Extra Contents (i.e: Forms)', '--', 'updateLog', 'processing' );
913 441 // $this->sse_log( 'templates', 'Import Templates (i.e: Header, Footer etc)', '--', 'updateLog', 'processing' );
@@ -916,12 +444,127 @@
916 444 // $this->sse_log( 'finalize', 'Finalizing Your Imports', '--', 'updateLog', 'processing' );
917 445 }
918 446
919 447 private function skipped_plugin(): bool {
920 - return empty($this->request_params['plugins']) || !is_array($this->request_params['plugins']);
448 + return empty( $this->request_params['plugins'] ) || ! is_array( $this->request_params['plugins'] );
921 449 }
922 450
451 + private function install_dependencies() {
452 + $allowed_themes = ['hello-elementor', 'twentytwentyfour'];
923 453
454 + if ( ! empty( $this->request_params['theme'] ) && is_array( $this->request_params['theme'] ) ) {
455 + // activate theme
456 + $theme = $this->request_params['theme'];
457 +
458 + $this->before_install_hook();
459 +
460 + if (isset($theme['stylesheet']) && in_array($theme['stylesheet'], $allowed_themes)) {
461 + // do_action('before_theme_activation', $theme); // Trigger action before theme activation
462 + $this->sse_log( 'theme', 'Installing and activating theme: ' . $theme['name'], 0 );
463 + $plugin_status = Installer::get_instance()->install_and_activate_theme( $theme['stylesheet'] );
464 +
465 + if (!$plugin_status['success']) {
466 + $this->sse_message( [
467 + 'action' => 'updateLog',
468 + 'status' => 'error',
469 + 'message' => "Failed to activate theme: " . $theme['name'],
470 + 'type' => "theme",
471 + 'progress' => 0
472 + ] );
473 + $this->dependency_data['theme'] = [
474 + 'success' => false,
475 + 'name' => $theme['name'],
476 + 'slug' => $theme['stylesheet'],
477 + 'message' => $plugin_status['message'] ?? ''
478 + ];
479 + } else {
480 + // do_action('after_theme_activation', $theme); // Trigger action after theme activation
481 +
482 + $this->sse_message( [
483 + 'action' => 'updateLog',
484 + 'status' => 'complete',
485 + 'message' => "Activated theme: " . $theme['name'],
486 + 'type' => "theme",
487 + 'progress' => 100
488 + ] );
489 + $this->dependency_data['theme'] = [
490 + 'success' => true,
491 + 'name' => $theme['name'],
492 + 'slug' => $theme['stylesheet'],
493 + 'message' => $plugin_status['message'] ?? ''
494 + ];
495 + }
496 + }
497 +
498 + $this->after_install_hook();
499 + }
500 + else {
501 + $this->removeLog( 'theme' );
502 + }
503 + if ( ! empty( $this->request_params['plugins'] ) && is_array( $this->request_params['plugins'] ) ) {
504 + // $this->sse_log( 'plugin', 'Installing Plugins', 1 );
505 + $total_plugin = count( $this->request_params['plugins'] );
506 +
507 + $total_plugin_installed = $total_plugin;
508 + $_installed_plugins = 0;
509 +
510 + $this->before_install_hook();
511 +
512 + foreach ( $this->request_params['plugins'] as $dependency ) {
513 + $this->sse_log( 'plugin', 'Installing required plugins: ' . $dependency['name'], floor( ( 100 * $_installed_plugins / $total_plugin ) ) );
514 +
515 + $dependency['slug'] = $dependency['plugin_original_slug'];
516 + $plugin_status = Installer::get_instance()->install( $dependency );
517 +
518 + if ( ! $plugin_status['success'] ) {
519 + $this->sse_message( [
520 + 'position' => 'plugin',
521 + 'action' => 'updateLog',
522 + 'status' => 'error',
523 + 'message' => 'Installation Failed: ' . $dependency['name'] . ' (' . ( $plugin_status['message'] ?? '' ) . ')',
524 + 'type' => "plugin_{$dependency['plugin_original_slug']}",
525 + 'progress' => 0
526 + ] );
527 +
528 + if(isset($dependency['mustHave']) && $dependency['mustHave']){
529 + $this->removeLog( 'plugin' );
530 + $this->throw( 'Installation Failed: ' . $dependency['name'] . ' (' . ( $plugin_status['message'] ?? '' ) . ')' );
531 + }
532 +
533 + $this->dependency_data['plugins']['failed'][] = [
534 + 'name' => $dependency['name'],
535 + 'slug' => $dependency['slug'],
536 + 'link' => $dependency['link'],
537 + 'message' => $plugin_status['message'] ?? ''
538 + ];
539 + } else {
540 + $_installed_plugins++;
541 + $total_plugin_installed--;
542 + }
543 + }
544 +
545 + $this->after_install_hook();
546 +
547 + $this->sse_message( [
548 + 'action' => 'updateLog',
549 + 'status' => 'complete',
550 + 'message' => "Installed required plugins ($_installed_plugins/$total_plugin)",
551 + 'type' => "plugin",
552 + 'progress' => 100
553 + ] );
554 + $this->dependency_data['plugins']['total'] = $total_plugin;
555 + $this->dependency_data['plugins']['succeed'] = $_installed_plugins;
556 + } else {
557 + $this->removeLog( 'plugin' );
558 + }
559 +
560 +
561 + $this->update_session_data( [
562 + 'dependency_data' => json_encode($this->dependency_data),
563 + ] );
564 + // $this->sse_log( 'plugin', 'Skipped Installing Plugins', '--', 'updateLog', 'skipped' );
565 + }
566 +
924 567 private function before_install_hook() {
925 568 // remove_all_actions( 'wp_loaded' );
926 569 // remove_all_actions( 'after_setup_theme' );
927 570 // remove_all_actions( 'plugins_loaded' );
@@ -944,301 +587,55 @@
944 587 private function start_content_import() {
945 588 add_filter('upload_mimes', array($this, 'allow_svg_upload'));
946 589 add_filter('elementor/files/allow_unfiltered_upload', '__return_true');
947 590
948 - $request_params = $this->get_session_data();
949 -
950 - $import = new Import(array_merge($request_params, [
951 - 'origin' => $this,
952 - 'manifest' => $this->manifest,
953 - ]));
591 + $import = new Import( $this );
954 592 $imported_data = $import->run();
955 593
956 - $import_status = $this->handle_import_status('success');
594 + $this->handle_import_status('success');
957 595
958 596 update_option('templately_flush_rewrite_rules', true, false);
959 597
960 - $normalized_data = $this->normalize_imported_data($imported_data);
961 - // Use timeout-aware wait handler for AI content processing
962 - if(!empty($request_params['ai_page_ids']) && empty($normalized_data['ai_content']['processed']['credit_cost'])){
963 - $processed_pages = get_option("templately_ai_processed_pages", []);
964 - $updated_ids = $processed_pages[$request_params['process_id']] ?? [];
965 -
966 - // Use the static timeout-aware wait handler from AIUtils
967 - AIUtils::handle_sse_wait_with_timeout(
968 - $this->session_id,
969 - 'ai_content_import_time',
970 - $updated_ids,
971 - $request_params['ai_page_ids'],
972 - [$this, 'sse_message'],
973 - [
974 - 'name' => 'ai-content',
975 - 'message' => __('Missing Credit Cost', 'templately'),
976 - ],
977 - null, // No specific template ID for this context
978 - 30
979 - );
980 - }
981 -
982 - // Get skipped items if skip feature was enabled
983 - $skipped_items = SessionData::get_skipped_items($this->session_id);
984 - if (!empty($skipped_items)) {
985 - $normalized_data['skipped_items'] = $skipped_items;
986 - }
987 -
988 - $this->sse_message([
598 + $this->sse_message( [
989 599 'type' => 'complete',
990 600 'action' => 'complete',
991 - 'results' => $normalized_data,
992 - ]);
993 -
994 - update_user_meta(get_current_user_id(), 'templately_fsi_pack_id', $request_params["id"]);
995 - if(!empty($import_status['hasFeedback'])){
996 - update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done');
997 - }
998 - else{
999 - update_user_meta(get_current_user_id(), 'templately_fsi_complete', true);
1000 - }
1001 -
1002 - do_action('templately_fsi_import_complete', $normalized_data);
1003 - $this->clear_data_file($request_params);
601 + 'results' => $this->normalize_imported_data( $imported_data )
602 + ] );
1004 603 }
1005 604
1006 - private function clear_data_file($request_params){
1007 - if(defined('TEMPLATELY_DEV') && TEMPLATELY_DEV){
1008 - return;
1009 - }
605 + private function normalize_imported_data( $data ) {
606 + $templates = ! empty( $data['templates']['succeed'] ) ? count( $data['templates']['succeed'] ) : 0;
607 + $template_types = ! empty( $data['templates']['template_types'] ) ? $data['templates']['template_types'] : [];
1010 608
1011 - // Handle directory cleanup
1012 - Utils::cleanup_directory($this->dir_path);
1013 - $upload_dir = wp_upload_dir();
1014 -
1015 - // Always save to preview directory for AI content workflow
1016 - $session_id = $request_params['session_id'] ?? '';
1017 - $pack_id = $request_params['id'] ?? '';
1018 -
1019 - // Set up directory paths for cleanup
1020 - $root_dir = $request_params['root_dir'] ?? trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp';
1021 - $prv_dir = $request_params['prv_dir'] ?? trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview';
1022 -
1023 - $processed_data = AIUtils::get_ai_process_data_by_session_id($session_id);
1024 -
1025 - if (!empty($pack_id) && !empty($session_id)) {
1026 - // Clean session data - keep only current session, remove others with same pack_id
1027 - // Modified to use SessionData directly
1028 - $removed_session_ids = SessionData::clean_by_pack_id($pack_id, $session_id);
1029 - }
1030 -
1031 - // Also run general expiration cleanup (7 days default)
1032 - // Modified to use SessionData directly
1033 - if (mt_rand(1, 100) <= 5) { // 5% chance to run on any import finish
1034 - $expired_cleanup = SessionData::cleanup_expired(7);
1035 -
1036 - // Clean AI process data - keep only current process, remove others with same pack_id
1037 - $current_process_id = !empty($processed_data['process_id']) ? $processed_data['process_id'] : null;
1038 - $removed_process_ids = AIUtils::clean_ai_process_data_by_pack_id($pack_id, $current_process_id);
1039 -
1040 - // Directory-based cleanup for session data directories
1041 - $this->cleanup_session_directories($root_dir, $pack_id, $session_id);
1042 -
1043 - // Directory-based cleanup for AI process data directories
1044 - $this->cleanup_ai_process_directories($prv_dir, $pack_id, $current_process_id);
1045 -
1046 - // Log cleanup results if in dev mode
1047 - if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1048 - if (!empty($removed_session_ids)) {
1049 - error_log('Templately: Cleaned up session IDs: ' . implode(', ', $removed_session_ids));
1050 - }
1051 - if (!empty($removed_process_ids)) {
1052 - error_log('Templately: Cleaned up process IDs: ' . implode(', ', $removed_process_ids));
1053 - }
1054 - }
1055 - }
1056 - }
1057 -
1058 - /**
1059 - * Directory-based cleanup for session data directories
1060 - * Scans the actual filesystem directories and removes directories that match cleanup criteria
1061 - *
1062 - * @param string $root_dir The root directory containing session directories
1063 - * @param string $pack_id The pack ID to match for cleanup
1064 - * @param string $current_session_id The current session ID to preserve
1065 - */
1066 - private function cleanup_session_directories($root_dir, $pack_id, $current_session_id) {
1067 - if (empty($root_dir) || !is_dir($root_dir) || empty($pack_id) || empty($current_session_id)) {
1068 - return;
1069 - }
1070 -
1071 - try {
1072 - // Get all session data to check pack_id associations
1073 - $all_session_data = SessionData::get_all_data();
1074 -
1075 - // Scan the actual directories in the filesystem
1076 - $directories = scandir($root_dir);
1077 - if ($directories === false) {
1078 - return;
1079 - }
1080 -
1081 - foreach ($directories as $dir_name) {
1082 - // Skip current directory, parent directory, and current session
1083 - if ($dir_name === '.' || $dir_name === '..' || $dir_name === $current_session_id) {
1084 - continue;
1085 - }
1086 -
1087 - $dir_path = trailingslashit($root_dir) . $dir_name;
1088 -
1089 - // Only process actual directories
1090 - if (!is_dir($dir_path)) {
1091 - continue;
1092 - }
1093 -
1094 - // Check if this directory should be cleaned up
1095 - $should_cleanup = false;
1096 -
1097 - // If we have session data for this directory, check if it matches the pack_id
1098 - if (isset($all_session_data[$dir_name]) &&
1099 - isset($all_session_data[$dir_name]['id']) &&
1100 - $all_session_data[$dir_name]['id'] === $pack_id) {
1101 - $should_cleanup = true;
1102 - } else if (!isset($all_session_data[$dir_name])) {
1103 - // This is an orphaned directory with no corresponding session data
1104 - $should_cleanup = true;
1105 - }
1106 -
1107 - if ($should_cleanup) {
1108 - Utils::cleanup_directory($dir_path);
1109 -
1110 - if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1111 - error_log('Templately: Cleaned up session directory: ' . $dir_name);
1112 - }
1113 - }
1114 - }
1115 - } catch (Exception $e) {
1116 - if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1117 - error_log('Templately: Error during session directory cleanup: ' . $e->getMessage());
1118 - }
1119 - }
1120 - }
1121 -
1122 - /**
1123 - * Directory-based cleanup for AI process data directories
1124 - * Scans the actual filesystem directories and removes directories that match cleanup criteria
1125 - *
1126 - * @param string $prv_dir The preview directory containing process directories
1127 - * @param string $pack_id The pack ID to match for cleanup
1128 - * @param string $current_process_id The current process ID to preserve (optional)
1129 - */
1130 - private function cleanup_ai_process_directories($prv_dir, $pack_id, $current_process_id = null) {
1131 - if (empty($prv_dir) || !is_dir($prv_dir) || empty($pack_id)) {
1132 - return;
1133 - }
1134 -
1135 - try {
1136 - // Get all AI process data to check pack_id associations
1137 - $ai_process_data = AIUtils::get_ai_process_data();
1138 -
1139 - // Scan the actual directories in the filesystem
1140 - $directories = scandir($prv_dir);
1141 - if ($directories === false) {
1142 - return;
1143 - }
1144 -
1145 - foreach ($directories as $dir_name) {
1146 - // Skip current directory, parent directory, and current process
1147 - if ($dir_name === '.' || $dir_name === '..' ||
1148 - (!empty($current_process_id) && $dir_name === $current_process_id)) {
1149 - continue;
1150 - }
1151 -
1152 - $dir_path = trailingslashit($prv_dir) . $dir_name;
1153 -
1154 - // Only process actual directories
1155 - if (!is_dir($dir_path)) {
1156 - continue;
1157 - }
1158 -
1159 - // Check if this directory should be cleaned up
1160 - $should_cleanup = false;
1161 -
1162 - // If we have process data for this directory, check if it matches the pack_id
1163 - if (isset($ai_process_data[$dir_name]) &&
1164 - is_array($ai_process_data[$dir_name]) &&
1165 - isset($ai_process_data[$dir_name]['pack_id']) &&
1166 - $ai_process_data[$dir_name]['pack_id'] === $pack_id) {
1167 - $should_cleanup = true;
1168 - } else if (!isset($ai_process_data[$dir_name])) {
1169 - // This is an orphaned directory with no corresponding process data
1170 - $should_cleanup = true;
1171 - }
1172 -
1173 - if ($should_cleanup) {
1174 - Utils::cleanup_directory($dir_path);
1175 -
1176 - if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1177 - error_log('Templately: Cleaned up AI process directory: ' . $dir_name);
1178 - }
1179 - }
1180 - }
1181 - } catch (Exception $e) {
1182 - if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1183 - error_log('Templately: Error during AI process directory cleanup: ' . $e->getMessage());
1184 - }
1185 - }
1186 - }
1187 -
1188 - private function normalize_imported_data($data) {
1189 - $request_params = $this->get_session_data();
1190 - $attachments = !empty($data['attachments']['succeed']) ? count($data['attachments']['succeed']) : 0;
1191 - $attachments_fail = !empty($data['attachments']['failed']) ? count($data['attachments']['failed']) : 0;
1192 - $attachments_errors = !empty($data['attachments_errors']) ? $data['attachments_errors'] : [];
1193 - $templates = !empty($data['templates']['succeed']) ? count($data['templates']['succeed']) : 0;
1194 - $template_types = !empty($data['templates']['template_types']) ? $data['templates']['template_types'] : [];
1195 - $dependency_data = !empty($data['dependency_data']) ? $data['dependency_data'] : [];
1196 -
1197 609 $post_types = [];
1198 610 $content_templates = [];
1199 - if (!empty($data['content']) && is_array($data['content'])) {
1200 - foreach ($data['content'] as $type => $type_data) {
1201 - $content_templates[$type] = !empty($type_data['succeed']) ? count($type_data['succeed']) : 0;
611 + if ( ! empty( $data['content'] ) && is_array( $data['content'] ) ) {
612 + foreach ( $data['content'] as $type => $type_data ) {
613 + $content_templates[ $type ] = ! empty( $type_data['succeed'] ) ? count( $type_data['succeed'] ) : 0;
1202 614 $post_types[] = $this->get_post_type_label_by_slug($type);
1203 615 }
1204 616 }
1205 617
1206 618 $contents = [];
1207 - if (!empty($data['wp-content']) && is_array($data['wp-content'])) {
1208 - foreach ($data['wp-content'] as $type => $type_data) {
1209 - $contents[$type] = !empty($type_data['succeed']) ? count($type_data['succeed']) : 0;
1210 - if (!in_array($type, ['wp_navigation', 'nav_menu_item'])) {
619 + if ( ! empty( $data['wp-content'] ) && is_array( $data['wp-content'] ) ) {
620 + foreach ( $data['wp-content'] as $type => $type_data ) {
621 + $contents[ $type ] = ! empty( $type_data['succeed'] ) ? count( $type_data['succeed'] ) : 0;
622 + if(!in_array($type, ['wp_navigation', 'nav_menu_item'])){
1211 623 $post_types[] = $this->get_post_type_label_by_slug($type);
1212 624 }
1213 625 }
1214 626 }
1215 627
1216 - $_processed_pages = AIUtils::get_processed_pages_data($request_params['process_id']);
1217 - $ai_content = [
1218 - 'requested' => $request_params['ai_page_ids'] ?? [],
1219 - 'processed' => $_processed_pages,
1220 - ];
628 + Helper::log( $data );
1221 629
1222 -
1223 - $result = [
1224 - 'attachments' => $attachments,
1225 - 'attachments_fail' => $attachments_fail,
1226 - 'attachments_errors' => $attachments_errors,
1227 - 'templates' => $templates,
1228 - 'contents' => $content_templates,
1229 - 'wp-content' => $contents,
1230 - 'post_types' => $post_types,
1231 - 'template_types' => $template_types,
1232 - 'ai_content' => $ai_content,
1233 - 'dependency_data' => $dependency_data,
1234 - 'home_url' => home_url('/'),
630 + return [
631 + 'templates' => $templates,
632 + 'contents' => $content_templates,
633 + 'wp-content' => $contents,
634 + 'post_types' => $post_types,
635 + 'template_types' => $template_types,
636 + 'dependency_data' => $this->dependency_data,
1235 637 ];
1236 -
1237 - Helper::log($data);
1238 - Helper::log($result);
1239 -
1240 - return $result;
1241 638 }
1242 639
1243 640 public function get_request_params() {
1244 641 return $this->request_params;
@@ -1250,12 +647,12 @@
1250 647 // // TODO: Implement the Revert Process.
1251 648 // }
1252 649 }
1253 650
1254 - public function redirect_for_archives($link, $post_id) {
1255 - $archive_settings = get_option('templately_post_archive');
1256 - if (!empty($archive_settings) && intval($archive_settings['post_id']) === intval($post_id)) {
1257 - $link = str_replace($post_id, $archive_settings['archive_id'], $link);
651 + public function redirect_for_archives( $link, $post_id ) {
652 + $archive_settings = get_option( 'templately_post_archive' );
653 + if ( ! empty( $archive_settings ) && intval( $archive_settings['post_id'] ) === intval( $post_id ) ) {
654 + $link = str_replace( $post_id, $archive_settings['archive_id'], $link );
1258 655 }
1259 656
1260 657 return $link;
1261 658 }
@@ -1265,50 +662,46 @@
1265 662 $mimes['svg'] = 'image/svg+xml';
1266 663 return $mimes;
1267 664 }
1268 665
1269 - public function register_shutdown() {
666 + public function register_shutdown(){
1270 667 $status = connection_status();
1271 668 $last_error = error_get_last();
1272 - if ($last_error && ($last_error['type'] === E_ERROR || $last_error['type'] === E_CORE_ERROR || $last_error['type'] === E_COMPILE_ERROR || $last_error['type'] === E_USER_ERROR)) {
1273 - if (!empty($last_error['message'])) {
669 + if ($status != CONNECTION_NORMAL && $last_error && $last_error['type'] === E_ERROR) {
670 + if ((defined('WP_DEBUG') && WP_DEBUG || defined('TEMPLATELY_EVENT_LOG') && TEMPLATELY_EVENT_LOG) && !empty($last_error['message'])) {
1274 671 $full_message = $last_error['message'];
1275 - $lines = explode("\n", $full_message);
1276 672
1277 - // For import status: first 5 lines
1278 - $import_status_message = implode("\n", array_slice($lines, 0, 5));
1279 - $import_status_message = str_replace(ABSPATH, 'ABSPATH/', $import_status_message);
673 + // Split the message at the first newline to remove the stack trace
674 + $message_parts = preg_split('/\n/', $full_message);
1280 675
1281 - // For SSE: first line only
1282 - $sse_message = $lines[0];
1283 - $sse_message = str_replace(ABSPATH, 'ABSPATH/', $sse_message);
676 + // The error message is the first part
677 + $error_message = $message_parts[0];
1284 678 } else {
1285 679 // Generic error message
1286 - $import_status_message = sprintf(__("It seems we're experiencing technical difficulties. Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://templately.com/?support=open');
1287 - $sse_message = $import_status_message;
680 + $error_message = sprintf(__("It seems we're experiencing technical difficulties. Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://wpdeveloper.com/support');
1288 681 }
1289 682
1290 - $this->handle_import_status('failed', $import_status_message);
1291 - $this->sse_message([
683 +
684 + $this->handle_import_status('failed', $error_message);
685 + // Handle the error, e.g. log it or display a message to the user
686 + $this->sse_message( [
1292 687 'action' => 'error',
1293 688 'status' => 'error',
1294 689 'type' => "error",
1295 - 'retry' => true,
1296 690 'title' => __("Oops!", "templately"),
1297 - 'message' => $sse_message,
1298 - 'error' => $last_error,
691 + 'message' => $error_message,
1299 692 // 'position' => 'plugin',
1300 693 // 'progress' => '--',
1301 - ]);
694 + ] );
1302 695 }
1303 696
1304 - $this->debug_log("Shutdown:.....");
1305 - $this->debug_log("connection_status: " . $this->getConnectionStatusText());
1306 - $this->debug_log($last_error);
697 + $this->debug_log( "Shutdown:....." );
698 + $this->debug_log( "connection_status: " . $this->getConnectionStatusText() );
699 + $this->debug_log( $last_error );
1307 700 }
1308 701
1309 702 public function handle_import_status($status, $description = '') {
1310 - if ($this->is_import_status_handled === $status) {
703 + if ($this->is_import_status_handled) {
1311 704 Helper::log("Import status already handled: $status");
1312 705 return null;
1313 706 }
1314 707 $this->is_import_status_handled = $status;
@@ -1315,39 +708,29 @@
1315 708
1316 709 $download_key = $this->download_key;
1317 710
1318 711 $headers = [
1319 - 'Content-Type' => 'application/json',
712 + 'Content-Type' => 'application/json',
1320 713 'Authorization' => 'Bearer ' . $this->api_key,
1321 714 'download_key' => $download_key,
1322 715 'download-key' => $download_key,
1323 716 'x-templately-ip' => Helper::get_ip(),
1324 - 'x-templately-url' => home_url('/'),
717 + 'x-templately-url' => home_url( '/' ),
1325 718 ];
1326 719
720 + $args = [
721 + 'headers' => $headers,
722 + ];
1327 723
1328 - $request_params = $this->get_session_data();
1329 - if(isset($request_params['process_id']) && !empty($request_params['ai_page_ids'])){
1330 - $updated_ids = AIUtils::get_processed_pages_data($request_params['process_id']);
1331 - $updated_pages = $updated_ids['pages'] ?? [];
1332 - $ai_page_ids = array_reduce($request_params['ai_page_ids'], 'array_merge', array());
1333 724
1334 - $headers['x-templately-ai-process-id'] = $request_params['process_id'];
1335 - $headers['x-templately-ai-requested-pages'] = implode(',', $ai_page_ids);
1336 - $headers['x-templately-ai-updated-pages'] = implode(',', array_keys($updated_pages));
1337 - $headers['x-templately-ai-missing-pages'] = implode(',', array_diff($ai_page_ids, array_keys($updated_pages)));
1338 - $headers['x-templately-ai-credit-cost'] = $updated_ids['credit_cost'] ?? null;
1339 - }
1340 -
1341 -
1342 - $extra_headers = $headers;
1343 -
1344 725 if ($status === 'success') {
1345 - $body = ['type' => 'pack'];
1346 - $response = Helper::make_api_post_request('v1/import/success', $body, $extra_headers);
726 + $url = $this->get_api_url( "v1", 'import/success');
727 + $args['body'] = json_encode(['type' => 'pack']);
728 + $response = wp_remote_post($url, $args);
1347 729 } elseif ($status === 'failed') {
1348 - $body = ['type' => 'pack', 'description' => $description ?: "Something Went wrong....."];
1349 - $response = Helper::make_api_post_request('v1/import/failed', $body, $extra_headers);
730 + $url = $this->get_api_url( "v1", 'import/failed');
731 + $args['body'] = json_encode(['type' => 'pack', 'description' => $description ?: "Something Went wrong....."]);
732 + $response = wp_remote_post($url, $args);
1350 733 }
1351 734
1352 735 Helper::log($response);
1353 736
@@ -1354,15 +737,12 @@
1354 737 if (is_wp_error($response)) {
1355 738 // Handle error
1356 739 Helper::log($response->get_error_message());
1357 740 } else {
1358 -
1359 - SessionData::set($this->session_id, 'is_import_status_handled', $this->is_import_status_handled);
1360 741 // Handle success
1361 742 $body = wp_remote_retrieve_body($response);
1362 - $data = json_decode($body, true);
1363 743 // Do something with $body
1364 - return $data;
744 + return $body;
1365 745 }
1366 746
1367 747 return null;
1368 748 }
@@ -1382,552 +762,56 @@
1382 762 }
1383 763
1384 764 protected function get_post_type_label_by_slug($slug) {
1385 765 $post_type_obj = get_post_type_object($slug);
1386 - if ($post_type_obj) {
766 + if($post_type_obj) {
1387 767 return $post_type_obj->label;
1388 768 }
1389 769 return null;
1390 770 }
1391 771
1392 - public function import_info() {
772 + public function import_info(){
1393 773
1394 774 $platform = isset($_GET['platform']) ? $_GET['platform'] : 'elementor';
1395 775 $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1396 - $isAi = isset($_GET['isAi']) ? $_GET['isAi'] : false;
1397 776
1398 - $data = $this->fetch_pack_info_from_api($id, $isAi);
1399 - if (is_wp_error($data)) {
1400 - wp_send_json_error($data->get_error_message());
777 + $response = wp_remote_get( $this->info_get_api_url($id), [
778 + 'timeout' => 30,
779 + 'headers' => [
780 + 'Authorization' => 'Bearer ' . $this->api_key,
781 + 'x-templately-ip' => Helper::get_ip(),
782 + 'x-templately-url' => home_url( '/' ),
783 + 'x-templately-version' => TEMPLATELY_VERSION,
784 + ]
785 + ] );
786 +
787 + if( is_wp_error( $response ) ){
788 + wp_send_json_error( $response->get_error_message() );
1401 789 return;
1402 790 }
1403 -
1404 - $business_niches = get_option('templately_ai_business_niches', []);
1405 - $data['data']['business_niches'] = $business_niches;
1406 -
1407 - if (isset($data['data']['manifest'])) {
1408 - $data['data']['manifest'] = json_decode($data['data']['manifest'], true);
1409 - }
1410 - if (isset($data['data']['settings'])) {
1411 - $data['data']['settings'] = json_decode($data['data']['settings'], true);
1412 - }
1413 -
1414 - if ($isAi) {
1415 - // Get the latest AI process for the current API key
1416 - $last_ai_process = AIUtils::get_latest_ai_process_by_api_key($id);
1417 - if ($last_ai_process) {
1418 - $data['data']['ai_process'] = $last_ai_process;
1419 - }
1420 -
1421 - if($last_ai_process && $id == $last_ai_process['pack_id']){
1422 - // Read AI preview content directly from files using the common function
1423 - $session_id = $last_ai_process['session_id'] ?? null;
1424 - $ai_page_ids = $last_ai_process['ai_page_ids'] ?? [];
1425 - $dir_path = null;
1426 -
1427 - // Get session data to retrieve dir_path
1428 - if ($session_id) {
1429 - $session_data = SessionData::get_data($session_id);
1430 - $dir_path = $session_data['dir_path'] ?? null;
1431 - }
1432 -
1433 - // Use the common function to read AI template data if we have the required data
1434 - if ($session_id && $ai_page_ids && $dir_path) {
1435 - $data['data']['ai_preview_content'] = AIUtils::read_ai_template_data($session_id, $ai_page_ids, $dir_path);
1436 - } else {
1437 - $data['data']['ai_preview_content'] = [];
1438 - }
1439 - }
1440 - }
1441 -
1442 - // Return the response body
1443 - wp_send_json($data);
1444 - }
1445 -
1446 - private function fetch_pack_info_from_api($id, $isAi) {
1447 - $extra_headers = [
1448 - 'x-templately-is-ai' => $isAi,
1449 - ];
1450 - $response = Helper::make_api_get_request("v2/import/info/pack/$id", [], $extra_headers, 30);
1451 -
1452 - if (is_wp_error($response)) {
1453 - return new \WP_Error('api_error', $response->get_error_message());
1454 - }
1455 791 // If the response code is not 200, return the error message
1456 - if (wp_remote_retrieve_response_code($response) != 200) {
1457 - $error_message = $this->extract_error_from_response($response);
1458 - return new \WP_Error('api_error', $error_message);
792 + if( wp_remote_retrieve_response_code( $response ) != 200 ){
793 + wp_send_json_error( json_decode(wp_remote_retrieve_body( $response )) );
794 + return;
1459 795 }
796 + // If the response body is JSON and it contains an error, return the error message
1460 797 // Retrieve Data from Response Body.
1461 - $body = wp_remote_retrieve_body($response);
798 + $body = wp_remote_retrieve_body( $response );
1462 799 $data = json_decode($body, true);
1463 800
1464 801 if (isset($data['error'])) {
1465 - return new \WP_Error('api_error', $data['error']);
1466 - }
1467 -
1468 - return $data;
1469 - }
1470 -
1471 - public function import_global_settings() {
1472 - $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1473 - $isAi = isset($_GET['isAi']) ? $_GET['isAi'] : false;
1474 -
1475 - $data = $this->fetch_pack_info_from_api($id, $isAi);
1476 - if (is_wp_error($data)) {
1477 - wp_send_json_error($data->get_error_message());
802 + wp_send_json_error($data['error']);
1478 803 return;
1479 804 }
1480 805
1481 - $settings = [];
1482 - if (isset($data['data']['settings'])) {
1483 - $settings = json_decode($data['data']['settings'], true);
806 + if(isset($data['data']['manifest'])){
807 + $data['data']['manifest'] = json_decode($data['data']['manifest'], true);
1484 808 }
1485 -
1486 - wp_send_json_success(['settings' => $settings]);
1487 - }
1488 -
1489 - public function update_imported_list($type, $id) {
1490 - $imported_list = get_option('templately_fsi_imported_list', []);
1491 - if(!in_array($id, $imported_list[$type] ?? [])){
1492 - $imported_list[$type][] = $id;
1493 - update_option('templately_fsi_imported_list', $imported_list, false);
809 + if(isset($data['data']['settings'])){
810 + $data['data']['settings'] = json_decode($data['data']['settings'], true);
1494 811 }
1495 - }
1496 812
1497 - /**
1498 - *
1499 - *
1500 - * @return void
1501 - */
1502 - protected function add_revert_hooks() {
1503 - add_action('wp_insert_post', function ($post_id) {
1504 - $this->update_imported_list('posts', $post_id);
1505 - });
1506 - add_action('add_attachment', function ($post_id) {
1507 - $this->update_imported_list('attachment', $post_id);
1508 - });
1509 - add_action('created_term', function ($term_id, $tt_id, $taxonomy, $args) {
1510 - $this->update_imported_list('term', [$term_id, $taxonomy]);
1511 - }, 10, 4);
1512 - add_action('registered_taxonomy', function ($taxonomy, $object_type, $taxonomy_object) {
1513 - $this->update_imported_list('taxonomy', $taxonomy);
1514 - }, 10, 3);
1515 - add_action('fluentform/form_imported', function ($formId){
1516 - $this->update_imported_list('fluentform', $formId);
1517 - }, 10, 1);
813 + // Return the response body
814 + wp_send_json($data);
1518 815 }
1519 816
1520 - public static function has_revert(){
1521 - $options = Utils::get_backup_options();
1522 - $imported_list = get_option('templately_fsi_imported_list', []);
1523 - if(!empty($options) || !empty($imported_list)){
1524 - return true;
1525 - }
1526 - return false;
1527 - }
1528 -
1529 - public function import_revert() {
1530 -
1531 - // // Get the nonce value from the request (usually from $_POST or $_GET)
1532 - // $received_nonce = isset($_REQUEST['_wpnonce']) ? $_REQUEST['_wpnonce'] : '';
1533 -
1534 - // // Verify the nonce using wp_verify_nonce()
1535 - // $verified = wp_verify_nonce($received_nonce, 'templately_pack_import_revert_nonce');
1536 -
1537 - // if (!$verified) {
1538 - // wp_send_json_error("Nonce not verified.");
1539 - // }
1540 -
1541 - delete_option('templately_import_platform');
1542 -
1543 - $option_active = null;
1544 - $options_deleted = false;
1545 - $imported_list_deleted = false;
1546 - $options = Utils::get_backup_options();
1547 - $status_args = [ 'post_type' => 'templately_library' ];
1548 - $all_post_url = add_query_arg( [
1549 - "page" => "templately_settings",
1550 - "path" => "settings/elementor/miscellaneous",
1551 - ], admin_url('admin.php' ));
1552 - // wp_send_json_success([$options]);
1553 -
1554 - if(class_exists('Elementor\Plugin')){
1555 - $kits_manager = Plugin::$instance->kits_manager;
1556 - $option_active = $kits_manager::OPTION_ACTIVE;
1557 - $kit = $kits_manager->get_active_kit();
1558 -
1559 - if ( ! $kit->get_id() ) {
1560 - $kit = $kits_manager->create_default();
1561 - update_option( $kits_manager::OPTION_ACTIVE, $kit );
1562 - }
1563 - }
1564 -
1565 -
1566 - if (!empty($options) && is_array($options)) {
1567 - foreach ($options as $key => $value) {
1568 - if ('stylesheet' === $key) {
1569 - if (get_option('stylesheet') !== $value) {
1570 - switch_theme($value);
1571 - }
1572 - } else if($option_active === $key && class_exists('Elementor\Plugin')) {
1573 - $kits_manager->revert( (int) $kits_manager->get_active_id(), (int) $value, 0 );
1574 - $kit = $kits_manager->get_active_kit();
1575 - $settings = $kit->get_data('settings');
1576 - if ( isset( $settings['site_logo'] ) ) {
1577 - set_theme_mod( 'custom_logo', $settings['site_logo']['id'] );
1578 - }
1579 - } else {
1580 - update_option($key, $value);
1581 - }
1582 - delete_option("__templately_$key");
1583 - $options_deleted = true;
1584 - }
1585 - }
1586 -
1587 - $imported_list = get_option('templately_fsi_imported_list', []);
1588 - if (!empty($imported_list) && is_array($imported_list)) {
1589 - $_GET['force_delete_kit'] = 1; // Fallback GET Ready!
1590 - foreach ($imported_list as $type => $list) {
1591 - if (empty($list) || !is_array($list)) {
1592 - continue;
1593 - }
1594 - // Loop through each item ID and delete it
1595 - foreach ($list as $key => $item_id) {
1596 - switch ($type) {
1597 - case 'posts':
1598 - // making sure default kit don't get deleted.
1599 - if($option_active && isset($options[$option_active]) && $options[$option_active] == $item_id){
1600 - break;
1601 - }
1602 - wp_delete_post($item_id, true); // Set true for permanent deletion
1603 - break;
1604 - case 'attachment':
1605 - wp_delete_attachment($item_id, true); // Set true for permanent deletion
1606 - break;
1607 - case 'term':
1608 - list($term_id, $taxonomy) = $item_id;
1609 - wp_delete_term($term_id, $taxonomy); // Use corresponding taxonomy
1610 - break;
1611 - case 'taxonomy':
1612 - // Taxonomies cannot be directly deleted. Consider de-registering it.
1613 - break;
1614 - case 'fluentform':
1615 - if(class_exists('\FluentForm\App\Models\Form')){
1616 - \FluentForm\App\Models\Form::remove($item_id);
1617 - }
1618 - break;
1619 - }
1620 - }
1621 - }
1622 -
1623 - $imported_list_deleted = true;
1624 - delete_option('templately_fsi_imported_list');
1625 - }
1626 -
1627 -
1628 - if($options_deleted || $imported_list_deleted){
1629 - sleep(5);
1630 - wp_send_json_success([ 'options' => $options_deleted, 'imported_list' => $imported_list_deleted, 'site_url' => home_url(), 'redirect' => $all_post_url ]);
1631 - }
1632 -
1633 - wp_send_json_error([ 'options' => $options_deleted, 'imported_list' => $imported_list_deleted, 'site_url' => home_url() ]);
1634 - }
1635 -
1636 - /**
1637 - * Extract error response from JSON response body
1638 - * Returns the entire decoded JSON response if Content-Type is JSON,
1639 - * otherwise returns a generic HTTP error message
1640 - *
1641 - * @param array $response The response from wp_remote_get or similar
1642 - * @return mixed Decoded JSON response or generic HTTP error string
1643 - */
1644 - private function extract_error_from_response($response) {
1645 - try {
1646 - $content_type = wp_remote_retrieve_header($response, 'content-type');
1647 - if (!empty($content_type) && strpos($content_type, 'application/json') !== false) {
1648 - $body = wp_remote_retrieve_body($response);
1649 - $error_data = json_decode($body, true);
1650 - if ($error_data !== null) {
1651 - return $error_data;
1652 - }
1653 - }
1654 - } catch (Exception $e) {
1655 - // If JSON parsing fails, fall through to generic error
1656 - }
1657 -
1658 - // Fallback to generic HTTP error message
1659 - return __('API request failed with response code ', 'templately') . wp_remote_retrieve_response_code($response);
1660 - }
1661 -
1662 - public function google_font() {
1663 - $result = get_transient('templately-google-fonts');
1664 -
1665 - if (false == $result) {
1666 - $response = Helper::make_api_get_request('v2/google-font', [], [], 30);
1667 -
1668 - if (is_wp_error($response)) {
1669 - wp_send_json_error($response->get_error_message());
1670 - }
1671 -
1672 - if (wp_remote_retrieve_response_code($response) != 200) {
1673 - $error_message = $this->extract_error_from_response($response);
1674 - wp_send_json_error($error_message, wp_remote_retrieve_response_code($response));
1675 - }
1676 -
1677 - $body = wp_remote_retrieve_body($response);
1678 - $data = json_decode($body, true);
1679 -
1680 - if (!isset($data['status']) || $data['status'] !== 'success') {
1681 - wp_send_json_error('API response indicates failure.');
1682 - }
1683 -
1684 - if (!isset($data['data'])) {
1685 - wp_send_json_error('API response missing data.');
1686 - }
1687 -
1688 - $result = $data['data'];
1689 - set_transient('templately-google-fonts', $result, DAY_IN_SECONDS);
1690 - }
1691 -
1692 - wp_send_json_success($result);
1693 - }
1694 -
1695 - /**
1696 - * AJAX handler: merge global kit settings into template content.
1697 - *
1698 - * Accepts (via POST JSON body):
1699 - * platform - 'elementor' | 'gutenberg'
1700 - * template - the template data array returned by /v1/insert
1701 - * settings - the kit settings array from import_info
1702 - *
1703 - * Returns the template data with globals resolved, or an error.
1704 - * The JS side should fall back to the unmerged template on any error.
1705 - */
1706 - public function merge_settings_with_template() {
1707 - $body = file_get_contents( 'php://input' );
1708 - $data = json_decode( $body, true );
1709 -
1710 - $platform = isset( $data['platform'] ) ? sanitize_text_field( $data['platform'] ) : 'elementor';
1711 - $template = $data['template'] ?? null;
1712 - $settings = $data['settings'] ?? null;
1713 -
1714 - if ( empty( $template ) || ! is_array( $template ) ) {
1715 - wp_send_json_error( 'Invalid template data.' );
1716 - return;
1717 - }
1718 -
1719 - if ( empty( $settings ) || ! is_array( $settings ) ) {
1720 - // No settings to merge – return template unchanged.
1721 - wp_send_json_success( $template );
1722 - return;
1723 - }
1724 -
1725 - try {
1726 - if ( $platform === 'elementor' ) {
1727 - if ( ! empty( $template['content'] ) && is_array( $template['content'] ) ) {
1728 - $template['content'] = ElementorSettingsMerger::merge( $template['content'], $settings );
1729 - }
1730 - } elseif ( $platform === 'gutenberg' ) {
1731 - if ( ! empty( $template['content'] ) && is_string( $template['content'] ) ) {
1732 - $template['content'] = GutenbergSettingsMerger::merge( $template['content'], $settings );
1733 - }
1734 - }
1735 -
1736 - wp_send_json_success( $template );
1737 - } catch ( \Exception $e ) {
1738 - Helper::log( 'merge_settings_with_template error: ' . $e->getMessage() );
1739 - // Return error so JS can fall back to unmerged template.
1740 - wp_send_json_error( 'Settings merge failed: ' . $e->getMessage() );
1741 - }
1742 - }
1743 -
1744 - public function ai_get_json() {
1745 - // read json data from post body
1746 - $body = file_get_contents('php://input');
1747 - $data = json_decode($body, true);
1748 -
1749 - if(empty($data['ai_page_ids'])){
1750 - wp_send_json_error('Invalid ai_page_ids');
1751 - return;
1752 - }
1753 -
1754 - if(!isset($_GET['session_id'])){
1755 - wp_send_json_error('Invalid session_id');
1756 - return;
1757 - }
1758 -
1759 - $session_id = isset($_GET['session_id']) ? sanitize_text_field($_GET['session_id']) : null;
1760 - $process_id = $data['process_id'] ?? null;
1761 - $ai_page_ids = $data['ai_page_ids'] ?? null;
1762 -
1763 - $this->request_params = $this->get_session_data();
1764 - try {
1765 - $this->manifest = $this->read_manifest($this->request_params['dir_path']);
1766 - } catch (\Exception $th) {
1767 - wp_send_json_error($th->getMessage());
1768 - }
1769 -
1770 - if(!empty($session_id) && empty($process_id)){
1771 - if ( !empty($this->request_params['process_id']) ){
1772 - $process_id = $this->request_params['process_id'] ?? null;
1773 - } else {
1774 - $process_id = AIUtils::get_ai_process_id_by_session_id($session_id);
1775 - }
1776 - }
1777 -
1778 - if(empty($process_id)){
1779 - wp_send_json_error('Invalid process_id');
1780 - return;
1781 - }
1782 -
1783 - $process_data = AIUtils::get_ai_process_data_by_process_id($process_id);
1784 - if (!empty($process_data['preview_error'])) {
1785 - wp_send_json_error($process_data['preview_error']);
1786 - }
1787 -
1788 - // Use the new common function to read AI template data directly
1789 - $result = AIUtils::read_ai_template_data($session_id, $ai_page_ids, $this->request_params['dir_path']);
1790 -
1791 - // Check if this is called from polling endpoint and include additional data
1792 - $response_data = ['process_id' => $process_id, 'templates' => $result];
1793 -
1794 - if (isset($this->polling_is_last_part)) {
1795 - $response_data['is_last_part'] = $this->polling_is_last_part;
1796 -
1797 - // Clean up the polling property
1798 - unset($this->polling_is_last_part);
1799 - }
1800 -
1801 - wp_send_json_success($response_data);
1802 - }
1803 -
1804 - /**
1805 - * AJAX handler for polling AI template generation status on local sites
1806 - * Makes GET request to API endpoint and returns data in same format as ai_get_json()
1807 - */
1808 - public function ai_poll_template() {
1809 - // Read JSON data from post body
1810 - $body = file_get_contents('php://input');
1811 - $data = json_decode($body, true);
1812 -
1813 - $process_id = $data['process_id'] ?? null;
1814 - $ai_page_ids = $data['ai_page_ids'] ?? null;
1815 -
1816 - if(empty($process_id)){
1817 - wp_send_json_error('Invalid process_id');
1818 - return;
1819 - }
1820 -
1821 - // Validate and get AI process data using centralized method
1822 - $process_data = AIUtils::validate_and_get_process_data($process_id);
1823 - if (is_wp_error($process_data)) {
1824 - $this->ai_get_json();
1825 - return;
1826 - }
1827 -
1828 - $session_id = $process_data['session_id'];
1829 - $ai_page_ids = $process_data['ai_page_ids'];
1830 -
1831 - // Use the common polling function to handle all template processing
1832 - $polling_result = AIUtils::poll_for_template($process_id, $session_id, $ai_page_ids);
1833 -
1834 - if (!$polling_result) {
1835 - // Polling failed, fallback to ai_get_json
1836 - $this->ai_get_json();
1837 - return;
1838 - }
1839 -
1840 - // After polling and processing templates, call ai_get_json() to return the data
1841 - // This reuses all the existing logic without duplication
1842 - $this->ai_get_json();
1843 - }
1844 -
1845 - /**
1846 - * Process AI preview content following the ai_get_json() pattern
1847 - *
1848 - * @param string $process_id The AI process ID
1849 - * @param array $ai_page_ids The AI page IDs data structure
1850 - * @param array $ai_preview_ids The AI preview IDs to process
1851 - * @return array Processed AI content data
1852 - */
1853 - private function process_ai_preview_content($process_id, $ai_page_ids, $ai_preview_ids) {
1854 - if (empty($process_id) || empty($ai_page_ids) || empty($ai_preview_ids)) {
1855 - return [];
1856 - }
1857 -
1858 - $all_ai_process_data = AIUtils::get_ai_process_data();
1859 - if (empty($all_ai_process_data[$process_id])) {
1860 - return [];
1861 - }
1862 - $ai_process_data = $all_ai_process_data[$process_id];
1863 - $_REQUEST['is_lightspeed'] = 'true';
1864 - $_REQUEST['session_id'] = $ai_process_data['session_id'] ?? null;
1865 - // Initialize session data and manifest following ai_get_json() pattern
1866 - $this->request_params = $this->get_session_data();
1867 - $this->manifest = $this->read_manifest($this->request_params['dir_path']);
1868 -
1869 - // Create Finalizer instance with the same configuration as ai_get_json()
1870 - $finalizer = new Finalizer(array_merge($this->request_params, [
1871 - 'origin' => $this,
1872 - 'manifest' => $this->manifest,
1873 - ]));
1874 - $finalizer->process_id = $process_id;
1875 - $finalizer->ai_page_ids = $ai_page_ids;
1876 -
1877 - $result = [];
1878 -
1879 - // Process each AI preview ID
1880 - foreach ($ai_preview_ids as $preview_id) {
1881 - // Extract type and sub_type metadata from ai_page_ids structure
1882 - $type_info = $this->extract_content_metadata($preview_id, $ai_page_ids);
1883 -
1884 - if ($type_info) {
1885 - $finalizer->type = $type_info['type'];
1886 - $finalizer->sub_type = $type_info['sub_type'];
1887 -
1888 - // Check if this is AI content before processing
1889 - if ($finalizer->isAiContent($preview_id)) {
1890 - // Process AI content using AIContentHelper trait
1891 - $ai_result = $finalizer->processAiContent($preview_id);
1892 - if ($ai_result['is_ai'] && !empty($ai_result['template_json'])) {
1893 - $template_json = $ai_result['template_json'];
1894 - $result[$preview_id] = $template_json;
1895 - } else if ($finalizer->isAiFileSkipped($preview_id)) {
1896 - // Handle skipped AI files
1897 - $result[$preview_id] = [];
1898 - }
1899 - }
1900 - }
1901 - }
1902 -
1903 - return $result;
1904 - }
1905 -
1906 - /**
1907 - * Extract content metadata (type and sub_type) from ai_page_ids structure
1908 - *
1909 - * @param string $preview_id The preview ID to find
1910 - * @param array $ai_page_ids The AI page IDs data structure
1911 - * @return array|null Array with 'type' and 'sub_type' keys, or null if not found
1912 - */
1913 - private function extract_content_metadata($preview_id, $ai_page_ids) {
1914 - if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
1915 - return null;
1916 - }
1917 -
1918 - // Search through the ai_page_ids structure to find the preview_id
1919 - foreach ($ai_page_ids as $key => $ids) {
1920 - if (is_array($ids) && in_array($preview_id, $ids)) {
1921 - // Extract type and sub_type from the key (e.g., 'content/page' or 'templates')
1922 - $type_arr = explode('/', $key);
1923 - return [
1924 - 'type' => $type_arr[0],
1925 - 'sub_type' => isset($type_arr[1]) ? $type_arr[1] : ''
1926 - ];
1927 - }
1928 - }
1929 -
1930 - return null;
1931 - }
1932 -
1933 -}
817 +}