<?php

/**
 * folder where we keep the landing pages downloaded from the cloud, relative to the wp-uploads folder
 */
defined( 'TVE_CLOUD_LP_FOLDER' ) || define( 'TVE_CLOUD_LP_FOLDER', 'tcb_lp_templates' );


/**
 * handles the communication with the landing pages API located at TCB_Landing_Page_Cloud_Templates_Api::API_URL
 *
 *
 * Class TCB_Landing_Page_Cloud_Templates_Api
 */
class TCB_Landing_Page_Cloud_Templates_Api {

	const API_KEY  = '@(#$*%)^SDFKNgjsdi870234521SADBNC#';
	const API_PASS = '!!@#ThriveIsTheBest123$$@#';

	const DEBUG_LOCAL = false;

	const API_URL         = 'http://landingpages.thrivethemes.com/cloud-api/index-api.php';
	const API_SERVICE     = 'http://service-api.thrivethemes.com/cloud-templates-api';
	const API_SERVICE_DEV = 'http://thrive.service/cloud-api/index-api.php';

	/**
	 * @var TCB_Landing_Page_Cloud_Templates_Api
	 */
	protected static $_instance = null;

	protected $received_auth_header = '';

	protected $secret_key = '@#$()%*%$^&*(#@$%@#$%93827456MASDFJIK3245';

	/**
	 * holds the last response from the API server
	 *
	 * @var array
	 */
	protected $response = [];

	/**
	 * holds the last request sent to the API server.
	 *
	 * @var array
	 */
	protected $request = [];

	/**
	 * singleton
	 *
	 * @return TCB_Landing_Page_Cloud_Templates_Api
	 */
	public static function getInstance() {
		if ( ! static::$_instance instanceof static ) {
			static::$_instance = new static();
		}

		return static::$_instance;
	}

	/**
	 * constructor - use getInstance
	 */
	protected function __construct() {

	}

	/**
	 * get the template list
	 *
	 * @return array
	 * @throws Exception if response data and success properties does not have expected values
	 *
	 * @deprecated. use self::get_template_list()
	 *
	 */
	public function getTemplateList() {
		return call_user_func( [ $this, 'get_template_list' ], func_get_args() );
	}

	/**
	 * Get a list of Landing Page templates from the cloud, filterable by using $filters
	 *
	 * @param array $filters
	 *
	 * @return mixed|void
	 * @throws Exception
	 */
	public function get_template_list( $filters = [] ) {
		$params = apply_filters( 'tcb_get_cloud_templates_default_args', array(
			'route'           => 'getAll',
			'tar_version'     => defined( 'TCB_CLOUD_DEBUG' ) && TCB_CLOUD_DEBUG ? '10' : TVE_VERSION,
			'tar_cloud_debug' => defined( 'TCB_CLOUD_DEBUG' ) && TCB_CLOUD_DEBUG ? '1' : '0',
		) );
		if ( ! empty( $filters ) ) {
			$params['filters'] = $filters; // allow filtering
		}

		$response = $this->_request( $params, 2 );
		$data     = json_decode( $response, true );

		if ( empty( $data['success'] ) ) {
			return [ 'error' => $data['error_message'] ];
		}

		if ( ! isset( $data['data'] ) ) {
			return [ 'error' => 'Could not fetch templates.' ];
		}

		$this->_validateReceivedHeader( $data );

		return $data['data'];
	}

	/**
	 * download the zip file for a template
	 *
	 * @param string|array $template If array, it must contain [key] and [uid] keys
	 *
	 * @return string the new template name (with the cloud prefix prepended)
	 *
	 * @throws Exception
	 */
	public function download( $template ) {
		$uid = '';
		if ( is_array( $template ) ) {
			$uid      = $template['uid']; // unique identifier from cloud
			$template = $template['key'];
		}
		/**
		 * first make sure we can save the downloaded template
		 */
		$upload = wp_upload_dir();
		if ( ! empty( $upload['error'] ) ) {
			throw new Exception( $upload['error'] );
		}

		/* create folders in uploads - make sure all of the required folders exist */
		$this->createFolders( $upload, $template );

		$params = [
			'route'       => 'download',
			'tar_version' => TVE_VERSION,
			'tpl'         => $template,
			'uid'         => $uid,
		];
		// LP v2 pages
		$version = 2;
		$body    = $this->_request( $params, $version );

		$control = [
			'auth' => $this->request['headers']['X-Thrive-Authenticate'],
			'tpl'  => $template,
		];

		/**
		 * this means an error -> error message is json_encoded
		 */
		if ( empty( $this->received_auth_header ) ) {
			$data = json_decode( $body, true );
			throw new Exception( isset( $data['error_message'] ) ? $data['error_message'] : 'Invalid response: ' . $body );
		}

		$this->_validateReceivedHeader( $control );

		/**
		 * at this point, $body holds the contents of the zip file
		 */
		$zip_path = trailingslashit( $upload['basedir'] ) . TVE_CLOUD_LP_FOLDER . '/' . $template . '.zip';

		tve_wp_upload_bits( $zip_path, $body );

		$transfer = new TCB_Landing_Page_Transfer();

		$result = $transfer->importFromCloud( $zip_path, $template );

		/* unlink the zip archive and the configuration file */
		@unlink( $zip_path );
		@unlink( trailingslashit( dirname( $zip_path ) ) . $template . '.json' );

		return $result;
	}

	/**
	 * send an API request
	 *
	 * @param array $params  body params to be sent in POST data
	 * @param mixed $version new LP version or TCB1.5 version (null)
	 *
	 * @return string the response body
	 *
	 * @throws Exception
	 */
	protected function _request( $params, $version = null ) {
		$params = array_merge( $params, TD_TTW_Connection::get_instance()->get_connection_data() );
		/**
		 * filter params before sending them to cloud
		 */
		$params = apply_filters( 'tcb_cloud_request_params', $params, $version );
		/* make sure each param is a string - this makes sure the encoding / decoding does not cause API validation error */
		foreach ( $params as $key => $param ) {
			if ( $param === null || ( ! is_string( $param ) && is_scalar( $param ) ) ) {
				$params[ $key ] = (string) $param;
			}
		}

		$params['pw'] = self::API_PASS;
		$headers      = array(
			'X-Thrive-Authenticate' => $this->_buildAuthString( $params ),
		);

		$this->request = [
			'headers' => $headers,
			'body'    => $params,
		];

		$url = defined( 'TCB_CLOUD_API_LOCAL' ) && TCB_CLOUD_API_LOCAL ? TCB_CLOUD_API_LOCAL : self::API_SERVICE;

		$url = add_query_arg( array(
			'p' => $this->calc_hash( $params ),
		), $url );

		if ( ! empty( $version ) ) {
			$url = add_query_arg( [
				'v' => $version,
			], $url );
		}

		$response = tve_dash_api_remote_post( $url, [
			'headers' => $headers,
			'body'    => $params,
		] );

		if ( $response instanceof WP_Error ) {
			throw new Exception( $response->get_error_message() );
		}

		$this->received_auth_header = wp_remote_retrieve_header( $response, 'X-Thrive-Authenticate' );
		/**
		 * also try lowercased, just in case this will be dependent of server
		 */
		if ( empty( $this->received_auth_header ) ) {
			$this->received_auth_header = wp_remote_retrieve_header( $response, 'x-thrive-authenticate' );
		}

		$this->response = $response;

		return wp_remote_retrieve_body( $response );
	}

	/**
	 * build an authentication string, from each of the POST (or $data) values, concatenated with self::API_KEY between '|'
	 *
	 * @param array $data
	 *
	 * @return string md5 hash of the string above
	 */
	protected function _buildAuthString( $data = null ) {
		$string = '';

		if ( null === $data ) {
			$data = $_POST;
		}

		foreach ( $data as $field => $value ) {
			if ( is_array( $value ) ) {
				$value = serialize( $value );
			}
			$string .= $field . '=' . $value;
			$string .= '|' . self::API_KEY . '|';
		}

		return md5( $string );
	}

	/**
	 * Calculates the has that signs the requests for service.thrivethemes.com
	 *
	 * @param $data
	 *
	 * @return string
	 */
	public function calc_hash( $data ) {
		return md5( $this->secret_key . serialize( $data ) . $this->secret_key );
	}

	/**
	 * validate the received header containing authentication data
	 *
	 *
	 * @param array $data
	 *
	 * @throws Exception
	 */
	protected function _validateReceivedHeader( $data ) {
		if ( $this->received_auth_header != $this->_buildAuthString( $data ) ) {
			throw new Exception( 'Invalid data received from the API' );
		}
	}

	/**
	 * create all the necessary folders inside a tcb_lp_templates folder in the wp-uploads dir
	 * required folders are:
	 *
	 * - js
	 * - lightboxes
	 * - menu
	 * - templates
	 *            - css
	 *                 - fonts
	 *                 - images
	 *            - thumbnails
	 *
	 *
	 * @param array  $wp_upload
	 * @param string $template_key
	 */
	public function createFolders( $wp_upload, $template_key ) {
		$folder_list = [
			'js',
			'lightboxes',
			'menu',
			'templates',
			'templates/css',
			'templates/css/fonts',
			'templates/css/fonts/' . $template_key,
			'templates/css/images',
			'templates/thumbnails',
		];

		/**
		 * this filter would allow adding some extra custom folders (if needed in the future)
		 *
		 */
		$folder_list = apply_filters( 'tcb_lp_extra_folders_create', $folder_list );

		$base = trailingslashit( $wp_upload['basedir'] ) . TVE_CLOUD_LP_FOLDER . '/';

		foreach ( $folder_list as $folder ) {
			if ( false === wp_mkdir_p( $base . $folder ) ) {
				throw new Exception( 'Could not create the templates folder' );
			}
		}
	}
}

/**
 * Import / Export functionality for landing pages
 *
 */
class TCB_Landing_Page_Transfer {
	/**
	 * holds ids of the exported lightboxes, to make sure we don't create an infinite loop
	 *
	 * @var array
	 */
	private $exported_lightboxes = [];

	/**
	 * flag that will be true when the icon pack needs to be included in the export
	 *
	 * @var bool
	 */
	private $export_icon_pack = false;

	/**
	 * holds the current WP Page (or landing page) that will be replaced with the imported landing page, OR the one that is being exported right now
	 *
	 * @var WP_Post
	 */
	private $current_page = null;

	/**
	 *
	 * @var array wordpress uploads folder
	 */
	private $wp_upload_dir = null;

	/**
	 * hold a copy of the config array for the import process
	 *
	 * @var array
	 */
	private $import_config = [];

	/**
	 * Zip data
	 *
	 * @var string
	 */
	protected $cfg_name = 'lp.json';
	protected $html_file_name = 'lp.html';
	protected $archive_prefix = 'tve-lp-';
	protected $thumbnail_prefix = 'lp-';

	/**
	 * Whether LP vars should be replaced by their values on export
	 */
	protected $allow_lp_vars = false;

	/**
	 * @var array
	 */
	public $image_map;
	public $export_symbol_ids;

	public function __construct() {
		$this->image_map         = [];
		$this->export_symbol_ids = [];
	}

