PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.5
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.5
3.8.0 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 All 112 releases
← All changes | includes/Core/Importer/FullSiteImport.php +1003 -454 3.1.10 → 3.4.5 View file →
@@ -1,12 +1,29 @@
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 +
3 14 namespace Templately\Core\Importer;
4 15
5 16 use Elementor\Plugin;
6 17 use Error;
7 18 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;
8 24 use Templately\Core\Importer\Utils\Utils;
25 +use Templately\Core\Importer\Utils\AIUtils;
9 26 use Templately\Utils\Base;
10 27 use Templately\Utils\Helper;
11 28 use Templately\Utils\Installer;
12 29 use Templately\Utils\Options;
@@ -19,25 +36,29 @@
19 36 protected $export;
20 37
21 38 private $version = '1.0.0';
22 39
23 - public $session_id;
24 40 public $download_key;
41 + protected $dev_mode = false;
42 + protected $api_key = '';
43 + protected $session_id = '';
44 + protected $documents_data = [];
45 + private $is_import_status_handled = false;
46 +
25 47 public $dir_path;
26 48 protected $filePath;
27 49 protected $tmp_dir = null;
28 - protected $dev_mode = false;
29 - protected $api_key = '';
30 50 public $request_params = [];
31 - protected $documents_data = [];
32 - protected $dependency_data = [];
33 - private $is_import_status_handled = false;
34 51
52 + // Polling-specific property for ai_poll_template()
53 + private $polling_is_last_part = null;
54 +
35 55 public function __construct() {
36 56 $this->dev_mode = defined('TEMPLATELY_DEV') && TEMPLATELY_DEV;
37 57 $this->api_key = Options::get_instance()->get('api_key');
38 58
39 59 $this->add_ajax_action('import_settings', $this);
60 + $this->add_ajax_action('create_session_and_download', $this);
40 61 $this->add_ajax_action('import_status', $this);
41 62 $this->add_ajax_action('import', $this);
42 63 $this->add_ajax_action('import_revert', $this);
43 64 $this->add_ajax_action('import_info', $this);
@@ -42,8 +63,11 @@
42 63 $this->add_ajax_action('import_revert', $this);
43 64 $this->add_ajax_action('import_info', $this);
44 65 $this->add_ajax_action('import_close_feedback_modal', $this);
45 66 $this->add_ajax_action('feedback_form', $this);
67 + $this->add_ajax_action('google_font', $this);
68 + $this->add_ajax_action('ai_get_json', $this);
69 + $this->add_ajax_action('ai_poll_template', $this);
46 70
47 71 add_action('admin_init', [$this, 'admin_init']);
48 72 // add_action('admin_notices', [$this, 'add_revert_button']);
49 73
@@ -82,9 +106,9 @@
82 106 wp_die();
83 107 }
84 108
85 109 // Call the actual handler method
86 - call_user_func([$object, $action]);
110 + call_user_func([$this, $action]);
87 111 });
88 112 }
89 113
90 114 public function admin_init() {
@@ -96,18 +120,181 @@
96 120
97 121 public function import_settings() {
98 122 $data = wp_unslash($_POST);
99 123
100 - update_option(self::SESSION_OPTION_KEY, $data);
124 + $upload_dir = wp_upload_dir();
101 125
126 + if(!empty($data['session_id'])){
127 + $session_id = $data['session_id'];
128 + $session_data = Utils::get_session_data($session_id);
129 + $data = array_merge($session_data, $data);
130 + }
131 + else {
132 + $session_id = uniqid();
133 + }
134 +
135 + $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
136 + $prv_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview' . DIRECTORY_SEPARATOR;
137 +
138 + $this->session_id = $session_id;
139 + $data['session_id'] = $session_id;
140 +
141 + $data['root_dir'] = $tmp_dir;
142 + $data['prv_dir'] = $prv_dir;
143 + $data['dir_path'] = $tmp_dir . $session_id . DIRECTORY_SEPARATOR;
144 + $data['zip_path'] = $tmp_dir . "{$session_id}.zip";
145 +
146 +
147 + if ( is_array( $data ) && ! empty( $data ) ) {
148 + foreach ( $data as $key => $value ) {
149 + $json = is_string($value) ? json_decode( $value, true ) : null;
150 + $data[ $key ] = $json !== null ? $json : $value;
151 + }
152 + }
153 +
154 + Utils::update_session_data($session_id, $data);
155 +
156 +
157 + //clear previous revert backup
158 + $options = Utils::get_backup_options();
159 + foreach ($options as $key => $value) {
160 + delete_option("__templately_$key");
161 + }
102 162 delete_option('templately_fsi_imported_list');
103 163 delete_option('templately_fsi_log');
104 164
105 165 wp_send_json_success([
106 166 'is_lightspeed' => !Helper::should_flush(),
167 + 'session_id' => $session_id,
107 168 ]);
108 169 }
109 170
171 + public function import_ai_settings() {
172 + $data = wp_unslash($_POST);
173 +
174 + $upload_dir = wp_upload_dir();
175 +
176 + // passed in post
177 + $session_id = $data['session_id'];
178 +
179 + $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
180 + $prv_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview' . DIRECTORY_SEPARATOR;
181 +
182 + $this->session_id = $session_id;
183 + $data['root_dir'] = $tmp_dir;
184 + $data['prv_dir'] = $prv_dir;
185 + $data['dir_path'] = $tmp_dir . $session_id . DIRECTORY_SEPARATOR;
186 + $data['zip_path'] = $tmp_dir . "{$session_id}.zip";
187 +
188 + // Handle isLocalSite flag conversion
189 + if (isset($data['isLocalSite'])) {
190 + $data['isLocalSite'] = filter_var($data['isLocalSite'], FILTER_VALIDATE_BOOLEAN);
191 + }
192 +
193 + if ( is_array( $data ) && ! empty( $data ) ) {
194 + foreach ( $data as $key => $value ) {
195 + $json = is_string($value) ? json_decode( $value, true ) : null;
196 + $data[ $key ] = $json !== null ? $json : $value;
197 + }
198 + }
199 +
200 + Utils::update_session_data($session_id, $data);
201 +
202 +
203 + return $data;
204 + }
205 +
206 + public function create_session_and_download() {
207 + if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
208 + exit;
209 + }
210 +
211 + add_filter( 'wp_image_editors', [ $this, 'wp_image_editors' ], 10, 1 );
212 +
213 + define('TEMPLATELY_START_TIME', microtime(true));
214 +
215 + register_shutdown_function( [ $this, 'register_shutdown' ] );
216 +
217 + // $this->finishRequestHeaders();
218 +
219 + try {
220 + // Get session data from AJAX request
221 + $session_data = $this->import_ai_settings();
222 +
223 + $this->request_params = $session_data;
224 + $this->initialize_props();
225 + $this->add_revert_hooks();
226 + $progress = $this->request_params['progress'] ?? [];
227 +
228 + if(empty($progress['create_log_dir'])){
229 + // Create Log Directory and if fail then chose option method
230 + LogHandler::create_log_dir();
231 +
232 + $progress['create_log_dir'] = true;
233 + $this->update_session_data( [
234 + 'progress' => $progress,
235 + ] );
236 + }
237 +
238 + $_id = isset($this->request_params['id']) ? (int) $this->request_params['id'] : null;
239 +
240 + if ($_id === null) {
241 + $this->throw(__('Invalid Pack ID.', 'templately'));
242 + }
243 +
244 + $this->check_writing_permission();
245 +
246 +
247 + if(empty($progress['download_zip'])){
248 +
249 + /**
250 + * Download the zip
251 + */
252 + $this->download_zip( $_id, true );
253 +
254 + $progress['download_zip'] = true;
255 + $this->update_session_data( [
256 + 'progress' => $progress,
257 + ] );
258 + }
259 +
260 + /**
261 + * Reading Manifest File
262 + */
263 + $this->manifest = $this->read_manifest($this->request_params['dir_path']);
264 +
265 + /**
266 + * Version Check
267 + */
268 + if ( ! empty( $this->manifest['version'] ) && version_compare( $this->manifest['version'], $this->version, '>' ) ) {
269 + $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
270 + }
271 +
272 + $platform = $this->manifest['platform'] ?? '';
273 + if($platform === 'elementor') {
274 + Helper::enable_elementor_container();
275 + }
276 +
277 + update_option('templately_import_platform', $platform);
278 +
279 + // Return success response for AJAX
280 + wp_send_json_success([
281 + 'session_id' => $this->session_id,
282 + 'pack_downloaded' => true,
283 + 'platform' => $platform,
284 + 'message' => __('Session created and pack downloaded successfully', 'templately')
285 + ]);
286 +
287 + } catch ( Exception $e ) {
288 + $should_retry = $e instanceof RetryableErrorException;
289 +
290 + wp_send_json_error([
291 + 'message' => $e->getMessage(),
292 + 'should_retry' => $should_retry
293 + ]);
294 + }
295 + }
296 +
110 297 public function import_close_feedback_modal() {
111 298 $return = null;
112 299 if(isset($_GET['closeAction']) && $_GET['closeAction']){
113 300 $review_email = isset($_POST['review-email']) ? sanitize_email($_POST['review-email']) : '';
@@ -120,19 +307,9 @@
120 307 'pack_id' => (int) $pack_id,
121 308 ]);
122 309
123 310 // Send the request to the API
124 - $response = wp_remote_post($this->get_api_url('v2', 'feedback/close'), [
125 - 'timeout' => 30,
126 - 'headers' => [
127 - 'Content-Type' => 'application/json',
128 - 'Authorization' => 'Bearer ' . $this->api_key,
129 - 'x-templately-ip' => Helper::get_ip(),
130 - 'x-templately-url' => home_url('/'),
131 - 'x-templately-version' => TEMPLATELY_VERSION,
132 - ],
133 - 'body' => $body,
134 - ]);
311 + $response = Helper::make_api_post_request('v2/feedback/close', json_decode($body, true), [], 30);
135 312 $body = wp_remote_retrieve_body($response);
136 313 $return = json_decode($body, true);
137 314 }
138 315 update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done');
@@ -153,35 +330,21 @@
153 330 'pack_id' => (int) $pack_id,
154 331 ]);
155 332
156 333 // Send the request to the API
157 - $response = wp_remote_post($this->get_api_url('v2', 'feedback/store'), [
158 - 'timeout' => 30,
159 - 'headers' => [
160 - 'Content-Type' => 'application/json',
161 - 'Authorization' => 'Bearer ' . $this->api_key,
162 - 'x-templately-ip' => Helper::get_ip(),
163 - 'x-templately-url' => home_url('/'),
164 - 'x-templately-version' => TEMPLATELY_VERSION,
165 - ],
166 - 'body' => $body,
167 - ]);
334 + $response = Helper::make_api_post_request('v2/feedback/store', json_decode($body, true), [], 30);
168 335
169 336 if (is_wp_error($response)) {
170 337 wp_send_json_error($response->get_error_message());
171 338 }
172 339
340 + if (wp_remote_retrieve_response_code($response) != 200 && wp_remote_retrieve_response_code($response) != 201) {
341 + wp_send_json_error('API request failed with response code ' . wp_remote_retrieve_response_code($response), wp_remote_retrieve_response_code($response));
342 + }
343 +
173 344 $body = wp_remote_retrieve_body($response);
174 345 $data = json_decode($body, true);
175 346
176 - if (wp_remote_retrieve_response_code($response) != 200 && wp_remote_retrieve_response_code($response) != 201) {
177 - if (isset($data['status'], $data['message']) && $data['status'] === 'error') {
178 - wp_send_json_error($data['message']);
179 - }
180 -
181 - wp_send_json_error('API request failed with response code ' . wp_remote_retrieve_response_code($response));
182 - }
183 -
184 347 if (!isset($data['status']) || $data['status'] !== 'success') {
185 348 wp_send_json_error('API response indicates failure.');
186 349 }
187 350
@@ -193,94 +356,18 @@
193 356
194 357 wp_send_json_success($result);
195 358 }
196 359
197 - private function update_session_data($data): bool {
198 - $old_data = $this->get_session_data();
360 + // Modified get_session_data to use the static version
361 + protected function get_session_data() {
362 + return Utils::get_session_data_by_id();
363 + }
199 364
200 - return update_option(self::SESSION_OPTION_KEY, wp_parse_args($data, $old_data));
201 - }
365 + // Modified update_session_data to use the static version
366 + protected function update_session_data($data) {
367 + return Utils::update_session_data_by_id($data);
368 + }
202 369
203 - protected function CallingFunctionName($id = null) {
204 - $return = 'unknown';
205 - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
206 -
207 - // Check if the trace has at least three elements
208 - if (isset($trace[2])) {
209 - $final_call = $trace[2];
210 -
211 - // Check if 'class' and 'function' indexes exist
212 - if (isset($final_call['class']) && isset($final_call['function'])) {
213 - $return = "{$final_call['class']}::{$final_call['function']}";
214 - }
215 - else if(isset($final_call['function'])){
216 - $return = $final_call['function'];
217 - }
218 - if(!empty($id)){
219 - $return .= "::$id";
220 - }
221 - }
222 -
223 - // Return a default value if 'class' or 'function' index does not exist, or if the trace doesn't have at least three elements
224 - return $return;
225 - }
226 -
227 -
228 - public function get_progress($defaults = [], $unique_id = null) {
229 - $calling_class = $this->CallingFunctionName($unique_id);
230 - if(isset($this->request_params['progress'][$calling_class])){
231 - return $this->request_params['progress'][$calling_class];
232 - }
233 - return $defaults;
234 - }
235 -
236 -
237 - public function update_progress( $progress, $imported_data = null, $unique_id = null ): bool {
238 - $calling_class = $this->CallingFunctionName($unique_id);
239 - $old_data = $this->get_session_data();
240 -
241 - $new_data = [];
242 -
243 - if($progress !== null){
244 - $new_data['progress'] = [$calling_class => $progress];
245 - }
246 - if($imported_data !== null){
247 - $new_data['imported_data'] = $imported_data;
248 - }
249 -
250 - $this->request_params = $this->recursive_wp_parse_args( $new_data, $old_data );
251 - return update_option( self::SESSION_OPTION_KEY, $this->request_params );
252 - }
253 -
254 - public function recursive_wp_parse_args( $args, $defaults ) {
255 - $args = (array) $args;
256 - $defaults = (array) $defaults;
257 - $r = $defaults;
258 - foreach ( $args as $key => $value ) {
259 - if ( is_array( $value ) && isset( $r[ $key ] ) ) {
260 - $r[ $key ] = $this->recursive_wp_parse_args( $value, $r[ $key ] );
261 - } else {
262 - $r[ $key ] = $value;
263 - }
264 - }
265 - return $r;
266 - }
267 -
268 - public function get_session_data(): array {
269 - $data = get_option(self::SESSION_OPTION_KEY, []);
270 -
271 - $options = [];
272 -
273 - if ( is_array( $data ) && ! empty( $data ) ) {
274 - foreach ( $data as $key => $value ) {
275 - $json = is_string($value) ? json_decode( $value, true ) : null;
276 - $options[ $key ] = $json !== null ? $json : $value;
277 - }
278 - }
279 -
280 - return $options;
281 - }
282 -
283 370 public function initialize_props() {
284 371 $data = $this->get_session_data();
285 372 if (isset($data['session_id'])) {
286 373 $this->session_id = $data['session_id'];
@@ -287,13 +374,16 @@
287 374 }
288 375 if (isset($data['dir_path'])) {
289 376 $this->dir_path = $data['dir_path'];
290 377 }
378 + if (isset($data['zip_path'])) {
379 + $this->filePath = $data['zip_path'];
380 + }
291 381 if (isset($data['download_key'])) {
292 382 $this->download_key = $data['download_key'];
293 383 }
294 - if (isset($data['dependency_data'])) {
295 - $this->dependency_data = $data['dependency_data'];
384 + if (isset($data['is_import_status_handled'])) {
385 + $this->is_import_status_handled = $data['is_import_status_handled'];
296 386 }
297 387 }
298 388
299 389 private function clear_session_data(): bool {
@@ -300,32 +390,59 @@
300 390 return delete_site_option(self::SESSION_OPTION_KEY);
301 391 }
302 392
303 393 private function finishRequestHeaders() {
304 - header( "Cache-Control: no-store, no-cache" );
305 - // header( 'Content-Type: text/event-stream, charset=UTF-8' );
306 - // header( "Connection: Keep-Alive" );
394 + if(Helper::should_flush()) {
395 + // Disable output buffering and compression
396 + @ini_set('output_buffering', 'Off');
397 + @ini_set('zlib.output_compression', 'Off');
398 + @ini_set('implicit_flush', 1);
307 399
308 - // Ignore user aborts and allow the script to run forever
309 - // (Use with caution, consider progress updates or timeouts)
310 - ignore_user_abort(true);
400 + // Time to run the import! Set no limit
401 + set_time_limit(0);
311 402
312 - // Time to run the import! Set no limit
313 - set_time_limit(0);
314 403
404 + // Set headers to prevent caching and buffering
405 + header('Content-Type: text/event-stream, charset=UTF-8');
406 + header('Cache-Control: no-cache, must-revalidate');
407 + header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
408 + header('Connection: Keep-Alive');
409 + header('Pragma: no-cache');
315 410
316 - if ( !empty($GLOBALS['is_nginx']) ) {
317 - header( 'X-Accel-Buffering: no' );
318 - header( 'Content-Encoding: none' );
319 - }
411 + if (!empty($GLOBALS['is_nginx'])) {
412 + header('X-Accel-Buffering: no');
413 + header('Content-Encoding: none');
414 + }
320 415
321 - // Send output as soon as possible during long-running process
322 - if (function_exists('fastcgi_finish_request')) {
323 - fastcgi_finish_request();
324 - } elseif (function_exists('litespeed_finish_request')) {
325 - litespeed_finish_request();
416 + flush();
417 + ob_flush();
418 + wp_ob_end_flush_all();
326 419 } else {
327 - wp_ob_end_flush_all();
420 + header("Cache-Control: no-store, no-cache");
421 + // header( 'Content-Type: text/event-stream, charset=UTF-8' );
422 + // header( "Connection: Keep-Alive" );
423 +
424 + // Ignore user aborts and allow the script to run forever
425 + // (Use with caution, consider progress updates or timeouts)
426 + ignore_user_abort(true);
427 +
428 + // Time to run the import! Set no limit
429 + set_time_limit(0);
430 +
431 +
432 + if (!empty($GLOBALS['is_nginx'])) {
433 + header('X-Accel-Buffering: no');
434 + header('Content-Encoding: none');
435 + }
436 +
437 + // Send output as soon as possible during long-running process
438 + if (function_exists('fastcgi_finish_request')) {
439 + fastcgi_finish_request();
440 + } elseif (function_exists('litespeed_finish_request')) {
441 + litespeed_finish_request();
442 + } else {
443 + wp_ob_end_flush_all();
444 + }
328 445 }
329 446 }
330 447
331 448 public function import() {
@@ -332,16 +449,15 @@
332 449 if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
333 450 exit;
334 451 }
335 452
453 + add_filter( 'wp_image_editors', [ $this, 'wp_image_editors' ], 10, 1 );
454 +
455 +
456 + define('TEMPLATELY_START_TIME', microtime(true));
457 +
336 458 // delete_option( 'templately_fsi_log' );
337 459
338 - $this->sse_message( [
339 - 'type' => 'start',
340 - 'action' => 'eventLog',
341 - 'results' => __METHOD__ . '::' . __LINE__,
342 - ] );
343 -
344 460 register_shutdown_function( [ $this, 'register_shutdown' ] );
345 461
346 462 $this->finishRequestHeaders();
347 463
@@ -346,14 +462,43 @@
346 462 $this->finishRequestHeaders();
347 463
348 464 try {
349 465 // TODO: Need to check if user is connected or not
466 + if(!empty($_GET['session_id'])){
467 + $this->session_id = sanitize_text_field($_GET['session_id']);
468 + }
469 + else {
470 + $this->throw(__('Invalid Session ID.', 'templately'));
471 + }
350 472
473 +
351 474 $this->request_params = $this->get_session_data();
352 - $this->initialize_props();
475 + $this->initialize_props();
353 476 $this->add_revert_hooks();
354 477 $progress = $this->request_params['progress'] ?? [];
355 478
479 + // Trigger action hook for network admin multisite handling
480 + do_action( 'templately_fsi_before_import', $this, $this->request_params );
481 +
482 + // Refresh progress after potential multisite creation
483 + $progress = $this->request_params['progress'] ?? [];
484 +
485 + if(empty($progress['create_log_dir'])){
486 + // Create Log Directory and if fail then chose option method
487 + LogHandler::create_log_dir();
488 +
489 + $progress['create_log_dir'] = true;
490 + $this->update_session_data( [
491 + 'progress' => $progress,
492 + ] );
493 + $this->sse_message( [
494 + 'type' => 'eventLog',
495 + 'action' => 'eventLog',
496 + 'info' => 'create_log_dir',
497 + 'results' => __METHOD__ . '::' . __LINE__,
498 + ] );
499 + }
500 +
356 501 $_id = isset($this->request_params['id']) ? (int) $this->request_params['id'] : null;
357 502
358 503 if ($_id === null) {
359 504 $this->throw(__('Invalid Pack ID.', 'templately'));
@@ -358,20 +503,28 @@
358 503 if ($_id === null) {
359 504 $this->throw(__('Invalid Pack ID.', 'templately'));
360 505 }
361 506
362 - if(empty($progress['download_zip'])){
363 - //clear previous revert backup
364 - $options = Utils::get_backup_options();
365 - foreach ($options as $key => $value) {
366 - delete_option("__templately_$key");
367 - }
507 + $this->sse_message( [
508 + 'type' => 'start',
509 + 'action' => 'eventLog',
510 + 'results' => __METHOD__ . '::' . __LINE__,
511 + ] );
368 512
513 + if(empty($progress['check_writing_permission'])){
369 514 /**
370 515 * Check Writing Permission
371 516 */
372 517 $this->check_writing_permission();
373 518
519 + $progress['check_writing_permission'] = true;
520 + $this->update_session_data( [
521 + 'progress' => $progress,
522 + ] );
523 + }
524 +
525 + if(empty($progress['download_zip'])){
526 +
374 527 /**
375 528 * Download the zip
376 529 */
377 530 $this->download_zip( $_id );
@@ -389,31 +542,14 @@
389 542 exit;
390 543 }
391 544
392 545
393 - if(empty($progress['download_dependencies'])){
394 - /**
395 - * Checking & Installing Plugin Dependencies
396 - */
397 - $this->install_dependencies();
398 546
399 - $progress['download_dependencies'] = true;
400 - $this->update_session_data( [
401 - 'progress' => $progress,
402 - ] );
403 - $this->sse_message( [
404 - 'type' => 'continue',
405 - 'action' => 'continue',
406 - 'results' => 'download_dependencies',
407 - ] );
408 - exit;
409 - }
410 547
411 -
412 548 /**
413 549 * Reading Manifest File
414 550 */
415 - $this->read_manifest();
551 + $this->manifest = $this->read_manifest($this->request_params['dir_path']);
416 552
417 553 /**
418 554 * Version Check
419 555 */
@@ -423,8 +559,18 @@
423 559 */
424 560 $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
425 561 }
426 562
563 + $platform = $this->manifest['platform'] ?? '';
564 + if($platform === 'elementor') {
565 + Helper::enable_elementor_container();
566 + }
567 +
568 +
569 +
570 + update_option('templately_import_platform', $platform);
571 +
572 +
427 573 /**
428 574 * Should Revert Old Data
429 575 */
430 576 // $this->revert();
@@ -434,15 +580,19 @@
434 580 */
435 581 $this->start_content_import();
436 582
437 583 } catch ( Exception $e ) {
584 + $should_retry = $e instanceof RetryableErrorException;
438 585 $this->handle_import_status('failed', $e->getMessage());
586 +
439 587 $this->sse_message([
440 - 'action' => 'error',
441 - 'status' => 'error',
442 - 'type' => "error",
443 - 'title' => __("Oops!", "templately"),
444 - 'message' => $e->getMessage()
588 + 'action' => 'error',
589 + 'status' => 'error',
590 + 'type' => "error",
591 + 'retry' => $should_retry,
592 + 'title' => __("Oops!", "templately"),
593 + 'message' => $e->getMessage(),
594 + 'trace' => $e->getTraceAsString(),
445 595 ]);
446 596 }
447 597
448 598 // if($_GET['part'] === 'import'){
@@ -450,18 +600,37 @@
450 600 // $this->clear_session_data();
451 601 // }
452 602 }
453 603
454 - public function import_status(){
455 - $log = get_option( 'templately_fsi_log' );
456 604
457 - if(!empty($log) && is_array($log) && isset($_GET['lastLogIndex'])){
458 - $lastLogIndex = (int) $_GET['lastLogIndex'];
459 - $log = array_slice($log, $lastLogIndex);
605 + public function wp_image_editors( $editors ) {
606 + // If GD is available, use only GD. Otherwise, fallback to all available editors.
607 + if ( is_callable( [ 'WP_Image_Editor_GD', 'test' ] ) && call_user_func( [ 'WP_Image_Editor_GD', 'test' ] ) ) {
608 + return [ 'WP_Image_Editor_GD' ];
460 609 }
461 - wp_send_json( ['count' => $log ? count($log) : 0, 'log' => $log] );
610 + return $editors;
462 611 }
463 612
613 + // Updated import_status method
614 + public function import_status() {
615 + $request_params = $this->get_session_data();
616 +
617 + if (isset($request_params['log_type']) && $request_params['log_type'] == 'file') {
618 + $log_index = isset($_GET['lastLogIndex']) ? (int) $_GET['lastLogIndex'] : 0;
619 + $log = LogHandler::read_log_file($log_index);
620 +
621 + wp_send_json(['count' => count($log), 'log' => $log]);
622 + } else {
623 + $log = get_option('templately_fsi_log');
624 +
625 + if (!empty($log) && is_array($log) && isset($_GET['lastLogIndex'])) {
626 + $lastLogIndex = (int) $_GET['lastLogIndex'];
627 + $log = array_slice($log, $lastLogIndex);
628 + }
629 + wp_send_json(['count' => $log ? count($log) : 0, 'log' => $log]);
630 + }
631 + }
632 +
464 633 /**
465 634 * @throws Exception
466 635 */
467 636 private function throw($message, $code = 0) {
@@ -469,8 +638,35 @@
469 638 error_log(print_r($message, 1));
470 639 }
471 640 throw new Exception($message);
472 641 }
642 + /**
643 + * @throws Exception
644 + */
645 + private function throw_non_retryable($message, $code = 0) {
646 + if ($this->dev_mode) {
647 + error_log(print_r($message, 1));
648 + }
649 + throw new NonRetirableErrorException($message);
650 + }
651 + /**
652 + * @throws Exception
653 + */
654 + private function throw_retryable($message, $code = 0) {
655 + if ($this->dev_mode) {
656 + error_log(print_r($message, 1));
657 + }
658 + throw new RetryableErrorException($message);
659 + }
660 + /**
661 + * @throws Exception
662 + */
663 + private function throw_unknown($message, $code = 0) {
664 + if ($this->dev_mode) {
665 + error_log(print_r($message, 1));
666 + }
667 + throw new UnknownErrorException($message);
668 + }
473 669
474 670 /**
475 671 * @throws Exception
476 672 */
@@ -489,31 +685,18 @@
489 685
490 686 $this->sse_log('writing_permission_check', __('Permission Passed', 'templately'), 100);
491 687 }
492 688
493 - private function get_api_url($version, $end_point): string {
494 - return $this->dev_mode ? "https://app.templately.dev/api/$version/" . $end_point : "https://app.templately.com/api/$version/" . $end_point;
495 - }
496 -
497 - private function info_get_api_url($id): string {
498 - return $this->dev_mode ? 'https://app.templately.dev/api/v1/import/info/pack/' . $id : 'https://app.templately.com/api/v1/import/info/pack/' . $id;
499 - }
500 -
501 689 /**
502 690 * @throws Exception
503 691 */
504 - private function download_zip( $id ) {
692 + private function download_zip( $id, $is_ai = false ) {
505 693 $this->sse_log( 'download', __( 'Downloading Template Pack', 'templately' ), 1 );
506 - $response = wp_remote_get( $this->get_api_url( "v2", "import/pack/$id" ), [
507 - 'timeout' => 30,
508 - 'headers' => [
509 - 'Content-Type' => 'application/json',
510 - 'Authorization' => 'Bearer ' . $this->api_key,
511 - 'x-templately-ip' => Helper::get_ip(),
512 - 'x-templately-url' => home_url('/'),
513 - 'x-templately-version' => TEMPLATELY_VERSION,
514 - ]
515 - ]);
694 + $extra_headers = [
695 + 'x-templately-is-ai' => $is_ai,
696 + 'x-templately-session-id' => $this->session_id,
697 + ];
698 + $response = Helper::make_api_get_request("v2/import/pack/$id", [], $extra_headers, 90);
516 699
517 700 $response_code = wp_remote_retrieve_response_code($response);
518 701 $content_type = wp_remote_retrieve_header($response, 'content-type');
519 702 $this->download_key = wp_remote_retrieve_header($response, 'download-key');
@@ -518,9 +701,9 @@
518 701 $content_type = wp_remote_retrieve_header($response, 'content-type');
519 702 $this->download_key = wp_remote_retrieve_header($response, 'download-key');
520 703
521 704 if (is_wp_error($response)) {
522 - $this->throw(__('Template pack download failed', 'templately') . $response->get_error_message());
705 + $this->throw_retryable(__('Template pack download failed', 'templately') . $response->get_error_message());
523 706 } else if ($response_code != 200) {
524 707 if (strpos($content_type, 'application/json') !== false) {
525 708 // Retrieve Data from Response Body.
526 709 $response_body = json_decode(wp_remote_retrieve_body($response), true);
@@ -526,33 +709,30 @@
526 709 $response_body = json_decode(wp_remote_retrieve_body($response), true);
527 710
528 711 // If the response body is JSON and it contains an error, throw an exception with the error message
529 712 if (isset($response_body['status']) && $response_body['status'] === 'error') {
530 - $this->throw($response_body['message']);
713 + $support_message = '';
714 + if(strpos($response_body['message'], 'https://wpdeveloper.com/support') === false){
715 + $support_message = sprintf(__(" Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://wpdeveloper.com/support');
716 + }
717 + $this->throw_non_retryable($response_body['message'] . $support_message);
531 718 }
532 719 }
533 - $this->throw(__('Template pack download failed with response code: ', 'templately') . $response_code);
720 + $this->throw_unknown(__('Template pack download failed with response code: ', 'templately') . $response_code);
534 721 }
535 722
536 - $this->sse_log('download', __('Template is getting ready', 'templately'), 57);
723 + $this->sse_log('download', __('Downloading Template Pack', 'templately'), 57);
537 724
538 - $session_id = uniqid();
539 - $this->dir_path = $this->tmp_dir . $session_id . DIRECTORY_SEPARATOR;
540 - $this->filePath = $this->tmp_dir . "{$session_id}.zip";
541 - $this->session_id = $session_id;
542 -
543 725 $this->update_session_data([
544 - 'session_id' => $this->session_id,
545 - 'dir_path' => $this->dir_path,
546 726 'download_key' => $this->download_key,
547 727 ]);
548 728
549 729 if (file_put_contents($this->filePath, $response['body'])) { // phpcs:ignore
550 - $this->sse_log('download', __('Template is getting ready', 'templately'), 100);
730 + $this->sse_log('download', __('Downloading Template Pack', 'templately'), 100);
551 731
552 732 $this->unzip();
553 733 } else {
554 - $this->throw(__('Downloading Failed. Please try again', 'templately'));
734 + $this->throw_retryable(__('Downloading Failed. Please try again', 'templately'));
555 735 }
556 736 }
557 737
558 738 /**
@@ -561,14 +741,45 @@
561 741 protected function unzip() {
562 742 if (!WP_Filesystem()) {
563 743 $this->throw(__('WP_Filesystem cannot be initialized', 'templately'));
564 744 }
565 -
566 745 $unzip = unzip_file($this->filePath, $this->dir_path);
567 746 if (is_wp_error($unzip)) {
568 747 $unzip = $this->unzip_file($this->filePath, $this->dir_path);
569 748 }
570 749
750 + $manifest_file = $this->dir_path . 'manifest.json';
751 +
752 + // If manifest.json is missing, but any subdirectory contains manifest.json, move all its contents up and remove the subdirectory.
753 + if ( ! file_exists( $manifest_file ) ) {
754 + $entries = array_diff( scandir( $this->dir_path ), [ '.', '..' ] );
755 + $dirs = array_filter( $entries, fn($e) => is_dir( $this->dir_path . $e ) );
756 + $files = array_filter( $entries, fn($e) => is_file( $this->dir_path . $e ) );
757 + foreach ($dirs as $subdir) {
758 + $subdir_path = $this->dir_path . $subdir . DIRECTORY_SEPARATOR;
759 + if ( file_exists( $subdir_path . 'manifest.json' ) ) {
760 + copy($subdir_path . 'manifest.json', $manifest_file);
761 +
762 + foreach ( array_diff( scandir( $subdir_path ), [ '.', '..' ] ) as $item ) {
763 + $src = $subdir_path . $item;
764 + $dst = $this->dir_path . $item;
765 + if (is_dir($src)) {
766 + if (!file_exists($dst)) {
767 + wp_mkdir_p($dst);
768 + }
769 + // Recursively copy directory
770 + $this->copyDirectory($src, $dst);
771 + } else {
772 + copy($src, $dst);
773 + }
774 + }
775 + // Remove the subdirectory and its contents
776 + $this->removeDirectory($subdir_path);
777 + break; // Only process the first subdir with manifest.json
778 + }
779 + }
780 + }
781 +
571 782 if (is_wp_error($unzip)) {
572 783 $error = $unzip->get_error_message();
573 784 if (empty($error)) {
574 785 // Generic error message
@@ -585,8 +796,45 @@
585 796 }
586 797 }
587 798
588 799 /**
800 + * Recursively copy a directory
801 + */
802 + private function copyDirectory($src, $dst) {
803 + $dir = opendir($src);
804 + wp_mkdir_p($dst);
805 + while(false !== ($file = readdir($dir))) {
806 + if (($file != '.') && ($file != '..')) {
807 + if (is_dir($src . DIRECTORY_SEPARATOR . $file)) {
808 + $this->copyDirectory($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file);
809 + } else {
810 + copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file);
811 + }
812 + }
813 + }
814 + closedir($dir);
815 + }
816 +
817 + /**
818 + * Recursively remove a directory
819 + */
820 + private function removeDirectory($dir) {
821 + if (!file_exists($dir)) return;
822 + $items = array_diff(scandir($dir), ['.', '..']);
823 + foreach ($items as $item) {
824 + $path = $dir . DIRECTORY_SEPARATOR . $item;
825 + if (is_dir($path)) {
826 + $this->removeDirectory($path);
827 + } else {
828 + unlink($path);
829 + }
830 + }
831 + rmdir($dir);
832 + }
833 +
834 +
835 +
836 + /**
589 837 * Unzip a specified ZIP file to a location on the Filesystem.
590 838 *
591 839 * @param string $file Full path and filename of ZIP archive.
592 840 * @param string $to Full path on the filesystem to extract archive to.
@@ -616,17 +864,18 @@
616 864
617 865 /**
618 866 * @throws Exception
619 867 */
620 - private function read_manifest() {
621 - $manifest_content = file_get_contents($this->dir_path . DIRECTORY_SEPARATOR . 'manifest.json');
868 + private function read_manifest($dir_path) {
869 + $manifest_content = file_get_contents($dir_path . 'manifest.json');
622 870 if (empty($manifest_content)) {
623 871 $this->throw(__('Cannot be imported, as the manifest file is corrupted', 'templately'));
624 872 }
625 873
626 - $this->manifest = json_decode($manifest_content, true);
874 + $manifest_content = json_decode($manifest_content, true);
627 875 $this->removeLog('temp');
628 876
877 + return $manifest_content;
629 878 // TODO: Read & Broadcast the LOG for waiting list
630 879 // $this->sse_log( 'plugin', 'Installing required plugins', '--', 'updateLog', 'processing' );
631 880 // // $this->sse_log( 'extra-content', 'Import Extra Contents (i.e: Forms)', '--', 'updateLog', 'processing' );
632 881 // $this->sse_log( 'templates', 'Import Templates (i.e: Header, Footer etc)', '--', 'updateLog', 'processing' );
@@ -638,174 +887,9 @@
638 887 private function skipped_plugin(): bool {
639 888 return empty($this->request_params['plugins']) || !is_array($this->request_params['plugins']);
640 889 }
641 890
642 - private function install_dependencies() {
643 - $progress = $this->request_params['progress'] ?? [];
644 891
645 - if ( empty($progress['theme_dependency']) && ! empty( $this->request_params['theme'] ) && is_array( $this->request_params['theme'] ) ) {
646 - // activate theme
647 - $theme = $this->request_params['theme'];
648 -
649 - // $this->before_install_hook();
650 -
651 - if (isset($theme['stylesheet'])) {
652 - // do_action('before_theme_activation', $theme); // Trigger action before theme activation
653 - $this->sse_log('theme', 'Installing and activating theme: ' . $theme['name'], 0);
654 - if (!get_option("__templately_stylesheet")) {
655 - $stylesheet = get_option('stylesheet');
656 - add_option("__templately_stylesheet", $stylesheet, '', 'no');
657 - }
658 -
659 - $plugin_status = Installer::get_instance()->install_and_activate_theme($theme['stylesheet']);
660 -
661 - if (!$plugin_status['success']) {
662 - $this->sse_message([
663 - 'action' => 'updateLog',
664 - 'status' => 'error',
665 - 'message' => "Failed to activate theme: " . $theme['name'],
666 - 'type' => "theme",
667 - 'progress' => 0
668 - ]);
669 - $this->dependency_data['theme'] = [
670 - 'success' => false,
671 - 'name' => $theme['name'],
672 - 'slug' => $theme['stylesheet'],
673 - 'message' => $plugin_status['message'] ?? ''
674 - ];
675 - $this->update_session_data( [
676 - 'dependency_data' => $this->dependency_data,
677 - ] );
678 - } else {
679 - // do_action('after_theme_activation', $theme); // Trigger action after theme activation
680 -
681 - $this->sse_message([
682 - 'action' => 'updateLog',
683 - 'status' => 'complete',
684 - 'message' => "Activated theme: " . $theme['name'],
685 - 'type' => "theme",
686 - 'progress' => 100
687 - ]);
688 - $this->dependency_data['theme'] = [
689 - 'success' => true,
690 - 'name' => $theme['name'],
691 - 'slug' => $theme['stylesheet'],
692 - 'message' => $plugin_status['message'] ?? ''
693 - ];
694 - $this->update_session_data( [
695 - 'dependency_data' => $this->dependency_data,
696 - ] );
697 -
698 - $progress['theme_dependency'] = true;
699 - $this->update_session_data( [
700 - 'progress' => $progress,
701 - ] );
702 - }
703 -
704 - // If it's not the last item, send the SSE message and exit
705 - $this->sse_message( [
706 - 'type' => 'continue',
707 - 'action' => 'continue',
708 - 'results' => __METHOD__ . '::' . __LINE__,
709 - ] );
710 - exit;
711 - }
712 -
713 - // $this->after_install_hook();
714 - }
715 - else if(empty($progress['theme_dependency'])) {
716 - $this->removeLog( 'theme' );
717 - }
718 - if ( ! empty( $this->request_params['plugins'] ) && is_array( $this->request_params['plugins'] ) ) {
719 - // $this->sse_log( 'plugin', 'Installing Plugins', 1 );
720 - $total_plugin = count($this->request_params['plugins']);
721 -
722 - // $total_plugin_installed = $total_plugin;
723 - $_installed_plugins = $this->dependency_data['_installed_plugins'] ?? 0;
724 - $progress['plugin_dependency'] = $progress['plugin_dependency'] ?? [];
725 -
726 - // $this->before_install_hook();
727 -
728 - foreach ( $this->request_params['plugins'] as $dependency ) {
729 - if(in_array($dependency['plugin_original_slug'], $progress['plugin_dependency'])){
730 - continue;
731 - }
732 - $_dependency = $dependency;
733 - $this->sse_log( 'plugin', 'Installing required plugins: ' . $dependency['name'], floor( ( 100 * $_installed_plugins / $total_plugin ) ) );
734 -
735 - $dependency['slug'] = $dependency['plugin_original_slug'];
736 - $plugin_status = Installer::get_instance()->install($dependency);
737 -
738 - if (!$plugin_status['success']) {
739 - $this->sse_message([
740 - 'position' => 'plugin',
741 - 'action' => 'updateLog',
742 - 'status' => 'error',
743 - 'message' => 'Installation Failed: ' . $dependency['name'] . ' (' . ($plugin_status['message'] ?? '') . ')',
744 - 'type' => "plugin_{$dependency['plugin_original_slug']}",
745 - 'progress' => 0
746 - ]);
747 -
748 - if (isset($dependency['mustHave']) && $dependency['mustHave']) {
749 - $this->removeLog('plugin');
750 - $this->throw('Installation Failed: ' . $dependency['name'] . ' (' . ($plugin_status['message'] ?? '') . ')');
751 - }
752 -
753 - $this->dependency_data['plugins']['failed'][] = [
754 - 'name' => $dependency['name'],
755 - 'slug' => $dependency['slug'],
756 - 'link' => $dependency['link'],
757 - 'message' => $plugin_status['message'] ?? ''
758 - ];
759 - } else {
760 - $_installed_plugins++;
761 - // $total_plugin_installed--;
762 -
763 - $this->dependency_data['_installed_plugins'] = $_installed_plugins;
764 - }
765 -
766 - $this->update_session_data( [
767 - 'dependency_data' => $this->dependency_data,
768 - ] );
769 -
770 - $progress['plugin_dependency'][] = $dependency['slug'];
771 - $this->update_session_data( [
772 - 'progress' => $progress,
773 - ] );
774 -
775 - // If it's not the last item, send the SSE message and exit
776 - if( end($this->request_params['plugins']) !== $_dependency ) {
777 - $this->sse_message( [
778 - 'type' => 'continue',
779 - 'action' => 'continue',
780 - 'results' => __METHOD__ . '::' . __LINE__,
781 - ] );
782 - exit;
783 - }
784 - }
785 -
786 - // $this->after_install_hook();
787 -
788 - $this->sse_message([
789 - 'action' => 'updateLog',
790 - 'status' => 'complete',
791 - 'message' => "Installed required plugins ($_installed_plugins/$total_plugin)",
792 - 'type' => "plugin",
793 - 'progress' => 100
794 - ]);
795 - $this->dependency_data['plugins']['total'] = $total_plugin;
796 - $this->dependency_data['plugins']['succeed'] = $_installed_plugins;
797 - } else {
798 - $this->removeLog('plugin');
799 - }
800 -
801 -
802 - $this->update_session_data([
803 - 'dependency_data' => json_encode($this->dependency_data),
804 - ]);
805 - // $this->sse_log( 'plugin', 'Skipped Installing Plugins', '--', 'updateLog', 'skipped' );
806 - }
807 -
808 892 private function before_install_hook() {
809 893 // remove_all_actions( 'wp_loaded' );
810 894 // remove_all_actions( 'after_setup_theme' );
811 895 // remove_all_actions( 'plugins_loaded' );
@@ -828,9 +912,14 @@
828 912 private function start_content_import() {
829 913 add_filter('upload_mimes', array($this, 'allow_svg_upload'));
830 914 add_filter('elementor/files/allow_unfiltered_upload', '__return_true');
831 915
832 - $import = new Import($this);
916 + $request_params = $this->get_session_data();
917 +
918 + $import = new Import(array_merge($request_params, [
919 + 'origin' => $this,
920 + 'manifest' => $this->manifest,
921 + ]));
833 922 $imported_data = $import->run();
834 923
835 924 $import_status = $this->handle_import_status('success');
836 925
@@ -835,15 +924,36 @@
835 924 $import_status = $this->handle_import_status('success');
836 925
837 926 update_option('templately_flush_rewrite_rules', true, false);
838 927
928 + $normalized_data = $this->normalize_imported_data($imported_data);
929 + // Use timeout-aware wait handler for AI content processing
930 + if(!empty($request_params['ai_page_ids']) && empty($normalized_data['ai_content']['processed']['credit_cost'])){
931 + $processed_pages = get_option("templately_ai_processed_pages", []);
932 + $updated_ids = $processed_pages[$request_params['process_id']] ?? [];
933 +
934 + // Use the static timeout-aware wait handler from AIUtils
935 + AIUtils::handle_sse_wait_with_timeout(
936 + $this->session_id,
937 + 'ai_content_import_time',
938 + $updated_ids,
939 + $request_params['ai_page_ids'],
940 + [$this, 'sse_message'],
941 + [
942 + 'name' => 'ai-content',
943 + 'message' => __('Missing Credit Cost', 'templately'),
944 + ],
945 + null // No specific template ID for this context
946 + );
947 + }
948 +
839 949 $this->sse_message([
840 950 'type' => 'complete',
841 951 'action' => 'complete',
842 - 'results' => $this->normalize_imported_data($imported_data)
952 + 'results' => $normalized_data,
843 953 ]);
844 954
845 - update_user_meta(get_current_user_id(), 'templately_fsi_pack_id', $this->request_params["id"]);
955 + update_user_meta(get_current_user_id(), 'templately_fsi_pack_id', $request_params["id"]);
846 956 if(!empty($import_status['hasFeedback'])){
847 957 update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done');
848 958 }
849 959 else{
@@ -848,13 +958,196 @@
848 958 }
849 959 else{
850 960 update_user_meta(get_current_user_id(), 'templately_fsi_complete', true);
851 961 }
962 +
963 + // $this->clear_data_file($request_params);
852 964 }
853 965
966 + private function clear_data_file($request_params){
967 + if(defined('TEMPLATELY_DEV') && TEMPLATELY_DEV){
968 + return;
969 + }
970 +
971 + // Handle directory cleanup
972 + Utils::cleanup_directory($this->dir_path);
973 + $upload_dir = wp_upload_dir();
974 +
975 + // Always save to preview directory for AI content workflow
976 + $session_id = $request_params['session_id'] ?? '';
977 + $pack_id = $request_params['id'] ?? '';
978 +
979 + // Set up directory paths for cleanup
980 + $root_dir = $request_params['root_dir'] ?? trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp';
981 + $prv_dir = $request_params['prv_dir'] ?? trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview';
982 +
983 + $processed_data = AIUtils::get_ai_process_data_by_session_id($session_id);
984 +
985 + // Clean up WordPress options data and corresponding directories
986 + if (!empty($pack_id) && !empty($session_id)) {
987 + // Clean session data - keep only current session, remove others with same pack_id
988 + $removed_session_ids = Utils::clean_session_data_by_pack_id($pack_id, $session_id);
989 +
990 + // Clean AI process data - keep only current process, remove others with same pack_id
991 + $current_process_id = !empty($processed_data['process_id']) ? $processed_data['process_id'] : null;
992 + $removed_process_ids = AIUtils::clean_ai_process_data_by_pack_id($pack_id, $current_process_id);
993 +
994 + // Directory-based cleanup for session data directories
995 + $this->cleanup_session_directories($root_dir, $pack_id, $session_id);
996 +
997 + // Directory-based cleanup for AI process data directories
998 + $this->cleanup_ai_process_directories($prv_dir, $pack_id, $current_process_id);
999 +
1000 + // Log cleanup results if in dev mode
1001 + if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1002 + if (!empty($removed_session_ids)) {
1003 + error_log('Templately: Cleaned up session IDs: ' . implode(', ', $removed_session_ids));
1004 + }
1005 + if (!empty($removed_process_ids)) {
1006 + error_log('Templately: Cleaned up process IDs: ' . implode(', ', $removed_process_ids));
1007 + }
1008 + }
1009 + }
1010 + }
1011 +
1012 + /**
1013 + * Directory-based cleanup for session data directories
1014 + * Scans the actual filesystem directories and removes directories that match cleanup criteria
1015 + *
1016 + * @param string $root_dir The root directory containing session directories
1017 + * @param string $pack_id The pack ID to match for cleanup
1018 + * @param string $current_session_id The current session ID to preserve
1019 + */
1020 + private function cleanup_session_directories($root_dir, $pack_id, $current_session_id) {
1021 + if (empty($root_dir) || !is_dir($root_dir) || empty($pack_id) || empty($current_session_id)) {
1022 + return;
1023 + }
1024 +
1025 + try {
1026 + // Get all session data to check pack_id associations
1027 + $all_session_data = Utils::get_all_session_data();
1028 +
1029 + // Scan the actual directories in the filesystem
1030 + $directories = scandir($root_dir);
1031 + if ($directories === false) {
1032 + return;
1033 + }
1034 +
1035 + foreach ($directories as $dir_name) {
1036 + // Skip current directory, parent directory, and current session
1037 + if ($dir_name === '.' || $dir_name === '..' || $dir_name === $current_session_id) {
1038 + continue;
1039 + }
1040 +
1041 + $dir_path = trailingslashit($root_dir) . $dir_name;
1042 +
1043 + // Only process actual directories
1044 + if (!is_dir($dir_path)) {
1045 + continue;
1046 + }
1047 +
1048 + // Check if this directory should be cleaned up
1049 + $should_cleanup = false;
1050 +
1051 + // If we have session data for this directory, check if it matches the pack_id
1052 + if (isset($all_session_data[$dir_name]) &&
1053 + isset($all_session_data[$dir_name]['id']) &&
1054 + $all_session_data[$dir_name]['id'] === $pack_id) {
1055 + $should_cleanup = true;
1056 + } else if (!isset($all_session_data[$dir_name])) {
1057 + // This is an orphaned directory with no corresponding session data
1058 + $should_cleanup = true;
1059 + }
1060 +
1061 + if ($should_cleanup) {
1062 + Utils::cleanup_directory($dir_path);
1063 +
1064 + if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1065 + error_log('Templately: Cleaned up session directory: ' . $dir_name);
1066 + }
1067 + }
1068 + }
1069 + } catch (Exception $e) {
1070 + if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1071 + error_log('Templately: Error during session directory cleanup: ' . $e->getMessage());
1072 + }
1073 + }
1074 + }
1075 +
1076 + /**
1077 + * Directory-based cleanup for AI process data directories
1078 + * Scans the actual filesystem directories and removes directories that match cleanup criteria
1079 + *
1080 + * @param string $prv_dir The preview directory containing process directories
1081 + * @param string $pack_id The pack ID to match for cleanup
1082 + * @param string $current_process_id The current process ID to preserve (optional)
1083 + */
1084 + private function cleanup_ai_process_directories($prv_dir, $pack_id, $current_process_id = null) {
1085 + if (empty($prv_dir) || !is_dir($prv_dir) || empty($pack_id)) {
1086 + return;
1087 + }
1088 +
1089 + try {
1090 + // Get all AI process data to check pack_id associations
1091 + $ai_process_data = AIUtils::get_ai_process_data();
1092 +
1093 + // Scan the actual directories in the filesystem
1094 + $directories = scandir($prv_dir);
1095 + if ($directories === false) {
1096 + return;
1097 + }
1098 +
1099 + foreach ($directories as $dir_name) {
1100 + // Skip current directory, parent directory, and current process
1101 + if ($dir_name === '.' || $dir_name === '..' ||
1102 + (!empty($current_process_id) && $dir_name === $current_process_id)) {
1103 + continue;
1104 + }
1105 +
1106 + $dir_path = trailingslashit($prv_dir) . $dir_name;
1107 +
1108 + // Only process actual directories
1109 + if (!is_dir($dir_path)) {
1110 + continue;
1111 + }
1112 +
1113 + // Check if this directory should be cleaned up
1114 + $should_cleanup = false;
1115 +
1116 + // If we have process data for this directory, check if it matches the pack_id
1117 + if (isset($ai_process_data[$dir_name]) &&
1118 + is_array($ai_process_data[$dir_name]) &&
1119 + isset($ai_process_data[$dir_name]['pack_id']) &&
1120 + $ai_process_data[$dir_name]['pack_id'] === $pack_id) {
1121 + $should_cleanup = true;
1122 + } else if (!isset($ai_process_data[$dir_name])) {
1123 + // This is an orphaned directory with no corresponding process data
1124 + $should_cleanup = true;
1125 + }
1126 +
1127 + if ($should_cleanup) {
1128 + Utils::cleanup_directory($dir_path);
1129 +
1130 + if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1131 + error_log('Templately: Cleaned up AI process directory: ' . $dir_name);
1132 + }
1133 + }
1134 + }
1135 + } catch (Exception $e) {
1136 + if (defined('TEMPLATELY_DEV') && TEMPLATELY_DEV) {
1137 + error_log('Templately: Error during AI process directory cleanup: ' . $e->getMessage());
1138 + }
1139 + }
1140 + }
1141 +
854 1142 private function normalize_imported_data($data) {
855 - $templates = !empty($data['templates']['succeed']) ? count($data['templates']['succeed']) : 0;
856 - $template_types = !empty($data['templates']['template_types']) ? $data['templates']['template_types'] : [];
1143 + $request_params = $this->get_session_data();
1144 + $attachments = !empty($data['attachments']['succeed']) ? count($data['attachments']['succeed']) : 0;
1145 + $attachments_fail = !empty($data['attachments']['failed']) ? count($data['attachments']['failed']) : 0;
1146 + $attachments_errors = !empty($data['attachments_errors']) ? $data['attachments_errors'] : [];
1147 + $templates = !empty($data['templates']['succeed']) ? count($data['templates']['succeed']) : 0;
1148 + $template_types = !empty($data['templates']['template_types']) ? $data['templates']['template_types'] : [];
1149 + $dependency_data = !empty($data['dependency_data']) ? $data['dependency_data'] : [];
857 1150
858 1151 $post_types = [];
859 1152 $content_templates = [];
860 1153 if (!empty($data['content']) && is_array($data['content'])) {
@@ -873,18 +1166,33 @@
873 1166 }
874 1167 }
875 1168 }
876 1169
1170 + $_processed_pages = AIUtils::get_processed_pages_data($request_params['process_id']);
1171 + $ai_content = [
1172 + 'requested' => $request_params['ai_page_ids'] ?? [],
1173 + 'processed' => $_processed_pages,
1174 + ];
1175 +
1176 +
1177 + $result = [
1178 + 'attachments' => $attachments,
1179 + 'attachments_fail' => $attachments_fail,
1180 + 'attachments_errors' => $attachments_errors,
1181 + 'templates' => $templates,
1182 + 'contents' => $content_templates,
1183 + 'wp-content' => $contents,
1184 + 'post_types' => $post_types,
1185 + 'template_types' => $template_types,
1186 + 'ai_content' => $ai_content,
1187 + 'dependency_data' => $dependency_data,
1188 + 'home_url' => home_url('/'),
1189 + ];
1190 +
877 1191 Helper::log($data);
1192 + Helper::log($result);
878 1193
879 - return [
880 - 'templates' => $templates,
881 - 'contents' => $content_templates,
882 - 'wp-content' => $contents,
883 - 'post_types' => $post_types,
884 - 'template_types' => $template_types,
885 - 'dependency_data' => $this->dependency_data,
886 - ];
1194 + return $result;
887 1195 }
888 1196
889 1197 public function get_request_params() {
890 1198 return $this->request_params;
@@ -914,31 +1222,35 @@
914 1222
915 1223 public function register_shutdown() {
916 1224 $status = connection_status();
917 1225 $last_error = error_get_last();
918 - if ($status != CONNECTION_NORMAL && $last_error && $last_error['type'] === E_ERROR) {
919 - if ((defined('WP_DEBUG') && WP_DEBUG || defined('TEMPLATELY_EVENT_LOG') && TEMPLATELY_EVENT_LOG) && !empty($last_error['message'])) {
1226 + 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)) {
1227 + if (!empty($last_error['message'])) {
920 1228 $full_message = $last_error['message'];
1229 + $lines = explode("\n", $full_message);
921 1230
922 - // Split the message at the first newline to remove the stack trace
923 - $message_parts = preg_split('/\n/', $full_message);
1231 + // For import status: first 5 lines
1232 + $import_status_message = implode("\n", array_slice($lines, 0, 5));
1233 + $import_status_message = str_replace(ABSPATH, 'ABSPATH/', $import_status_message);
924 1234
925 - // The error message is the first part
926 - $error_message = $message_parts[0];
1235 + // For SSE: first line only
1236 + $sse_message = $lines[0];
1237 + $sse_message = str_replace(ABSPATH, 'ABSPATH/', $sse_message);
927 1238 } else {
928 1239 // Generic error message
929 - $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');
1240 + $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://wpdeveloper.com/support');
1241 + $sse_message = $import_status_message;
930 1242 }
931 1243
932 -
933 - $this->handle_import_status('failed', $error_message);
934 - // Handle the error, e.g. log it or display a message to the user
1244 + $this->handle_import_status('failed', $import_status_message);
935 1245 $this->sse_message([
936 1246 'action' => 'error',
937 1247 'status' => 'error',
938 1248 'type' => "error",
1249 + 'retry' => true,
939 1250 'title' => __("Oops!", "templately"),
940 - 'message' => $error_message,
1251 + 'message' => $sse_message,
1252 + 'error' => $last_error,
941 1253 // 'position' => 'plugin',
942 1254 // 'progress' => '--',
943 1255 ]);
944 1256 }
@@ -948,9 +1260,9 @@
948 1260 $this->debug_log($last_error);
949 1261 }
950 1262
951 1263 public function handle_import_status($status, $description = '') {
952 - if ($this->is_import_status_handled) {
1264 + if ($this->is_import_status_handled === $status) {
953 1265 Helper::log("Import status already handled: $status");
954 1266 return null;
955 1267 }
956 1268 $this->is_import_status_handled = $status;
@@ -965,21 +1277,31 @@
965 1277 'x-templately-ip' => Helper::get_ip(),
966 1278 'x-templately-url' => home_url('/'),
967 1279 ];
968 1280
969 - $args = [
970 - 'headers' => $headers,
971 - ];
972 1281
1282 + $request_params = $this->get_session_data();
1283 + if(isset($request_params['process_id']) && !empty($request_params['ai_page_ids'])){
1284 + $updated_ids = AIUtils::get_processed_pages_data($request_params['process_id']);
1285 + $updated_pages = $updated_ids['pages'] ?? [];
1286 + $ai_page_ids = array_reduce($request_params['ai_page_ids'], 'array_merge', array());
973 1287
1288 + $headers['x-templately-ai-process-id'] = $request_params['process_id'];
1289 + $headers['x-templately-ai-requested-pages'] = implode(',', $ai_page_ids);
1290 + $headers['x-templately-ai-updated-pages'] = implode(',', array_keys($updated_pages));
1291 + $headers['x-templately-ai-missing-pages'] = implode(',', array_diff($ai_page_ids, array_keys($updated_pages)));
1292 + $headers['x-templately-ai-credit-cost'] = $updated_ids['credit_cost'] ?? null;
1293 + }
1294 +
1295 +
1296 + $extra_headers = $headers;
1297 +
974 1298 if ($status === 'success') {
975 - $url = $this->get_api_url("v1", 'import/success');
976 - $args['body'] = json_encode(['type' => 'pack']);
977 - $response = wp_remote_post($url, $args);
1299 + $body = ['type' => 'pack'];
1300 + $response = Helper::make_api_post_request('v1/import/success', $body, $extra_headers);
978 1301 } elseif ($status === 'failed') {
979 - $url = $this->get_api_url("v1", 'import/failed');
980 - $args['body'] = json_encode(['type' => 'pack', 'description' => $description ?: "Something Went wrong....."]);
981 - $response = wp_remote_post($url, $args);
1302 + $body = ['type' => 'pack', 'description' => $description ?: "Something Went wrong....."];
1303 + $response = Helper::make_api_post_request('v1/import/failed', $body, $extra_headers);
982 1304 }
983 1305
984 1306 Helper::log($response);
985 1307
@@ -986,8 +1308,12 @@
986 1308 if (is_wp_error($response)) {
987 1309 // Handle error
988 1310 Helper::log($response->get_error_message());
989 1311 } else {
1312 +
1313 + $this->update_session_data([
1314 + 'is_import_status_handled' => $this->is_import_status_handled,
1315 + ]);
990 1316 // Handle success
991 1317 $body = wp_remote_retrieve_body($response);
992 1318 $data = json_decode($body, true);
993 1319 // Do something with $body
@@ -1022,18 +1348,14 @@
1022 1348 public function import_info() {
1023 1349
1024 1350 $platform = isset($_GET['platform']) ? $_GET['platform'] : 'elementor';
1025 1351 $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1352 + $isAi = isset($_GET['isAi']) ? $_GET['isAi'] : false;
1026 1353
1027 - $response = wp_remote_get($this->info_get_api_url($id), [
1028 - 'timeout' => 30,
1029 - 'headers' => [
1030 - 'Authorization' => 'Bearer ' . $this->api_key,
1031 - 'x-templately-ip' => Helper::get_ip(),
1032 - 'x-templately-url' => home_url('/'),
1033 - 'x-templately-version' => TEMPLATELY_VERSION,
1034 - ]
1035 - ]);
1354 + $extra_headers = [
1355 + 'x-templately-is-ai' => $isAi,
1356 + ];
1357 + $response = Helper::make_api_get_request("v2/import/info/pack/$id", [], $extra_headers, 30);
1036 1358
1037 1359 if (is_wp_error($response)) {
1038 1360 wp_send_json_error($response->get_error_message());
1039 1361 return;
@@ -1039,9 +1361,9 @@
1039 1361 return;
1040 1362 }
1041 1363 // If the response code is not 200, return the error message
1042 1364 if (wp_remote_retrieve_response_code($response) != 200) {
1043 - wp_send_json_error(json_decode(wp_remote_retrieve_body($response)));
1365 + wp_send_json_error(json_decode(wp_remote_retrieve_body($response)), wp_remote_retrieve_response_code($response));
1044 1366 return;
1045 1367 }
1046 1368 // If the response body is JSON and it contains an error, return the error message
1047 1369 // Retrieve Data from Response Body.
@@ -1052,8 +1374,11 @@
1052 1374 wp_send_json_error($data['error']);
1053 1375 return;
1054 1376 }
1055 1377
1378 + $business_niches = get_option('templately_ai_business_niches', []);
1379 + $data['data']['business_niches'] = $business_niches;
1380 +
1056 1381 if (isset($data['data']['manifest'])) {
1057 1382 $data['data']['manifest'] = json_decode($data['data']['manifest'], true);
1058 1383 }
1059 1384 if (isset($data['data']['settings'])) {
@@ -1059,8 +1384,36 @@
1059 1384 if (isset($data['data']['settings'])) {
1060 1385 $data['data']['settings'] = json_decode($data['data']['settings'], true);
1061 1386 }
1062 1387
1388 + if ($isAi) {
1389 + // Get the latest AI process for the current API key
1390 + $last_ai_process = AIUtils::get_latest_ai_process_by_api_key($id);
1391 + if ($last_ai_process) {
1392 + $data['data']['ai_process'] = $last_ai_process;
1393 + }
1394 +
1395 + if($id == $last_ai_process['pack_id']){
1396 + // Read AI preview content directly from files using the common function
1397 + $session_id = $last_ai_process['session_id'] ?? null;
1398 + $ai_page_ids = $last_ai_process['ai_page_ids'] ?? [];
1399 + $dir_path = null;
1400 +
1401 + // Get session data to retrieve dir_path
1402 + if ($session_id) {
1403 + $session_data = Utils::get_session_data($session_id);
1404 + $dir_path = $session_data['dir_path'] ?? null;
1405 + }
1406 +
1407 + // Use the common function to read AI template data if we have the required data
1408 + if ($session_id && $ai_page_ids && $dir_path) {
1409 + $data['data']['ai_preview_content'] = AIUtils::read_ai_template_data($session_id, $ai_page_ids, $dir_path);
1410 + } else {
1411 + $data['data']['ai_preview_content'] = [];
1412 + }
1413 + }
1414 + }
1415 +
1063 1416 // Return the response body
1064 1417 wp_send_json($data);
1065 1418 }
1066 1419
@@ -1065,10 +1418,12 @@
1065 1418 }
1066 1419
1067 1420 public function update_imported_list($type, $id) {
1068 1421 $imported_list = get_option('templately_fsi_imported_list', []);
1069 - $imported_list[$type][] = $id;
1070 - update_option('templately_fsi_imported_list', $imported_list);
1422 + if(!in_array($id, $imported_list[$type] ?? [])){
1423 + $imported_list[$type][] = $id;
1424 + update_option('templately_fsi_imported_list', $imported_list, false);
1425 + }
1071 1426 }
1072 1427
1073 1428 /**
1074 1429 *
@@ -1113,14 +1468,19 @@
1113 1468 // if (!$verified) {
1114 1469 // wp_send_json_error("Nonce not verified.");
1115 1470 // }
1116 1471
1472 + delete_option('templately_import_platform');
1473 +
1117 1474 $option_active = null;
1118 1475 $options_deleted = false;
1119 1476 $imported_list_deleted = false;
1120 1477 $options = Utils::get_backup_options();
1121 1478 $status_args = [ 'post_type' => 'templately_library' ];
1122 - $all_post_url = admin_url(add_query_arg( $status_args, 'edit.php' ));
1479 + $all_post_url = add_query_arg( [
1480 + "page" => "templately_settings",
1481 + "path" => "settings/elementor/miscellaneous",
1482 + ], admin_url('admin.php' ));
1123 1483 // wp_send_json_success([$options]);
1124 1484
1125 1485 if(class_exists('Elementor\Plugin')){
1126 1486 $kits_manager = Plugin::$instance->kits_manager;
@@ -1203,37 +1563,226 @@
1203 1563
1204 1564 wp_send_json_error([ 'options' => $options_deleted, 'imported_list' => $imported_list_deleted, 'site_url' => home_url() ]);
1205 1565 }
1206 1566
1567 + public function google_font() {
1568 + $result = get_transient('templately-google-fonts');
1569 +
1570 + if (false == $result) {
1571 + $response = Helper::make_api_get_request('v2/google-font', [], [], 30);
1572 +
1573 + if (is_wp_error($response)) {
1574 + wp_send_json_error($response->get_error_message());
1575 + }
1576 +
1577 + if (wp_remote_retrieve_response_code($response) != 200) {
1578 + wp_send_json_error('API request failed with response code ' . wp_remote_retrieve_response_code($response), wp_remote_retrieve_response_code($response));
1579 + }
1580 +
1581 + $body = wp_remote_retrieve_body($response);
1582 + $data = json_decode($body, true);
1583 +
1584 + if (!isset($data['status']) || $data['status'] !== 'success') {
1585 + wp_send_json_error('API response indicates failure.');
1586 + }
1587 +
1588 + if (!isset($data['data'])) {
1589 + wp_send_json_error('API response missing data.');
1590 + }
1591 +
1592 + $result = $data['data'];
1593 + set_transient('templately-google-fonts', $result, DAY_IN_SECONDS);
1594 + }
1595 +
1596 + wp_send_json_success($result);
1597 + }
1598 +
1599 + public function ai_get_json() {
1600 + // read json data from post body
1601 + $body = file_get_contents('php://input');
1602 + $data = json_decode($body, true);
1603 +
1604 + if(empty($data['ai_page_ids'])){
1605 + wp_send_json_error('Invalid ai_page_ids');
1606 + return;
1607 + }
1608 +
1609 + if(!isset($_GET['session_id'])){
1610 + wp_send_json_error('Invalid session_id');
1611 + return;
1612 + }
1613 +
1614 + $session_id = isset($_GET['session_id']) ? sanitize_text_field($_GET['session_id']) : null;
1615 + $process_id = $data['process_id'] ?? null;
1616 + $ai_page_ids = $data['ai_page_ids'] ?? null;
1617 +
1618 + $this->request_params = $this->get_session_data();
1619 + try {
1620 + $this->manifest = $this->read_manifest($this->request_params['dir_path']);
1621 + } catch (\Exception $th) {
1622 + wp_send_json_error($th->getMessage());
1623 + }
1624 +
1625 + if(!empty($session_id) && empty($process_id)){
1626 + if ( !empty($this->request_params['process_id']) ){
1627 + $process_id = $this->request_params['process_id'] ?? null;
1628 + } else {
1629 + $process_id = AIUtils::get_ai_process_id_by_session_id($session_id);
1630 + }
1631 + }
1632 +
1633 + if(empty($process_id)){
1634 + wp_send_json_error('Invalid process_id');
1635 + return;
1636 + }
1637 +
1638 + $process_data = AIUtils::get_ai_process_data_by_process_id($process_id);
1639 + if (!empty($process_data['preview_error'])) {
1640 + wp_send_json_error($process_data['preview_error']);
1641 + }
1642 +
1643 + // Use the new common function to read AI template data directly
1644 + $result = AIUtils::read_ai_template_data($session_id, $ai_page_ids, $this->request_params['dir_path']);
1645 +
1646 + // Check if this is called from polling endpoint and include additional data
1647 + $response_data = ['process_id' => $process_id, 'templates' => $result];
1648 +
1649 + if (isset($this->polling_is_last_part)) {
1650 + $response_data['is_last_part'] = $this->polling_is_last_part;
1651 +
1652 + // Clean up the polling property
1653 + unset($this->polling_is_last_part);
1654 + }
1655 +
1656 + wp_send_json_success($response_data);
1657 + }
1658 +
1207 1659 /**
1208 - * Adds "Import" button on module list page
1660 + * AJAX handler for polling AI template generation status on local sites
1661 + * Makes GET request to API endpoint and returns data in same format as ai_get_json()
1209 1662 */
1210 - public function add_revert_button() {
1211 - $screen = get_current_screen();
1212 - // Not our post type, exit earlier
1213 - // You can remove this if condition if you don't have any specific post type to restrict to.
1214 - if ('templately_library' != $screen->post_type) {
1663 + public function ai_poll_template() {
1664 + // Read JSON data from post body
1665 + $body = file_get_contents('php://input');
1666 + $data = json_decode($body, true);
1667 +
1668 + $process_id = $data['process_id'] ?? null;
1669 + $ai_page_ids = $data['ai_page_ids'] ?? null;
1670 +
1671 + if(empty($process_id)){
1672 + wp_send_json_error('Invalid process_id');
1215 1673 return;
1216 1674 }
1217 1675
1218 - // Generate a nonce with a unique action name for security
1219 - $revert_nonce = wp_create_nonce('templately_pack_import_revert_nonce');
1676 + // Validate and get AI process data using centralized method
1677 + $process_data = AIUtils::validate_and_get_process_data($process_id);
1678 + if (is_wp_error($process_data)) {
1679 + $this->ai_get_json();
1680 + return;
1681 + }
1220 1682
1221 - // Build the URL with the nonce
1222 - $revert_url = add_query_arg('action', 'templately_pack_import_revert', admin_url('admin-ajax.php'));
1223 - $revert_url = add_query_arg('_wpnonce', $revert_nonce, $revert_url);
1224 - ?>
1683 + $session_id = $process_data['session_id'];
1684 + $ai_page_ids = $process_data['ai_page_ids'];
1225 1685
1226 - <div class="templately-fsi-revert-wrapper clearfix" style="clear: both; background: #fff; padding: 20px; display: none;">
1227 - <div class="templately-fsi-revert-notice__content">
1228 - <h3>Revert to previous website</h3>
1686 + // Use the common polling function to handle all template processing
1687 + $polling_result = AIUtils::poll_for_template($process_id, $session_id, $ai_page_ids);
1229 1688
1230 - <p>This usually takes a few moments. Please don’t close the window until the process in finished.</p>
1689 + if (!$polling_result) {
1690 + // Polling failed, fallback to ai_get_json
1691 + $this->ai_get_json();
1692 + return;
1693 + }
1231 1694
1232 - <div class="templately-fsi-revert-notice__actions">
1233 - <a href="<?php echo $revert_url;?>" class="button"><span>Revert Now</span></a>
1234 - </div>
1235 - </div>
1236 - </div>
1237 - <?php
1695 + // After polling and processing templates, call ai_get_json() to return the data
1696 + // This reuses all the existing logic without duplication
1697 + $this->ai_get_json();
1238 1698 }
1699 +
1700 + /**
1701 + * Process AI preview content following the ai_get_json() pattern
1702 + *
1703 + * @param string $process_id The AI process ID
1704 + * @param array $ai_page_ids The AI page IDs data structure
1705 + * @param array $ai_preview_ids The AI preview IDs to process
1706 + * @return array Processed AI content data
1707 + */
1708 + private function process_ai_preview_content($process_id, $ai_page_ids, $ai_preview_ids) {
1709 + if (empty($process_id) || empty($ai_page_ids) || empty($ai_preview_ids)) {
1710 + return [];
1711 + }
1712 +
1713 + $all_ai_process_data = AIUtils::get_ai_process_data();
1714 + if (empty($all_ai_process_data[$process_id])) {
1715 + return [];
1716 + }
1717 + $ai_process_data = $all_ai_process_data[$process_id];
1718 + $_REQUEST['is_lightspeed'] = 'true';
1719 + $_REQUEST['session_id'] = $ai_process_data['session_id'] ?? null;
1720 + // Initialize session data and manifest following ai_get_json() pattern
1721 + $this->request_params = $this->get_session_data();
1722 + $this->manifest = $this->read_manifest($this->request_params['dir_path']);
1723 +
1724 + // Create Finalizer instance with the same configuration as ai_get_json()
1725 + $finalizer = new Finalizer(array_merge($this->request_params, [
1726 + 'origin' => $this,
1727 + 'manifest' => $this->manifest,
1728 + ]));
1729 + $finalizer->process_id = $process_id;
1730 + $finalizer->ai_page_ids = $ai_page_ids;
1731 +
1732 + $result = [];
1733 +
1734 + // Process each AI preview ID
1735 + foreach ($ai_preview_ids as $preview_id) {
1736 + // Extract type and sub_type metadata from ai_page_ids structure
1737 + $type_info = $this->extract_content_metadata($preview_id, $ai_page_ids);
1738 +
1739 + if ($type_info) {
1740 + $finalizer->type = $type_info['type'];
1741 + $finalizer->sub_type = $type_info['sub_type'];
1742 +
1743 + // Check if this is AI content before processing
1744 + if ($finalizer->isAiContent($preview_id)) {
1745 + // Process AI content using AIContentHelper trait
1746 + $ai_result = $finalizer->processAiContent($preview_id);
1747 + if ($ai_result['is_ai'] && !empty($ai_result['template_json'])) {
1748 + $template_json = $ai_result['template_json'];
1749 + $result[$preview_id] = $template_json;
1750 + } else if ($finalizer->isAiFileSkipped($preview_id)) {
1751 + // Handle skipped AI files
1752 + $result[$preview_id] = [];
1753 + }
1754 + }
1755 + }
1756 + }
1757 +
1758 + return $result;
1759 + }
1760 +
1761 + /**
1762 + * Extract content metadata (type and sub_type) from ai_page_ids structure
1763 + *
1764 + * @param string $preview_id The preview ID to find
1765 + * @param array $ai_page_ids The AI page IDs data structure
1766 + * @return array|null Array with 'type' and 'sub_type' keys, or null if not found
1767 + */
1768 + private function extract_content_metadata($preview_id, $ai_page_ids) {
1769 + if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
1770 + return null;
1771 + }
1772 +
1773 + // Search through the ai_page_ids structure to find the preview_id
1774 + foreach ($ai_page_ids as $key => $ids) {
1775 + if (is_array($ids) && in_array($preview_id, $ids)) {
1776 + // Extract type and sub_type from the key (e.g., 'content/page' or 'templates')
1777 + $type_arr = explode('/', $key);
1778 + return [
1779 + 'type' => $type_arr[0],
1780 + 'sub_type' => isset($type_arr[1]) ? $type_arr[1] : ''
1781 + ];
1782 + }
1783 + }
1784 +
1785 + return null;
1786 + }
1787 +
1239 1788 }