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