	/**
	 * get the wp upload dir info
	 *
	 * @return array
	 *
	 * @throws Exception
	 */
	protected function getUploadDir() {
		if ( ! is_null( $this->wp_upload_dir ) ) {
			return $this->wp_upload_dir;
		}

		$this->wp_upload_dir = wp_upload_dir();

		if ( ! empty( $this->wp_upload_dir['error'] ) ) {
			throw new Exception( sprintf( __( 'Could not determine uploads folder (%s)', 'thrive-cb' ), $this->wp_upload_dir['error'] ) );
		}

		return $this->wp_upload_dir;
	}

	/**
	 * Get symbol IDs that should be exported
	 *
	 * @param $content
	 *
	 * @return void
	 */
	public function parse_content_symbols( $content ) {
		preg_match_all( TCB_Symbol_Template::SYMBOL_CONFIG_REGEX, $content, $matches );
		if ( ! empty( $matches ) && count( $matches ) === 2 ) {
			$this->export_symbol_ids = array_merge( $this->export_symbol_ids, $matches[1] );
		}

		preg_match_all( TCB_Symbol_Template::SYMBOL_SHORTCODE_REGEX, $content, $matches );
		if ( ! empty( $matches ) && count( $matches ) === 2 ) {
			$this->export_symbol_ids = array_merge( $this->export_symbol_ids, $matches[1] );
		}

		$this->export_symbol_ids = array_unique( $this->export_symbol_ids );
	}

	/**
	 * Export lp sections - header + footer and maybe top & bottom theme sections
	 *
	 * @param $page_id
	 *
	 * @return mixed|void
	 */
	public function export_sections( $page_id ) {
		$sections = [];

		foreach ( [ 'header', 'footer' ] as $section ) {
			$key = "_tve_$section";

			$section_id = (int) get_post_meta( $page_id, $key, true );

			if ( ! empty( $section_id ) ) {
				$symbol = TCB_Symbol_Template::get_instance( $section_id )->export();
				add_filter( 'tve_should_minify_css', '__return_false' );
				$symbol['meta_input']['tve_custom_css'] = tve_prepare_global_variables_for_front( $symbol['meta_input']['tve_custom_css'], true );
				remove_filter( 'tve_should_minify_css', '__return_false' );
				$this->parse_content_symbols( $symbol['meta_input']['tve_updated_post'] );

				$symbol = json_encode( $symbol );

				$symbol = static::replace_ids_on_export( $symbol );

				$this->exportParseImages( $symbol );

				$sections [ $section ] = json_decode( $symbol, true );
			}
		}

		/**
		 * Filter sections to export for the lp
		 *
		 * @param array $sections
		 * @param int   $page_id
		 * @param TCB_Landing_Page_Transfer
		 *
		 * @return array
		 */
		return apply_filters( 'tcb_lp_sections_export', $sections, $page_id, $this );
	}


	/**
	 * Export symbols used in the content
	 *
	 * @param $page_id
	 *
	 * @return mixed|void
	 */
	public function export_symbols( $page_id ) {
		$symbols = [];

		foreach ( $this->export_symbol_ids as $symbol_id ) {
			$symbol = TCB_Symbol_Template::get_instance( $symbol_id )->export();
			add_filter( 'tve_should_minify_css', '__return_false' );
			$symbol['meta_input']['tve_custom_css'] = tve_prepare_global_variables_for_front( $symbol['meta_input']['tve_custom_css'], true );
			remove_filter( 'tve_should_minify_css', '__return_false' );
			$symbol = json_encode( $symbol );

			$symbol = static::replace_ids_on_export( $symbol );

			$this->exportParseImages( $symbol );
			$symbols [ $symbol_id ] = json_decode( $symbol, true );
		}


		/**
		 * Filter sections to export for the lp
		 *
		 * @param array $sections
		 * @param int   $page_id
		 * @param TCB_Landing_Page_Transfer
		 *
		 * @return array
		 */
		return apply_filters( 'tcb_lp_symbols_export', $symbols, $page_id, $this );
	}


	/**
	 * create a zip file containing the landing page export
	 *
	 * @param int    $page_id
	 * @param string $template_name
	 * @param int    $thumbnail_attachment_id the id of the attachment to use as a thumbnail
	 * @param bool   $return_data             whether or not to just return the generated data or archive it in a zip file
	 *
	 * @return mixed|array with information about the zip file:
	 *      (
	 *          'path' => full path to the zip file
	 *          'url' => web-accessible url to the zip file
	 *      )
	 *
	 * @throws Exception
	 *
	 */
	public function export( $page_id, $template_name, $thumbnail_attachment_id = 0, $return_data = false ) {
		/* first, some general checks */
		if ( ! class_exists( 'ZipArchive' ) ) {
			throw new Exception( __( 'The PHP ZipArchive extension must be enabled in order to use this functionality. Please contact your hosting provider.', 'thrive-cb' ) );
		}

		$template_name = sanitize_title( $template_name );

		/* build the meta-values config for the landing page */
		$config = $this->getTCBMeta( $page_id );

		$config['cloud_template'] = tve_is_cloud_template( $config['tve_landing_page'] );

		$config['tve_version']            = TVE_VERSION; /* just to be able to validate imports later on, if we'll need some kind of compatibility checks */
		$config['tve_original_post_type'] = get_post_type( $page_id ); /* just in case we need it to prevent imports to other post types */

		$page_content = apply_filters( 'tcb.export_page_content', $config['tve_updated_post'], $page_id );
		unset( $config['tve_updated_post'] ); // we do not need these in the config file

		$page_content = static::replace_ids_on_export( $page_content );

		$this->parse_content_symbols( $page_content );

		/* parse and replace all image urls (both from img src and background: url(...) */
		$this->exportImageMap( $page_content, $config );

		$config['sections'] = $this->export_sections( $page_id );

		/* handle any possible included lightboxes */
		$config['lightbox'] = [];

		/* include any lightbox that's setup as a page-level event */
		if ( ! empty( $config['tve_page_events'] ) ) {
			foreach ( $config['tve_page_events'] as $event ) {
				$this->exportLightboxFromEvent( $event, $config );
			}
		}

		/* parse the page contents to include any lightbox that's setup inside the html contents, e.g. from a link / button */
		$this->exportParseLightboxes( $page_content, $config );

		if ( $return_data ) {
			return [
				'page_meta'         => $config,
				'page_content'      => $page_content,
				'image_map'         => $this->image_map,
				'export_symbol_ids' => $this->export_symbol_ids,
			];
		}

		if ( ! empty( $this->export_symbol_ids ) ) {
			$config['symbols'] = $this->export_symbols( $page_id );
		}

		$uploads_dir = $this->getUploadDir();

		$zip_filename = $this->archive_prefix . $template_name . '.zip';

		@unlink( $uploads_dir['path'] . $zip_filename );

		WP_Filesystem();

		$old_umask = umask( 0 );
		$zip       = new ZipArchive();
		if ( $zip->open( trailingslashit( $uploads_dir['path'] ) . $zip_filename, ZipArchive::CREATE ) !== true ) {
			throw new Exception( 'Could not create zip archive' );
		}

		/* add images directly to the zip archive, in the images/ folder */
		$zip->addEmptyDir( 'images' );
		foreach ( $this->image_map as $key => $info ) {
			$config['image_map'][ $key ] = $info['name'];
			$zip->addFile( $info['path'], 'images/' . $key . '.img_file' );
		}

		/* check if the icon pack needs to be exported and include it in the archive file */
		$this->exportIconPack( $zip, $config );

		/* check if there is any font added through the Font Import Manager */
		$this->exportFontPack( $zip, $config );

		/* add lightbox template html to the lightboxes/ folder */
		if ( ! empty( $config['lightbox'] ) ) {
			$zip->addEmptyDir( 'lightboxes' );
			foreach ( $config['lightbox'] as $id => $data ) {
				$zip->addFromString( 'lightboxes/' . $id . '.html', $data['tve_updated_post'] );
				unset( $config['lightbox'][ $id ]['tve_updated_post'] );
				/* any extra icon_packs that might be included in the lightbox */
				$this->exportExtraIconPacks( $zip, $config['lightbox'][ $id ] );

				$this->exportFontPack( $zip, $config['lightbox'][ $id ] );
			}
		}

		/* add the font_pack config to the configuration array, if it has been included */
		$config['font_pack'] = isset( $this->exported_font_pack ) ? $this->exported_font_pack : [];

		/* add the contents of the page, in a separate file */
		$zip->addFromString( $this->html_file_name, $page_content );

		/* it's possible that this landing page or some of it's lightboxes have some extra icon packs (added from previous imports) */
		$this->exportExtraIconPacks( $zip, $config );

		/* also include the thumbnail for the landing page, if any */
		if ( ! empty( $thumbnail_attachment_id ) ) {
			$this->exportThumbnail( $thumbnail_attachment_id, $zip, $config );
		} else {
			$exported = $this->exportGeneratedThumbnail( $page_id, $zip );

			if ( $exported ) {
				$config['thumbnail'] = $this->thumbnail_prefix . $page_id . '.png';
			}

		}

		/* add the json config file */
		$zip->addFromString( $this->cfg_name, json_encode( $config ) );

		if ( ! @$zip->close() ) {
			throw new Exception( 'Could not write the zip file' );
		}

		umask( $old_umask );

		return array(
			'path' => trailingslashit( $uploads_dir['path'] ) . $zip_filename,
			'url'  => trailingslashit( $uploads_dir['url'] ) . $zip_filename,
		);
	}

	/**
	 * also include the thumbnail chosen by the user, if empty => the default LP thumbnail will be used
	 *
	 * @param int        $thumbnail_attachment_id
	 * @param ZipArchive $zip
	 * @param array      $config
	 */
	protected function exportThumbnail( $thumbnail_attachment_id, ZipArchive $zip, &$config ) {
		if ( empty( $thumbnail_attachment_id ) || ! wp_attachment_is_image( $thumbnail_attachment_id ) ) {
			return;
		}

		$file = get_attached_file( $thumbnail_attachment_id, true );
		if ( ! is_file( $file ) ) {
			return;
		}

		$thumb_name = basename( $file );

		$zip->addEmptyDir( 'thumbnail' );
		if ( false === $zip->addFile( $file, 'thumbnail/' . $thumb_name ) ) {
			$zip->deleteName( 'thumbnail' );

			return;
		}

		$config['thumbnail'] = $thumb_name;

	}

	/**
	 * Get the generated preview of the LP(it's previously done on save)
	 *
	 * @param $page_id
	 * @param $zip
	 *
	 * @return false
	 */
	protected function exportGeneratedThumbnail( $page_id, $zip ) {
		$upload = tve_filter_landing_page_preview_location( wp_upload_dir() );
		$path   = $upload['path'] . '/' . $this->thumbnail_prefix . $page_id . '.png';

		$zip->addEmptyDir( 'thumbnail' );

		if ( is_file( $path ) && $zip->addFile( $path, 'thumbnail/' . $this->thumbnail_prefix . $page_id . '.png' ) === false ) {
			$zip->deleteName( 'thumbnail' );

			return false;
		}

		return true;
	}

