PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.7.2
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.7.2
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 3.1.10 All 111 releases
templately / includes / Core / Importer / Utils / SessionData.php

SessionData.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.7.2, at includes/Core/Importer/Utils/SessionData.php

565 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Core\Importer\Utils;
4
5 use Templately\Core\Importer\FullSiteImport;
6
7 /**
8 * SessionData - Centralized session data management
9 *
10 * Provides path-based access to session data without recursive merging.
11 * Replaces the problematic recursive_wp_parse_args pattern.
12 */
13 class SessionData {
14
15 /**
16 * Get individual session option key
17 *
18 * @param string $session_id The session ID
19 * @return string The option key for this session
20 */
21 private static function get_session_option_key($session_id) {
22 return 'templately_session_' . $session_id;
23 }
24
25 /**
26 * Get all session data for a specific session
27 *
28 * @param string $session_id The session ID
29 * @return array The session data
30 */
31 public static function get_data($session_id): array {
32 if (empty($session_id)) {
33 return [];
34 }
35
36 // First try new individual option
37 $option_key = self::get_session_option_key($session_id);
38 $data = get_option($option_key, null);
39
40 if (is_array($data)) {
41 return $data;
42 }
43
44 // Fallback to legacy option
45 $legacy_data = get_option(FullSiteImport::SESSION_OPTION_KEY, []);
46 if (isset($legacy_data[$session_id]) && is_array($legacy_data[$session_id])) {
47 return $legacy_data[$session_id];
48 }
49
50 return [];
51 }
52
53 /**
54 * Save full session data (overwrites existing)
55 * Use this when you need to save the entire session data object
56 *
57 * @param string $session_id The session ID
58 * @param array $data The complete session data
59 * @return bool Success status
60 */
61 public static function save($session_id, $data): bool {
62 if (empty($session_id) || !is_array($data)) {
63 return false;
64 }
65
66 $option_key = self::get_session_option_key($session_id);
67
68 // Add timestamp for expiry tracking
69 $data['_updated_at'] = time();
70
71 // Write to individual option (autoload = no for memory efficiency)
72 return update_option($option_key, $data, false);
73 }
74
75 /**
76 * Get a value at a specific path from session data
77 * Uses dot notation for nested access: "loop.progress.ClassName"
78 *
79 * @param string $session_id The session ID
80 * @param string $path Dot-notation path to the value
81 * @param mixed $default Default value if path doesn't exist
82 * @return mixed The value at the path or default
83 */
84 public static function get($session_id, $path, $default = null) {
85 $data = self::get_data($session_id);
86 $keys = explode('.', $path);
87
88 foreach ($keys as $key) {
89 if (!is_array($data) || !isset($data[$key])) {
90 return $default;
91 }
92 $data = $data[$key];
93 }
94
95 return $data;
96 }
97
98 /**
99 * Set a value at a specific path in session data (no merging)
100 * Uses dot notation for nested access: "loop.progress.ClassName"
101 *
102 * @param string $session_id The session ID
103 * @param string $path Dot-notation path to set
104 * @param mixed $value The value to set
105 * @return bool Success status
106 */
107 public static function set($session_id, $path, $value): bool {
108 if (empty($session_id)) {
109 return false;
110 }
111
112 $data = self::get_data($session_id);
113 $keys = explode('.', $path);
114 $current = &$data;
115
116 // Navigate to the target location
117 foreach ($keys as $i => $key) {
118 if ($i === count($keys) - 1) {
119 // Last key - set the value
120 $current[$key] = $value;
121 } else {
122 // Intermediate key - ensure it's an array
123 if (!isset($current[$key]) || !is_array($current[$key])) {
124 $current[$key] = [];
125 }
126 $current = &$current[$key];
127 }
128 }
129
130 // Add timestamp for expiry tracking
131 $data['_updated_at'] = time();
132
133 // Write to individual option (autoload = no for memory efficiency)
134 $option_key = self::get_session_option_key($session_id);
135 return update_option($option_key, $data, false);
136 }
137
138 /**
139 * Append a value to an array at a specific path
140 *
141 * @param string $session_id The session ID
142 * @param string $path Dot-notation path to the array
143 * @param mixed $value The value to append
144 * @return bool Success status
145 */
146 public static function append($session_id, $path, $value): bool {
147 $current = self::get($session_id, $path, []);
148
149 if (!is_array($current)) {
150 $current = [];
151 }
152
153 $current[] = $value;
154 return self::set($session_id, $path, $current);
155 }
156
157 /**
158 * Delete session data
159 *
160 * @param string $session_id The session ID
161 * @return bool Success status
162 */
163 public static function delete($session_id): bool {
164 if (empty($session_id)) {
165 return false;
166 }
167
168 $deleted = false;
169
170 // Delete individual option
171 $option_key = self::get_session_option_key($session_id);
172 if (get_option($option_key) !== false) {
173 $deleted = delete_option($option_key);
174 }
175
176 // Also remove from legacy option if exists
177 $legacy_data = get_option(FullSiteImport::SESSION_OPTION_KEY, []);
178 if (isset($legacy_data[$session_id])) {
179 unset($legacy_data[$session_id]);
180 update_option(FullSiteImport::SESSION_OPTION_KEY, $legacy_data);
181 $deleted = true;
182 }
183
184 return $deleted;
185 }
186
187 /**
188 * Get calling function identifier for scoped storage
189 * Migrated from Loop::CallingFunctionName()
190 *
191 * @param string|null $unique_id Optional unique identifier to append
192 * @param bool $function Include function name
193 * @param bool $line Include line number
194 * @param int $level Backtrace level (adjust based on call stack depth)
195 * @return string The calling identifier
196 */
197 public static function get_calling_identifier($unique_id = null, $function = true, $line = false, $level = 3): string {
198 $return = 'unknown';
199 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, ($level + 1));
200
201 // Check if the trace has at least the required elements
202 if (isset($trace[$level])) {
203 $final_call = $trace[$level];
204 $return = '';
205
206 if (isset($final_call['object'])) {
207 $return .= get_class($final_call['object']);
208 } elseif (isset($final_call['class'])) {
209 $return .= $final_call['class'];
210 }
211
212 if ($function && isset($final_call['function'])) {
213 $return .= ($return ? '::' : '') . $final_call['function'];
214 }
215
216 // Line number should be from previous level (where the function was called FROM)
217 if ($line && isset($trace[$level - 1]['line'])) {
218 $return .= ($return ? '::' : '') . $trace[$level - 1]['line'];
219 }
220
221 if (!empty($unique_id)) {
222 $return .= ($return ? '::' : '') . $unique_id;
223 }
224
225 if (!$return) {
226 $return = 'unknown';
227 }
228 }
229
230 return $return;
231 }
232
233 // ============================================================================
234 // Loop Helper Functions
235 // ============================================================================
236
237 /**
238 * Check if a key has been processed in the loop
239 *
240 * @param string $session_id The session ID
241 * @param string $calling_class The calling class identifier
242 * @param mixed $key The item key to check
243 * @return bool True if processed
244 */
245 public static function is_key_processed($session_id, $calling_class, $key) {
246 $progress = self::get($session_id, "loop.progress.{$calling_class}", []);
247 return in_array("key_{$key}", $progress, true);
248 }
249
250 /**
251 * Mark a key as processed in the loop
252 *
253 * @param string $session_id The session ID
254 * @param string $calling_class The calling class identifier
255 * @param mixed $key The item key to mark
256 * @return bool Success status
257 */
258 public static function mark_key_processed($session_id, $calling_class, $key) {
259 return self::append($session_id, "loop.progress.{$calling_class}", "key_{$key}");
260 }
261
262 /**
263 * Set loop result for a calling context
264 *
265 * @param string $session_id The session ID
266 * @param string $calling_class The calling class identifier
267 * @param array $result The result data
268 * @return bool Success status
269 */
270 public static function set_loop_result($session_id, $calling_class, $result) {
271 return self::set($session_id, "loop.result.{$calling_class}", $result);
272 }
273
274 /**
275 * Get loop result for a calling context
276 *
277 * @param string $session_id The session ID
278 * @param string $calling_class The calling class identifier
279 * @param array $default Default value if not found
280 * @return array The result data
281 */
282 public static function get_loop_result($session_id, $calling_class, $default = []) {
283 return self::get($session_id, "loop.result.{$calling_class}", $default);
284 }
285
286 // ============================================================================
287 // FullSiteImport Step Helper Functions
288 // ============================================================================
289
290 /**
291 * Check if an import step has been completed
292 *
293 * @param string $session_id The session ID
294 * @param string $step_name The step name (e.g., 'download_zip')
295 * @return bool True if completed
296 */
297 public static function is_step_complete($session_id, $step_name) {
298 return (bool) self::get($session_id, "progress.{$step_name}", false);
299 }
300
301 /**
302 * Mark an import step as complete
303 *
304 * @param string $session_id The session ID
305 * @param string $step_name The step name (e.g., 'download_zip')
306 * @return bool Success status
307 */
308 public static function mark_step_complete($session_id, $step_name) {
309 return self::set($session_id, "progress.{$step_name}", true);
310 }
311
312 // ============================================================================
313 // Skip-on-Error Tracking Functions
314 // ============================================================================
315
316 /**
317 * Increment error attempts for a specific loop item
318 *
319 * @param string $session_id The session ID
320 * @param string $calling_class The calling class identifier
321 * @param mixed $key The item key
322 * @return int The new error attempt count
323 */
324 public static function increment_error_attempts($session_id, $calling_class, $key): int {
325 $current = self::get($session_id, "loop.error_attempts.{$calling_class}.key_{$key}", 0);
326 $new_count = $current + 1;
327 self::set($session_id, "loop.error_attempts.{$calling_class}.key_{$key}", $new_count);
328 return $new_count;
329 }
330
331 /**
332 * Get error attempt count for a specific loop item
333 *
334 * @param string $session_id The session ID
335 * @param string $calling_class The calling class identifier
336 * @param mixed $key The item key
337 * @return int The error attempt count
338 */
339 public static function get_error_attempts($session_id, $calling_class, $key): int {
340 return (int) self::get($session_id, "loop.error_attempts.{$calling_class}.key_{$key}", 0);
341 }
342
343 /**
344 * Reset error attempts for a specific loop item
345 *
346 * @param string $session_id The session ID
347 * @param string $calling_class The calling class identifier
348 * @param mixed $key The item key
349 * @return bool Success status
350 */
351 public static function reset_error_attempts($session_id, $calling_class, $key): bool {
352 return self::set($session_id, "loop.error_attempts.{$calling_class}.key_{$key}", 0);
353 }
354
355 /**
356 * Mark a loop item as skipped
357 *
358 * @param string $session_id The session ID
359 * @param string $calling_class The calling class identifier
360 * @param mixed $key The item key
361 * @param string $reason The reason for skipping
362 * @return bool Success status
363 */
364 public static function mark_key_skipped($session_id, $calling_class, $key, $reason = ''): bool {
365 $skip_data = [
366 'class' => $calling_class,
367 'key' => $key,
368 'reason' => $reason,
369 'timestamp' => time(),
370 ];
371 return self::append($session_id, "loop.skipped_items", $skip_data);
372 }
373
374 /**
375 * Check if a loop item has been skipped
376 *
377 * @param string $session_id The session ID
378 * @param string $calling_class The calling class identifier
379 * @param mixed $key The item key
380 * @return bool True if skipped
381 */
382 public static function is_key_skipped($session_id, $calling_class, $key): bool {
383 $skipped_items = self::get($session_id, "loop.skipped_items", []);
384 foreach ($skipped_items as $item) {
385 if ($item['class'] === $calling_class && $item['key'] === $key) {
386 return true;
387 }
388 }
389 return false;
390 }
391
392 /**
393 * Get all skipped items for a session
394 *
395 * @param string $session_id The session ID
396 * @return array Array of skipped items
397 */
398 public static function get_skipped_items($session_id): array {
399 return self::get($session_id, "loop.skipped_items", []);
400 }
401
402 /**
403 * Increment consecutive skip counter
404 *
405 * @param string $session_id The session ID
406 * @return int The new consecutive skip count
407 */
408 public static function increment_consecutive_skips($session_id): int {
409 $current = self::get($session_id, "loop.consecutive_skips", 0);
410 $new_count = $current + 1;
411 self::set($session_id, "loop.consecutive_skips", $new_count);
412 return $new_count;
413 }
414
415 /**
416 * Reset consecutive skip counter
417 *
418 * @param string $session_id The session ID
419 * @return bool Success status
420 */
421 public static function reset_consecutive_skips($session_id): bool {
422 return self::set($session_id, "loop.consecutive_skips", 0);
423 }
424
425 /**
426 * Get consecutive skip count
427 *
428 * @param string $session_id The session ID
429 * @return int The consecutive skip count
430 */
431 public static function get_consecutive_skips($session_id): int {
432 return (int) self::get($session_id, "loop.consecutive_skips", 0);
433 }
434
435 // ============================================================================
436 // Utility Functions
437 // ============================================================================
438
439 /**
440 * Get session ID from request
441 *
442 * @return string|null The session ID or null
443 */
444 public static function get_session_id() {
445 $session_id = null;
446 if (!empty($_REQUEST['session_id'])) {
447 $session_id = sanitize_text_field($_REQUEST['session_id']);
448 }
449 return $session_id;
450 }
451
452 // ============================================================================
453 // Cleanup Functions
454 // ============================================================================
455
456 /**
457 * Get all session data - ONLY use for cleanup operations
458 * This queries both new individual options and legacy option
459 *
460 * @return array Array of session_id => session_data
461 */
462 public static function get_all_data(): array {
463 global $wpdb;
464
465 $all_sessions = [];
466
467 // Get sessions from new individual options
468 $option_prefix = 'templately_session_';
469 $sql = $wpdb->prepare(
470 "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s",
471 $wpdb->esc_like($option_prefix) . '%'
472 );
473 $results = $wpdb->get_results($sql);
474
475 if ($results) {
476 foreach ($results as $row) {
477 $session_id = str_replace($option_prefix, '', $row->option_name);
478 $session_data = maybe_unserialize($row->option_value);
479 if (is_array($session_data)) {
480 $all_sessions[$session_id] = $session_data;
481 }
482 }
483 }
484
485 // Also get legacy data from single option for backward compatibility
486 $legacy_data = get_option(FullSiteImport::SESSION_OPTION_KEY, []);
487 if (is_array($legacy_data) && !empty($legacy_data)) {
488 foreach ($legacy_data as $session_id => $session_data) {
489 // Don't overwrite if already exists in new format
490 if (!isset($all_sessions[$session_id]) && is_array($session_data)) {
491 $all_sessions[$session_id] = $session_data;
492 }
493 }
494 }
495
496 return $all_sessions;
497 }
498
499 /**
500 * Clean session data by pack ID, keeping only the current session
501 * Removes all session entries with the same pack_id except the current session
502 *
503 * @param string $pack_id The pack ID to match for cleanup
504 * @param string $current_session_id The current session ID to preserve
505 * @return array Array of removed session IDs
506 */
507 public static function clean_by_pack_id($pack_id, $current_session_id): array {
508 // DISABLED: Cleanup temporarily disabled during migration testing
509 return [];
510
511 // Original implementation:
512 // if (empty($pack_id) || empty($current_session_id)) {
513 // return [];
514 // }
515
516 // $all_session_data = self::get_all_data();
517 // $removed_session_ids = [];
518
519 // foreach ($all_session_data as $session_id => $session_data) {
520 // if ($session_id === $current_session_id) {
521 // continue;
522 // }
523 // if (isset($session_data['id']) && $session_data['id'] === $pack_id) {
524 // self::delete($session_id);
525 // $removed_session_ids[] = $session_id;
526 // }
527 // }
528
529 // return $removed_session_ids;
530 }
531
532 /**
533 * Clean up expired sessions based on time threshold
534 * Removes sessions older than the specified number of days
535 *
536 * @param int $max_age_days Maximum age in days (default 7)
537 * @return array Array with 'removed_count' and 'removed_ids'
538 */
539 public static function cleanup_expired($max_age_days = 7): array {
540 // DISABLED: Cleanup temporarily disabled during migration testing
541 return [
542 'removed_count' => 0,
543 'removed_ids' => [],
544 ];
545
546 // Original implementation:
547 // $all_session_data = self::get_all_data();
548 // $removed_session_ids = [];
549 // $threshold_time = time() - ($max_age_days * DAY_IN_SECONDS);
550
551 // foreach ($all_session_data as $session_id => $session_data) {
552 // $updated_at = isset($session_data['_updated_at']) ? (int) $session_data['_updated_at'] : 0;
553 // if ($updated_at === 0 || $updated_at < $threshold_time) {
554 // self::delete($session_id);
555 // $removed_session_ids[] = $session_id;
556 // }
557 // }
558
559 // return [
560 // 'removed_count' => count($removed_session_ids),
561 // 'removed_ids' => $removed_session_ids,
562 // ];
563 }
564 }
565