* Import and Export data.
* @package Core\Portability
* Handles the portability workflow.
* @package ET\Core\Portability
class ET_Core_Portability {
* @var ET_Core_Data_Utils
* Whether or not an import is in progress.
protected static $_doing_import = false;
* @param string $context Portability context previously registered.
public function __construct( $context ) {
$this->instance = et_core_cache_get( $context, 'et_core_portability' );
self::$_ = ET_Core_Data_Utils::instance();
if ( $this->instance && $this->instance->view ) {
if ( et_core_is_fb_enabled() ) {
add_action( 'admin_footer', array( $this, 'modal' ) );
add_action( 'customize_controls_print_footer_scripts', array( $this, 'modal' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'assets' ), 5 );
public static function doing_import() {
return self::$_doing_import;
* Import a previously exported layout.
* @since 3.10 Return the result of the import instead of dieing.
* @param string $file_context Accepts 'upload', 'sideload'. Default 'upload'.
public function import( $file_context = 'upload' ) {
$this->prevent_failure();
self::$_doing_import = true;
$timestamp = $this->get_timestamp();
$filesystem = $this->set_filesystem();
$temp_file_id = sanitize_file_name( $timestamp );
$temp_file = $this->has_temp_file( $temp_file_id, 'et_core_import' );
$include_global_presets = isset( $_POST['include_global_presets'] ) ? wp_validate_boolean( $_POST['include_global_presets'] ) : false;
$import = json_decode( $filesystem->get_contents( $temp_file ), true );
if ( ! isset( $_FILES['file']['name'] ) || ! et_()->ends_with( sanitize_file_name( $_FILES['file']['name'] ), '.json' ) ) {
return array( 'message' => 'invalideFile' );
if ( ! in_array( $file_context, array( 'upload', 'sideload' ) ) ) {
$file_context = 'upload';
$handle_file = "wp_handle_{$file_context}";
$upload = $handle_file( $_FILES['file'], array(
* Fires before an uploaded Portability JSON file is processed.
* @param string $file The absolute path to the uploaded JSON file's temporary location.
do_action( 'et_core_portability_import_file', $upload['file'] );
$temp_file = $this->temp_file( $temp_file_id, 'et_core_import', $upload['file'] );
$import = json_decode( $filesystem->get_contents( $temp_file ), true );
$import = $this->validate( $import );
$import['data'] = $this->apply_query( $import['data'], 'set' );
if ( ! isset( $import['context'] ) || ( isset( $import['context'] ) && $import['context'] !== $this->instance->context ) ) {
$this->delete_temp_files( 'et_core_import' );
return array( 'message' => 'importContextFail' );
$filesystem->put_contents( $upload['file'], wp_json_encode( (array) $import ) );
// Upload images and replace current urls.
if ( isset( $import['images'] ) ) {
$images = $this->maybe_paginate_images( (array) $import['images'], 'upload_images', $timestamp );
$import['data'] = $this->replace_images_urls( $images, $import['data'] );
$success = array( 'timestamp' => $timestamp );
$this->delete_temp_files( 'et_core_import' );
if ( 'options' === $this->instance->type ) {
// Reset all data besides excluded data.
$current_data = $this->apply_query( get_option( $this->instance->target, array() ), 'unset' );
if ( isset( $data['wp_custom_css'] ) && function_exists( 'wp_update_custom_css_post' ) ) {
wp_update_custom_css_post( $data['wp_custom_css'] );
if ( 'yes' === get_theme_mod( 'et_pb_css_synced', 'no' ) ) {
// If synced, clear the legacy custom css value to avoid unwanted merging of old and new css.
$data[ "{$shortname}_custom_css" ] = '';
// Merge remaining current data with new data and update options.
update_option( $this->instance->target, array_merge( $current_data, $data ) );
set_theme_mod( 'et_pb_css_synced', 'no' );
// Pass the post content and let js save the post.
if ( 'post' === $this->instance->type ) {
$success['postContent'] = reset( $data );
do_shortcode( $success['postContent'] );
$success['migrations'] = ET_Builder_Module_Settings_Migration::$migrated;
$success['presets'] = isset( $import['presets'] ) && is_array( $import['presets'] ) ? $import['presets'] : (object) array();
if ( 'post_type' === $this->instance->type ) {
$preset_rewrite_map = array();
if ( ! empty( $import['presets'] ) && $include_global_presets ) {
$preset_rewrite_map = $this->prepare_to_import_layout_presets( $import['presets'] );
$global_presets = $import['presets'];
foreach ( $data as &$post ) {
$shortcode_object = et_fb_process_shortcode( $post['post_content'] );
if ( ! empty( $import['presets'] ) ) {
if ( $include_global_presets ) {
$this->rewrite_module_preset_ids( $shortcode_object, $import['presets'], $preset_rewrite_map );
$this->apply_global_presets( $shortcode_object, $import['presets'] );
$post_content = et_fb_process_to_shortcode( $shortcode_object, array(), '', false );
// Add slashes for post content to avoid unwanted unslashing (by wp_unslash) while post is inserting.
$post['post_content'] = wp_slash( $post_content );
// Upload thumbnail image if exist.
if ( ! empty( $post['post_meta'] ) && ! empty( $post['post_meta']['_thumbnail_id'] ) ) {
$post_thumbnail_origin_id = (int) $post['post_meta']['_thumbnail_id'][0];
if ( ! empty( $import['thumbnails'] ) && ! empty( $import['thumbnails'][ $post_thumbnail_origin_id ] ) ) {
$post_thumbnail_new = $this->upload_images( $import['thumbnails'][ $post_thumbnail_origin_id ] );
$new_thumbnail_data = reset( $post_thumbnail_new );
// New thumbnail image was uploaded and it should be updated.
if ( isset( $new_thumbnail_data['replacement_id'] ) ) {
$new_thumbnail_id = $new_thumbnail_data['replacement_id'];
$post['thumbnail'] = $new_thumbnail_id;
if ( ! function_exists( 'wp_crop_image' ) ) {
include ABSPATH . 'wp-admin/includes/image.php';
$thumbnail_path = get_attached_file( $new_thumbnail_id );
// Generate all the image sizes and update thumbnail metadata.
$new_metadata = wp_generate_attachment_metadata( $new_thumbnail_id, $thumbnail_path );
wp_update_attachment_metadata( $new_thumbnail_id, $new_metadata );
if ( ! $this->import_posts( $data ) ) {
* Filters the error message when {@see ET_Core_Portability::import()} fails.
* @param mixed $error_message Default is `null`.
if ( $error_message = apply_filters( 'et_core_portability_import_error_message', false ) ) {
$error_message = array( 'message' => $error_message );
if ( ! empty( $global_presets ) ) {
if ( ! $this->import_global_presets( $global_presets ) ) {
if ( $error_message = apply_filters( 'et_core_portability_import_error_message', false ) ) {
$error_message = array( 'message' => $error_message );
public function export( $return = false ) {
$this->prevent_failure();
et_core_nonce_verified_previously();
$timestamp = $this->get_timestamp();
$filesystem = $this->set_filesystem();
$temp_file_id = sanitize_file_name( $timestamp );
$temp_file = $this->has_temp_file( $temp_file_id, 'et_core_export' );
$apply_global_presets = isset( $_POST['apply_global_presets'] ) ? wp_validate_boolean( $_POST['apply_global_presets'] ) : false;
$file_data = json_decode( $filesystem->get_contents( $temp_file ) );
$data = (array) $file_data->data;
$global_presets = $file_data->presets;
$temp_file = $this->temp_file( $temp_file_id, 'et_core_export' );
if ( 'options' === $this->instance->type ) {
$data = get_option( $this->instance->target, array() );
// Export the Customizer "Additional CSS" value as well.
if ( function_exists( 'wp_get_custom_css' ) ) {
$data[ 'wp_custom_css' ] = wp_get_custom_css();
if ( 'post' === $this->instance->type ) {
if ( ! ( isset( $_POST['post'] ) || isset( $_POST['content'] ) ) ) {
$fields_validatation = array(
// no post_content as the default case for no fields_validation will run it through perms based wp_kses_post, which is exactly what we want.
'post_content' => stripcslashes( $_POST['content'] ), // need to run this through stripcslashes() as thats what wp_kses_post() expects.
$post_data = $this->validate( $post_data, $fields_validatation );
$data = array( $post_data['ID'] => $post_data['post_content'] );
if ( isset( $_POST['global_presets'] ) ) {
$global_presets = json_decode( stripslashes( $_POST['global_presets'] ) );
if ( 'post_type' === $this->instance->type ) {
$data = $this->export_posts_query();
$data = $this->apply_query( $data, 'set' );
if ( 'post_type' === $this->instance->type ) {
$used_global_presets = array();
'apply_global_presets' => true,
foreach ( $data as $post ) {
$shortcode_object = et_fb_process_shortcode( $post->post_content );
if ( $apply_global_presets ) {
$post->post_content = et_fb_process_to_shortcode( $shortcode_object, $options, '', false );
$used_global_presets = array_merge(
$this->get_used_global_presets( $shortcode_object, $used_global_presets ),
if ( ! empty ( $used_global_presets ) ) {
$global_presets = (object) $used_global_presets;
// put contents into file, this is temporary,
// if images get paginated, this content will be brought back out
// of a temp file in paginated request
'presets' => $global_presets,
$filesystem->put_contents( $temp_file, wp_json_encode( $file_data ) );
$thumbnails = $this->_get_thumbnail_images( $data );
$images = $this->get_data_images( $data );
'context' => $this->instance->context,
'presets' => $global_presets,
'images' => $this->maybe_paginate_images( $images, 'encode_images', $timestamp ),
'thumbnails' => $thumbnails,
// Return exported content instead of printing it
$filesystem->put_contents( $temp_file, wp_json_encode( (array) $data ) );
wp_send_json_success( array( 'timestamp' => $timestamp ) );
* Serialize a single layout post in chunks.
* @param integer $id Unique ID to represent this layout serialization.
* @param integer $post_id
* @param array $theme_builder_meta
public function serialize_layout( $id, $post_id, $content, $theme_builder_meta = array(), $chunk = 0 ) {
$this->prevent_failure();
$fields_validatation = array(
// No post_content as the default case for no fields_validation will run it through perms based wp_kses_post, which is exactly what we want.
// Need to run this through stripcslashes() as thats what wp_kses_post() expects.
'post_content' => stripcslashes( $content ),
$post_data = $this->validate( $post_data, $fields_validatation );
$data = array( $post_data['ID'] => $post_data['post_content'] );
$data = $this->apply_query( $data, 'set' );
$images = $this->get_data_images( $data );
$images = $this->chunk_images( $images, 'encode_images', $id, $chunk );
'context' => 'et_builder',
'images' => $images['images'],
'post_title' => get_post_field( 'post_title', $post_id ),
'post_type' => get_post_type( $post_id ),
'theme_builder' => $theme_builder_meta,
$chunks = $images['chunks'];
$ready = $images['ready'];
* Serialize Theme Builder templates in chunks.
* @param integer $id Unique ID to represent this theme builder serialization process.
* @param integer $step_index
public function serialize_theme_builder( $id, $step, $steps, $step_index = 0, $chunk = 0 ) {
if ( $step_index >= $steps ) {
$this->prevent_failure();
$temp_file_id = sanitize_file_name( 'et_theme_builder_' . $id );
$temp_file = $this->has_temp_file( $temp_file_id, 'et_core_export' );
$data = json_decode( $this->get_filesystem()->get_contents( $temp_file ), true );
$temp_file = $this->temp_file( $temp_file_id, 'et_core_export' );
'context' => 'et_theme_builder',
'has_default_template' => false,
'has_global_layouts' => false,
switch ( $step['type'] ) {
$header_id = $step['data']['layouts']['header']['id'];
$body_id = $step['data']['layouts']['body']['id'];
$footer_id = $step['data']['layouts']['footer']['id'];
$is_default = $step['data']['default'];
if ( 0 !== $header_id && ! current_user_can( 'edit_post', $header_id ) ) {
$step['data']['layouts']['header']['id'] = 0;
if ( 0 !== $body_id && ! current_user_can( 'edit_post', $body_id ) ) {
$step['data']['layouts']['body']['id'] = 0;
if ( 0 !== $footer_id && ! current_user_can( 'edit_post', $footer_id ) ) {
$step['data']['layouts']['footer']['id'] = 0;
$data['has_default_template'] = true;
$data['templates'][] = $step['data'];
$post_id = $step['data']['post_id'];
$is_global = $step['data']['is_global'];
if ( ! current_user_can( 'edit_post', $post_id ) ) {
if ( 0 === $chunk && isset( $data['layouts'][ $post_id ] ) ) {
// The layout is already exported.
$data['has_global_layouts'] = true;
$step_data = $this->serialize_layout(