	/**
	 *
	 * parse the HTML content, search for local images (stored on the server) AND images used in inline styles, retrieve the image paths and copy them to the $dest folder for each encountered image
	 * it will also replace the image srcs in HTML to a list of keys
	 *
	 * @param string $content
	 */
	public function exportParseImages( &$content ) {
		$site_url = str_replace( [ 'http://', 'https://', '//' ], '', site_url() );

		/* regular expression that finds image links that point to the current server */
		$image_regexp = '#(http:|https:)?(\\\?/\\\?/)(' . preg_quote( $site_url, '#' ) . ')([^ "\']+?)(\.[png|svg|webp|gif|jpg|jpeg]+)#is';

		if ( preg_match_all( $image_regexp, $content, $matches ) ) {
			foreach ( $matches[4] as $index => $src ) {

				$img_path = $src . $matches[5][ $index ];

				$img_src    = $matches[3][ $index ] . $img_path;
				$no_qstring = explode( '?', $img_src );
				$img_src    = $no_qstring[0];

				$unslashed_img_src = wp_unslash( $img_src );

				$full_image_path = untrailingslashit( ABSPATH ) . $img_path;
				if ( ! file_exists( $full_image_path ) ) {
					continue;
				}

				$replacement = md5( $unslashed_img_src );

				$protocol = ( empty( $matches[1][ $index ] ) ? '' : $matches[1][ $index ] ) . $matches[2][ $index ];

				$this->image_map[ $replacement ] = array(
					'name' => basename( $img_src ),
					'path' => $full_image_path,
					'url'  => wp_unslash( $protocol ) . $unslashed_img_src,
				);

				$content = str_replace( [
					'https:' . $matches[0][ $index ],
					'http:' . $matches[0][ $index ],
					$matches[0][ $index ],
				], '{{img=' . $replacement . '}}', $content );
			}
		}

		/* return the current image map so the function can be used as a standalone function without the export one */

		return $this->image_map;
	}

	/**
	 * get all the meta fields used by TCB for a page / lightbox
	 *
	 * @param int $post_id
	 *
	 * @return array
	 */
	protected function getTCBMeta( $post_id ) {
		$config                = [];
		$non_lp_dependent_keys = [ 'tve_landing_page', 'tve_disable_theme_dependency', TCB_LP_Palettes::LP_PALETTES, TCB_LP_Palettes::LP_PALETTES_CONFIG ];

		foreach ( tve_get_used_meta_keys() as $key ) {
			$config[ $key ] = in_array( $key, $non_lp_dependent_keys ) ? get_post_meta( $post_id, $key, true ) : tve_get_post_meta( $post_id, $key );
		}

		unset( $config['tve_content_before_more'], $config['tve_save_post'] );

		if ( ! empty( $config['thrive_icon_pack'] ) ) {
			$this->export_icon_pack = true;
		}

		$post                 = get_post( $post_id );
		$config['post_title'] = $post->post_title;
		if ( apply_filters( 'tcb_allow_landing_page_set_data', true ) ) {
			$this->prepare_global_data_for_export( $config );
		}

		return apply_filters( 'tcb_lp_transfer_meta', $config, $post_id );
	}

	/**
	 * replace all image links from $post_content and both global configuration (e.g. page background, lightbox background)
	 *
	 * @param string $post_content
	 * @param array  $config
	 */
	protected function exportImageMap( &$post_content, &$config ) {
		$this->exportParseImages( $post_content );

		/* global setting for page background - pattern */
		if ( ! empty( $config['tve_globals']['lp_bgp'] ) ) {
			$this->exportParseImages( $config['tve_globals']['lp_bgp'] );
		}
		/* global setting for page background - image */
		if ( ! empty( $config['tve_globals']['lp_bgi'] ) ) {
			$this->exportParseImages( $config['tve_globals']['lp_bgi'] );
		}

		/* lightbox - global setting - content background */
		if ( ! empty( $config['tve_globals']['l_cimg'] ) ) {
			$this->exportParseImages( $config['tve_globals']['l_cimg'] );
			unset( $config['tve_globals']['l_cpat'] );
		} elseif ( ! empty( $config['tve_globals']['l_cpat'] ) ) {
			$this->exportParseImages( $config['tve_globals']['l_cpat'] );
		}

		/**
		 * User custom CSS styles - background images
		 */
		if ( ! empty( $config['tve_custom_css'] ) ) {
			$this->exportParseImages( $config['tve_custom_css'] );
		}
	}

	/**
	 * parse the contents and include in the export file any lightboxes that are used in event managers for individual elements
	 *
	 * @param string $content
	 * @param array  $config
	 */
	protected function exportParseLightboxes( $content, &$config ) {
		$events = $this->getRelevantEvents( $content );

		foreach ( $events as $event ) {
			$this->exportLightboxFromEvent( $event, $config );
		}
	}

	/**
	 * parses $content for any events registered from the TCB event manager that have as actions "thrive_lightbox"
	 * and retrieves an array of all such events
	 *
	 * @param string $content
	 *
	 * @return array array of registered events
	 */
	protected function getRelevantEvents( $content ) {
		/* bail early if no event is defined */
		if ( strpos( $content, '__TCB_EVENT_' ) === false ) {
			return [];
		}

		$event_pattern = "#data-tcb-events=('|\")__TCB_EVENT_(.+?)_TNEVE_BCT__('|\")#";

		if ( ! preg_match_all( $event_pattern, $content, $matches ) ) {
			return [];
		}
		$events = [];

		foreach ( $matches[2] as $i => $str ) {
			$str = htmlspecialchars_decode( $str ); // the actual matched regexp group
			if ( ! ( $elem_events = json_decode( $str, true ) ) ) {
				$elem_events = [];
			}
			if ( empty( $elem_events ) ) {
				continue;
			}
			foreach ( $elem_events as $event ) {
				if ( empty( $event ) || $event['a'] !== 'thrive_lightbox' ) {
					continue;
				}
				$events [] = $event;
			}
		}

		return $events;
	}

	/**
	 * add a lightbox to the exported landing page
	 *
	 * @param array $event
	 * @param array $config
	 */
	protected function exportLightboxFromEvent( $event, &$config ) {
		if ( empty( $event ) || ! is_array( $event ) || empty( $event['a'] ) || $event['a'] !== 'thrive_lightbox' ) {
			return;
		}

		if ( empty( $event['config'] ) || empty( $event['config']['l_id'] ) ) {
			return;
		}

		$lb_id = $event['config']['l_id'];

		if ( isset( $this->exported_lightboxes[ $lb_id ] ) || get_post_type( $lb_id ) !== 'tcb_lightbox' ) {
			return;
		}

		$this->exported_lightboxes[ $lb_id ] = true;

		if ( empty( $config['lightbox'][ $lb_id ] ) ) {

			$lb_config                    = $this->getTCBMeta( $lb_id );
			$lb_config['tve_lp_lightbox'] = get_post_meta( $lb_id, 'tve_lp_lightbox', true );

			// parse images
			$this->exportImageMap( $lb_config['tve_updated_post'], $lb_config );

			/* also handle the "lightbox in lightbox" cases */
			$this->exportParseLightboxes( $lb_config['tve_updated_post'], $config );

			$config['lightbox'][ $lb_id ] = $lb_config;
		}
	}

	/**
	 * check if the export archive needs to contain the icon pack also and include the css and fonts for the icon pack
	 *
	 * @param ZipArchive $zip
	 * @param array      $config
	 */
	public function exportIconPack( ZipArchive $zip, &$config ) {
		if ( ! $this->export_icon_pack ) {
			return;
		}

		$thrive_icon_pack = get_option( 'thrive_icon_pack' );
		if ( empty( $thrive_icon_pack ) ) {
			return;
		}

		if ( ! is_dir( $thrive_icon_pack['folder'] ) || ! is_file( trailingslashit( $thrive_icon_pack['folder'] ) . 'style.css' ) ) {
			return;
		}

		$zip->addEmptyDir( 'icon-pack' );
		$zip->addFile( trailingslashit( $thrive_icon_pack['folder'] ) . 'style.css', 'icon-pack/style.css' );

		/* add all font files */
		$zip->addEmptyDir( 'icon-pack/fonts' );

		/**
		 * TCB-1683 php < 5.3 does not have support for ZipArchive::addGlob()
		 */
		$icon_files = glob( trailingslashit( $thrive_icon_pack['folder'] ) . 'fonts/*', GLOB_BRACE );
		foreach ( $icon_files as $file ) {
			$zip->addFile( $file, 'icon-pack/fonts/' . basename( $file ) );
		}

		$config['icon_pack'] = [
			'font-family' => $thrive_icon_pack['fontFamily'],
			'icons'       => $thrive_icon_pack['icons'],
		];
	}

	/**
	 *
	 * if this is a previously imported landing page, it can have any number of extra icon packs associated. we need to include those in the export archive
	 *
	 * export any extra icon packs that might be associated with this landing page
	 * there can by any number of icon packs
	 *
	 * @param ZipArchive $zip
	 * @param array      $config
	 */
	public function exportExtraIconPacks( ZipArchive $zip, &$config ) {
		if ( empty( $config['tve_globals']['extra_icons'] ) || empty( $config['tve_globals']['used_icon_packs'] ) ) {
			return;
		}

		$used_icon_packs = $config['tve_globals']['used_icon_packs'];

		foreach ( $config['tve_globals']['extra_icons'] as $i => $icon_pack ) {
			if ( empty( $icon_pack ) || ! in_array( $icon_pack['font-family'], $used_icon_packs ) ) {
				unset( $config['tve_globals']['extra_icons'][ $i ] );
				continue;
			}
			if ( ! is_dir( $icon_pack['folder'] ) || ! is_file( trailingslashit( $icon_pack['folder'] ) . 'style.css' ) ) {
				unset( $config['tve_globals']['extra_icons'][ $i ] );
				continue;
			}
			$icon_zip_folder = $icon_pack['font-family'];
			$zip->addEmptyDir( $icon_zip_folder );
			$zip->addFile( trailingslashit( $icon_pack['folder'] ) . 'style.css', $icon_zip_folder . '/style.css' );

			$zip->addEmptyDir( $icon_zip_folder . '/fonts' );

			/**
			 * TCB-1683 php < 5.3 does not have support for ZipArchive::addGlob()
			 */
			$icon_files = glob( trailingslashit( $icon_pack['folder'] ) . 'fonts/*', GLOB_BRACE );
			foreach ( $icon_files as $icon_file ) {
				$zip->addFile( $icon_file, $icon_zip_folder . '/fonts/' . basename( $icon_file ) );
			}

			unset( $config['tve_globals']['extra_icons'][ $i ]['folder'], $config['tve_globals']['extra_icons'][ $i ]['css'] );
		}

		$config['tve_globals']['extra_icons'] = array_values( $config['tve_globals']['extra_icons'] );

	}

