PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.2.1
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.2.1
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
templately / includes / Core / Importer / FullSiteImport.php

FullSiteImport.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.2.1, at includes/Core/Importer/FullSiteImport.php

1,376 lines 42.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * remove 'action' => 'continue',
5 * way to retry
6 * way to skip if failed multiple times
7 *
8 * @todo: on runner check for timeout and retry
9 * @todo: use ErrorException on runner to skip item when error occurs: not useful
10 *
11 */
12
13
14 namespace Templately\Core\Importer;
15
16 use Elementor\Plugin;
17 use Error;
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\Utils\LogHandler;
23 use Templately\Core\Importer\Utils\Utils;
24 use Templately\Utils\Base;
25 use Templately\Utils\Helper;
26 use Templately\Utils\Installer;
27 use Templately\Utils\Options;
28
29 class FullSiteImport extends Base {
30 use LogHelper;
31
32 const SESSION_OPTION_KEY = 'templately_import_session';
33 public $manifest;
34 protected $export;
35
36 private $version = '1.0.0';
37
38 public $session_id;
39 public $download_key;
40 public $dir_path;
41 protected $filePath;
42 protected $tmp_dir = null;
43 protected $dev_mode = false;
44 protected $api_key = '';
45 public $request_params = [];
46 protected $documents_data = [];
47 protected $dependency_data = [];
48 private $is_import_status_handled = false;
49
50 public function __construct() {
51 $this->dev_mode = defined('TEMPLATELY_DEV') && TEMPLATELY_DEV;
52 $this->api_key = Options::get_instance()->get('api_key');
53
54 $this->add_ajax_action('import_settings', $this);
55 $this->add_ajax_action('import_status', $this);
56 $this->add_ajax_action('import', $this);
57 $this->add_ajax_action('import_revert', $this);
58 $this->add_ajax_action('import_info', $this);
59 $this->add_ajax_action('import_close_feedback_modal', $this);
60 $this->add_ajax_action('feedback_form', $this);
61 $this->add_ajax_action('google_font', $this);
62
63 add_action('admin_init', [$this, 'admin_init']);
64 // add_action('admin_notices', [$this, 'add_revert_button']);
65
66 if(isset($_GET['action']) && ($_GET['action'] == 'templately_pack_import' || $_GET['action'] == 'templately_pack_import_status')) {
67 add_filter('wp_redirect', '__return_false', 999);
68 }
69
70 if ($this->dev_mode) {
71 add_filter('http_request_host_is_external', '__return_true');
72 add_filter('http_request_args', function ($args) {
73 $args['sslverify'] = false;
74
75 return $args;
76 });
77 }
78 }
79
80 public function add_ajax_action($action, $object) {
81 add_action("wp_ajax_templately_pack_$action", function() use ($action, $object) {
82 // Check nonce
83 $nonce = null;
84 if(isset($_POST['nonce'])){
85 $nonce = $_POST['nonce'];
86 }
87 if(isset($_GET['nonce'])){
88 $nonce = $_GET['nonce'];
89 }
90 if (!$nonce || !wp_verify_nonce($nonce, 'templately_nonce')) {
91 wp_send_json_error(['message' => __('Invalid nonce', 'templately')]);
92 wp_die();
93 }
94
95 // Check user capability
96 if (!current_user_can('install_plugins') || !current_user_can('install_themes')) {
97 wp_send_json_error(['message' => __('Insufficient permissions', 'templately')]);
98 wp_die();
99 }
100
101 // Call the actual handler method
102 call_user_func([$object, $action]);
103 });
104 }
105
106 public function admin_init() {
107 if (get_option('templately_flush_rewrite_rules', false)) {
108 flush_rewrite_rules();
109 delete_option('templately_flush_rewrite_rules');
110 }
111 }
112
113 public function import_settings() {
114 $data = wp_unslash($_POST);
115
116 $session_id = uniqid();
117 $this->session_id = $session_id;
118 $data['session_id'] = $session_id;
119
120 update_option(self::SESSION_OPTION_KEY, $data);
121
122 delete_option('templately_fsi_imported_list');
123 delete_option('templately_fsi_log');
124
125 wp_send_json_success([
126 'is_lightspeed' => !Helper::should_flush(),
127 'session_id' => $session_id,
128 ]);
129 }
130
131 public function import_close_feedback_modal() {
132 $return = null;
133 if(isset($_GET['closeAction']) && $_GET['closeAction']){
134 $review_email = isset($_POST['review-email']) ? sanitize_email($_POST['review-email']) : '';
135 $pack_id = get_user_meta(get_current_user_id(), 'templately_fsi_pack_id', true);
136
137 // Prepare the body of the request
138 $body = json_encode([
139 'action' => $_GET['closeAction'],
140 'email' => $review_email,
141 'pack_id' => (int) $pack_id,
142 ]);
143
144 // Send the request to the API
145 $response = wp_remote_post($this->get_api_url('v2', 'feedback/close'), [
146 'timeout' => 30,
147 'headers' => [
148 'Content-Type' => 'application/json',
149 'Authorization' => 'Bearer ' . $this->api_key,
150 'x-templately-ip' => Helper::get_ip(),
151 'x-templately-url' => home_url('/'),
152 'x-templately-version' => TEMPLATELY_VERSION,
153 ],
154 'body' => $body,
155 ]);
156 $body = wp_remote_retrieve_body($response);
157 $return = json_decode($body, true);
158 }
159 update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done');
160 wp_send_json_success($return);
161 }
162 public function feedback_form() {
163 // Get data from $_POST
164 $review_description = isset($_POST['review-description']) ? sanitize_textarea_field($_POST['review-description']) : '';
165 $review_email = isset($_POST['review-email']) ? sanitize_email($_POST['review-email']) : '';
166 $rating = isset($_POST['rating']) ? sanitize_text_field($_POST['rating']) : '';
167 $pack_id = get_user_meta(get_current_user_id(), 'templately_fsi_pack_id', true);
168
169 // Prepare the body of the request
170 $body = json_encode([
171 'description' => $review_description,
172 'email' => $review_email,
173 'rating' => (int) $rating,
174 'pack_id' => (int) $pack_id,
175 ]);
176
177 // Send the request to the API
178 $response = wp_remote_post($this->get_api_url('v2', 'feedback/store'), [
179 'timeout' => 30,
180 'headers' => [
181 'Content-Type' => 'application/json',
182 'Authorization' => 'Bearer ' . $this->api_key,
183 'x-templately-ip' => Helper::get_ip(),
184 'x-templately-url' => home_url('/'),
185 'x-templately-version' => TEMPLATELY_VERSION,
186 ],
187 'body' => $body,
188 ]);
189
190 if (is_wp_error($response)) {
191 wp_send_json_error($response->get_error_message());
192 }
193
194 if (wp_remote_retrieve_response_code($response) != 200 && wp_remote_retrieve_response_code($response) != 201) {
195 wp_send_json_error('API request failed with response code ' . wp_remote_retrieve_response_code($response), wp_remote_retrieve_response_code($response));
196 }
197
198 $body = wp_remote_retrieve_body($response);
199 $data = json_decode($body, true);
200
201 if (!isset($data['status']) || $data['status'] !== 'success') {
202 wp_send_json_error('API response indicates failure.');
203 }
204
205 if (!isset($data['message'])) {
206 wp_send_json_error('API response missing data.');
207 }
208
209 $result = $data['message'];
210
211 wp_send_json_success($result);
212 }
213
214 public static function _update_session_data($data): bool {
215 $old_data = self::_get_session_data();
216
217 return update_option(self::SESSION_OPTION_KEY, wp_parse_args($data, $old_data));
218 }
219
220 private function update_session_data($data): bool {
221 return self::_update_session_data($data);
222 }
223
224 protected function CallingFunctionName($id = null) {
225 $return = 'unknown';
226 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
227
228 // Check if the trace has at least three elements
229 if (isset($trace[2])) {
230 $final_call = $trace[2];
231
232 // Check if 'class' and 'function' indexes exist
233 if (isset($final_call['class']) && isset($final_call['function'])) {
234 $return = "{$final_call['class']}::{$final_call['function']}";
235 }
236 else if(isset($final_call['function'])){
237 $return = $final_call['function'];
238 }
239 if(!empty($id)){
240 $return .= "::$id";
241 }
242 }
243
244 // Return a default value if 'class' or 'function' index does not exist, or if the trace doesn't have at least three elements
245 return $return;
246 }
247
248
249 public function get_progress($defaults = [], $unique_id = null) {
250 $calling_class = $this->CallingFunctionName($unique_id);
251 if(isset($this->request_params['progress'][$calling_class])){
252 return $this->request_params['progress'][$calling_class];
253 }
254 return $defaults;
255 }
256
257
258 public function update_progress( $progress, $imported_data = null, $unique_id = null ): bool {
259 $calling_class = $this->CallingFunctionName($unique_id);
260 $old_data = $this->get_session_data();
261
262 $new_data = [];
263
264 if($progress !== null){
265 $new_data['progress'] = [$calling_class => $progress];
266 }
267 if($imported_data !== null){
268 $new_data['imported_data'] = $imported_data;
269 }
270
271 $this->request_params = $this->recursive_wp_parse_args( $new_data, $old_data );
272 return update_option( self::SESSION_OPTION_KEY, $this->request_params );
273 }
274
275 public function recursive_wp_parse_args( $args, $defaults ) {
276 $args = (array) $args;
277 $defaults = (array) $defaults;
278 $r = $defaults;
279 foreach ( $args as $key => $value ) {
280 if ( is_array( $value ) && isset( $r[ $key ] ) ) {
281 $r[ $key ] = $this->recursive_wp_parse_args( $value, $r[ $key ] );
282 } else {
283 $r[ $key ] = $value;
284 }
285 }
286 return $r;
287 }
288
289 public static function _get_session_data(): array {
290 $data = get_option(self::SESSION_OPTION_KEY, []);
291
292 $options = [];
293
294 if ( is_array( $data ) && ! empty( $data ) ) {
295 foreach ( $data as $key => $value ) {
296 $json = is_string($value) ? json_decode( $value, true ) : null;
297 $options[ $key ] = $json !== null ? $json : $value;
298 }
299 }
300
301 return $options;
302 }
303
304 public function get_session_data(): array {
305 return self::_get_session_data();
306 }
307
308 public function initialize_props() {
309 $data = $this->get_session_data();
310 if (isset($data['session_id'])) {
311 $this->session_id = $data['session_id'];
312 }
313 if (isset($data['dir_path'])) {
314 $this->dir_path = $data['dir_path'];
315 }
316 if (isset($data['download_key'])) {
317 $this->download_key = $data['download_key'];
318 }
319 if (isset($data['dependency_data'])) {
320 $this->dependency_data = $data['dependency_data'];
321 }
322 }
323
324 private function clear_session_data(): bool {
325 return delete_site_option(self::SESSION_OPTION_KEY);
326 }
327
328 private function finishRequestHeaders() {
329 if(Helper::should_flush()) {
330 // Disable output buffering and compression
331 @ini_set('output_buffering', 'Off');
332 @ini_set('zlib.output_compression', 'Off');
333 @ini_set('implicit_flush', 1);
334
335 // Time to run the import! Set no limit
336 set_time_limit(0);
337
338
339 // Set headers to prevent caching and buffering
340 header('Content-Type: text/event-stream, charset=UTF-8');
341 header('Cache-Control: no-cache, must-revalidate');
342 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
343 header('Connection: Keep-Alive');
344 header('Pragma: no-cache');
345
346 if (!empty($GLOBALS['is_nginx'])) {
347 header('X-Accel-Buffering: no');
348 header('Content-Encoding: none');
349 }
350
351 flush();
352 ob_flush();
353 wp_ob_end_flush_all();
354 } else {
355 header("Cache-Control: no-store, no-cache");
356 // header( 'Content-Type: text/event-stream, charset=UTF-8' );
357 // header( "Connection: Keep-Alive" );
358
359 // Ignore user aborts and allow the script to run forever
360 // (Use with caution, consider progress updates or timeouts)
361 ignore_user_abort(true);
362
363 // Time to run the import! Set no limit
364 set_time_limit(0);
365
366
367 if (!empty($GLOBALS['is_nginx'])) {
368 header('X-Accel-Buffering: no');
369 header('Content-Encoding: none');
370 }
371
372 // Send output as soon as possible during long-running process
373 if (function_exists('fastcgi_finish_request')) {
374 fastcgi_finish_request();
375 } elseif (function_exists('litespeed_finish_request')) {
376 litespeed_finish_request();
377 } else {
378 wp_ob_end_flush_all();
379 }
380 }
381 }
382
383 public function import() {
384 if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
385 exit;
386 }
387
388 define('TEMPLATELY_START_TIME', microtime(true));
389
390 // delete_option( 'templately_fsi_log' );
391
392 register_shutdown_function( [ $this, 'register_shutdown' ] );
393
394 $this->finishRequestHeaders();
395
396 try {
397 // TODO: Need to check if user is connected or not
398
399
400 $this->request_params = $this->get_session_data();
401 $this->initialize_props();
402 $this->add_revert_hooks();
403 $progress = $this->request_params['progress'] ?? [];
404
405 if(empty($progress['create_log_dir'])){
406 // Create Log Directory and if fail then chose option method
407 LogHandler::create_log_dir();
408
409 $progress['create_log_dir'] = true;
410 $this->update_session_data( [
411 'progress' => $progress,
412 ] );
413 $this->sse_message( [
414 'type' => 'eventLog',
415 'action' => 'eventLog',
416 'info' => 'create_log_dir',
417 'results' => __METHOD__ . '::' . __LINE__,
418 ] );
419 }
420
421 $_id = isset($this->request_params['id']) ? (int) $this->request_params['id'] : null;
422
423 if ($_id === null) {
424 $this->throw(__('Invalid Pack ID.', 'templately'));
425 }
426
427 $this->sse_message( [
428 'type' => 'start',
429 'action' => 'eventLog',
430 'results' => __METHOD__ . '::' . __LINE__,
431 ] );
432
433 if(empty($progress['download_zip'])){
434 //clear previous revert backup
435 $options = Utils::get_backup_options();
436 foreach ($options as $key => $value) {
437 delete_option("__templately_$key");
438 }
439
440 /**
441 * Check Writing Permission
442 */
443 $this->check_writing_permission();
444
445 /**
446 * Download the zip
447 */
448 $this->download_zip( $_id );
449
450 $progress['download_zip'] = true;
451 $this->update_session_data( [
452 'progress' => $progress,
453 ] );
454 $this->sse_message( [
455 'type' => 'continue',
456 'action' => 'continue',
457 'info' => 'download_zip',
458 'results' => __METHOD__ . '::' . __LINE__,
459 ] );
460 exit;
461 }
462
463
464 if(empty($progress['download_dependencies'])){
465 /**
466 * Checking & Installing Plugin Dependencies
467 */
468 $this->install_dependencies();
469
470 $progress['download_dependencies'] = true;
471 $this->update_session_data( [
472 'progress' => $progress,
473 ] );
474 $this->sse_message( [
475 'type' => 'continue',
476 'action' => 'continue',
477 'results' => 'download_dependencies',
478 ] );
479 exit;
480 }
481
482
483 /**
484 * Reading Manifest File
485 */
486 $this->read_manifest();
487
488 /**
489 * Version Check
490 */
491 if ( ! empty( $this->manifest['version'] ) && version_compare( $this->manifest['version'], $this->version, '>' ) ) {
492 /**
493 * FIXME: The message should be re-written (by content/support team).
494 */
495 $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
496 }
497
498 /**
499 * Should Revert Old Data
500 */
501 // $this->revert();
502
503 /**
504 * Platform Based Templates Import
505 */
506 $this->start_content_import();
507
508 } catch ( Exception $e ) {
509 $should_retry = $e instanceof RetryableErrorException;
510 if(!$should_retry){
511 $this->handle_import_status('failed', $e->getMessage());
512 }
513
514 $this->sse_message([
515 'action' => 'error',
516 'status' => 'error',
517 'type' => "error",
518 'retry' => $should_retry,
519 'title' => __("Oops!", "templately"),
520 'message' => $e->getMessage()
521 ]);
522 }
523
524 // if($_GET['part'] === 'import'){
525 // TODO: cleanup
526 // $this->clear_session_data();
527 // }
528 }
529
530 // Updated import_status method
531 public function import_status() {
532 $request_params = $this->get_session_data();
533
534 if (isset($request_params['log_type']) && $request_params['log_type'] == 'file') {
535 $log_index = isset($_GET['lastLogIndex']) ? (int) $_GET['lastLogIndex'] : 0;
536 $log = LogHandler::read_log_file($log_index);
537
538 wp_send_json(['count' => count($log), 'log' => $log]);
539 } else {
540 $log = get_option('templately_fsi_log');
541
542 if (!empty($log) && is_array($log) && isset($_GET['lastLogIndex'])) {
543 $lastLogIndex = (int) $_GET['lastLogIndex'];
544 $log = array_slice($log, $lastLogIndex);
545 }
546 wp_send_json(['count' => $log ? count($log) : 0, 'log' => $log]);
547 }
548 }
549
550 /**
551 * @throws Exception
552 */
553 private function throw($message, $code = 0) {
554 if ($this->dev_mode) {
555 error_log(print_r($message, 1));
556 }
557 throw new Exception($message);
558 }
559 /**
560 * @throws Exception
561 */
562 private function throw_non_retryable($message, $code = 0) {
563 if ($this->dev_mode) {
564 error_log(print_r($message, 1));
565 }
566 throw new NonRetryableErrorException($message);
567 }
568 /**
569 * @throws Exception
570 */
571 private function throw_retryable($message, $code = 0) {
572 if ($this->dev_mode) {
573 error_log(print_r($message, 1));
574 }
575 throw new RetryableErrorException($message);
576 }
577 /**
578 * @throws Exception
579 */
580 private function throw_unknown($message, $code = 0) {
581 if ($this->dev_mode) {
582 error_log(print_r($message, 1));
583 }
584 throw new UnknownErrorException($message);
585 }
586
587 /**
588 * @throws Exception
589 */
590 private function check_writing_permission() {
591 $upload_dir = wp_upload_dir();
592
593 if (!is_writable($upload_dir['basedir'])) {
594 $this->throw(__('Upload directory is not writable.', 'templately'));
595 }
596
597 $this->tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
598
599 if (!is_dir($this->tmp_dir)) {
600 wp_mkdir_p($this->tmp_dir);
601 }
602
603 $this->sse_log('writing_permission_check', __('Permission Passed', 'templately'), 100);
604 }
605
606 private function get_api_url($version, $end_point): string {
607 return $this->dev_mode ? "https://app.templately.dev/api/$version/" . $end_point : "https://app.templately.com/api/$version/" . $end_point;
608 }
609
610 private function info_get_api_url($id): string {
611 return $this->dev_mode ? 'https://app.templately.dev/api/v1/import/info/pack/' . $id : 'https://app.templately.com/api/v1/import/info/pack/' . $id;
612 }
613
614 /**
615 * @throws Exception
616 */
617 private function download_zip( $id ) {
618 $this->sse_log( 'download', __( 'Downloading Template Pack', 'templately' ), 1 );
619 $response = wp_remote_get( $this->get_api_url( "v2", "import/pack/$id" ), [
620 'timeout' => 30,
621 'headers' => [
622 'Content-Type' => 'application/json',
623 'Authorization' => 'Bearer ' . $this->api_key,
624 'x-templately-ip' => Helper::get_ip(),
625 'x-templately-url' => home_url('/'),
626 'x-templately-version' => TEMPLATELY_VERSION,
627 ]
628 ]);
629
630 $response_code = wp_remote_retrieve_response_code($response);
631 $content_type = wp_remote_retrieve_header($response, 'content-type');
632 $this->download_key = wp_remote_retrieve_header($response, 'download-key');
633
634 if (is_wp_error($response)) {
635 $this->throw_retryable(__('Template pack download failed', 'templately') . $response->get_error_message());
636 } else if ($response_code != 200) {
637 if (strpos($content_type, 'application/json') !== false) {
638 // Retrieve Data from Response Body.
639 $response_body = json_decode(wp_remote_retrieve_body($response), true);
640
641 // If the response body is JSON and it contains an error, throw an exception with the error message
642 if (isset($response_body['status']) && $response_body['status'] === 'error') {
643 $support_message = '';
644 if(strpos($response_body['message'], 'https://wpdeveloper.com/support') === false){
645 $support_message = sprintf(__(" Please try again or contact <a href='%s' target='_blank'>support</a>.", "templately"), 'https://wpdeveloper.com/support');
646 }
647 $this->throw_non_retryable($response_body['message'] . $support_message);
648 }
649 }
650 $this->throw_unknown(__('Template pack download failed with response code: ', 'templately') . $response_code);
651 }
652
653 $this->sse_log('download', __('Downloading Template Pack', 'templately'), 57);
654
655 // $session_id = uniqid();
656 $this->dir_path = $this->tmp_dir . $this->session_id . DIRECTORY_SEPARATOR;
657 $this->filePath = $this->tmp_dir . "{$this->session_id}.zip";
658 // $this->session_id = $session_id;
659
660 $this->update_session_data([
661 'session_id' => $this->session_id,
662 'dir_path' => $this->dir_path,
663 'download_key' => $this->download_key,
664 ]);
665
666 if (file_put_contents($this->filePath, $response['body'])) { // phpcs:ignore
667 $this->sse_log('download', __('Downloading Template Pack', 'templately'), 100);
668
669 $this->unzip();
670 } else {
671 $this->throw_retryable(__('Downloading Failed. Please try again', 'templately'));
672 }
673 }
674
675 /**
676 * @throws Exception
677 */
678 protected function unzip() {
679 if (!WP_Filesystem()) {
680 $this->throw(__('WP_Filesystem cannot be initialized', 'templately'));
681 }
682
683 $unzip = unzip_file($this->filePath, $this->dir_path);
684 if (is_wp_error($unzip)) {
685 $unzip = $this->unzip_file($this->filePath, $this->dir_path);
686 }
687
688 if (is_wp_error($unzip)) {
689 $error = $unzip->get_error_message();
690 if (empty($error)) {
691 // Generic error message
692 Helper::log($unzip);
693 $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');
694 $this->throw($error_message);
695 } else {
696 $this->throw($unzip->get_error_message());
697 }
698 }
699
700 if ($unzip) {
701 unlink($this->filePath);
702 }
703 }
704
705 /**
706 * Unzip a specified ZIP file to a location on the Filesystem.
707 *
708 * @param string $file Full path and filename of ZIP archive.
709 * @param string $to Full path on the filesystem to extract archive to.
710 * @return true|WP_Error True on success, WP_Error on failure.
711 */
712 function unzip_file($file, $to) {
713 try {
714 $zip = new \ZipArchive;
715
716 $res = $zip->open($file);
717 if ($res === TRUE) {
718 $zip->extractTo($to);
719 $zip->close();
720
721 return true;
722 }
723 } catch (\Throwable $th) {
724 return new \WP_Error('exception_caught', $th->getMessage());
725 }
726
727 if (isset($zip)) {
728 return new \WP_Error('zip_error_' . $zip->status, $zip->getStatusString());
729 } else {
730 return new \WP_Error('unknown_error', '');
731 }
732 }
733
734 /**
735 * @throws Exception
736 */
737 private function read_manifest() {
738 $manifest_content = file_get_contents($this->dir_path . DIRECTORY_SEPARATOR . 'manifest.json');
739 if (empty($manifest_content)) {
740 $this->throw(__('Cannot be imported, as the manifest file is corrupted', 'templately'));
741 }
742
743 $this->manifest = json_decode($manifest_content, true);
744 $this->removeLog('temp');
745
746 // TODO: Read & Broadcast the LOG for waiting list
747 // $this->sse_log( 'plugin', 'Installing required plugins', '--', 'updateLog', 'processing' );
748 // // $this->sse_log( 'extra-content', 'Import Extra Contents (i.e: Forms)', '--', 'updateLog', 'processing' );
749 // $this->sse_log( 'templates', 'Import Templates (i.e: Header, Footer etc)', '--', 'updateLog', 'processing' );
750 // // $this->sse_log( 'content', 'Import Pages, Posts etc', '--', 'updateLog', 'processing' );
751 // $this->sse_log( 'wp-content', 'Importing Pages, Posts, Navigation, etc', '--', 'updateLog', 'processing' );
752 // $this->sse_log( 'finalize', 'Finalizing Your Imports', '--', 'updateLog', 'processing' );
753 }
754
755 private function skipped_plugin(): bool {
756 return empty($this->request_params['plugins']) || !is_array($this->request_params['plugins']);
757 }
758
759 private function install_dependencies() {
760 $progress = $this->request_params['progress'] ?? [];
761
762 if ( empty($progress['theme_dependency']) && ! empty( $this->request_params['theme'] ) && is_array( $this->request_params['theme'] ) ) {
763 // activate theme
764 $theme = $this->request_params['theme'];
765
766 // $this->before_install_hook();
767
768 if (isset($theme['stylesheet'])) {
769 $stylesheet = get_option('stylesheet');
770
771 // do_action('before_theme_activation', $theme); // Trigger action before theme activation
772 $this->sse_log('theme', 'Installing and Activating Theme: ' . $theme['name'], 0);
773 if (!get_option("__templately_stylesheet")) {
774 add_option("__templately_stylesheet", $stylesheet, '', 'no');
775 }
776
777 $plugin_status = Installer::get_instance()->install_and_activate_theme($theme['stylesheet']);
778
779 if (!$plugin_status['success']) {
780 $this->sse_message([
781 'action' => 'updateLog',
782 'status' => 'error',
783 'message' => "Failed to activate theme: " . $theme['name'],
784 'type' => "theme",
785 'progress' => 0
786 ]);
787 $this->dependency_data['theme'] = [
788 'success' => false,
789 'name' => $theme['name'],
790 'slug' => $theme['stylesheet'],
791 'message' => $plugin_status['message'] ?? ''
792 ];
793 $this->update_session_data( [
794 'dependency_data' => $this->dependency_data,
795 ] );
796 } else {
797 // do_action('after_theme_activation', $theme); // Trigger action after theme activation
798
799 $this->sse_message([
800 'action' => 'updateLog',
801 'status' => 'complete',
802 'message' => "Activated theme: " . $theme['name'],
803 'type' => "theme",
804 'progress' => 100
805 ]);
806 $this->dependency_data['theme'] = [
807 'success' => true,
808 'name' => $theme['name'],
809 'slug' => $theme['stylesheet'],
810 'message' => $plugin_status['message'] ?? ''
811 ];
812 $this->update_session_data( [
813 'dependency_data' => $this->dependency_data,
814 ] );
815
816 $progress['theme_dependency'] = true;
817 $this->update_session_data( [
818 'progress' => $progress,
819 ] );
820 }
821
822 if($theme['stylesheet'] !== $stylesheet){
823 // If it's not the last item, send the SSE message and exit
824 $this->sse_message( [
825 'type' => 'continue',
826 'action' => 'continue',
827 'results' => __METHOD__ . '::' . __LINE__,
828 ] );
829 exit;
830 }
831 }
832
833 // $this->after_install_hook();
834 }
835 else if(empty($progress['theme_dependency'])) {
836 $this->removeLog( 'theme' );
837 }
838 if ( ! empty( $this->request_params['plugins'] ) && is_array( $this->request_params['plugins'] ) ) {
839 // $this->sse_log( 'plugin', 'Installing Plugins', 1 );
840 $total_plugin = count($this->request_params['plugins']);
841
842 // $total_plugin_installed = $total_plugin;
843 $_installed_plugins = $this->dependency_data['_installed_plugins'] ?? 0;
844 $progress['plugin_dependency'] = $progress['plugin_dependency'] ?? [];
845
846 // $this->before_install_hook();
847
848 foreach ( $this->request_params['plugins'] as $dependency ) {
849 if(in_array($dependency['plugin_original_slug'], $progress['plugin_dependency'])){
850 continue;
851 }
852 $this->sse_log( 'plugin', 'Installing Required Plugins: ' . $dependency['name'], floor( ( 100 * $_installed_plugins / $total_plugin ) ) );
853
854 $_dependency = $dependency;
855 $is_installed = Helper::is_plugins_installed($dependency['plugin_file']);
856 $dependency['slug'] = $dependency['plugin_original_slug'];
857 $plugin_status = Installer::get_instance()->install($dependency);
858
859 if (!$plugin_status['success']) {
860 $this->sse_message([
861 'position' => 'plugin',
862 'action' => 'updateLog',
863 'status' => 'error',
864 'message' => 'Installation Failed: ' . $dependency['name'] . ' (' . ($plugin_status['message'] ?? '') . ')',
865 'type' => "plugin_{$dependency['plugin_original_slug']}",
866 'progress' => 0
867 ]);
868
869 if (isset($dependency['mustHave']) && $dependency['mustHave']) {
870 $this->removeLog('plugin');
871 $this->throw('Installation Failed: ' . $dependency['name'] . ' (' . ($plugin_status['message'] ?? '') . ')');
872 }
873
874 $this->dependency_data['plugins']['failed'][] = [
875 'name' => $dependency['name'],
876 'slug' => $dependency['slug'],
877 'link' => $dependency['link'],
878 'message' => $plugin_status['message'] ?? ''
879 ];
880 } else {
881 $_installed_plugins++;
882 // $total_plugin_installed--;
883
884 $this->dependency_data['_installed_plugins'] = $_installed_plugins;
885 }
886
887 $this->update_session_data( [
888 'dependency_data' => $this->dependency_data,
889 ] );
890
891 $progress['plugin_dependency'][] = $dependency['slug'];
892 $this->update_session_data( [
893 'progress' => $progress,
894 ] );
895
896 // If it's not the last item, send the SSE message and exit
897 if(!$is_installed && end($this->request_params['plugins']) !== $_dependency ) {
898 $this->sse_message( [
899 'type' => 'continue',
900 'action' => 'continue',
901 'results' => __METHOD__ . '::' . __LINE__,
902 ] );
903 exit;
904 }
905 }
906
907 // $this->after_install_hook();
908
909 $this->sse_message([
910 'action' => 'updateLog',
911 'status' => 'complete',
912 'message' => "Installed Required Plugins ($_installed_plugins/$total_plugin)",
913 'type' => "plugin",
914 'progress' => 100
915 ]);
916 $this->dependency_data['plugins']['total'] = $total_plugin;
917 $this->dependency_data['plugins']['succeed'] = $_installed_plugins;
918 } else {
919 $this->removeLog('plugin');
920 }
921
922
923 $this->update_session_data([
924 'dependency_data' => json_encode($this->dependency_data),
925 ]);
926 // $this->sse_log( 'plugin', 'Skipped Installing Plugins', '--', 'updateLog', 'skipped' );
927 }
928
929 private function before_install_hook() {
930 // remove_all_actions( 'wp_loaded' );
931 // remove_all_actions( 'after_setup_theme' );
932 // remove_all_actions( 'plugins_loaded' );
933 // remove_all_actions( 'init' );
934
935 // making sure so that no redirection happens during plugin installation and hooks triggered bellow.
936 add_filter('wp_redirect', '__return_false', 999);
937 }
938
939 private function after_install_hook() {
940 // do_action( 'wp_loaded' );
941 // do_action( 'after_setup_theme' );
942 // do_action( 'plugins_loaded' );
943 // do_action( 'init' );
944 }
945
946 /**
947 * @throws Exception
948 */
949 private function start_content_import() {
950 add_filter('upload_mimes', array($this, 'allow_svg_upload'));
951 add_filter('elementor/files/allow_unfiltered_upload', '__return_true');
952
953 $import = new Import($this);
954 $imported_data = $import->run();
955
956 $import_status = $this->handle_import_status('success');
957
958 update_option('templately_flush_rewrite_rules', true, false);
959
960 $this->sse_message([
961 'type' => 'complete',
962 'action' => 'complete',
963 'results' => $this->normalize_imported_data($imported_data)
964 ]);
965
966 update_user_meta(get_current_user_id(), 'templately_fsi_pack_id', $this->request_params["id"]);
967 if(!empty($import_status['hasFeedback'])){
968 update_user_meta(get_current_user_id(), 'templately_fsi_complete', 'done');
969 }
970 else{
971 update_user_meta(get_current_user_id(), 'templately_fsi_complete', true);
972 }
973 }
974
975 private function normalize_imported_data($data) {
976 $attachments = !empty($data['attachments']['succeed']) ? count($data['attachments']['succeed']) : 0;
977 $attachments_fail = !empty($data['attachments']['failed']) ? count($data['attachments']['failed']) : 0;
978 $attachments_errors = !empty($data['attachments_errors']) ? $data['attachments_errors'] : [];
979 $templates = !empty($data['templates']['succeed']) ? count($data['templates']['succeed']) : 0;
980 $template_types = !empty($data['templates']['template_types']) ? $data['templates']['template_types'] : [];
981
982 $post_types = [];
983 $content_templates = [];
984 if (!empty($data['content']) && is_array($data['content'])) {
985 foreach ($data['content'] as $type => $type_data) {
986 $content_templates[$type] = !empty($type_data['succeed']) ? count($type_data['succeed']) : 0;
987 $post_types[] = $this->get_post_type_label_by_slug($type);
988 }
989 }
990
991 $contents = [];
992 if (!empty($data['wp-content']) && is_array($data['wp-content'])) {
993 foreach ($data['wp-content'] as $type => $type_data) {
994 $contents[$type] = !empty($type_data['succeed']) ? count($type_data['succeed']) : 0;
995 if (!in_array($type, ['wp_navigation', 'nav_menu_item'])) {
996 $post_types[] = $this->get_post_type_label_by_slug($type);
997 }
998 }
999 }
1000
1001 Helper::log($data);
1002
1003 return [
1004 'attachments' => $attachments,
1005 'attachments_fail' => $attachments_fail,
1006 'attachments_errors' => $attachments_errors,
1007 'templates' => $templates,
1008 'contents' => $content_templates,
1009 'wp-content' => $contents,
1010 'post_types' => $post_types,
1011 'template_types' => $template_types,
1012 'dependency_data' => $this->dependency_data,
1013 ];
1014 }
1015
1016 public function get_request_params() {
1017 return $this->request_params;
1018 }
1019
1020 private function revert() {
1021 // $request = $this->get_request_params();
1022 // if ( isset( $request['revert'] ) && $request['revert'] ) {
1023 // // TODO: Implement the Revert Process.
1024 // }
1025 }
1026
1027 public function redirect_for_archives($link, $post_id) {
1028 $archive_settings = get_option('templately_post_archive');
1029 if (!empty($archive_settings) && intval($archive_settings['post_id']) === intval($post_id)) {
1030 $link = str_replace($post_id, $archive_settings['archive_id'], $link);
1031 }
1032
1033 return $link;
1034 }
1035
1036 public function allow_svg_upload($mimes) {
1037 // Allow SVG
1038 $mimes['svg'] = 'image/svg+xml';
1039 return $mimes;
1040 }
1041
1042 public function register_shutdown() {
1043 $status = connection_status();
1044 $last_error = error_get_last();
1045 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)) {
1046 if (!empty($last_error['message'])) {
1047 $full_message = $last_error['message'];
1048 // Extract the first line from the error message
1049 $firstLine = strtok($full_message, "\n");
1050
1051 // Remove absolute paths by replacing the WordPress directory path with a placeholder
1052 $error_message = str_replace(ABSPATH, 'ABSPATH/', $firstLine);
1053 } else {
1054 // Generic error message
1055 $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');
1056 }
1057
1058
1059 $this->handle_import_status('failed', $error_message);
1060 // Handle the error, e.g. log it or display a message to the user
1061 $this->sse_message([
1062 'action' => 'error',
1063 'status' => 'error',
1064 'type' => "error",
1065 'retry' => true,
1066 'title' => __("Oops!", "templately"),
1067 'message' => $error_message,
1068 // 'position' => 'plugin',
1069 // 'progress' => '--',
1070 ]);
1071 }
1072
1073 $this->debug_log("Shutdown:.....");
1074 $this->debug_log("connection_status: " . $this->getConnectionStatusText());
1075 $this->debug_log($last_error);
1076 }
1077
1078 public function handle_import_status($status, $description = '') {
1079 if ($this->is_import_status_handled) {
1080 Helper::log("Import status already handled: $status");
1081 return null;
1082 }
1083 $this->is_import_status_handled = $status;
1084
1085 $download_key = $this->download_key;
1086
1087 $headers = [
1088 'Content-Type' => 'application/json',
1089 'Authorization' => 'Bearer ' . $this->api_key,
1090 'download_key' => $download_key,
1091 'download-key' => $download_key,
1092 'x-templately-ip' => Helper::get_ip(),
1093 'x-templately-url' => home_url('/'),
1094 ];
1095
1096 $args = [
1097 'headers' => $headers,
1098 ];
1099
1100
1101 if ($status === 'success') {
1102 $url = $this->get_api_url("v1", 'import/success');
1103 $args['body'] = json_encode(['type' => 'pack']);
1104 $response = wp_remote_post($url, $args);
1105 } elseif ($status === 'failed') {
1106 $url = $this->get_api_url("v1", 'import/failed');
1107 $args['body'] = json_encode(['type' => 'pack', 'description' => $description ?: "Something Went wrong....."]);
1108 $response = wp_remote_post($url, $args);
1109 }
1110
1111 Helper::log($response);
1112
1113 if (is_wp_error($response)) {
1114 // Handle error
1115 Helper::log($response->get_error_message());
1116 } else {
1117 // Handle success
1118 $body = wp_remote_retrieve_body($response);
1119 $data = json_decode($body, true);
1120 // Do something with $body
1121 return $data;
1122 }
1123
1124 return null;
1125 }
1126
1127 protected function getConnectionStatusText() {
1128 $status = connection_status();
1129 switch ($status) {
1130 case CONNECTION_NORMAL:
1131 return "Normal";
1132 case CONNECTION_ABORTED:
1133 return "Aborted";
1134 case CONNECTION_TIMEOUT:
1135 return "Timeout";
1136 default:
1137 return "Unknown";
1138 }
1139 }
1140
1141 protected function get_post_type_label_by_slug($slug) {
1142 $post_type_obj = get_post_type_object($slug);
1143 if ($post_type_obj) {
1144 return $post_type_obj->label;
1145 }
1146 return null;
1147 }
1148
1149 public function import_info() {
1150
1151 $platform = isset($_GET['platform']) ? $_GET['platform'] : 'elementor';
1152 $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1153
1154 $response = wp_remote_get($this->info_get_api_url($id), [
1155 'timeout' => 30,
1156 'headers' => [
1157 'Authorization' => 'Bearer ' . $this->api_key,
1158 'x-templately-ip' => Helper::get_ip(),
1159 'x-templately-url' => home_url('/'),
1160 'x-templately-version' => TEMPLATELY_VERSION,
1161 ]
1162 ]);
1163
1164 if (is_wp_error($response)) {
1165 wp_send_json_error($response->get_error_message());
1166 return;
1167 }
1168 // If the response code is not 200, return the error message
1169 if (wp_remote_retrieve_response_code($response) != 200) {
1170 wp_send_json_error(json_decode(wp_remote_retrieve_body($response)), wp_remote_retrieve_response_code($response));
1171 return;
1172 }
1173 // If the response body is JSON and it contains an error, return the error message
1174 // Retrieve Data from Response Body.
1175 $body = wp_remote_retrieve_body($response);
1176 $data = json_decode($body, true);
1177
1178 if (isset($data['error'])) {
1179 wp_send_json_error($data['error']);
1180 return;
1181 }
1182
1183 if (isset($data['data']['manifest'])) {
1184 $data['data']['manifest'] = json_decode($data['data']['manifest'], true);
1185 }
1186 if (isset($data['data']['settings'])) {
1187 $data['data']['settings'] = json_decode($data['data']['settings'], true);
1188 }
1189
1190 // Return the response body
1191 wp_send_json($data);
1192 }
1193
1194 public function update_imported_list($type, $id) {
1195 $imported_list = get_option('templately_fsi_imported_list', []);
1196 $imported_list[$type][] = $id;
1197 update_option('templately_fsi_imported_list', $imported_list);
1198 }
1199
1200 /**
1201 *
1202 *
1203 * @return void
1204 */
1205 protected function add_revert_hooks() {
1206 add_action('wp_insert_post', function ($post_id) {
1207 $this->update_imported_list('posts', $post_id);
1208 });
1209 add_action('add_attachment', function ($post_id) {
1210 $this->update_imported_list('attachment', $post_id);
1211 });
1212 add_action('created_term', function ($term_id, $tt_id, $taxonomy, $args) {
1213 $this->update_imported_list('term', [$term_id, $taxonomy]);
1214 }, 10, 4);
1215 add_action('registered_taxonomy', function ($taxonomy, $object_type, $taxonomy_object) {
1216 $this->update_imported_list('taxonomy', $taxonomy);
1217 }, 10, 3);
1218 add_action('fluentform/form_imported', function ($formId){
1219 $this->update_imported_list('fluentform', $formId);
1220 }, 10, 1);
1221 }
1222
1223 public static function has_revert(){
1224 $options = Utils::get_backup_options();
1225 $imported_list = get_option('templately_fsi_imported_list', []);
1226 if(!empty($options) || !empty($imported_list)){
1227 return true;
1228 }
1229 return false;
1230 }
1231
1232 public function import_revert() {
1233
1234 // // Get the nonce value from the request (usually from $_POST or $_GET)
1235 // $received_nonce = isset($_REQUEST['_wpnonce']) ? $_REQUEST['_wpnonce'] : '';
1236
1237 // // Verify the nonce using wp_verify_nonce()
1238 // $verified = wp_verify_nonce($received_nonce, 'templately_pack_import_revert_nonce');
1239
1240 // if (!$verified) {
1241 // wp_send_json_error("Nonce not verified.");
1242 // }
1243
1244 $option_active = null;
1245 $options_deleted = false;
1246 $imported_list_deleted = false;
1247 $options = Utils::get_backup_options();
1248 $status_args = [ 'post_type' => 'templately_library' ];
1249 $all_post_url = admin_url(add_query_arg( $status_args, 'edit.php' ));
1250 // wp_send_json_success([$options]);
1251
1252 if(class_exists('Elementor\Plugin')){
1253 $kits_manager = Plugin::$instance->kits_manager;
1254 $option_active = $kits_manager::OPTION_ACTIVE;
1255 $kit = $kits_manager->get_active_kit();
1256
1257 if ( ! $kit->get_id() ) {
1258 $kit = $kits_manager->create_default();
1259 update_option( $kits_manager::OPTION_ACTIVE, $kit );
1260 }
1261 }
1262
1263
1264 if (!empty($options) && is_array($options)) {
1265 foreach ($options as $key => $value) {
1266 if ('stylesheet' === $key) {
1267 if (get_option('stylesheet') !== $value) {
1268 switch_theme($value);
1269 }
1270 } else if($option_active === $key && class_exists('Elementor\Plugin')) {
1271 $kits_manager->revert( (int) $kits_manager->get_active_id(), (int) $value, 0 );
1272 $kit = $kits_manager->get_active_kit();
1273 $settings = $kit->get_data('settings');
1274 if ( isset( $settings['site_logo'] ) ) {
1275 set_theme_mod( 'custom_logo', $settings['site_logo']['id'] );
1276 }
1277 } else {
1278 update_option($key, $value);
1279 }
1280 delete_option("__templately_$key");
1281 $options_deleted = true;
1282 }
1283 }
1284
1285 $imported_list = get_option('templately_fsi_imported_list', []);
1286 if (!empty($imported_list) && is_array($imported_list)) {
1287 $_GET['force_delete_kit'] = 1; // Fallback GET Ready!
1288 foreach ($imported_list as $type => $list) {
1289 if (empty($list) || !is_array($list)) {
1290 continue;
1291 }
1292 // Loop through each item ID and delete it
1293 foreach ($list as $key => $item_id) {
1294 switch ($type) {
1295 case 'posts':
1296 // making sure default kit don't get deleted.
1297 if($option_active && isset($options[$option_active]) && $options[$option_active] == $item_id){
1298 break;
1299 }
1300 wp_delete_post($item_id, true); // Set true for permanent deletion
1301 break;
1302 case 'attachment':
1303 wp_delete_attachment($item_id, true); // Set true for permanent deletion
1304 break;
1305 case 'term':
1306 list($term_id, $taxonomy) = $item_id;
1307 wp_delete_term($term_id, $taxonomy); // Use corresponding taxonomy
1308 break;
1309 case 'taxonomy':
1310 // Taxonomies cannot be directly deleted. Consider de-registering it.
1311 break;
1312 case 'fluentform':
1313 if(class_exists('\FluentForm\App\Models\Form')){
1314 \FluentForm\App\Models\Form::remove($item_id);
1315 }
1316 break;
1317 }
1318 }
1319 }
1320
1321 $imported_list_deleted = true;
1322 delete_option('templately_fsi_imported_list');
1323 }
1324
1325
1326 if($options_deleted || $imported_list_deleted){
1327 sleep(5);
1328 wp_send_json_success([ 'options' => $options_deleted, 'imported_list' => $imported_list_deleted, 'site_url' => home_url(), 'redirect' => $all_post_url ]);
1329 }
1330
1331 wp_send_json_error([ 'options' => $options_deleted, 'imported_list' => $imported_list_deleted, 'site_url' => home_url() ]);
1332 }
1333
1334 public function google_font() {
1335 $result = get_transient('templately-google-fonts');
1336
1337 if (false == $result) {
1338 $response = wp_remote_get($this->get_api_url('v2', 'google-font'), [
1339 'timeout' => 30,
1340 'headers' => [
1341 'Authorization' => 'Bearer ' . $this->api_key,
1342 'x-templately-ip' => Helper::get_ip(),
1343 'x-templately-url' => home_url( '/' ),
1344 'x-templately-version' => TEMPLATELY_VERSION,
1345 ]
1346 ]);
1347
1348 if (is_wp_error($response)) {
1349 wp_send_json_error($response->get_error_message());
1350 }
1351
1352 if (wp_remote_retrieve_response_code($response) != 200) {
1353 wp_send_json_error('API request failed with response code ' . wp_remote_retrieve_response_code($response), wp_remote_retrieve_response_code($response));
1354 }
1355
1356 $body = wp_remote_retrieve_body($response);
1357 $data = json_decode($body, true);
1358
1359 if (!isset($data['status']) || $data['status'] !== 'success') {
1360 wp_send_json_error('API response indicates failure.');
1361 }
1362
1363 if (!isset($data['data'])) {
1364 wp_send_json_error('API response missing data.');
1365 }
1366
1367 $result = $data['data'];
1368 set_transient('templately-google-fonts', $result, DAY_IN_SECONDS);
1369 }
1370
1371 wp_send_json_success($result);
1372 }
1373
1374
1375 }
1376