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 / Cleanup / LogsTask.php

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

128 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Retain only the newest import logs.
4 *
5 * @package Templately
6 */
7
8 namespace Templately\Modules\FullSiteImport\Cleanup;
9
10 use Templately\Modules\FullSiteImport\Utils\SessionData;
11 use Templately\Modules\Utilities\Cleanup\CleanupTask;
12 use Templately\Modules\Utilities\Cleanup\Context;
13 use Templately\Modules\Utilities\Cleanup\Remover;
14 use Templately\Modules\Utilities\Cleanup\RetentionKind;
15 use Templately\Modules\Utilities\Cleanup\Scanner;
16 use Templately\Modules\Utilities\Cleanup\TaskResult;
17 use WP_Error;
18
19 /**
20 * COUNT-kind, not age-kind, and that is a deliberate choice rather than an
21 * inconsistency: support wants the last N runs whatever their age. An age rule
22 * would delete the log of a week-old failure someone is still asking about and
23 * keep two hundred from one busy afternoon.
24 *
25 * Scoped to `fsi-*.log`. The log directory is SHARED — it also holds the
26 * `.htaccess` and `index.php` guards that keep it from being served publicly,
27 * the plugin's own hash-named diagnostic log and its rotated generation, and the
28 * developer HTTP inspector's JSONL file. Enumerating the whole directory is what
29 * caused the shipped defect where the guards, being the oldest files present,
30 * were deleted first.
31 *
32 * The engine's guard denylist would now refuse those files anyway; matching only
33 * our own logs means we never ask.
34 */
35 class LogsTask implements CleanupTask {
36
37 public function descriptor(): array {
38 return [
39 'id' => 'fsi-logs',
40 'label' => __( 'Old import logs', 'templately' ),
41 'group' => 'imports',
42 'scope' => 'files',
43 'retention_kind' => RetentionKind::COUNT,
44 'destructive' => true,
45 'schedulable' => true,
46 ];
47 }
48
49 public function estimate( Context $context ): TaskResult {
50 return $this->sweep( $context->with_dry_run( true ) );
51 }
52
53 public function run( Context $context ): TaskResult {
54 return $this->sweep( $context );
55 }
56
57 /**
58 * The log directory, resolved through the ENGINE's base.
59 *
60 * Deliberately not `LogHandler::get_log_dir()`, which reads `wp_upload_dir()`
61 * directly and so ignores `templately_cleanup_base_dir`. The two resolve to
62 * the same place in production, but in a test that redirects the base they
63 * diverge — and then this task hands real paths to a `Remover` whose
64 * containment check is scoped to the scratch directory, so every removal is
65 * refused as out-of-base and the task silently does nothing. A cleanup task
66 * that quietly no-ops under test is worse than one that fails.
67 */
68 private function log_dir(): string {
69 return Scanner::get_base_dir() . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR;
70 }
71
72 private function sweep( Context $context ): TaskResult {
73 $result = TaskResult::empty();
74
75 $logs = glob( $this->log_dir() . 'fsi-*.log' );
76 if ( ! is_array( $logs ) ) {
77 return $result;
78 }
79
80 $keep = $context->policy()->keep_count();
81 if ( count( $logs ) <= $keep ) {
82 return $result;
83 }
84
85 // The log of the session running right now is never a candidate, even if
86 // it is somehow the oldest — it is being appended to.
87 $current = SessionData::get_session_id();
88 if ( $current ) {
89 $live = $this->log_dir() . 'fsi-' . $current . '.log';
90 Remover::protect( [ $live ] );
91 $logs = array_values(
92 array_filter(
93 $logs,
94 static function ( $path ) use ( $live ) {
95 return $path !== $live;
96 }
97 )
98 );
99 }
100
101 usort(
102 $logs,
103 static function ( $a, $b ) {
104 return filemtime( $a ) <=> filemtime( $b );
105 }
106 );
107
108 $surplus = array_slice( $logs, 0, max( 0, count( $logs ) - $keep ) );
109
110 foreach ( $surplus as $path ) {
111 if ( ! $context->has_budget( 0.5 ) ) {
112 return $result->stopped_on_budget();
113 }
114
115 $outcome = Remover::delete_file( $path, $context );
116
117 if ( $outcome instanceof WP_Error ) {
118 $result->fail( sprintf( '%s: %s', basename( $path ), $outcome->get_error_message() ) );
119 continue;
120 }
121
122 $result->merge( $outcome );
123 }
124
125 return $result;
126 }
127 }
128