	/**
	 * check if the export archive needs to contain the custom font pack, or any user-defined fonts
	 *
	 * @param ZipArchive $zip
	 * @param array      $config
	 */
	public function exportFontPack( ZipArchive $zip, &$config ) {
		if ( empty( $config['thrive_tcb_post_fonts'] ) && empty( $config['tve_globals']['extra_fonts'] ) ) {
			return;
		}

		/* grab all the used fonts */
		$used_font_classes = isset( $config['tve_globals']['font_cls'] ) ? $config['tve_globals']['font_cls'] : [];
		$included          = [];

		$all_fonts = $this->getThriveFonts();

		foreach ( $used_font_classes as $font_class ) {
			$font_index = $this->findFontByProps( [ 'font_class' => $font_class ], $all_fonts, [ 'font_class' ] );
			if ( false === $font_index ) {
				continue;
			}
			$font = $all_fonts[ $font_index ];
			if ( Tve_Dash_Font_Import_Manager::isImportedFont( $font['font_name'] ) ) {
				$font['imported'] = true;
				$export_font_pack = true; // export the files included in the font pack
			}
			$included [] = $font;
		}

		$config['thrive_tcb_post_fonts'] = $included;

		if ( isset( $export_font_pack ) && ! isset( $this->exported_font_pack ) ) {
			$this->exported_font_pack = [];

			$font_pack = $this->getImportedFontPack();

			if ( ! empty( $font_pack ) && ! empty( $font_pack['folder'] ) && ! empty( $font_pack['css_file'] ) ) {
				$css_file = basename( $font_pack['css_file'] );

				$this->exportAddFontToZip( $zip, $font_pack['folder'], $font_pack['css_file'] );

				$this->exported_font_pack = array(
					'name' => basename( $font_pack['folder'] ),
					'css'  => $css_file,
				);
			}
		}

		/* finally, check for any extra fonts (that might exist from previous imports of landing pages)*/
		if ( empty( $config['tve_globals']['extra_fonts'] ) ) {
			return;
		}

		foreach ( $config['tve_globals']['extra_fonts'] as $i => $font ) {
			if ( ! empty( $font['ignore'] ) ) { // if not used, do not include it
				unset( $config['tve_globals']['extra_fonts'][ $i ] );
				continue;
			}

			$this->exportAddFontToZip( $zip, $font['font_folder'], basename( $font['font_url'] ) );
			$config['tve_globals']['extra_fonts'][ $i ]['font_folder'] = basename( $font['font_folder'] );

		}

		$config['tve_globals']['extra_fonts'] = array_values( $config['tve_globals']['extra_fonts'] );

	}

	/**
	 * add a font folder to the zip archive
	 *
	 * @param ZipArchive $zip
	 * @param string     $folder_path
	 * @param string     $css_file
	 * @param string     $zip_folder_name if empty, it will take basename($folder_path)
	 */
	protected function exportAddFontToZip( ZipArchive $zip, $folder_path, $css_file, $zip_folder_name = null ) {
		$css_file = basename( $css_file );

		$css_path = trailingslashit( $folder_path ) . $css_file;
		if ( ! is_dir( $folder_path ) || ! is_file( $css_path ) ) {
			return;
		}

		$name = $zip_folder_name !== null ? $zip_folder_name : basename( $folder_path );

		$zip->addEmptyDir( 'font-pack/' . $name );
		$zip->addFile( $css_path, 'font-pack/' . $name . '/' . $css_file );

		/* add all font files */

		/**
		 * TCB-1683 php < 5.3 does not have support for ZipArchive::addGlob()
		 */
		$font_files = glob( trailingslashit( $folder_path ) . '*.{woff,WOFF,eot,EOT,woff2,WOFF2,ttf,TTF,svg,SVG}', GLOB_BRACE );
		foreach ( $font_files as $font_file ) {
			$zip->addFile( $font_file, 'font-pack/' . $name . '/' . basename( $font_file ) );
		}

	}


	/**
	 * get the path to a writable folder in this WP install
	 *
	 * @return string the path to a writable (temp) folder
	 * @throws Exception
	 *
	 */
	protected function getTempFolder() {
		$folder = get_temp_dir();

		if ( ! wp_is_writable( $folder ) ) {
			$folder = wp_upload_dir();
		}

		if ( ! wp_is_writable( $folder ) ) {
			throw new Exception( 'Could not find a writable folder to create the archive in. Does your wp-content/uploads folder exist?', 'thrive-cb' );
		}

		return $folder;
	}

	/** --------------------------------------- IMPORT methods --------------------------------------- */

	/**
	 * import a landing page from a zip file located in the attachment received as parameter
	 *
	 * @param mixed $file
	 * @param int   $page_id - the existing page which will be transformed into a landing page
	 *
	 * @return array $page_id the ID of the landing page
	 *
	 * @throws Exception
	 */
	public function import( $file, $page_id ) {

		$template_name = $this->importValidateFile( $file );

		$zip = new ZipArchive();
		if ( $zip->open( $file ) !== true ) {
			throw new Exception( __( 'Could not open the archive file', 'thrive-cb' ) );
		}
		$config              = $this->importReadConfig( $zip );
		$this->import_config = $config;
		$this->current_page  = get_post( $page_id );

		/**
		 * if this is a cloud template, we need to make sure the user has the cloud template files downloaded
		 */
		if ( ! empty( $config['cloud_template'] ) ) {
			$existing = tve_get_downloaded_templates();
			if ( ! isset( $existing[ $config['tve_landing_page'] ] ) ) {
				try {
					TCB_Landing_Page_Cloud_Templates_Api::getInstance()->download( $config['tve_landing_page'] );
				} catch ( Exception $e ) {
					// this means the LP is not available in the cloud anymore.
				}
			}
		}

		/* Import any extra icon_packs that might be associated with the landing page */
		$this->importExtraIconPacks( $config, $zip );
		/* Import any fonts that might be included in the export file, for the landing page */
		/* the $font_class_map will hold a mapping between old classes (.ttfmx) and newly created classes */
		$font_class_map = $this->importFonts( $config, $zip );

		$config = static::replace_ids_on_import( $config );

		/* if any landing page fonts have been setup, we need to replace the font_classes for the selectors, with the values from font_class_map */
		if ( ! empty( $config['tve_globals']['landing_fonts'] ) ) {
			foreach ( $config['tve_globals']['landing_fonts'] as $key => $data ) {
				if ( ! isset( $font_class_map[ $data['css_class'] ] ) ) {
					continue;
				}
				$config['tve_globals']['landing_fonts'][ $key ]['css_class'] = $font_class_map[ $data['css_class'] ];
			}
		}

		/* also for any of the lightboxes */
		if ( ! empty( $config['lightbox'] ) ) {
			foreach ( $config['lightbox'] as $id => $item ) {
				$this->importExtraIconPacks( $config['lightbox'][ $id ], $zip );
				$font_class_map = array_merge( $font_class_map, $this->importFonts( $config['lightbox'][ $id ], $zip ) );
			}
		}

		/* 1b. include the icon pack as an extra resource to be saved in the landing page */
		if ( ! empty( $config['icon_pack'] ) ) {
			$new_icon_pack = $this->importIconPack( $config, $zip );
			if ( ! empty( $new_icon_pack ) && ! empty( $config['icon_pack']['icons'] ) && count( $config['icon_pack']['icons'] ) === count( $new_icon_pack['icons'] ) && $new_icon_pack['icons'][0] !== $config['icon_pack']['icons'][0] ) {
				$icon_search  = $config['icon_pack']['icons'];
				$icon_replace = $new_icon_pack['icons'];
			}

			/* the default icons should not be included anymore in the LP or any of the lightboxes */
			if ( ! empty( $config['thrive_icon_pack'] ) ) {
				$config['thrive_icon_pack']                  = 0;
				$config['tve_globals']['used_icon_packs']    = empty( $config['tve_globals']['used_icon_packs'] ) ? [] : $config['tve_globals']['used_icon_packs'];
				$config['tve_globals']['extra_icons']        = empty( $config['tve_globals']['extra_icons'] ) ? [] : $config['tve_globals']['extra_icons'];
				$config['tve_globals']['extra_icons'] []     = $new_icon_pack;
				$config['tve_globals']['used_icon_packs'] [] = $new_icon_pack['font-family'];
			}
			foreach ( $config['lightbox'] as $id => $item ) {
				if ( ! empty( $item['thrive_icon_pack'] ) ) {
					$config['lightbox'][ $id ]['thrive_icon_pack']                  = 0;
					$config['lightbox'][ $id ]['tve_globals']['used_icon_packs']    = empty( $config['lightbox'][ $id ]['tve_globals']['used_icon_packs'] ) ? [] : $config['lightbox'][ $id ]['tve_globals']['used_icon_packs'];
					$config['lightbox'][ $id ]['tve_globals']['extra_icons']        = empty( $config['lightbox'][ $id ]['tve_globals']['extra_icons'] ) ? [] : $config['lightbox'][ $id ]['tve_globals']['extra_icons'];
					$config['lightbox'][ $id ]['tve_globals']['extra_icons'] []     = $new_icon_pack;
					$config['lightbox'][ $id ]['tve_globals']['used_icon_packs'] [] = $new_icon_pack['font-family'];
				}
			}
		}

		/* 2. import all the images (add them as attachments) and store the links in the config array */
		$this->importImages( $config, $zip );

		/* 3. import all lightboxes (create new posts with type tcb_lightbox) */
		$lightbox_id_map = $this->importLightboxes( $config );

		/* 3. page-level events - check if we have a lightbox there and replace its ID */
		if ( ! empty( $config['tve_page_events'] ) ) {
			foreach ( $config['tve_page_events'] as $index => $evt ) {
				if ( $evt['a'] !== 'thrive_lightbox' || empty( $evt['config']['l_id'] ) || ! isset( $lightbox_id_map[ $evt['config']['l_id'] ] ) ) {
					continue;
				}
				$config['tve_page_events'][ $index ]['config']['l_id'] = $lightbox_id_map[ $evt['config']['l_id'] ];
			}
		}

		/* 4. set all post-meta values to the lightbox */
		$this->importParseImageLinks( $config );
		update_post_meta( $page_id, 'tve_landing_page', $config['tve_landing_page'] );

		if ( ! empty( $config['tve_disable_theme_dependency'] ) ) {
			//Options that disable the CSS from theme. Can be 1 if activated or 0 if not activated
			update_post_meta( $page_id, 'tve_disable_theme_dependency', $config['tve_disable_theme_dependency'] );
		}

		foreach ( tve_get_used_meta_keys() as $meta_key ) {
			if ( ! isset( $config[ $meta_key ] ) || $meta_key === 'tve_landing_page' ) {
				continue;
			}
			$data = $this->replace_content_symbols_on_insert( $config[ $meta_key ] );
			tve_update_post_meta( $page_id, $meta_key, $data );
		}

		/* 5. update the meta-field for TCB content - we need to parse the content and replace lightbox ids inside the event manager parameters */
		$page_content = $zip->getFromName( $this->html_file_name );

		$page_content = static::replace_ids_on_import( $page_content );

		$this->importParseImageLinks( $page_content );
		$this->importReplaceLightboxIds( $page_content, $lightbox_id_map );
		if ( isset( $icon_search, $icon_replace ) ) {
			$page_content = str_replace( $icon_search, $icon_replace, $page_content );
		}
		$page_content = $this->importReplaceFontClasses( $font_class_map, $page_content );

		$this->import_symbols( $config );

		$page_content = $this->replace_content_symbols_on_insert( $page_content );

		tve_update_post_meta( $page_id, 'tve_updated_post', $page_content );

		/* 6. search inside the lightboxes for event manager actions that trigger other lightboxes and replace those also */
		if ( ! empty( $config['lightbox'] ) ) {
			foreach ( $config['lightbox'] as $old_id => $data ) {
				$lb_content = $zip->getFromName( 'lightboxes/' . $old_id . '.html' );
				$this->importReplaceLightboxIds( $lb_content, $lightbox_id_map );
				$this->importParseImageLinks( $lb_content );
				if ( isset( $icon_search, $icon_replace ) ) {
					$lb_content = str_replace( $icon_search, $icon_replace, $lb_content );
				}
				$lb_content = $this->importReplaceFontClasses( $font_class_map, $lb_content );
				update_post_meta( $lightbox_id_map[ $old_id ], 'tve_updated_post', $lb_content );
			}
		}

		/* import header, footer and maybe theme sections */
		$config = $this->import_sections( $page_id, $config );

		/* finally, save this import also as a landing page template */
		$template_name = str_replace( $this->archive_prefix, '', $template_name );

		/**
		 * Change imported lps name before saving as template
		 *
		 */
		$template_name = apply_filters( 'tve_imported_lp_name', $template_name );

		/**
		 * For smart Landing Pages, we need to make sure that keys needed for smart landing pages exists inside the json file
		 */
		$smart_lp_keys = [ 'page_vars', 'page_styles', 'page_palettes' ];
		foreach ( $smart_lp_keys as $smart_lp_key ) {
			if ( ! isset( $config[ $smart_lp_key ] ) ) {
				$config[ $smart_lp_key ] = [];
			}
		}

		$template_data = [
			'content'            => $page_content,
			'inline_css'         => preg_replace( '/\.page-id-\d*/m', ".page-id-$page_id", $config['tve_custom_css'] ),
			'custom_css'         => $config['tve_user_custom_css'],
			'tve_globals'        => $config['tve_globals'],
			'tve_global_scripts' => $config['tve_global_scripts'],
			'name'               => $template_name,
			'template'           => $config['tve_landing_page'],
			'tags'               => 'imported-template',
			'date'               => gmdate( 'Y-m-d' ),
			'imported'           => 1,
			'zip_filesize'       => filesize( $file ),
			'thumbnail'          => $this->importThumbnail( $config, $zip ),
			'sections'           => isset( $config['sections'] ) ? $config['sections'] : [],
			'tpl_skin_tag'       => isset( $config['skin_tag'] ) ? $config['skin_tag'] : '',
		];


		//save page colors & elements for saved template too
		if ( ! empty( $config['page_vars']['colors'] ) ) {
			$template_data['tpl_colours'] = $config['page_vars']['colors'];
		}
		if ( ! empty( $config['page_vars']['gradients'] ) ) {
			$template_data['tpl_gradients'] = $config['page_vars']['gradients'];
		}
		if ( ! empty( $config['page_palettes'] ) ) {
			$template_data['tpl_palettes'] = $config['page_palettes'];
		}

		if ( ! empty( $config[ TCB_LP_Palettes::LP_PALETTES ] ) ) {
			$template_data['tpl_palettes_v2'] = $config[ TCB_LP_Palettes::LP_PALETTES ];
		}

		if ( ! empty( $config[ TCB_LP_Palettes::LP_PALETTES_CONFIG ] ) ) {
			$template_data['tpl_palettes_config_v2'] = $config[ TCB_LP_Palettes::LP_PALETTES_CONFIG ];
		}

		$elements = [
			'button',
			'section',
			'contentbox',
		];
		foreach ( $elements as $element ) {
			if ( ! empty( $config['page_styles'][ $element ] ) ) {
				$template_data[ 'tpl_' . $element ] = $config['page_styles'][ $element ];
			}
		}

		$template_id = \TCB\SavedLandingPages\Saved_Lp::insert( $template_data );

		if ( $page_id ) {
			$landing_page = tcb_landing_page( $page_id );
			$landing_page->set_lp_cloud_template_action( $config );
			$landing_page->update_template_css_variables( $config['page_vars'] );
			$landing_page->update_template_global_styles( $config['page_styles'] );
			$landing_page->update_template_palettes( $config['page_palettes'], true );
		}

		return compact( 'page_id', 'template_id' );
	}

