PluginProbe
Elementor Website Builder – more than just a page builder / 3.11.2
Elementor Website Builder – more than just a page builder v3.11.2
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 / processes / import.php

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

722 lines 21.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\App\Modules\ImportExport\Processes;
4
5 use Elementor\App\Modules\ImportExport\Compatibility\Base_Adapter;
6 use Elementor\App\Modules\ImportExport\Compatibility\Envato;
7 use Elementor\App\Modules\ImportExport\Compatibility\Kit_Library;
8 use Elementor\App\Modules\ImportExport\Utils;
9 use Elementor\Core\Base\Document;
10 use Elementor\Plugin;
11
12 use Elementor\App\Modules\ImportExport\Runners\Import\Elementor_Content;
13 use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
14 use Elementor\App\Modules\ImportExport\Runners\Import\Plugins;
15 use Elementor\App\Modules\ImportExport\Runners\Import\Site_Settings;
16 use Elementor\App\Modules\ImportExport\Runners\Import\Taxonomies;
17 use Elementor\App\Modules\ImportExport\Runners\Import\Templates;
18 use Elementor\App\Modules\ImportExport\Runners\Import\Wp_Content;
19 use Elementor\App\Modules\ImportExport\Module;
20
21 class Import {
22 const MANIFEST_ERROR_KEY = 'manifest-error';
23
24 const ZIP_FILE_ERROR_KEY = 'zip-file-error';
25
26 /**
27 * @var Import_Runner_Base[]
28 */
29 protected $runners = [];
30
31 /**
32 * The session ID of the import process.
33 * This ID is uniquely generated for each import process (by the temp folder which contains the extracted kit files).
34 *
35 * @var string
36 */
37 private $session_id;
38
39 /**
40 * Adapter for the kit compatibility.
41 *
42 * @var Base_Adapter[]
43 */
44 private $adapters;
45
46 /**
47 * Document's elements that imported during the process.
48 *
49 * @var array
50 */
51 private $documents_elements = [];
52
53 /**
54 * Path to the extracted kit files.
55 *
56 * @var string
57 */
58 private $extracted_directory_path;
59
60 /**
61 * Imported kit manifest.
62 *
63 * @var array
64 */
65 private $manifest;
66
67 /**
68 * Imported kit site settings. (e.g: custom_colors, custom_typography, etc.)
69 *
70 * @var array
71 */
72 private $site_settings;
73
74 /**
75 * Selected content types to import.
76 *
77 * @var array
78 */
79 private $settings_include;
80
81 /**
82 * Referer of the import. (e.g: kit-library, local, etc.)
83 *
84 * @var string
85 */
86 private $settings_referrer;
87
88 /**
89 * All the conflict between the exited templates and the kit templates.
90 *
91 * @var array
92 */
93 private $settings_conflicts;
94
95 /**
96 * Selected elementor templates conditions to override.
97 *
98 * @var array
99 */
100 private $settings_selected_override_conditions;
101
102 /**
103 * Selected custom post types to import.
104 *
105 * @var array
106 */
107 private $settings_selected_custom_post_types;
108
109 /**
110 * Selected plugins to import.
111 *
112 * @var array
113 */
114 private $settings_selected_plugins;
115
116 /**
117 * The imported data output.
118 *
119 * @var array
120 */
121 private $imported_data = [];
122
123 /**
124 * The metadata output of the import runners.
125 * Will be saved in the import_session and will be used to revert the import process.
126 *
127 * @var array
128 */
129 private $runners_import_metadata = [];
130
131 /**
132 * @param string $path session_id | zip_file_path
133 * @param array $settings Use to determine which content to import.
134 * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.)
135 * @param array|null $old_instance An array of old instance parameters that will be used for creating new instance.
136 * We are using it for quick creation of the instance when the import process is being split into chunks.
137 * @throws \Exception
138 */
139 public function __construct( string $path, array $settings = [], array $old_instance = null ) {
140 if ( ! empty( $old_instance ) ) {
141 $this->set_import_object( $old_instance );
142 } else {
143 if ( is_file( $path ) ) {
144 $this->extracted_directory_path = $this->extract_zip( $path );
145 } else {
146 $elementor_tmp_directory = Plugin::$instance->uploads_manager->get_temp_dir();
147 $path = $elementor_tmp_directory . basename( $path );
148
149 if ( ! is_dir( $path ) ) {
150 throw new \Exception( 'Couldn’t execute the import process because the import session does not exist.' );
151 }
152
153 $this->extracted_directory_path = $path . '/';
154 }
155
156 $this->session_id = basename( $this->extracted_directory_path );
157 $this->settings_referrer = ! empty( $settings['referrer'] ) ? $settings['referrer'] : 'local';
158 $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null;
159
160 // Using isset and not empty is important since empty array is valid option.
161 $this->settings_selected_override_conditions = $settings['overrideConditions'] ?? null;
162 $this->settings_selected_custom_post_types = $settings['selectedCustomPostTypes'] ?? null;
163 $this->settings_selected_plugins = $settings['plugins'] ?? null;
164
165 $this->manifest = $this->read_manifest_json();
166 $this->site_settings = $this->read_site_settings_json();
167
168 $this->set_default_settings();
169 }
170 }
171
172 /**
173 * Set the import object parameters.
174 *
175 * @param array $instance
176 * @return void
177 */
178 private function set_import_object( array $instance ) {
179 $this->session_id = $instance['session_id'];
180
181 $instance_data = $instance['instance_data'];
182
183 $this->extracted_directory_path = $instance_data['extracted_directory_path'];
184 $this->runners = $instance_data['runners'];
185 $this->adapters = $instance_data['adapters'];
186
187 $this->manifest = $instance_data['manifest'];
188 $this->site_settings = $instance_data['site_settings'];
189
190 $this->settings_include = $instance_data['settings_include'];
191 $this->settings_referrer = $instance_data['settings_referrer'];
192 $this->settings_conflicts = $instance_data['settings_conflicts'];
193 $this->settings_selected_override_conditions = $instance_data['settings_selected_override_conditions'];
194 $this->settings_selected_custom_post_types = $instance_data['settings_selected_custom_post_types'];
195 $this->settings_selected_plugins = $instance_data['settings_selected_plugins'];
196
197 $this->documents_elements = $instance_data['documents_elements'];
198 $this->imported_data = $instance_data['imported_data'];
199 $this->runners_import_metadata = $instance_data['runners_import_metadata'];
200 }
201
202 /**
203 * Creating a new instance of the import process by the id of the old import session.
204 *
205 * @param string $session_id
206 *
207 * @return Import
208 * @throws \Exception
209 */
210 public static function from_session( string $session_id ): Import {
211 $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS );
212
213 if ( ! $import_sessions || ! isset( $import_sessions[ $session_id ] ) ) {
214 throw new \Exception( 'Couldn’t execute the import process because the import session does not exist.' );
215 }
216
217 $import_session = $import_sessions[ $session_id ];
218
219 return new self( $session_id, [], $import_session );
220 }
221
222 /**
223 * Register a runner.
224 * Be aware that the runner will be executed in the order of registration, the order is crucial for the import process.
225 *
226 * @param Import_Runner_Base $runner_instance
227 */
228 public function register( Import_Runner_Base $runner_instance ) {
229 $this->runners[ $runner_instance::get_name() ] = $runner_instance;
230 }
231
232 public function register_default_runners() {
233 $this->register( new Site_Settings() );
234 $this->register( new Plugins() );
235 $this->register( new Templates() );
236 $this->register( new Taxonomies() );
237 $this->register( new Elementor_Content() );
238 $this->register( new Wp_Content() );
239 }
240
241 /**
242 * Set default settings for the import.
243 */
244 private function set_default_settings() {
245 if ( ! is_array( $this->get_settings_include() ) ) {
246 $this->settings_include( $this->get_default_settings_include() );
247 }
248
249 if ( ! is_array( $this->get_settings_conflicts() ) ) {
250 $this->settings_conflicts( $this->get_default_settings_conflicts() );
251 }
252
253 if ( ! is_array( $this->get_settings_selected_override_conditions() ) ) {
254 $this->settings_selected_override_conditions( $this->get_default_settings_override_conditions() );
255 }
256
257 if ( ! is_array( $this->get_settings_selected_custom_post_types() ) ) {
258 $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() );
259 }
260
261 if ( ! is_array( $this->get_settings_selected_plugins() ) ) {
262 $this->settings_selected_plugins( $this->get_default_settings_plugins() );
263 }
264 }
265
266 /**
267 * Execute the import process.
268 *
269 * @return array The imported data output.
270 *
271 * @throws \Exception If no import runners have been specified.
272 */
273 public function run() {
274 if ( empty( $this->runners ) ) {
275 throw new \Exception( 'Couldn’t execute the import process because no import runners have been specified. Try again by specifying import runners.' );
276 }
277
278 $data = [
279 'session_id' => $this->session_id,
280 'include' => $this->settings_include,
281 'manifest' => $this->manifest,
282 'site_settings' => $this->site_settings,
283 'selected_plugins' => $this->settings_selected_plugins,
284 'extracted_directory_path' => $this->extracted_directory_path,
285 'selected_custom_post_types' => $this->settings_selected_custom_post_types,
286 ];
287
288 $this->init_import_session();
289
290 add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 );
291
292 // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads.
293 Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
294
295 foreach ( $this->runners as $runner ) {
296 if ( $runner->should_import( $data ) ) {
297 $import = $runner->import( $data, $this->imported_data );
298 $this->imported_data = array_merge_recursive( $this->imported_data, $import );
299
300 $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata();
301 }
302 }
303
304 // After the upload complete, set the elementor upload state back to false.
305 Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
306
307 remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 );
308
309 $this->finalize_import_session_option();
310
311 $this->save_elements_of_imported_posts();
312
313 Plugin::$instance->uploads_manager->remove_file_or_dir( $this->extracted_directory_path );
314 return $this->imported_data;
315 }
316
317 /**
318 * Run specific runner by runner_name
319 *
320 * @param string $runner_name
321 *
322 * @return array
323 *
324 * @throws \Exception If no export runners have been specified.
325 */
326 public function run_runner( string $runner_name ): array {
327 if ( empty( $this->runners ) ) {
328 throw new \Exception( 'Couldn’t execute the import process because no import runners have been specified. Try again by specifying import runners.' );
329 }
330
331 $data = [
332 'session_id' => $this->session_id,
333 'include' => $this->settings_include,
334 'manifest' => $this->manifest,
335 'site_settings' => $this->site_settings,
336 'selected_plugins' => $this->settings_selected_plugins,
337 'extracted_directory_path' => $this->extracted_directory_path,
338 'selected_custom_post_types' => $this->settings_selected_custom_post_types,
339 ];
340
341 add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 );
342
343 // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads.
344 Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
345
346 $runner = $this->runners[ $runner_name ];
347
348 if ( empty( $runner ) ) {
349 throw new \Exception( 'Couldn’t execute the import process because the import runner was not found. Try again by specifying an import runner.' );
350 }
351
352 if ( $runner->should_import( $data ) ) {
353 $import = $runner->import( $data, $this->imported_data );
354 $this->imported_data = array_merge_recursive( $this->imported_data, $import );
355
356 $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata();
357 }
358
359 // After the upload complete, set the elementor upload state back to false.
360 Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
361
362 remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 );
363
364 $is_last_runner = key( array_slice( $this->runners, -1, 1, true ) ) === $runner_name;
365 if ( $is_last_runner ) {
366 $this->finalize_import_session_option();
367 $this->save_elements_of_imported_posts();
368 } else {
369 $this->update_instance_data_in_import_session_option();
370 }
371
372 return [
373 'status' => 'success',
374 'runner' => $runner_name,
375 ];
376 }
377
378 /**
379 * Create and save all the instance data to the import sessions option.
380 *
381 * @return void
382 */
383 public function init_import_session( $save_instance_data = false ) {
384 $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS );
385
386 $import_sessions[ $this->session_id ] = [
387 'session_id' => $this->session_id,
388 'kit_title' => $this->manifest['title'] ?? '',
389 'kit_name' => $this->manifest['name'] ?? '',
390 'kit_thumbnail' => $this->manifest['thumbnail'] ?? '',
391 'kit_source' => $this->settings_referrer,
392 'user_id' => get_current_user_id(),
393 'start_timestamp' => current_time( 'timestamp' ),
394 ];
395
396 if ( $save_instance_data ) {
397 $import_sessions[ $this->session_id ]['instance_data'] = [
398 'extracted_directory_path' => $this->extracted_directory_path,
399 'runners' => $this->runners,
400 'adapters' => $this->adapters,
401
402 'manifest' => $this->manifest,
403 'site_settings' => $this->site_settings,
404
405 'settings_include' => $this->settings_include,
406 'settings_referrer' => $this->settings_referrer,
407 'settings_conflicts' => $this->settings_conflicts,
408 'settings_selected_override_conditions' => $this->settings_selected_override_conditions,
409 'settings_selected_custom_post_types' => $this->settings_selected_custom_post_types,
410 'settings_selected_plugins' => $this->settings_selected_plugins,
411
412 'documents_elements' => $this->documents_elements,
413 'imported_data' => $this->imported_data,
414 'runners_import_metadata' => $this->runners_import_metadata,
415 ];
416 }
417
418 update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false );
419 }
420
421 public function get_runners_name(): array {
422 return array_keys( $this->runners );
423 }
424
425 public function get_manifest() {
426 return $this->manifest;
427 }
428
429 public function get_extracted_directory_path() {
430 return $this->extracted_directory_path;
431 }
432
433 public function get_session_id() {
434 return $this->session_id;
435 }
436
437 public function get_adapters() {
438 return $this->adapters;
439 }
440
441 public function get_imported_data() {
442 return $this->imported_data;
443 }
444
445 /**
446 * Get settings by key.
447 * Used for backward compatibility.
448 *
449 * @param string $key The key of the setting.
450 */
451 public function get_settings( $key ) {
452 switch ( $key ) {
453 case 'include':
454 return $this->get_settings_include();
455
456 case 'overrideConditions':
457 return $this->get_settings_selected_override_conditions();
458
459 case 'selectedCustomPostTypes':
460 return $this->get_settings_selected_custom_post_types();
461
462 case 'plugins':
463 return $this->get_settings_selected_plugins();
464
465 default:
466 return [];
467 }
468 }
469
470 public function settings_include( array $settings_include ) {
471 $this->settings_include = $settings_include;
472
473 return $this;
474 }
475
476 public function get_settings_include() {
477 return $this->settings_include;
478 }
479
480 public function settings_referrer( $settings_referrer ) {
481 $this->settings_referrer = $settings_referrer;
482
483 return $this;
484 }
485
486 public function get_settings_referrer() {
487 return $this->settings_referrer;
488 }
489
490 public function settings_conflicts( array $settings_conflicts ) {
491 $this->settings_conflicts = $settings_conflicts;
492
493 return $this;
494 }
495
496 public function get_settings_conflicts() {
497 return $this->settings_conflicts;
498 }
499
500 public function settings_selected_override_conditions( array $settings_selected_override_conditions ) {
501 $this->settings_selected_override_conditions = $settings_selected_override_conditions;
502
503 return $this;
504 }
505
506 public function get_settings_selected_override_conditions() {
507 return $this->settings_selected_override_conditions;
508 }
509
510 public function settings_selected_custom_post_types( array $settings_selected_custom_post_types ) {
511 $this->settings_selected_custom_post_types = $settings_selected_custom_post_types;
512
513 return $this;
514 }
515
516 public function get_settings_selected_custom_post_types() {
517 return $this->settings_selected_custom_post_types;
518 }
519
520 public function settings_selected_plugins( array $settings_selected_plugins ) {
521 $this->settings_selected_plugins = $settings_selected_plugins;
522
523 return $this;
524 }
525
526 public function get_settings_selected_plugins() {
527 return $this->settings_selected_plugins;
528 }
529
530 /**
531 * Prevent saving elements on elementor post creation.
532 *
533 * @param array $data
534 * @param Document $document
535 *
536 * @return array
537 */
538 public function prevent_saving_elements_on_post_creation( array $data, Document $document ) {
539 if ( isset( $data['elements'] ) ) {
540 $this->documents_elements[ $document->get_main_id() ] = $data['elements'];
541
542 $data['elements'] = [];
543 }
544
545 return $data;
546 }
547
548 /**
549 * Extract the zip file.
550 *
551 * @param string $zip_path The path to the zip file.
552 * @return string The extracted directory path.
553 */
554 private function extract_zip( $zip_path ) {
555 $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $zip_path, [ 'json', 'xml' ] );
556
557 if ( is_wp_error( $extraction_result ) ) {
558 throw new \Error( static::ZIP_FILE_ERROR_KEY );
559 }
560
561 return $extraction_result['extraction_directory'];
562 }
563
564 /**
565 * Get the manifest file from the extracted directory and adapt it if needed.
566 *
567 * @return string The manifest file content.
568 */
569 private function read_manifest_json() {
570 $manifest = Utils::read_json_file( $this->extracted_directory_path . 'manifest' );
571
572 if ( ! $manifest ) {
573 throw new \Error( static::MANIFEST_ERROR_KEY );
574 }
575
576 $this->init_adapters( $manifest );
577
578 foreach ( $this->adapters as $adapter ) {
579 $manifest = $adapter->adapt_manifest( $manifest );
580 }
581
582 return $manifest;
583 }
584
585 /**
586 * Init the adapters and determine which ones to use.
587 *
588 * @param array $manifest_data The manifest file content.
589 */
590 private function init_adapters( array $manifest_data ) {
591 $this->adapters = [];
592
593 /** @var Base_Adapter[] $adapter_types */
594 $adapter_types = [ Envato::class, Kit_Library::class ];
595
596 foreach ( $adapter_types as $adapter_type ) {
597 if ( $adapter_type::is_compatibility_needed( $manifest_data, [ 'referrer' => $this->get_settings_referrer() ] ) ) {
598 $this->adapters[] = new $adapter_type( $this );
599 }
600 }
601 }
602
603 /**
604 * Get the site settings file from the extracted directory and adapt it if needed.
605 *
606 * @return string The site settings file content.
607 */
608 private function read_site_settings_json() {
609 $site_settings = Utils::read_json_file( $this->extracted_directory_path . 'site-settings' );
610
611 foreach ( $this->adapters as $adapter ) {
612 $site_settings = $adapter->adapt_site_settings( $site_settings, $this->manifest, $this->extracted_directory_path );
613 }
614
615 return $site_settings;
616 }
617
618 /**
619 * Get all the custom post types in the kit.
620 *
621 * @return array Custom post types names.
622 */
623 private function get_default_settings_custom_post_types() {
624 if ( empty( $this->manifest['custom-post-type-title'] ) ) {
625 return [];
626 }
627
628 $manifest_post_types = array_keys( $this->manifest['custom-post-type-title'] );
629
630 return array_diff( $manifest_post_types, Utils::get_builtin_wp_post_types() );
631 }
632
633 /**
634 * Get the default settings of elementor templates conditions to override.
635 *
636 * @return array
637 */
638 private function get_default_settings_conflicts() {
639 if ( empty( $this->manifest['templates'] ) ) {
640 return [];
641 }
642
643 return apply_filters( 'elementor/import/get_default_settings_conflicts', [], $this->manifest['templates'] );
644 }
645
646 /**
647 * Get the default settings of elementor templates conditions to override.
648 *
649 * @return array
650 */
651 private function get_default_settings_override_conditions() {
652 if ( empty( $this->settings_conflicts ) ) {
653 return [];
654 }
655
656 return array_keys( $this->settings_conflicts );
657 }
658
659 /**
660 * Get the default settings of the plugins that should be imported.
661 *
662 * @return array
663 */
664 private function get_default_settings_plugins() {
665 return ! empty( $this->manifest['plugins'] ) ? $this->manifest['plugins'] : [];
666 }
667
668 /**
669 * Get the default settings of which content types should be imported.
670 *
671 * @return array
672 */
673 private function get_default_settings_include() {
674 return [ 'templates', 'plugins', 'content', 'settings' ];
675 }
676
677 /**
678 * Get the data that requires updating/replacement when imported.
679 *
680 * @return array{post_ids: array, term_ids: array}
681 */
682 private function get_imported_data_replacements() : array {
683 return [
684 'post_ids' => Utils::map_old_new_post_ids( $this->imported_data ),
685 'term_ids' => Utils::map_old_new_term_ids( $this->imported_data ),
686 ];
687 }
688
689 /**
690 * Save the prevented elements on elementor post creation elements.
691 * Handle the replacement of all the dynamic content of the elements that probably have been changed during the import.
692 */
693 private function save_elements_of_imported_posts() {
694 foreach ( $this->documents_elements as $new_id => $document_elements ) {
695 $document = Plugin::$instance->documents->get( $new_id );
696 $updated_elements = $document->on_import_update_dynamic_content( $document_elements, $this->get_imported_data_replacements() );
697 $document->save( [ 'elements' => $updated_elements ] );
698 }
699 }
700
701 private function update_instance_data_in_import_session_option() {
702 $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS );
703
704 $import_sessions[ $this->session_id ]['instance_data']['documents_elements'] = $this->documents_elements;
705 $import_sessions[ $this->session_id ]['instance_data']['imported_data'] = $this->imported_data;
706 $import_sessions[ $this->session_id ]['instance_data']['runners_import_metadata'] = $this->runners_import_metadata;
707
708 update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false );
709 }
710
711 private function finalize_import_session_option() {
712 $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS );
713
714 unset( $import_sessions[ $this->session_id ]['instance_data'] );
715
716 $import_sessions[ $this->session_id ]['end_timestamp'] = current_time( 'timestamp' );
717 $import_sessions[ $this->session_id ]['runners'] = $this->runners_import_metadata;
718
719 update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false );
720 }
721 }
722