PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / full-site-import / Concerns / HandlesSession.php

HandlesSession.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at modules/full-site-import/Concerns/HandlesSession.php

287 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\FullSiteImport\Concerns;
4
5 use Elementor\Plugin;
6 use Error;
7 use Exception;
8 use Templately\Modules\FullSiteImport\Exception\FatalErrorException;
9 use Templately\Modules\FullSiteImport\Exception\RetryableErrorException;
10 use Templately\Modules\FullSiteImport\Exception\UnknownErrorException;
11 use Templately\Modules\FullSiteImport\Runners\Finalizer;
12 use Templately\Modules\FullSiteImport\Utils\LogHandler;
13 use Templately\Modules\FullSiteImport\Utils\Utils;
14 use Templately\Modules\FullSiteImport\Utils\SessionData;
15 use Templately\Modules\FullSiteImport\Utils\AIUtils;
16 use Templately\Utils\Helper;
17 use Templately\Utils\Options;
18 use Templately\Utils\Response\AjaxResponder;
19 use Templately\Utils\Response\ErrorCode;
20
21 /**
22 * HandlesSession — extracted verbatim from FullSiteImport (behavior-preserving).
23 * Composed back into FullSiteImport via `use`; $this and method resolution unchanged.
24 */
25 trait HandlesSession {
26 /**
27 * Shared session-creation core (037 US3 / FR-006).
28 *
29 * The dir-path setup (`root_dir`/`prv_dir`/`dir_path`/`zip_path`), the
30 * per-field JSON-decode pass, and `SessionData::save()` are identical between
31 * the plain-FSI (`import_settings`) and AI-FSI (`import_ai_settings`) entry
32 * points — they were hand-synced duplicates. This helper owns that common
33 * core; each caller keeps only its genuinely path-specific steps (plain:
34 * session-id resolution + revert/log clear + `wp_send_json`; AI: `isLocalSite`
35 * coercion + download front-load). Behavior is byte-identical to the inline
36 * versions (FR-007): the dir-path keys are independent of the AI-only
37 * `isLocalSite` key, so the order relative to that coercion is immaterial.
38 *
39 * @param string $session_id The (already-resolved, sanitized) session id.
40 * @param array $data The request/session data to persist.
41 * @return array The prepared data (dir paths set, string fields JSON-decoded).
42 */
43 private function prepare_session($session_id, $data) {
44
45 $tmp_dir = Helper::upload_dir('tmp');
46 $prv_dir = Helper::upload_dir('preview');
47
48 $this->session_id = $session_id;
49
50 $data['root_dir'] = $tmp_dir;
51 $data['prv_dir'] = $prv_dir;
52 $data['dir_path'] = $tmp_dir . $session_id . DIRECTORY_SEPARATOR;
53 $data['zip_path'] = $tmp_dir . "{$session_id}.zip";
54
55 if ( is_array( $data ) && ! empty( $data ) ) {
56 foreach ( $data as $key => $value ) {
57 $json = is_string($value) ? json_decode( $value, true ) : null;
58 $data[ $key ] = $json !== null ? $json : $value;
59 }
60 }
61
62 SessionData::save($session_id, $data);
63
64 return $data;
65 }
66
67 public function import_settings() {
68 $user = Options::get_instance()->get('user');
69 if (!empty($user['is_disconnected'])) {
70 AjaxResponder::error(ErrorCode::SITE_DISCONNECTED, __('Your site connection is disconnected. Please migrate your connection first.', 'templately'));
71 return;
72 return;
73 }
74
75 $data = wp_unslash($_POST);
76
77 $is_resume = false;
78
79 if(!empty($data['session_id'])){
80 $session_id = $data['session_id'];
81 // Security: Sanitize session_id from user input
82 $session_id = AIUtils::sanitize_path_component($data['session_id'], 'session_id');
83 if (is_wp_error($session_id)) {
84 AjaxResponder::error(ErrorCode::INVALID_REQUEST, $session_id->get_error_message());
85 return;
86 return;
87 }
88 if (SessionData::is_completed($session_id)) {
89 // Replay guard (spec 042 FR-032): never resume a session whose
90 // import already succeeded — its progress markers would make every
91 // runner skip and instantly replay the OLD results. Mint a fresh
92 // session; the client's own id was stale.
93 Helper::log("Refused resume of completed session {$session_id}; minting a fresh one", 'fsi-session', 'warning');
94 $session_id = wp_generate_uuid4();
95 } else {
96 $session_data = SessionData::get_data($session_id);
97 // Re-attaching to an in-flight session (retry/resume) — its
98 // half-import's revert backups must survive.
99 $is_resume = !empty($session_data);
100 $data = array_merge($session_data, $data);
101 }
102 }
103 else {
104 // Not uniqid(): that is a formatted microtime, so the path it keys in
105 // web-served wp-uploads is enumerable by anyone who knows roughly when
106 // an import ran. The chatbot flow already sends a client-side uuid4
107 // here, so this only brings the fallback in line with it.
108 $session_id = wp_generate_uuid4();
109 }
110
111 $data['session_id'] = $session_id;
112
113 // 037 US3: dir-path setup + JSON-decode + save via the shared helper.
114 $data = $this->prepare_session($session_id, $data);
115
116
117 // Clear the previous import's revert backup — ONLY when launching a
118 // fresh import. Clearing on a resume/retry wiped the very backups the
119 // in-flight import had just written, corrupting "Revert to old website"
120 // for exactly the runs most likely to need it.
121 if (!$is_resume) {
122 $options = Utils::get_backup_options();
123 foreach ($options as $key => $value) {
124 delete_option("__templately_$key");
125 }
126 delete_option('templately_fsi_imported_list');
127 delete_option('templately_fsi_log');
128 }
129
130 AjaxResponder::success([
131 'is_lightspeed' => !Helper::should_flush(),
132 'session_id' => $session_id,
133 ]);
134 }
135
136 public function import_ai_settings() {
137 $data = wp_unslash($_POST);
138
139 // Security: Sanitize session_id from user input
140 $session_id = AIUtils::sanitize_path_component($data['session_id'], 'session_id');
141 if (is_wp_error($session_id)) {
142 AjaxResponder::error(ErrorCode::INVALID_REQUEST, $session_id->get_error_message());
143 return;
144 return;
145 }
146
147 // Path-specific: coerce the AI-only isLocalSite flag BEFORE the shared
148 // JSON-decode pass (the dir-path keys the helper sets are independent of
149 // this key, so ordering is preserved).
150 if (isset($data['isLocalSite'])) {
151 $data['isLocalSite'] = filter_var($data['isLocalSite'], FILTER_VALIDATE_BOOLEAN);
152 }
153
154 // 037 US3: dir-path setup + JSON-decode + save via the shared helper.
155 $data = $this->prepare_session($session_id, $data);
156
157 return $data;
158 }
159
160 public function create_session_and_download() {
161 if ( ! $this->dev_mode && ! wp_doing_ajax() ) {
162 exit;
163 }
164
165 Utils::add_gd_editor_filter();
166
167 define('TEMPLATELY_START_TIME', microtime(true));
168
169 register_shutdown_function( [ $this, 'register_shutdown' ] );
170
171 // $this->finishRequestHeaders();
172
173 try {
174 // Get session data from AJAX request
175 $session_data = $this->import_ai_settings();
176
177 $this->request_params = $session_data;
178 $this->initialize_props();
179 $this->add_revert_hooks();
180 $progress = $this->request_params['progress'] ?? [];
181
182 if(empty($progress['create_log_dir'])){
183 // Create Log Directory and if fail then chose option method
184 LogHandler::create_log_dir();
185
186 SessionData::mark_step_complete($this->session_id, 'create_log_dir');
187 }
188
189 $_id = isset($this->request_params['id']) ? (int) $this->request_params['id'] : null;
190
191 if ($_id === null) {
192 $this->throw(__('Invalid Pack ID.', 'templately'));
193 }
194
195 $this->check_writing_permission();
196
197
198 if(empty($progress['download_zip'])){
199
200 /**
201 * Download the zip
202 */
203 $this->download_zip( $_id, true );
204
205 SessionData::mark_step_complete($this->session_id, 'download_zip');
206 }
207
208 /**
209 * Reading Manifest File
210 */
211 $this->manifest = $this->read_manifest($this->request_params['dir_path']);
212
213 /**
214 * Version Check
215 */
216 if ( ! empty( $this->manifest['version'] ) && version_compare( $this->manifest['version'], $this->version, '>' ) ) {
217 $this->throw( __( 'Please update the templately plugin.', 'templately' ) );
218 }
219
220 $platform = $this->manifest['platform'] ?? '';
221 if($platform === 'elementor') {
222 Helper::enable_elementor_container();
223 }
224
225 update_option('templately_import_platform', $platform);
226
227 // Return success response for AJAX
228 AjaxResponder::success([
229 'session_id' => $this->session_id,
230 'pack_downloaded' => true,
231 'platform' => $platform,
232 'message' => __('Session created and pack downloaded successfully', 'templately')
233 ]);
234
235 } catch ( Exception $e ) {
236 // `retryable` is a first-class field of the envelope now, so the client
237 // reads it from the same place for every failure instead of from this
238 // endpoint's bespoke `should_retry` key.
239 $code = $e instanceof RetryableErrorException ? ErrorCode::TIMEOUT : ErrorCode::SERVER_ERROR;
240
241 AjaxResponder::error($code, $e->getMessage());
242 }
243 }
244
245 // Modified get_session_data to use SessionData
246 public function get_session_data() {
247 if ($session_id = SessionData::get_session_id()) {
248 return SessionData::get_data($session_id);
249 }
250 return [];
251 }
252
253 // === DEPRECATED / UNUSED (kept intentionally) ===
254 // Modified update_session_data to use SessionData
255 public function update_session_data($data) {
256 if ($session_id = SessionData::get_session_id()) {
257 return SessionData::save($session_id, array_merge($this->get_session_data(), $data));
258 }
259 return false;
260 }
261
262 public function initialize_props() {
263 $data = $this->get_session_data();
264 if (isset($data['session_id'])) {
265 $this->session_id = $data['session_id'];
266 }
267 if (isset($data['dir_path'])) {
268 $this->dir_path = $data['dir_path'];
269 }
270 if (isset($data['zip_path'])) {
271 $this->filePath = $data['zip_path'];
272 }
273 if (isset($data['download_key'])) {
274 $this->download_key = $data['download_key'];
275 }
276 if (isset($data['is_import_status_handled'])) {
277 $this->is_import_status_handled = $data['is_import_status_handled'];
278 }
279 }
280
281 // === DEPRECATED / UNUSED (kept intentionally) ===
282 public function clear_session_data(): bool {
283 return delete_site_option(self::SESSION_OPTION_KEY);
284 }
285
286 }
287