	/**
	 * Import header and footer if present in archive
	 *
	 * @param $page_id
	 * @param $config
	 *
	 * @return array
	 */
	public function import_sections( $page_id, $config ) {
		foreach ( [ 'header', 'footer' ] as $section ) {
			if ( ! empty( $config['sections'][ $section ] ) ) {
				$old_id = $config['sections'][ $section ]['ID'];
				$data   = json_encode( $config['sections'][ $section ] );
				$data   = $this->replace_image_hash( $data );
				$data   = $this->replace_content_symbols_on_insert( $data );

				$data = json_decode( $data, true );

				if ( empty( $GLOBALS['tcb_symbol_map'][ $old_id ] ) ) {
					$section_id = TCB_Symbol_Template::import( $data, $section );
				} else {
					$section_id = $GLOBALS['tcb_symbol_map'][ $old_id ];
				}

				if ( ! empty( $section_id ) ) {
					update_post_meta( $page_id, "_tve_$section", $section_id );
					$GLOBALS['tcb_symbol_map'][ $old_id ] = $section_id;
					$config['sections'][ $section ]['ID'] = $section_id;
				}

				$config['sections'][ $section ]['meta_input'] = $data['meta_input'];
			}
		}

		/**
		 * Filter config after importing sections
		 *
		 * @param array $sections
		 * @param int   $page_id
		 * @param TCB_Landing_Page_Transfer
		 *
		 * @return array
		 */
		return apply_filters( 'tcb_lp_sections_import', $config, $page_id, $this );
	}


	/**
	 * Insert the symbols and set their mapping
	 *
	 * @param $config
	 *
	 * @return void
	 */
	public function import_symbols( $config ) {
		if ( ! empty( $config['symbols'] ) ) {
			foreach ( $config['symbols'] as $symbol ) {
				$old_symbol_id = $symbol['ID'];
				if ( empty( $GLOBALS['tcb_symbol_map'][ $old_symbol_id ] ) ) {
					$data                                        = json_encode( $symbol );
					$data                                        = $this->replace_image_hash( $data );
					$data                                        = json_decode( $data, true );
					$symbol_id                                   = TCB_Symbol_Template::import( $data );
					$GLOBALS['tcb_symbol_map'][ $old_symbol_id ] = $symbol_id;
				}
			}
		}

	}

	/**
	 * Get existing symbols from the content and replace them with the new ones
	 *
	 * @param $content
	 *
	 * @return mixed
	 */
	public function replace_content_symbols_on_insert( $content ) {
		if ( ! empty( $GLOBALS['tcb_symbol_map'] ) && is_string( $content ) ) {
			preg_match_all( TCB_Symbol_Template::SYMBOL_CONFIG_REGEX, $content, $cfg_matches );
			preg_match_all( TCB_Symbol_Template::SYMBOL_SHORTCODE_REGEX, $content, $shortcode_matches );

			$content = $this->replace_symbols_in_matches( $content, $cfg_matches );
			$content = $this->replace_symbols_in_matches( $content, $shortcode_matches );

			foreach ( $GLOBALS['tcb_symbol_map'] as $old_symbol_id => $new_symbol_id ) {
				$content = str_replace( 'thrv_symbol_' . $old_symbol_id, 'thrv_symbol_' . $new_symbol_id, $content );
			}
		}

		return $content;
	}

	/**
	 * Replace old symbols with new ones based on the mapping
	 *
	 * @param $content
	 * @param $matches
	 *
	 * @return array|mixed|string|string[]
	 */
	public function replace_symbols_in_matches( $content, $matches ) {

		if ( ! empty( $matches ) && count( $matches ) === 2 ) {
			foreach ( $matches[1] as $match_index => $symbol_id ) {
				if ( isset( $GLOBALS['tcb_symbol_map'][ $symbol_id ] ) ) {
					$old_symbol = $matches[0][ $match_index ];
					$new_symbol = str_replace( $symbol_id, $GLOBALS['tcb_symbol_map'][ $symbol_id ], $old_symbol );
					$content    = str_replace( $old_symbol, $new_symbol, $content );
				}
			}
		}

		return $content;

	}

	/**
	 * validate if the file exists and it is a zip archive
	 *
	 * @param string $file
	 *
	 * @return string the file name without extension
	 *
	 * @throws Exception
	 */
	protected function importValidateFile( $file ) {
		if ( ! is_file( $file ) ) {
			throw new Exception( __( 'Could not find the archive file on the server. Please make sure the file exists.', 'thrive-cb' ) );
		}

		$name = basename( $file );
		$info = pathinfo( $name );

		if ( $info['extension'] !== 'zip' && $info['extension'] !== 'ZIP' ) {
			throw new Exception( __( 'Invalid file specified', 'thrive-cb' ) );
		}

		if ( ! class_exists( 'ZipArchive' ) ) {
			throw new Exception( __( 'The PHP ZipArchive extension must be enabled in order to use this functionality. Please contact your hosting provider.', 'thrive-cb' ) );
		}

		return str_ireplace( '.zip', '', $name );
	}

	/**
	 * check if all the required files are inside the zip archive
	 *
	 * @param ZipArchive $zip
	 *
	 * @return array the decoded configuration array
	 *
	 * @throws Exception
	 */
	protected function importReadConfig( ZipArchive $zip ) {
		/* 1. check if json exists in the archive */

		if ( false === $zip->locateName( $this->cfg_name ) ) {
			throw new Exception( __( 'This archive does not seem to be generated from Thrive Architect - unable to find the config file', 'thrive-cb' ) );
		}

		/* 2. check if html exists - the Landing Page contents */
		if ( false === $zip->locateName( $this->html_file_name ) ) {
			throw new Exception( __( 'This archive does not seem to be generated from Thrive Architect - unable to find the content file', 'thrive-cb' ) );
		}

		/* 3. extract the config array from the json config file */
		$contents = trim( $zip->getFromName( $this->cfg_name ) );
		$config   = json_decode( $zip->getFromName( $this->cfg_name ), true );

		/**
		 * Also support the base64-encoded config files
		 */
		if ( empty( $config ) ) {
			$config = @unserialize( @base64_decode( $contents ) );
		}


		if ( empty( $config ) ) {
			throw new Exception( __( 'This archive does not seem to be generated from Thrive Architect - could not decode the configuration', 'thrive-cb' ) );
		}

		if ( $this->should_check_lp_page() && empty( $config['tve_landing_page'] ) ) {
			throw new Exception( __( 'This archive does not seem to be generated from Thrive Architect - configuration file is invalid', 'thrive-cb' ) );
		}

		/* 4. check for all lightboxes to exist as files */
		if ( ! empty( $config['lightbox'] ) ) {
			foreach ( $config['lightbox'] as $id => $data ) {
				if ( false == $zip->locateName( 'lightboxes/' . $id . '.html' ) ) {
					throw new Exception( __( 'Could not locate the lightbox #' . $id, 'thrive-cb' ) );
				}
				if ( ! array_key_exists( 'post_title', $data ) ) {
					throw new Exception( __( 'Values are missing from the configuration (post_title)', 'thrive-cb' ) );
				}
			}
		}

		/* 5. check that all images included in the config are also included as files in the archive */
		if ( ! empty( $config['image_map'] ) ) {
			foreach ( $config['image_map'] as $hash => $original_name ) {
				if ( false === $zip->locateName( 'images/' . $hash . '.img_file' ) ) {
					throw new Exception( __( 'Image is missing from the archive: ' . $original_name, 'thrive-cb' ) );
				}
			}
		}

		return $config;
	}

