PluginProbe
Elementor Website Builder – more than just a page builder / 3.30.0-beta1
Elementor Website Builder – more than just a page builder v3.30.0-beta1
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / app / modules / import-export / module.php

module.php in Elementor Website Builder – more than just a page builder 3.30.0-beta1, at app/modules/import-export/module.php

1,021 lines 33.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\App\Modules\ImportExport;
3
4 use Elementor\App\Modules\ImportExport\Processes\Export;
5 use Elementor\App\Modules\ImportExport\Processes\Import;
6 use Elementor\App\Modules\ImportExport\Processes\Revert;
7 use Elementor\Core\Base\Module as BaseModule;
8 use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
9 use Elementor\Core\Files\Uploads_Manager;
10 use Elementor\App\Modules\KitLibrary\Module as KitLibrary;
11 use Elementor\Modules\System_Info\Reporters\Server;
12 use Elementor\Plugin;
13 use Elementor\Tools;
14 use Elementor\Utils as ElementorUtils;
15 use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Import Export Module
23 *
24 * Responsible for initializing Elementor App functionality
25 */
26 class Module extends BaseModule {
27 const FORMAT_VERSION = '2.0';
28
29 const EXPORT_TRIGGER_KEY = 'elementor_export_kit';
30
31 const UPLOAD_TRIGGER_KEY = 'elementor_upload_kit';
32
33 const IMPORT_TRIGGER_KEY = 'elementor_import_kit';
34
35 const IMPORT_RUNNER_TRIGGER_KEY = 'elementor_import_kit__runner';
36
37 const REFERRER_KIT_LIBRARY = 'kit-library';
38
39 const REFERRER_LOCAL = 'local';
40
41 const REFERRER_CLOUD = 'cloud';
42
43 const PLUGIN_PERMISSIONS_ERROR_KEY = 'plugin-installation-permissions-error';
44
45 const KIT_LIBRARY_ERROR_KEY = 'invalid-kit-library-zip-error';
46
47 const NO_WRITE_PERMISSIONS_KEY = 'no-write-permissions';
48
49 const THIRD_PARTY_ERROR = 'third-party-error';
50
51 const DOMDOCUMENT_MISSING = 'domdocument-missing';
52
53 const OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS = 'elementor_import_sessions';
54
55 const OPTION_KEY_ELEMENTOR_REVERT_SESSIONS = 'elementor_revert_sessions';
56
57 const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = '_elementor_import_session_id';
58
59 const META_KEY_ELEMENTOR_EDIT_MODE = '_elementor_edit_mode';
60 const IMPORT_PLUGINS_ACTION = 'import-plugins';
61 const EXPORT_SOURCE_CLOUD = 'cloud';
62 const EXPORT_SOURCE_FILE = 'file';
63
64 /**
65 * Assigning the export process to a property, so we can use the process from outside the class.
66 *
67 * @var Export
68 */
69 public $export;
70
71 /**
72 * Assigning the import process to a property, so we can use the process from outside the class.
73 *
74 * @var Import
75 */
76 public $import;
77
78 /**
79 * Assigning the revert process to a property, so we can use the process from outside the class.
80 *
81 * @var Revert
82 */
83 public $revert;
84
85 /**
86 * Get name.
87 *
88 * @access public
89 *
90 * @return string
91 */
92 public function get_name() {
93 return 'import-export';
94 }
95
96 public function __construct() {
97 $this->register_actions();
98
99 if ( ElementorUtils::is_wp_cli() ) {
100 \WP_CLI::add_command( 'elementor kit', WP_CLI::class );
101 }
102
103 ( new Usage() )->register();
104
105 $this->revert = new Revert();
106 }
107
108 public function get_init_settings() {
109 if ( ! Plugin::$instance->app->is_current() ) {
110 return [];
111 }
112
113 return $this->get_config_data();
114 }
115
116 /**
117 * Register the import/export tab in elementor tools.
118 */
119 public function register_settings_tab( Tools $tools ) {
120 $tools->add_tab( 'import-export-kit', [
121 'label' => esc_html__( 'Import / Export Kit', 'elementor' ),
122 'sections' => [
123 'intro' => [
124 'label' => esc_html__( 'Template Kits', 'elementor' ),
125 'callback' => function() {
126 $this->render_import_export_tab_content();
127 },
128 'fields' => [],
129 ],
130 ],
131 ] );
132 }
133
134 /**
135 * Render the import/export tab content.
136 */
137 private function render_import_export_tab_content() {
138 $intro_text_link = sprintf( '<a href="https://go.elementor.com/wp-dash-import-export-general/" target="_blank">%s</a>', esc_html__( 'Learn more', 'elementor' ) );
139
140 $intro_text = sprintf(
141 /* translators: 1: New line break, 2: Learn more link. */
142 __( 'Design sites faster with a template kit that contains some or all components of a complete site, like templates, content & site settings.%1$sYou can import a kit and apply it to your site, or export the elements from this site to be used anywhere else. %2$s', 'elementor' ),
143 '<br>',
144 $intro_text_link
145 );
146
147 $is_cloud_kits_available = Plugin::$instance->experiments->is_feature_active( 'cloud-library' ) && KitLibrary::get_cloud_app()->is_eligible();
148
149 $content_data = [
150 'export' => [
151 'title' => esc_html__( 'Export a Template Kit', 'elementor' ),
152 'button' => [
153 'url' => Plugin::$instance->app->get_base_url() . '#/export',
154 'text' => esc_html__( 'Export', 'elementor' ),
155 ],
156 'description' => esc_html__( 'Bundle your whole site - or just some of its elements - to be used for another website.', 'elementor' ),
157 'link' => [
158 'url' => 'https://go.elementor.com/wp-dash-import-export-export-flow/',
159 'text' => esc_html__( 'Learn More', 'elementor' ),
160 ],
161 ],
162 'import' => [
163 'title' => esc_html__( 'Import a Template Kit', 'elementor' ),
164 'button' => [
165 'url' => Plugin::$instance->app->get_base_url() . '#/import',
166 'text' => $is_cloud_kits_available ? esc_html__( 'Upload .zip file', 'elementor' ) : esc_html__( 'Import', 'elementor' ),
167 ],
168 'description' => esc_html__( 'Apply the design and settings of another site to this one.', 'elementor' ),
169 'link' => [
170 'url' => 'https://go.elementor.com/wp-dash-import-export-import-flow/',
171 'text' => esc_html__( 'Learn More', 'elementor' ),
172 ],
173 ],
174 ];
175
176 if ( $is_cloud_kits_available ) {
177 $content_data['import']['button_secondary'] = [
178 'url' => Plugin::$instance->app->get_base_url() . '#/kit-library/cloud',
179 'text' => esc_html__( 'Choose from Cloud Library', 'elementor' ),
180 ];
181 }
182
183 $last_imported_kit = $this->revert->get_last_import_session();
184 $penultimate_imported_kit = $this->revert->get_penultimate_import_session();
185
186 $user_date_format = get_option( 'date_format' );
187 $user_time_format = get_option( 'time_format' );
188 $date_format = $user_date_format . ' ' . $user_time_format;
189
190 $should_show_revert_section = $this->should_show_revert_section( $last_imported_kit );
191
192 if ( $should_show_revert_section ) {
193 if ( ! empty( $penultimate_imported_kit ) ) {
194 $revert_text = sprintf(
195 esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s %3$s and revert to the site setting that came with "%4$s" on %5$s.', 'elementor' ),
196 ! empty( $last_imported_kit['kit_title'] ) ? $last_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ),
197 gmdate( $date_format, $last_imported_kit['start_timestamp'] ),
198 '<br>',
199 ! empty( $penultimate_imported_kit['kit_title'] ) ? $penultimate_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ),
200 gmdate( $date_format, $penultimate_imported_kit['start_timestamp'] )
201 );
202 } else {
203 $revert_text = sprintf(
204 esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s.%3$s Your original site settings will be restored.', 'elementor' ),
205 ! empty( $last_imported_kit['kit_title'] ) ? $last_imported_kit['kit_title'] : esc_html__( 'imported kit', 'elementor' ),
206 gmdate( $date_format, $last_imported_kit['start_timestamp'] ),
207 '<br>'
208 );
209 }
210 }
211 ?>
212
213 <div class="tab-import-export-kit__content">
214 <p class="tab-import-export-kit__info"><?php ElementorUtils::print_unescaped_internal_string( $intro_text ); ?></p>
215
216 <div class="tab-import-export-kit__wrapper">
217 <?php foreach ( $content_data as $data ) {
218 $this->print_item_content( $data );
219 } ?>
220 </div>
221
222 <?php
223 if ( $should_show_revert_section ) {
224
225 $link_attributes = [
226 'href' => $this->get_revert_href(),
227 'id' => 'elementor-import-export__revert_kit',
228 'class' => 'button',
229 ];
230 ?>
231 <div class="tab-import-export-kit__revert">
232 <h2>
233 <?php ElementorUtils::print_unescaped_internal_string( esc_html__( 'Remove the most recent Kit', 'elementor' ) ); ?>
234 </h2>
235 <p class="tab-import-export-kit__info">
236 <?php ElementorUtils::print_unescaped_internal_string( $revert_text ); ?>
237 </p>
238 <?php $this->render_last_kit_thumbnail( $last_imported_kit ); ?>
239 <a <?php ElementorUtils::print_html_attributes( $link_attributes ); ?> >
240 <?php ElementorUtils::print_unescaped_internal_string( esc_html__( 'Remove Kit', 'elementor' ) ); ?>
241 </a>
242 </div>
243 <?php } ?>
244 </div>
245 <?php
246 }
247
248 private function print_item_content( $data ) {
249 $is_cloud_kits_feature_active = Plugin::$instance->experiments->is_feature_active( 'cloud-library' );
250
251 if ( $is_cloud_kits_feature_active ) { ?>
252 <div class="tab-import-export-kit__container">
253 <div class="tab-import-export-kit__box">
254 <h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2>
255 </div>
256 <p class="description"><?php ElementorUtils::print_unescaped_internal_string( $data['description'] ); ?></p>
257 <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php ElementorUtils::print_unescaped_internal_string( $data['link']['text'] ); ?></a>
258 <div class="tab-import-export-kit__box action-buttons">
259 <?php if ( ! empty( $data['button_secondary'] ) ) : ?>
260 <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['url'] ); ?>" class="elementor-button e-btn-txt e-btn-txt-border">
261 <?php ElementorUtils::print_unescaped_internal_string( $data['button_secondary']['text'] ); ?>
262 </a>
263 <?php endif; ?>
264 <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button e-primary">
265 <?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?>
266 </a>
267 </div>
268 </div>
269 <?php } else { ?>
270 <div class="tab-import-export-kit__container">
271 <div class="tab-import-export-kit__box">
272 <h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2>
273 <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button e-primary">
274 <?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?>
275 </a>
276 </div>
277 <p><?php ElementorUtils::print_unescaped_internal_string( $data['description'] ); ?></p>
278 <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php ElementorUtils::print_unescaped_internal_string( $data['link']['text'] ); ?></a>
279 </div>
280 <?php }
281 }
282
283 private function get_revert_href(): string {
284 $admin_post_url = admin_url( 'admin-post.php?action=elementor_revert_kit' );
285 $nonced_admin_post_url = wp_nonce_url( $admin_post_url, 'elementor_revert_kit' );
286 return $this->maybe_add_referrer_param( $nonced_admin_post_url );
287 }
288
289 /**
290 * Checks if referred by a kit and adds the referrer ID to the href
291 *
292 * @param string $href
293 *
294 * @return string
295 */
296 private function maybe_add_referrer_param( string $href ): string {
297 $param_name = 'referrer_kit';
298
299 if ( empty( $_GET[ $param_name ] ) ) {
300 return $href;
301 }
302
303 return add_query_arg( $param_name, sanitize_key( $_GET[ $param_name ] ), $href );
304 }
305
306 /**
307 * Render the last kit thumbnail if exists
308 *
309 * @param $last_imported_kit
310 *
311 * @return void
312 */
313 private function render_last_kit_thumbnail( $last_imported_kit ) {
314 if ( empty( $last_imported_kit['kit_thumbnail'] ) ) {
315 return;
316 }
317
318 ?>
319 <div class="tab-import-export-kit__kit-item-row">
320 <article class="tab-import-export-kit__kit-item">
321 <header>
322 <h3>
323 <?php echo esc_html( $last_imported_kit['kit_title'] ); ?>
324 </h3>
325 </header>
326 <img
327 src="<?php echo esc_url( $last_imported_kit['kit_thumbnail'] ); ?>"
328 alt="<?php echo esc_attr( $last_imported_kit['kit_title'] ); ?>"
329 loading="lazy"
330 >
331 </article>
332 </div>
333 <?php
334 }
335
336 /**
337 * Upload a kit zip file and get the kit data.
338 *
339 * Assigning the Import process to the 'import' property,
340 * so it will be available to use in different places such as: WP_Cli, Pro, etc.
341 *
342 * @param string $file Path to the file.
343 * @param string $referrer Referrer of the file 'local' or 'kit-library'.
344 * @return array
345 * @throws \Exception
346 */
347 public function upload_kit( $file, $referrer ) {
348 $this->ensure_writing_permissions();
349
350 $this->import = new Import( $file, [ 'referrer' => $referrer ] );
351
352 return [
353 'session' => $this->import->get_session_id(),
354 'manifest' => $this->import->get_manifest(),
355 'conflicts' => $this->import->get_settings_conflicts(),
356 ];
357 }
358
359 /**
360 * Import a kit by session_id.
361 * Upload and import a kit by kit zip file.
362 *
363 * If the split_to_chunks flag is true, the process won't start
364 * It will initialize the import process and return the session_id and the runners.
365 *
366 * Assigning the Import process to the 'import' property,
367 * so it will be available to use in different places such as: WP_Cli, Pro, etc.
368 *
369 * @param string $path Path to the file or session_id.
370 * @param array $settings Settings the import use to determine which content to import.
371 * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.)
372 * @param bool $split_to_chunks Determine if the import process should be split into chunks.
373 * @return array
374 * @throws \Exception
375 */
376 public function import_kit( string $path, array $settings, bool $split_to_chunks = false ): array {
377 $this->ensure_writing_permissions();
378 $this->ensure_DOMDocument_exists();
379
380 $this->import = new Import( $path, $settings );
381 $this->import->register_default_runners();
382
383 remove_filter( 'elementor/document/save/data', [ Plugin::$instance->modules_manager->get_modules( 'content-sanitizer' ), 'sanitize_content' ] );
384 do_action( 'elementor/import-export/import-kit', $this->import );
385
386 if ( $split_to_chunks ) {
387 $this->import->init_import_session( true );
388
389 return [
390 'session' => $this->import->get_session_id(),
391 'runners' => $this->import->get_runners_name(),
392 ];
393 }
394
395 return $this->import->run();
396 }
397
398 /**
399 * Resuming import process by re-creating the import instance and running the specific runner.
400 *
401 * @param string $session_id The id off the import session.
402 * @param string $runner_name The specific runner that we want to run.
403 *
404 * @return array Two types of response.
405 * 1. The status and the runner name.
406 * 2. The imported data. (Only if the runner is the last one in the import process)
407 * @throws \Exception
408 */
409 public function import_kit_by_runner( string $session_id, string $runner_name ): array {
410 // Check session_id
411 $this->import = Import::from_session( $session_id );
412 $runners = $this->import->get_runners_name();
413
414 $run = $this->import->run_runner( $runner_name );
415
416 if ( end( $runners ) === $run['runner'] ) {
417 return $this->import->get_imported_data();
418 }
419
420 return $run;
421 }
422
423 /**
424 * Export a kit.
425 *
426 * Assigning the Export process to the 'export' property,
427 * so it will be available to use in different places such as: WP_Cli, Pro, etc.
428 *
429 * @param array $settings Settings the export use to determine which content to export.
430 * (e.g: include, kit_info, selected_plugins, selected_cpt, etc.)
431 * @return array
432 * @throws \Exception
433 */
434 public function export_kit( array $settings ) {
435 $this->ensure_writing_permissions();
436
437 $this->export = new Export( $settings );
438 $this->export->register_default_runners();
439
440 do_action( 'elementor/import-export/export-kit', $this->export );
441
442 return $this->export->run();
443 }
444
445 /**
446 * Handle revert kit ajax request.
447 */
448 public function revert_last_imported_kit() {
449 $this->revert = new Revert();
450 $this->revert->register_default_runners();
451
452 do_action( 'elementor/import-export/revert-kit', $this->revert );
453
454 $this->revert->run();
455 }
456
457
458 /**
459 * Handle revert last imported kit ajax request.
460 */
461 public function handle_revert_last_imported_kit() {
462 check_admin_referer( 'elementor_revert_kit' );
463
464 $this->revert_last_imported_kit();
465
466 wp_safe_redirect( admin_url( 'admin.php?page=' . Tools::PAGE_ID . '#tab-import-export-kit' ) );
467 die;
468 }
469
470 /**
471 * Register appropriate actions.
472 */
473 private function register_actions() {
474 add_action( 'admin_init', function() {
475 if ( wp_doing_ajax() &&
476 isset( $_POST['action'] ) &&
477 wp_verify_nonce( ElementorUtils::get_super_global_value( $_POST, '_nonce' ), Ajax::NONCE_KEY ) &&
478 current_user_can( 'manage_options' )
479 ) {
480 $this->maybe_handle_ajax();
481 }
482 } );
483
484 add_action( 'admin_post_elementor_revert_kit', [ $this, 'handle_revert_last_imported_kit' ] );
485
486 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
487
488 $page_id = Tools::PAGE_ID;
489
490 add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'register_settings_tab' ] );
491
492 // TODO 18/04/2023 : This needs to be moved to the runner itself after https://elementor.atlassian.net/browse/HTS-434 is done.
493 if ( self::IMPORT_PLUGINS_ACTION === ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_X_ELEMENTOR_ACTION' ) ) {
494 add_filter( 'woocommerce_create_pages', [ $this, 'empty_pages' ], 10, 0 );
495 }
496 // TODO ^^^
497 }
498
499 /**
500 * Prevent the creation of the default WooCommerce pages (Cart, Checkout, etc.)
501 *
502 * TODO 18/04/2023 : This needs to be moved to the runner itself after https://elementor.atlassian.net/browse/HTS-434 is done.
503 *
504 * @return array
505 */
506 public function empty_pages(): array {
507 return [];
508 }
509
510 private function ensure_writing_permissions() {
511 $server = new Server();
512
513 $paths_to_check = [
514 Server::KEY_PATH_WP_CONTENT_DIR => $server->get_system_path( Server::KEY_PATH_WP_CONTENT_DIR ),
515 Server::KEY_PATH_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_UPLOADS_DIR ),
516 Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ),
517 ];
518
519 $permissions = $server->get_paths_permissions( $paths_to_check );
520
521 // WP Content dir has to be exists and writable.
522 if ( ! $permissions[ Server::KEY_PATH_WP_CONTENT_DIR ]['write'] ) {
523 throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
524 }
525
526 // WP Uploads dir has to be exists and writable.
527 if ( ! $permissions[ Server::KEY_PATH_UPLOADS_DIR ]['write'] ) {
528 throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
529 }
530
531 // Elementor uploads dir permissions is divided to 2 cases:
532 // 1. If the dir exists, it has to be writable.
533 // 2. If the dir doesn't exist, the parent dir has to be writable (wp uploads dir), so we can create it.
534 if ( $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['exists'] && ! $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['write'] ) {
535 throw new \Error( self::NO_WRITE_PERMISSIONS_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
536 }
537 }
538
539 private function ensure_DOMDocument_exists() {
540 if ( ! class_exists( 'DOMDocument' ) ) {
541 throw new \Error( self::DOMDOCUMENT_MISSING ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
542 }
543 }
544
545 /**
546 * Enqueue admin scripts
547 */
548 public function enqueue_scripts() {
549 wp_enqueue_script(
550 'elementor-import-export-admin',
551 $this->get_js_assets_url( 'import-export-admin' ),
552 [ 'elementor-common' ],
553 ELEMENTOR_VERSION,
554 true
555 );
556
557 wp_localize_script(
558 'elementor-import-export-admin',
559 'elementorImportExport',
560 [
561 'lastImportedSession' => $this->revert->get_last_import_session(),
562 'appUrl' => Plugin::$instance->app->get_base_url() . '#/kit-library',
563 ]
564 );
565 }
566
567 /**
568 * Assign each ajax action to a method.
569 */
570 private function maybe_handle_ajax() {
571 // phpcs:ignore WordPress.Security.NonceVerification.Missing
572 $action = ElementorUtils::get_super_global_value( $_POST, 'action' );
573
574 try {
575 switch ( $action ) {
576 case static::EXPORT_TRIGGER_KEY:
577 $this->handle_export_kit();
578 break;
579
580 case static::UPLOAD_TRIGGER_KEY:
581 $this->handle_upload_kit();
582 break;
583
584 case static::IMPORT_TRIGGER_KEY:
585 $this->handle_import_kit();
586 break;
587
588 case static::IMPORT_RUNNER_TRIGGER_KEY:
589 $this->handle_import_kit__runner();
590 break;
591
592 default:
593 break;
594 }
595 } catch ( \Error $e ) {
596 if ( isset( $this->import ) ) {
597 $this->import->finalize_import_session_option();
598 }
599
600 Plugin::$instance->logger->get_logger()->error( $e->getMessage(), [
601 'meta' => [
602 'trace' => $e->getTraceAsString(),
603 ],
604 ] );
605
606 if ( isset( $this->import ) && $this->is_third_party_class( $e->getTrace()[0]['class'] ) ) {
607 wp_send_json_error( self::THIRD_PARTY_ERROR, 500 );
608 }
609
610 wp_send_json_error( $e->getMessage(), 500 );
611 }
612 }
613
614 /**
615 * Handle upload kit ajax request.
616 */
617 private function handle_upload_kit() {
618 // PHPCS - A URL that should contain special chars (auth headers information).
619 $file_url = isset( $_POST['e_import_file'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
620 ? wp_unslash( $_POST['e_import_file'] ) // phpcs:ignore
621 : '';
622 $kit_id = ElementorUtils::get_super_global_value( $_POST, 'kit_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
623
624 $is_import_from_library = ! empty( $file_url );
625 $is_import_from_cloud = isset( $_POST['source'] ) && self::REFERRER_CLOUD === ElementorUtils::get_super_global_value( $_POST, 'source' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
626
627 if ( $is_import_from_cloud ) {
628 $result = $this->handle_import_kit_from_cloud( $kit_id );
629 $file_url = $result['file_url'];
630 } elseif ( $is_import_from_library ) {
631 $result = $this->handle_import_kit_from_library( $file_url );
632 } else {
633 $result = $this->handle_import_kit_from_upload();
634 }
635
636 Plugin::$instance->logger->get_logger()->info( 'Uploading Kit: ', [
637 'meta' => [
638 'kit_id' => ElementorUtils::get_super_global_value( $_POST, 'kit_id' ), // phpcs:ignore WordPress.Security.NonceVerification.Missing
639 'referrer' => $result['referrer'],
640 ],
641 ] );
642
643 $uploaded_kit = $this->upload_kit( $result['file_name'], $result['referrer'] );
644
645 $session_dir = $uploaded_kit['session'];
646 $manifest = $uploaded_kit['manifest'];
647 $conflicts = $uploaded_kit['conflicts'];
648
649 if ( $is_import_from_cloud || $is_import_from_library ) {
650 Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $result['file_name'] ) );
651 }
652
653 if ( isset( $manifest['plugins'] ) && ! current_user_can( 'install_plugins' ) ) {
654 throw new \Error( static::PLUGIN_PERMISSIONS_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
655 }
656
657 $result = [
658 'session' => $session_dir,
659 'manifest' => $manifest,
660 ];
661
662 if ( isset( $file_url ) ) {
663 $result['file_url'] = $file_url;
664 }
665
666 if ( ! empty( $conflicts ) ) {
667 $result['conflicts'] = $conflicts;
668 } else {
669 // Moved into the IE process \Elementor\App\Modules\ImportExport\Processes\Import::get_default_settings_conflicts
670 // TODO: remove in 3.10.0
671 $result = apply_filters( 'elementor/import/stage_1/result', $result );
672 }
673
674 wp_send_json_success( $result );
675 }
676
677 protected function handle_import_kit_from_library( $file_url ) {
678 if (
679 ! wp_verify_nonce( ElementorUtils::get_super_global_value( $_POST, 'e_kit_library_nonce' ), 'kit-library-import' )
680 ) {
681 throw new \Error( 'Invalid kit library nonce.' );
682 }
683
684 if ( ! filter_var( $file_url, FILTER_VALIDATE_URL ) || 0 !== strpos( $file_url, 'http' ) ) {
685 throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
686 }
687
688 return [
689 'file_name' => $this->get_remote_kit_zip( $file_url ),
690 'referrer' => static::REFERRER_KIT_LIBRARY,
691 ];
692 }
693
694 protected function handle_import_kit_from_upload() {
695 return [
696 // PHPCS - Already validated in caller function.
697 'file_name' => ElementorUtils::get_super_global_value( $_FILES, 'e_import_file' )['tmp_name'], // phpcs:ignore WordPress.Security.NonceVerification.Missing
698 'referrer' => static::REFERRER_LOCAL,
699 ];
700 }
701
702 protected function handle_import_kit_from_cloud( $kit_id ) {
703 $kit = KitLibrary::get_cloud_app()->get_kit( [
704 'id' => $kit_id,
705 ] );
706
707 if ( is_wp_error( $kit ) ) {
708 wp_send_json_error( $kit->get_error_message() );
709 }
710
711 if ( empty( $kit['downloadUrl'] ) ) {
712 throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
713 }
714
715 return [
716 'file_name' => $this->get_remote_kit_zip( $kit['downloadUrl'] ),
717 'referrer' => static::REFERRER_CLOUD,
718 'file_url' => $kit['downloadUrl'],
719 ];
720 }
721 protected function get_remote_kit_zip( $url ) {
722 $remote_zip_request = wp_safe_remote_get( $url );
723
724 if ( is_wp_error( $remote_zip_request ) ) {
725 Plugin::$instance->logger->get_logger()->error( $remote_zip_request->get_error_message() );
726 throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
727 }
728
729 if ( 200 !== $remote_zip_request['response']['code'] ) {
730 Plugin::$instance->logger->get_logger()->error( $remote_zip_request['response']['message'] );
731 throw new \Error( static::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
732 }
733
734 return Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], 'kit.zip' );
735 }
736
737 /**
738 * Handle import kit ajax request.
739 */
740 private function handle_import_kit() {
741 // PHPCS - Already validated in caller function
742 $settings = json_decode( ElementorUtils::get_super_global_value( $_POST, 'data' ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
743 $tmp_folder_id = $settings['session'];
744
745 $import = $this->import_kit( $tmp_folder_id, $settings, true );
746
747 // get_settings_config() added manually because the frontend Ajax request doesn't trigger the get_init_settings().
748 $import['configData'] = $this->get_config_data();
749
750 Plugin::$instance->logger->get_logger()->info(
751 sprintf( 'Selected import runners: %1$s',
752 implode( ', ', $import['runners'] )
753 )
754 );
755
756 wp_send_json_success( $import );
757 }
758
759 /**
760 * Handle ajax request for running specific runner in the import kit process.
761 */
762 private function handle_import_kit__runner() {
763 // PHPCS - Already validated in caller function
764 $settings = json_decode( ElementorUtils::get_super_global_value( $_POST, 'data' ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
765 $session_id = $settings['session'];
766 $runner = $settings['runner'];
767
768 $import = $this->import_kit_by_runner( $session_id, $runner );
769
770 // get_settings_config() added manually because the frontend Ajax request doesn't trigger the get_init_settings().
771 $import['configData'] = $this->get_config_data();
772
773 if ( ! empty( $import['status'] ) ) {
774 Plugin::$instance->logger->get_logger()->info(
775 sprintf( 'Import runner completed: %1$s %2$s',
776 $import['runner'],
777 ( 'success' === $import['status'] ? '' : '' )
778 )
779 );
780 }
781
782 do_action( 'elementor/import-export/import-kit/runner/after-run', $import );
783
784 wp_send_json_success( $import );
785 }
786
787 /**
788 * Handle export kit ajax request.
789 */
790 private function handle_export_kit() {
791 // PHPCS - Already validated in caller function
792 $settings = json_decode( ElementorUtils::get_super_global_value( $_POST, 'data' ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
793 $source = $settings['kitInfo']['source'];
794
795 $export = $this->export_kit( $settings );
796
797 $file_name = $export['file_name'];
798 $file = ElementorUtils::file_get_contents( $file_name );
799
800 if ( ! $file ) {
801 throw new \Error( 'Could not read the exported file.' );
802 }
803
804 Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) );
805
806 $result = [
807 'manifest' => $export['manifest'],
808 ];
809
810 if ( self::EXPORT_SOURCE_CLOUD === $source ) {
811 $raw_screen_shot = base64_decode( substr( $settings['screenShotBlob'], strlen( 'data:image/png;base64,' ) ) );
812 $title = $export['manifest']['title'];
813 $description = $export['manifest']['description'];
814
815 $kit = KitLibrary::get_cloud_app()->create_kit(
816 $title,
817 $description,
818 $file,
819 $raw_screen_shot,
820 $settings['include'],
821 );
822
823 if ( is_wp_error( $kit ) ) {
824 wp_send_json_error( $kit );
825 }
826
827 $result['kit'] = $kit;
828 } else {
829 $result['file'] = base64_encode( $file );
830 }
831
832 wp_send_json_success( $result );
833 }
834
835 /**
836 * Get config data that will be exposed to the frontend.
837 */
838 private function get_config_data() {
839 $export_nonce = wp_create_nonce( 'elementor_export' );
840 $export_url = add_query_arg( [ '_nonce' => $export_nonce ], Plugin::$instance->app->get_base_url() );
841
842 return [
843 'exportURL' => $export_url,
844 'summaryTitles' => $this->get_summary_titles(),
845 'builtinWpPostTypes' => ImportExportUtils::get_builtin_wp_post_types(),
846 'elementorPostTypes' => ImportExportUtils::get_elementor_post_types(),
847 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(),
848 'elementorHomePageUrl' => $this->get_elementor_home_page_url(),
849 'recentlyEditedElementorPageUrl' => $this->get_recently_edited_elementor_page_url(),
850 'tools_url' => Tools::get_url(),
851 'importSessions' => Revert::get_import_sessions(),
852 'lastImportedSession' => $this->revert->get_last_import_session(),
853 'kitPreviewNonce' => wp_create_nonce( 'kit_thumbnail' ),
854 ];
855 }
856
857 /**
858 * Get labels of Elementor document types, Elementor Post types, WordPress Post types and Custom Post types.
859 */
860 private function get_summary_titles() {
861 $summary_titles = [];
862
863 $document_types = Plugin::$instance->documents->get_document_types();
864
865 foreach ( $document_types as $name => $document_type ) {
866 $summary_titles['templates'][ $name ] = [
867 'single' => $document_type::get_title(),
868 'plural' => $document_type::get_plural_title(),
869 ];
870 }
871
872 $elementor_post_types = ImportExportUtils::get_elementor_post_types();
873 $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types();
874 $post_types = array_merge( $elementor_post_types, $wp_builtin_post_types );
875
876 foreach ( $post_types as $post_type ) {
877 $post_type_object = get_post_type_object( $post_type );
878
879 $summary_titles['content'][ $post_type ] = [
880 'single' => $post_type_object->labels->singular_name ?? '',
881 'plural' => $post_type_object->label ?? '',
882 ];
883 }
884
885 $custom_post_types = ImportExportUtils::get_registered_cpt_names();
886 if ( ! empty( $custom_post_types ) ) {
887 foreach ( $custom_post_types as $custom_post_type ) {
888
889 $custom_post_types_object = get_post_type_object( $custom_post_type );
890 // CPT data appears in two arrays:
891 // 1. content object: in order to show the export summary when completed in getLabel function
892 $summary_titles['content'][ $custom_post_type ] = [
893 'single' => $custom_post_types_object->labels->singular_name ?? '',
894 'plural' => $custom_post_types_object->label ?? '',
895 ];
896
897 // 2. customPostTypes object: in order to actually export the data
898 $summary_titles['content']['customPostTypes'][ $custom_post_type ] = [
899 'single' => $custom_post_types_object->labels->singular_name ?? '',
900 'plural' => $custom_post_types_object->label ?? '',
901 ];
902 }
903 }
904
905 $active_kit = Plugin::$instance->kits_manager->get_active_kit();
906
907 foreach ( $active_kit->get_tabs() as $key => $tab ) {
908 $summary_titles['site-settings'][ $key ] = $tab->get_title();
909 }
910
911 return $summary_titles;
912 }
913
914 public function should_show_revert_section( $last_imported_kit ) {
915 if ( empty( $last_imported_kit ) ) {
916 return false;
917 }
918
919 // TODO: BC - remove in the future
920 // The 'templates' runner was in core and moved to the Pro plugin. (Part of it still exits in the Core for BC)
921 // The runner that is in the core version is missing the revert functionality,
922 // therefore we shouldn't display the revert section if the import process done with the core version.
923 $is_import_templates_ran = isset( $last_imported_kit['runners']['templates'] );
924 if ( $this->has_pro() && $is_import_templates_ran ) {
925 $has_imported_templates = ! empty( $last_imported_kit['runners']['templates'] );
926
927 return $has_imported_templates;
928 }
929
930 return true;
931 }
932
933 public function has_pro(): bool {
934 return ElementorUtils::has_pro();
935 }
936
937 private function get_elementor_editor_home_page_url() {
938 if ( 'page' !== get_option( 'show_on_front' ) ) {
939 return '';
940 }
941
942 $frontpage_id = get_option( 'page_on_front' );
943
944 return $this->get_elementor_editor_page_url( $frontpage_id );
945 }
946
947 private function get_elementor_home_page_url() {
948 if ( 'page' !== get_option( 'show_on_front' ) ) {
949 return '';
950 }
951
952 $frontpage_id = get_option( 'page_on_front' );
953
954 return $this->get_elementor_page_url( $frontpage_id );
955 }
956
957 private function get_recently_edited_elementor_page_url() {
958 $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] );
959
960 if ( ! isset( $query->post ) ) {
961 return '';
962 }
963
964 return $this->get_elementor_page_url( $query->post->ID );
965 }
966
967 private function get_recently_edited_elementor_editor_page_url() {
968 $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] );
969
970 if ( ! isset( $query->post ) ) {
971 return '';
972 }
973
974 return $this->get_elementor_editor_page_url( $query->post->ID );
975 }
976
977 private function get_elementor_document( $page_id ) {
978 $document = Plugin::$instance->documents->get( $page_id );
979
980 if ( ! $document || ! $document->is_built_with_elementor() ) {
981 return false;
982 }
983
984 return $document;
985 }
986
987 private function get_elementor_page_url( $page_id ) {
988 $document = $this->get_elementor_document( $page_id );
989
990 return $document ? $document->get_preview_url() : '';
991 }
992
993 private function get_elementor_editor_page_url( $page_id ) {
994 $document = $this->get_elementor_document( $page_id );
995
996 return $document ? $document->get_edit_url() : '';
997 }
998
999 /**
1000 * @param string $class
1001 *
1002 * @return bool
1003 */
1004 public function is_third_party_class( $class ) {
1005 $allowed_classes = [
1006 'Elementor\\',
1007 'ElementorPro\\',
1008 'WP_',
1009 'wp_',
1010 ];
1011
1012 foreach ( $allowed_classes as $allowed_class ) {
1013 if ( str_starts_with( $class, $allowed_class ) ) {
1014 return false;
1015 }
1016 }
1017
1018 return true;
1019 }
1020 }
1021