# templately/trunk/modules/full-site-import/FullSiteImport.php

Templately – Elementor &amp; Gutenberg Template Library: 6500+ Free &amp; Pro Ready Templates And Cloud!, version trunk. 270 lines.

- Page: https://pluginprobe.com/plugins/templately/trunk/code/modules/full-site-import/FullSiteImport.php
- Raw: https://pluginprobe.com/plugins/templately/trunk/raw/modules/full-site-import/FullSiteImport.php
- Modified: 2026-09-24T05:45:44+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/templately/trunk/code/modules/full-site-import/FullSiteImport.php#L10-L20`.

```php
<?php

/**
 * remove 'action'  => 'continue',
 * way to retry
 * way to skip if failed multiple times
 *
 *	@todo: on runner check for timeout and retry
 *	@todo: use ErrorException on runner to skip item when error occurs: not useful
 *
 */


namespace Templately\Modules\FullSiteImport;

use Elementor\Plugin;
use Error;
use Exception;
use Templately\Modules\FullSiteImport\Exception\FatalErrorException;
use Templately\Modules\FullSiteImport\Exception\RetryableErrorException;
use Templately\Modules\FullSiteImport\Exception\UnknownErrorException;
use Templately\Modules\FullSiteImport\Runners\Finalizer;
use Templately\Modules\FullSiteImport\Utils\LogHandler;
use Templately\Modules\FullSiteImport\Utils\Utils;
use Templately\Modules\FullSiteImport\Utils\SessionData;
use Templately\Modules\FullSiteImport\Utils\AIUtils;
use Templately\Utils\Base;
use Templately\Utils\Response\AjaxResponder;
use Templately\Utils\Helper;
use Templately\Utils\Installer;
use Templately\Utils\Options;

class FullSiteImport extends Base {
	use Concerns\LogHelper;
	use Concerns\ThrowsErrors;
	use Concerns\HandlesSession;
	use Concerns\DownloadsPack;
	use Concerns\RunsImport;
	use Concerns\ReportsStatus;
	use Concerns\NormalizesResult;
	use Concerns\CleansUp;
	use Concerns\TracksRevert;
	use Concerns\HandlesAiEndpoints;

	const SESSION_OPTION_KEY = 'templately_import_session';

	/**
	 * Extensions a pack carries that WordPress does not accept as an upload.
	 *
	 * Everything else comes from get_allowed_mime_types(), so this list only has
	 * to name what the pack format adds on top of it:
	 *
	 * - `json` — every pack is built from JSON (manifest, templates, content),
	 *   and WordPress has never allowed .json uploads.
	 * - `xml`  — the WXR files under wp-content/. Also not an allowed upload:
	 *   core hands .xml to the importer plugin rather than the media library.
	 * - `ai`   — the marker segment in `{id}.ai.json`, an AI-generated page. Not
	 *   the Illustrator format.
	 * - `svg`  — packs ship SVG artwork. This plugin already allows SVG uploads
	 *   for the duration of an import (see allow_svg_upload()), but that filter is
	 *   only added in start_content_import(), which runs AFTER extraction — so at
	 *   sweep time get_allowed_mime_types() does not have it and the pack's SVGs
	 *   would be deleted. It has to be named here.
	 * - fonts  — a pack may ship webfonts, and none of these are upload types.
	 *   Passive assets; nothing here is executable by any server.
	 *
	 * Adding to this list is how a new pack file type gets through. It fails
	 * closed and logs, so a missing entry shows up as a dropped file in the
	 * import log — not as a silent hole.
	 *
	 * Lives on the class rather than the DownloadsPack trait that reads it:
	 * trait constants are PHP 8.2+, and the advertised floor here is 7.2.
	 */
	const PACK_EXTENSIONS = [
		'json', 'xml', 'ai',
		'svg', 'svgz',
		'woff', 'woff2', 'ttf', 'otf', 'eot',
	];
	public    $manifest;
	protected $export;

	private $version = '1.0.0';

	public    $download_key;
	protected $dev_mode       = false;
	protected $api_key        = '';
	protected $session_id        = '';
	protected $documents_data = [];
	private   $is_import_status_handled = false;

	public    $dir_path;
	protected $filePath;
	protected $tmp_dir        = null;
	public    $request_params = [];

	// Polling-specific property for ai_poll_template()
	private   $polling_is_last_part = null;

	public function __construct() {
		$this->dev_mode = defined('TEMPLATELY_DEV') && TEMPLATELY_DEV;
		$this->api_key  = Options::get_instance()->get('api_key');

		// Independent endpoint handlers (registered through the same nonce/cap wrapper).
		// `import_revert` moved to modules/import-revert (2026-07-23) — it arrives
		// through the templately_fsi_ajax_handlers seam below, like the feedback shims.
		$asset     = new Ajax\AssetController();
		$pack_info = new Ajax\PackInfoController();

		$this->add_ajax_action('import_settings', $this);
		$this->add_ajax_action('create_session_and_download', $this);
		$this->add_ajax_action('import_status', $this);
		$this->add_ajax_action('import', $this);
		$this->add_ajax_action('import_info', $pack_info);
		$this->add_ajax_action('import_global_settings', $pack_info);
		$this->add_ajax_action('google_font', $asset);
		$this->add_ajax_action('ai_get_json', $this);
		$this->add_ajax_action('ai_poll_template', $this);
		$this->add_ajax_action('merge_settings_with_template', $asset);

		// Cross-module AJAX handlers arrive through this seam instead of direct class
		// imports (e.g. post-import-feedback supplies its two deprecated shims — see
		// that module.php). Applied on 'init' rather than here because FSI boots before
		// the supplying modules (alphabetical discovery order); admin-ajax.php fires
		// wp_ajax_* well after 'init', so registration is never late.
		add_action('init', function () {
			$handlers = apply_filters('templately_fsi_ajax_handlers', []);
			foreach ($handlers as $action => $handler) {
				$this->add_ajax_action($action, $handler);
			}
		});

		add_action('admin_init', [$this, 'admin_init']);

		// Cleanup scheduling moved to `modules/utilities/` (spec 052). This module
		// contributes tasks to the one shared sweep instead of running its own
		// daily event, which pruned options only and never touched the disk.

		if(isset($_GET['action']) && ($_GET['action'] == 'templately_pack_import' || $_GET['action'] == 'templately_pack_import_status')) {
			add_filter('wp_redirect', '__return_false', 999);
		}

		if ($this->dev_mode) {
			add_filter('http_request_host_is_external', '__return_true');
			add_filter('http_request_args', function ($args) {
				$args['sslverify'] = false;

				return $args;
			});
		}
	}

	public function add_ajax_action($action, $object) {
		add_action("wp_ajax_templately_pack_$action", function() use ($action, $object) {
			// The nonce + capability gate lives in AjaxResponder::guard() so that it
			// answers with the same envelope (and real machine codes) as everything
			// else. It sends and dies on failure. Spec 043 / PRD PHP-5.
			if (!AjaxResponder::guard('templately_nonce', ['install_plugins', 'install_themes'])) {
				return;
			}

			// Every AI/FSI step arrives through here, so this is the one place that can say
			// WHICH step a run reached and how the request ended. Without it a run that
			// stopped between two steps was indistinguishable from one that never started
			// the next.
			//
			// The end is reported from a shutdown handler and NOT gated on "did the handler
			// return", because these handlers answer through AjaxResponder, which `die()`s —
			// so the normal, successful path never comes back here. A flag set after
			// `call_user_func()` would therefore mark every success as a failure. The
			// shutdown handler states the facts instead and lets the reader judge:
			// `connection_aborted=1` means the browser hung up (ignore_user_abort is 0, so
			// PHP stops at the next write, silently), and a non-null `last_error` means PHP
			// died.
			$started = microtime( true );
			Helper::log( sprintf( 'ajax: templately_pack_%s — start', $action ), 'fsi_ajax', 'info' );

			register_shutdown_function( function () use ( $action, $started ) {
				Helper::log(
					sprintf(
						'ajax: templately_pack_%s — ended after %.1fs (connection_aborted=%d, http_code=%s, last_error=%s)',
						$action,
						microtime( true ) - $started,
						connection_aborted(),
						function_exists( 'http_response_code' ) ? (string) http_response_code() : '?',
						wp_json_encode( error_get_last() )
					),
					'fsi_ajax',
					connection_aborted() || null !== error_get_last() ? 'warning' : 'info'
				);
			} );

			// Call the actual handler method on the registered object (defaults to $this).
			call_user_func([$object, $action]);
		});
	}























	private function skipped_plugin(): bool {
		return empty($this->request_params['plugins']) || !is_array($this->request_params['plugins']);
	}








	public function get_request_params() {
		return $this->request_params;
	}

	/**
	 * Kept on FullSiteImport for backward compatibility (called as FullSiteImport::has_revert()).
	 * The revert surface moved to modules/import-revert (2026-07-23), which answers
	 * the `templately_has_revert` filter — no class reference in this direction.
	 */
	public static function has_revert() {
		return (bool) apply_filters( 'templately_has_revert', false );
	}



	public function allow_svg_upload($mimes) {
		// Allow SVG
		$mimes['svg'] = 'image/svg+xml';
		return $mimes;
	}



















}

```