	/**
	 * Whether or not we expect a LP to be imported
	 *
	 * @return bool
	 */
	protected function should_check_lp_page() {
		return true;
	}

	/**
	 * save all images as attachments
	 *
	 * @param array      $config
	 * @param ZipArchive $zip
	 *
	 * @throws Exception
	 */
	protected function importImages( $config, ZipArchive $zip ) {
		if ( empty( $config['image_map'] ) ) {
			$this->image_map = [];
		} else {
			foreach ( $config['image_map'] as $hash => $image_name ) {
				$this->image_map[ $hash ] = $this->insertAttachment( $image_name, $zip->getFromName( 'images/' . $hash . '.img_file' ) );
			}
		}
	}

	/**
	 * uploads an image to the wp media folder and insert it as an attachment
	 *
	 * @param string $image_name
	 * @param string $image_content full content of the image file
	 *
	 * @return string the url to the uploaded image
	 *
	 * @throws Exception
	 */
	protected function insertAttachment( $image_name, $image_content ) {
		$upload = wp_upload_bits( $image_name, null, $image_content );
		if ( ! empty( $upload['error'] ) ) {
			throw new Exception( sprintf( __( 'Could not upload the image: %s', 'thrive-cb' ), $image_name ) );
		}
		/* add the image to the media library, as an attachment */
		$wp_filetype   = wp_check_filetype( $image_name, null );
		$attachment    = array(
			'post_mime_type' => $wp_filetype['type'],
			'post_parent'    => 0,
			'post_title'     => preg_replace( '/\.[^.]+$/', '', $image_name ),
			'post_content'   => '',
			'post_status'    => 'inherit',
		);
		$attachment_id = wp_insert_attachment( $attachment, $upload['file'] );
		if ( ! is_wp_error( $attachment_id ) ) {
			/* if we leave these filters in, the generate_attachment_metadata will run forever */
			remove_all_filters( 'wp_generate_attachment_metadata' );
			remove_all_filters( 'intermediate_image_sizes' );
			/* some themes / plugins fill this directly. empty it so that no extra size is generated */
			global $_wp_additional_image_sizes;
			$_wp_additional_image_sizes = [];

			require_once ABSPATH . 'wp-admin/includes/image.php';
			$attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
			wp_update_attachment_metadata( $attachment_id, $attachment_data );
		}

		return $upload['url'];
	}

	/**
	 * return $string with all occurrences of {{img=hash}} replace with the new image links
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public function replace_image_hash( $string ) {
		$search  = [];
		$replace = [];
		foreach ( $this->image_map as $hash => $url ) {
			$search []  = '{{img=' . $hash . '}}';
			$replace [] = $url;
		}

		return str_replace( $search, $replace, $string );
	}

	/**
	 * replace all occurrences of {img=hash} with the generated URLs for the new images
	 *
	 * @param string|array $content the post content (or the configuration the lightbox / landing page - to replace background images and stuff)
	 */
	protected function importParseImageLinks( &$content ) {
		if ( is_array( $content ) ) {

			/* global setting for page background - image */
			if ( ! empty( $content['tve_globals']['lp_bgi'] ) ) {
				$content['tve_globals']['lp_bgi'] = $this->replace_image_hash( $content['tve_globals']['lp_bgi'] );
			}
			if ( ! empty( $content['tve_globals']['lp_bgp'] ) ) {
				$content['tve_globals']['lp_bgp'] = $this->replace_image_hash( $content['tve_globals']['lp_bgp'] );
			}

			/* lightbox - global setting - content background */
			if ( ! empty( $content['tve_globals']['l_cimg'] ) ) {
				$content['tve_globals']['l_cimg'] = $this->replace_image_hash( $content['tve_globals']['l_cimg'] );
			} elseif ( ! empty( $content['tve_globals']['l_cpat'] ) ) {
				$content['tve_globals']['l_cpat'] = $this->replace_image_hash( $content['tve_globals']['l_cpat'] );
			}

			/* CSS - head_css rules */
			if ( ! empty( $content['tve_custom_css'] ) ) {
				$content['tve_custom_css'] = $this->replace_image_hash( $content['tve_custom_css'] );
			}

			return;
		}

		$content = $this->replace_image_hash( $content );
	}

	/**
	 * import (create) the lightboxes included in the export file
	 * this does not import the lb contents yet
	 *
	 * @param array $config
	 *
	 * @return array map of old_id => new_id for each lightbox
	 */
	protected function importLightboxes( &$config ) {
		if ( empty( $config['lightbox'] ) ) {
			return [];
		}

		$lightbox_id_map = [];

		foreach ( $config['lightbox'] as $id => $lb_data ) {
			$this->importParseImageLinks( $lb_data );
			$globals = $lb_data['tve_globals'];
			unset( $lb_data['tve_globals'] );
			$lightbox_id_map[ $id ] = TCB_Lightbox::create( str_replace( '(Imported)', '', $lb_data['post_title'] ) . '(Imported)', '', $globals, $lb_data );
		}

		return $lightbox_id_map;
	}

	/**
	 * parse the content for event manager configurations and replace old lightbox ids with new ones
	 *
	 * @param string $content
	 * @param array  $lightbox_id_map
	 */
	protected function importReplaceLightboxIds( &$content, $lightbox_id_map ) {
		if ( empty( $lightbox_id_map ) ) {
			return;
		}

		$search = $replace = [];

		foreach ( $lightbox_id_map as $old_id => $new_id ) {
			$search []  = '&quot;l_id&quot;:&quot;' . $old_id . '&quot;';
			$replace [] = '&quot;l_id&quot;:&quot;' . $new_id . '&quot;';
		}

		$content = str_replace( $search, $replace, $content );

	}

	/**
	 * import the main icon pack (the main Thrive Icon Pack setup on the site where the user made the export)
	 *
	 * @param array      $config
	 * @param ZipArchive $zip
	 *
	 * @return array with new icon classes.
	 *
	 * @throws Exception
	 */
	protected function importIconPack( $config, ZipArchive $zip ) {
		if ( empty( $config['icon_pack']['icons'] ) ) {
			return [];
		}

		if ( false === $zip->locateName( 'icon-pack/style.css' ) || false === $zip->locateName( 'icon-pack/fonts/' ) ) {
			return [];
		}

		$font_css = $zip->getFromName( 'icon-pack/style.css' );

		$old_font_family = $config['icon_pack']['font-family'];
		$new_font_family = uniqid( 'tve-ff-' . $this->current_page->ID . '-' );

		/* create a new folder in the uploads_dir for the font-pack */
		$upload_dir    = $this->getUploadDir();
		$font_dir_path = trailingslashit( $upload_dir['basedir'] ) . $new_font_family . '/fonts';

		if ( ! wp_mkdir_p( $font_dir_path ) ) {
			throw new Exception( __( 'Could not create the folder for the Icon Pack', 'thrive-cb' ) );
		}

		$icon_pack = [
			'font-family' => $new_font_family,
		];

		$new_cls_prefix = uniqid( 'tve-icm-' ) . '-';

		/* replace the font-family declaration AND all the class names with a prefix */
		$font_css = preg_replace( '#font-family:(.*?)' . preg_quote( $old_font_family, '#' ) . '(.*)$#m', 'font-family:$1' . $new_font_family . '$2', $font_css );

		/* replace all the CSS classes with the new cls prefix */
		/*
            do a preg_match do find the icon class. usually this is how the icomoon css file declares its classes:
            [class^="icon2-"], [class*=" icon2-"] {...}
            if we cannot find this pattern, there is no point in going forward, we cannot determine the global class selector
        */
		if ( preg_match_all( '#\[class(.*?)("|\')(.+?)("|\')#m', $font_css, $matches ) ) {
			$old_cls_prefix = $matches[3][0];
			$font_css       = preg_replace( '#\[class(.*?)("|\')(.+?)("|\')#m', '[class$1$2' . $new_cls_prefix . '$4', $font_css );
		}

		$new_icons = $config['icon_pack']['icons'];
		if ( isset( $old_cls_prefix ) ) {
			$font_css = str_replace( '.' . $old_cls_prefix, '.' . $new_cls_prefix, $font_css );
			foreach ( $new_icons as $i => $icon ) {
				$new_icons[ $i ] = str_replace( $old_cls_prefix, $new_cls_prefix, $icon );
			}
		}
		$icon_pack['icons'] = $new_icons;
		/* upload the font css file */
		tve_wp_upload_bits( trailingslashit( dirname( $font_dir_path ) ) . 'style.css', $font_css );

		/* save the URL to the uploaded css file */
		$icon_pack['css'] = trailingslashit( $upload_dir['baseurl'] ) . $new_font_family . '/style.css';

		/* save the folder path to the saved icon pack */
		$icon_pack['folder'] = str_replace( '\\', '\\\\', trailingslashit( $upload_dir['basedir'] ) . $new_font_family );

		/* go through the zip contents and grab all the font files - upload them to the fonts folder ($font_dir_path)  */
		$i = 0;
		while ( $info = $zip->statIndex( $i ) ) {
			if ( strpos( $info['name'], 'icon-pack/fonts/' ) === 0 && ! empty( $info['size'] ) ) {
				$pathinfo = pathinfo( $info['name'] );
				if ( empty( $pathinfo['extension'] )
				     || ! in_array( strtolower( $pathinfo['extension'] ), [
						'woff',
						'woff2',
						'ttf',
						'svg',
						'eot',
					] )
				) {
					$i ++;
					continue;
				}
				tve_wp_upload_bits( trailingslashit( $font_dir_path ) . basename( $info['name'] ), $zip->getFromIndex( $i ) );
			}
			$i ++;
		}

		return $icon_pack;

	}

