PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.2.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.2.0
3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 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.0, at includes/Core/Importer/FullSiteImport.php

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