	/**
	 * import any extra icon_packs that might be existing in the $config array from a previous import
	 *
	 * @param array      $config
	 * @param ZipArchive $zip
	 *
	 * @return mixed
	 *
	 * @throws Exception
	 */
	protected function importExtraIconPacks( &$config, ZipArchive $zip ) {
		if ( empty( $config['tve_globals']['extra_icons'] ) ) {
			return;
		}

		foreach ( $config['tve_globals']['extra_icons'] as $i => $icon_pack ) {
			$family = $icon_pack['font-family'];
			if ( false === $zip->locateName( $family . '/style.css' ) || false === $zip->locateName( $family . '/fonts/' ) ) {
				unset( $config['tve_globals']['extra_icons'][ $i ] );
				continue;
			}

			/* create a new folder in the uploads_dir for the font-pack */
			$upload_dir    = $this->getUploadDir();
			$font_dir_path = trailingslashit( $upload_dir['basedir'] ) . $family . '/fonts';

			if ( ! wp_mkdir_p( $font_dir_path ) ) {
				throw new Exception( __( 'Could not create the folder for the Icon Pack', 'thrive-cb' ) );
			}

			/* upload the font css file */
			tve_wp_upload_bits( trailingslashit( dirname( $font_dir_path ) ) . 'style.css', $zip->getFromName( $family . '/style.css' ) );

			/* save the URL to the uploaded css file */
			$icon_pack['css'] = trailingslashit( $upload_dir['baseurl'] ) . $family . '/style.css';

			/* save the folder path to the saved icon pack */
			$icon_pack['folder'] = str_replace( '\\', '\\\\', trailingslashit( $upload_dir['basedir'] ) . $family );

			/* go through the zip contents and grab all the font files - upload them to the fonts folder ($font_dir_path)  */
			$zip_index = 0;
			while ( $info = $zip->statIndex( $zip_index ) ) {
				if ( strpos( $info['name'], $family . '/fonts/' ) === 0 && ! empty( $info['size'] ) ) {
					$pathinfo = pathinfo( $info['name'] );
					if ( empty( $pathinfo['extension'] )
					     || ! in_array( strtolower( $pathinfo['extension'] ), [
							'woff',
							'ttf',
							'svg',
							'eot',
						] )
					) {
						$zip_index ++;
						continue;
					}
					tve_wp_upload_bits( trailingslashit( $font_dir_path ) . basename( $info['name'] ), $zip->getFromIndex( $zip_index ) );
				}
				$zip_index ++;
			}
			$config['tve_globals']['extra_icons'][ $i ] = $icon_pack;

		}

		$config['tve_globals']['extra_icons'] = array_values( $config['tve_globals']['extra_icons'] );

	}

	/**
	 * create new fonts from the exported ones, return a class map in the form of .ttfmx => .ttfmy - to be used to replace occurences of ttfmx with ttfmy in the page / lightbox contents
	 *
	 * @param array      $config
	 * @param ZipArchive $zip
	 *
	 * @return array
	 */
	public function importFonts( &$config, ZipArchive $zip ) {
		if ( empty( $config['thrive_tcb_post_fonts'] ) && empty( $config['tve_globals']['extra_fonts'] ) ) {
			return [];
		}

		$class_map = [];

		$config['tve_globals']['font_cls'] = [];

		$config['tve_globals']['extra_fonts'] = isset( $config['tve_globals']['extra_fonts'] ) ? $config['tve_globals']['extra_fonts'] : [];

		$this->import_config['imported_font_index'] = isset( $this->import_config['imported_font_index'] ) ? $this->import_config['imported_font_index'] : 0;
		$this->import_config['imported_font_index'] ++;

		$this->import_config['imported_fonts'] = isset( $this->import_config['imported_fonts'] ) ? $this->import_config['imported_fonts'] : [];

		/* check for any extra_fonts included in the import file */
		if ( ! empty( $config['tve_globals']['extra_fonts'] ) ) {
			foreach ( $config['tve_globals']['extra_fonts'] as $i => $font ) {
				$location = $this->importFontPack( $zip, $font['font_folder'], basename( $font['font_url'] ), $font['font_folder'] ); // use the same folder name
				if ( empty( $location ) ) {
					unset( $config['tve_globals']['extra_fonts'][ $i ] );
					continue;
				}

				$already_imported_index = $this->findFontByProps( $font, $this->import_config['imported_fonts'] );

				if ( false === $already_imported_index ) {
					$font['font_url']    = $location['url'];
					$font['font_folder'] = str_replace( '\\', '\\\\', $location['path'] );

					$font['font_id']                  = $this->import_config['imported_font_index'] ++;
					$new_css_class                    = 'ttfm-i-' . $font['font_id'];
					$class_map[ $font['font_class'] ] = $new_css_class;
					$font['font_class']               = $new_css_class;

					$this->import_config['imported_fonts'] [] = $font;
				} else {
					$font = $this->import_config['imported_fonts'][ $already_imported_index ];
				}

				$config['tve_globals']['extra_fonts'][ $i ] = $font;

			}
		}

		$config['tve_globals']['extra_fonts'] = array_values( $config['tve_globals']['extra_fonts'] );

		$all_fonts = $this->getThriveFonts();

		foreach ( $config['thrive_tcb_post_fonts'] as $index => $font_data ) {
			if ( empty( $font_data['imported'] ) ) {
				$font_index = $this->findFontByProps( $font_data, $all_fonts );
				if ( $font_index === false ) {
					$new_font = $this->addThriveFont( $font_data, $all_fonts );
				} else {
					$new_font = $all_fonts[ $font_index ];
				}

				if ( $font_data['font_class'] != $new_font['font_class'] ) {
					$class_map[ $font_data['font_class'] ] = $new_font['font_class'];
				}
				if ( tve_is_safe_font( $new_font ) ) {
					/* it does not require an URL */
					unset( $config['thrive_tcb_post_fonts'][ $index ] );
				} else {
					$config['thrive_tcb_post_fonts'][ $index ] = tve_custom_font_get_link( $new_font );
				}

				/* add the font class to the global font_cls field */
				$config['tve_globals']['font_cls'][] = $new_font['font_class'];
				continue;
			}

			if ( empty( $this->import_config['font_pack'] ) ) {
				unset( $config['thrive_tcb_post_fonts'][ $index ] );
				continue;
			}

			/* at this point, the included font was imported using the thrive font import manager */
			$location = $this->importFontPack( $zip, $this->import_config['font_pack']['name'], $this->import_config['font_pack']['css'] );
			if ( empty( $location ) ) { // nothing we can do here, the archive seems corrupted
				unset( $config['thrive_tcb_post_fonts'][ $index ] );
				continue;
			}

			$already_imported_index = $this->findFontByProps( $font_data, $this->import_config['imported_fonts'] );

			/* search and see if this has already been imported in this run */
			if ( false === $already_imported_index ) {
				$font_data['font_url']    = $location['url'];
				$font_data['font_folder'] = str_replace( '\\', '\\\\', $location['path'] );

				$font_data['font_id']                  = $this->import_config['imported_font_index'] ++;
				$new_css_class                         = 'ttfm-i-' . $font_data['font_id'];
				$class_map[ $font_data['font_class'] ] = $new_css_class;
				$font_data['font_class']               = $new_css_class;
				unset( $font_data['imported'] );
				$this->import_config['imported_fonts'][] = $font_data;
			} else {
				$font_data = $this->import_config['imported_fonts'][ $already_imported_index ];
			}

			$config['tve_globals']['extra_fonts'][] = $font_data;

			/* this can be removed from here at the moment */
			unset( $config['thrive_tcb_post_fonts'][ $index ] );
		}

		/* reorder the array, to skip non-consecutive indexes */
		$config['thrive_tcb_post_fonts'] = array_values( $config['thrive_tcb_post_fonts'] );

		return $class_map;
	}

	/**
	 * import the actual files for the font pack included in the archive
	 *
	 * @param ZipArchive $zip
	 * @param string     $folder_name     folder name where the font files are stored in the zip archive
	 * @param string     $css_file        name of the css file from the font folder
	 * @param string     $new_folder_name if empty, a random folder name will be generated
	 *
	 * @return array|false
	 *  url -> the full url to the css font definition file
	 *  path -> the full path to the folder containing the font files
	 *
	 * @throws Exception
	 */
	public function importFontPack( $zip, $folder_name, $css_file, $new_folder_name = null ) {
		if ( ! empty( $this->import_config['font_pack_imported'][ $folder_name ] ) ) {
			return $this->import_config['font_pack_imported'][ $folder_name ];
		}

		$zip_path = 'font-pack/' . $folder_name;
		if ( false === $zip->locateName( $zip_path . '/' . $css_file ) ) {
			throw new Exception( sprintf( __( 'Could not find the main imported font in the archive file (%s)', 'thrive-cb' ), $zip_path ) );
		}

		$folder = null === $new_folder_name ? ( substr( $folder_name, 0, 10 ) . uniqid( '-imp-' ) ) : $new_folder_name;

		/* create a new folder in the uploads_dir for the font-pack */
		$upload_dir    = $this->getUploadDir();
		$font_dir_path = trailingslashit( $upload_dir['basedir'] ) . $folder;

		if ( ! wp_mkdir_p( $font_dir_path ) ) {
			throw new Exception( __( 'Could not create the folder for the Custom Font Pack', 'thrive-cb' ) );
		}

		/* upload the font css file */
		tve_wp_upload_bits( trailingslashit( $font_dir_path ) . $css_file, $zip->getFromName( $zip_path . '/' . $css_file ) );

		/* save the URL to the uploaded css file */
		$this->import_config['font_pack_imported'][ $folder_name ] = array(
			'url'  => trailingslashit( $upload_dir['baseurl'] ) . $folder . '/' . $css_file,
			'path' => $font_dir_path,
		);

		/* go through the zip contents and grab all the font files - upload them to the fonts folder ($font_dir_path)  */
		$zip_index = 0;
		while ( $info = $zip->statIndex( $zip_index ) ) {
			if ( strpos( $info['name'], $folder_name ) === false ) {
				$zip_index ++;
				continue;
			}
			if ( ! preg_match( '#\.(woff|eot|woff2|ttf|svg)$#i', $info['name'] ) ) {
				$zip_index ++;
				continue;
			}
			tve_wp_upload_bits( trailingslashit( $font_dir_path ) . basename( $info['name'] ), $zip->getFromIndex( $zip_index ) );
			$zip_index ++;
		}

		return $this->import_config['font_pack_imported'][ $folder_name ];

	}

	/**
	 * replace all occurences of the array keys of the $class_map with the array values
	 * the problem here is that we cannot go over the string more than once - it's possible that some of the replacements are identical with some of the searches
	 *
	 * @param array  $class_map
	 * @param string $content
	 *
	 * @return string
	 */
	protected function importReplaceFontClasses( $class_map, $content ) {
		if ( empty( $class_map ) ) {
			return $content;
		}

		$search = array_keys( $class_map );

		/* hold this to be used in the callback function for replacing */
		$this->import_temp_replace_map = $class_map;

		$pattern = '#(' . implode( '|', $search ) . ')#s';

		return preg_replace_callback( $pattern, [ $this, '_importReplaceFontClassCallback' ], $content );
	}

	private function _importReplaceFontClassCallback( $matches ) {
		return $this->import_temp_replace_map[ $matches[0] ];
	}

	/**
	 * check if there is a thumbnail included in the archive and import it
	 *
	 * @param array      $config configuration array
	 * @param ZipArchive $zip    zip archive containing the exported landing page
	 *
	 * @return string the url to the uploaded thumbnail file
	 */
	protected function importThumbnail( $config, ZipArchive $zip ) {
		if ( empty( $config['thumbnail'] ) ) {
			return '';
		}

		if ( false === $zip->locateName( 'thumbnail/' . $config['thumbnail'] ) ) {
			return '';
		}

		try {
			return $this->insertAttachment( $config['thumbnail'], $zip->getFromName( 'thumbnail/' . $config['thumbnail'] ) );
		} catch ( Exception $e ) {
			return '';
		}

	}

	/**
	 * wp_upload_bits does not allow creating subfolders in the upload_dir structure
	 * we need to upload some things into a grouped manner - e.g. font files for custom icons
	 *
	 * @param string $full_path full file path
	 * @param string $contents
	 *
	 * @return true on success
	 * @throws Exception
	 *
	 */
	protected function wpUploadBits( $full_path, $contents ) {

		$ifp = @ fopen( $full_path, 'wb' );
		if ( ! $ifp ) {
			throw new Exception( sprintf( __( 'Could not write file %s', 'thrive-cb' ), basename( $full_path ) ) );
		}

		@fwrite( $ifp, $contents );
		fclose( $ifp );
		clearstatcache();

		// Set correct file permissions
		$stat  = @ stat( dirname( $full_path ) );
		$perms = $stat['mode'] & 0007777;
		$perms = $perms & 0000666;
		@ chmod( $full_path, $perms );
		clearstatcache();

		return true;
	}

	/**
	 * get all fonts that are defined from the font manager
	 *
	 * @return array
	 */
	protected function getThriveFonts() {
		$fonts = json_decode( get_option( 'thrive_font_manager_options', '[]' ), true );

		return empty( $fonts ) ? [] : $fonts;

	}

	/**
	 * update (save) the fonts (some new fonts might have been added during the import process)
	 *
	 * @param array|string $fonts
	 */
	protected function updateThriveFonts( $fonts ) {
		if ( is_array( $fonts ) ) {
			$fonts = json_encode( $fonts );
		}

		update_option( 'thrive_font_manager_options', $fonts );
	}

	/**
	 * check if we can find the font defined by $font properties in the $fonts array
	 *
	 * @param array $font         font properties
	 * @param array $fonts        array to search in
	 * @param array $field_search an array of fields which all need to match
	 *
	 * @return int|false the index of the match, or false otherwise
	 */
	protected function findFontByProps( $font, $fonts, $field_search = [ 'font_name', 'font_style', 'font_bold', 'font_color' ] ) {
		foreach ( $fonts as $i => $f ) {
			$found = true;
			foreach ( $field_search as $field ) {
				if ( $font[ $field ] !== $f[ $field ] ) {
					$found = false;
					break;
				}
			}
			if ( $found ) {
				return $i;
			}
		}

		return false;
	}

	/**
	 * Replace menu id because it won't be valid on next import
	 *
	 * @param $content
	 *
	 * @return array|string|string[]|null
	 */
	public static function replace_ids_on_export( $content ) {

		$content = preg_replace_callback( '#>__CONFIG_widget_menu__.*(menu_id[^"]*":[^"]*"\d*[^"]*").*__CONFIG_widget_menu__<#mU', static function ( $matches ) {
			return str_replace( $matches[1], preg_replace( '/\d+/', '__TVE_EXPORTED_MENU_ID__', $matches[1] ), $matches[0] );
		}, $content );

		/* remove urls from logo's because they are not relevant for the export */
		$content = preg_replace( '#\[tcb_logo href="[^\"]*"#m', '[tcb_logo', $content );

		return $content;
	}

	/**
	 * Replace menu ids back after import
	 *
	 * @param string|array $content
	 *
	 * @return array|string|string[]
	 */
	public static function replace_ids_on_import( $content ) {

		$is_string = is_string( $content );

		if ( ! $is_string ) {
			$content = json_encode( $content );
		}

		$menu_id = tve_get_default_menu();

		$content = str_replace( '__TVE_EXPORTED_MENU_ID__', $menu_id, $content );

		if ( ! $is_string ) {
			$content = json_decode( $content, true );
		}

		return $content;
	}

	/**
	 * add new font (same functionality as the thrive font manager)
	 *
	 * @param array $font
	 * @param array $fonts
	 *
	 * @return array the added font (having a new font_id and a new font_class fields)
	 */
	protected function addThriveFont( $font, &$fonts ) {
		$last               = end( $fonts );
		$font['font_id']    = empty( $last ) ? 1 : ( $last['font_id'] + 1 );
		$font['font_class'] = 'ttfm' . $font['font_id'];

		$fonts [] = $font;

		$this->updateThriveFonts( $fonts );

		return $font;

	}

	/**
	 * get the imported font pack metadata, if any
	 *
	 * @return array
	 */
	public function getImportedFontPack() {
		return get_option( Tve_Dash_Font_Import_Manager::OPTION_NAME, [] );
	}

	/** import template from a zip archive that's downloaded from the cloud */

	/**
	 * The zip archive is already downloaded from the cloud and stored in the wp-uploads folder
	 *
	 * @param string $zip_file_path
	 * @param string $template_key
	 * @param bool   $skip_zip_check whether or not to check the zip file for integrity - from templates downloaded directly from the cloud, this can remain true
	 *
	 * @return mixed the new template name (with the prefix appended to it)
	 * @throws Exception
	 */
	public function importFromCloud( $zip_file_path, $template_key, $skip_zip_check = true ) {
		$old_umask = umask( 0 );

		defined( 'FS_METHOD' ) || define( 'FS_METHOD', 'direct' );
		/** @var $wp_filesystem WP_Filesystem_Base */
		global $wp_filesystem;

		$upload         = $this->getUploadDir();
		$wp_uploads_dir = $upload['basedir'];

		if ( ! function_exists( 'WP_filesystem' ) ) {
			include ABSPATH . 'wp-admin/includes/file.php';
		}

		if ( FS_METHOD !== 'direct' ) {
			WP_Filesystem( array(
				'hostname' => defined( 'FTP_HOST' ) ? FTP_HOST : '',
				'username' => defined( 'FTP_USER' ) ? FTP_USER : '',
				'password' => defined( 'FTP_PASS' ) ? FTP_PASS : '',
			) );
			if ( FS_METHOD !== 'ssh2' ) {
				$wp_uploads_dir = str_replace( ABSPATH, '', $wp_uploads_dir );
			}
		} else {
			WP_Filesystem();
		}

		if ( ! $wp_filesystem->connect() && $wp_filesystem->errors instanceof WP_Error ) {
			throw new Exception( $wp_filesystem->errors->get_error_message() );
		}

		$folder = trailingslashit( $wp_uploads_dir ) . TVE_CLOUD_LP_FOLDER . '/';

		if ( $skip_zip_check ) {
			/* this means the template archive is coming directly from the Thrive Template Cloud, we can trust it */
			$result = unzip_file( $zip_file_path, $folder );

			if ( $result instanceof WP_Error ) {
				umask( $old_umask );
				throw new Exception( __( 'Could not extract the archive file', 'thrive-cb' ) );
			}
		}

		if ( ! $wp_filesystem->is_readable( $folder . $template_key . '.json' ) ) {
			throw new Exception( __( 'Could not read configuration file for the template', 'thrive-cb' ) );
		}

		$config = json_decode( $wp_filesystem->get_contents( $folder . $template_key . '.json' ), true );

		if ( ! isset( $config['check'] ) ) {
			throw new Exception( __( 'Could not validate the configuration file for this template', 'thrive-cb' ) );
		}

		// validate the checksum of the data in the config array
		$check = $config['check'];
		unset( $config['check'] );

		if ( $check != md5( TCB_Landing_Page_Cloud_Templates_Api::API_KEY . serialize( $config ) ) ) {
			throw new Exception( __( 'Could not validate the configuration file for this template', 'thrive-cb' ) );
		}

		$downloaded_templates = tve_get_downloaded_templates();

		$config['thumb'] = trailingslashit( $upload['baseurl'] ) . TVE_CLOUD_LP_FOLDER . '/templates/thumbnails/' . $template_key . '.png';

		$downloaded_templates[ $template_key ] = $config;

		tve_save_downloaded_templates( $downloaded_templates );

		return $template_key;
	}

	/**
	 * Prepares the page for export
	 *
	 * Searches for Global Data (css variables and global styles) and replaces them with their corresponding static data
	 *
	 * @param array $config
	 */
	private function prepare_global_data_for_export( &$config ) {

		if ( ! empty( $config['tve_custom_css'] ) ) {

			if ( strpos( $config['tve_updated_post'], TVE_GLOBAL_STYLE_CLS_PREFIX ) !== false ) {

				if ( ! empty( $_POST['global_styles_css'] ) && is_array( $_POST['global_styles_css'] ) ) {
					foreach ( $_POST['global_styles_css'] as $media => $css ) { // phpcs:ignore
						$config['tve_custom_css'] = implode( '@media ' . $media . '{' . stripslashes( $_POST['global_styles_css'][ $media ] ), explode( '@media ' . $media . '{', $config['tve_custom_css'] ) ); // phpcs:ignore

					}

					if ( ! empty( $_POST['global_import_rules'] ) && is_array( $_POST['global_import_rules'] ) ) {
						/**
						 * If there are global import rules, we need to prepend them into custom css
						 */
						$config['tve_custom_css'] = implode( '', wp_unslash( $_POST['global_import_rules'] ) ) . $config['tve_custom_css'];
					}
				}

				$config['tve_updated_post'] = preg_replace( '#(' . TVE_GLOBAL_STYLE_CLS_PREFIX . '[a-zA-Z0-9]+-(?!tpl_)\w+)#', '$2', $config['tve_updated_post'] );
			}

			/**
			 * When using export TAR function
			 */
			$config['tve_custom_css'] = tve_prepare_global_variables_for_front( $config['tve_custom_css'], true, $this->allow_lp_vars );
		}

		if ( ! empty( $_REQUEST['post_id'] ) && is_numeric( $_REQUEST['post_id'] ) ) {
			$post = tcb_post( absint( $_REQUEST['post_id'] ) );
			if ( $post->is_landing_page() ) {
				$config = array_merge( $config, tcb_landing_page( absint( $_REQUEST['post_id'] ) )->prepare_landing_page_for_export() );
			}
		}
	}

}

/** various utility functions for handling file operations */

/**
 * wp_upload_bits does not allow creating subfolders in the upload_dir structure
 * we need to upload some things into a grouped manner - e.g. font files for custom icons
 *
 * @param string $full_path full file path
 * @param string $contents
 *
 * @return true on success
 * @throws Exception
 *
 */
function tve_wp_upload_bits( $full_path, $contents ) {
	$ifp = @ fopen( $full_path, 'wb' );
	if ( ! $ifp ) {
		throw new Exception( sprintf( __( 'Could not write file %s', 'thrive-cb' ), basename( $full_path ) ) );
	}

	@fwrite( $ifp, $contents );
	fclose( $ifp );
	clearstatcache();

	// Set correct file permissions
	$stat  = @ stat( dirname( $full_path ) );
	$perms = $stat['mode'] & 0007777;
	$perms = $perms & 0000666;
	@ chmod( $full_path, $perms );
	clearstatcache();

	return true;
}
