Completed
Push — master ( 9ff8c9...39b277 )
by David
02:37
created

Ttl_Cache_Cleaner::deactivate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains the Cache Cleaner class which will scan the cache folder and delete stale files.
4
 *
5
 * If after deleting stale files, the disk usage is over the specified limit (100 M by default), then also non-stale
6
 * older files are deleted.
7
 *
8
 * @author David Riccitelli <[email protected]>
9
 * @since 3.22.5
10
 *
11
 * @package Wordlift
12
 * @subpackage Wordlift\Cache
13
 */
14
15
namespace Wordlift\Cache;
16
17
use Exception;
18
19
defined( 'WORDLIFT_CACHE_DEFAULT_TTL' ) || define( 'WORDLIFT_CACHE_DEFAULT_TTL', 86400 );  // 24 hours
20
defined( 'WORDLIFT_CACHE_DEFAULT_MAX_SIZE' ) || define( 'WORDLIFT_CACHE_DEFAULT_MAX_SIZE', 104857600 ); // 100 M
21
22
class Ttl_Cache_Cleaner {
23
24
	const PATH = 0;
25
	const MTIME = 1;
26
	const SIZE = 2;
27
28
	/**
29
	 * The max TTL in seconds.
30
	 *
31
	 * @access private
32
	 * @var int $ttl The max TTL in seconds.
33
	 */
34
	private $ttl;
35
36
	/**
37
	 * The max size in bytes.
38
	 *
39
	 * @access private
40
	 * @var int $ttl The max size in bytes.
41
	 */
42
	private $max_size;
43
44
	/**
45
	 * Ttl_Cache_Cleaner constructor.
46
	 *
47
	 * @param int $ttl The max TTL in seconds.
48
	 * @param int $max_size The max size in bytes.
49
	 */
50
	public function __construct( $ttl = WORDLIFT_CACHE_DEFAULT_TTL, $max_size = WORDLIFT_CACHE_DEFAULT_MAX_SIZE ) {
51
52
		$this->ttl      = $ttl;
53
		$this->max_size = $max_size;
54
55
		add_action( 'wp_ajax_wl_ttl_cache_cleaner__cleanup', array( $this, 'cleanup' ) );
56
		add_action( 'wl_ttl_cache_cleaner__cleanup', array( $this, 'cleanup' ) );
57
58
		// Do not bother to configure scheduled tasks while running on the front-end.
59
		if ( is_admin() && ! wp_next_scheduled( 'wl_ttl_cache_cleaner__cleanup' ) ) {
60
			wp_schedule_event( time(), 'hourly', 'wl_ttl_cache_cleaner__cleanup' );
61
		}
62
63
	}
64
65
	public static function deactivate() {
66
67
		$timestamp = wp_next_scheduled( 'wl_ttl_cache_cleaner__cleanup' );
68
		wp_unschedule_event( $timestamp, 'wl_ttl_cache_cleaner__cleanup' );
69
70
	}
71
72
	public function cleanup() {
73
74
		// Get all the files, recursive.
75
		$files = $this->reduce( array(), Ttl_Cache::get_cache_folder() );
76
77
78
		// Get the max mtime.
79
		$max_mtime = time() - WORDLIFT_CACHE_DEFAULT_TTL;
80
81
		// Keep the original count for statistics that we're going to send the client.
82
		$original_count = count( $files );
83
84
		// Sort by size ascending.
85
		usort( $files, function ( $f1, $f2 ) {
86
			if ( $f1[ Ttl_Cache_Cleaner::MTIME ] === $f2[ Ttl_Cache_Cleaner::MTIME ] ) {
87
				return 0;
88
			}
89
90
			return ( $f1[ Ttl_Cache_Cleaner::MTIME ] < $f2[ Ttl_Cache_Cleaner::MTIME ] ) ? - 1 : 1;
91
		} );
92
93
		// Start removing stale files.
94
		for ( $i = 0; $i < count( $files ); $i ++ ) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
95
			$file = $files[ $i ];
96
			// Break if the mtime is within the range.
97
			if ( $file[ Ttl_Cache_Cleaner::MTIME ] > $max_mtime ) {
98
				break;
99
			}
100
101
			unset( $files[ $i ] );
102
			@unlink( $file[ Ttl_Cache_Cleaner::PATH ] );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
103
		}
104
105
		// Calculate the size.
106
		$total_size = array_reduce( $files, function ( $carry, $item ) {
107
108
			return $carry + $item[ Ttl_Cache_Cleaner::SIZE ];
109
		}, 0 );
110
111
112
		// Remove files until we're within the max size.
113
		while ( $total_size > $this->max_size ) {
114
			$file       = array_shift( $files );
115
			$total_size -= $file[ Ttl_Cache_Cleaner::SIZE ];
116
			@unlink( $file[ Ttl_Cache_Cleaner::PATH ] );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
117
		}
118
119
		// Send back some stats.
120
		wp_send_json_success( array(
121
			'initial_count' => $original_count,
122
			'current_count' => count( $files ),
123
			'current_size'  => $total_size,
124
		) );
125
	}
126
127
	private function reduce( $accumulator, $path ) {
128
129
		// Open the dir handle.
130
		$handle = opendir( $path );
131
132
		// Catch exceptions to be sure to close the dir handle.
133
		try {
134
			$accumulator = @$this->_reduce( $accumulator, $path, $handle );
135
		} catch ( Exception $e ) {
136
			// Do nothing.
137
		}
138
139
		// Finally close the directory handle.
140
		closedir( $handle );
141
142
		return $accumulator;
143
	}
144
145
	/**
146
	 * @param $accumulator
147
	 * @param $path
148
	 * @param $handle
149
	 *
150
	 * @return array
151
	 */
152
	private function _reduce( $accumulator, $path, $handle ) {
153
154
		while ( false !== ( $entry = readdir( $handle ) ) ) {
155
156
			// Skip to the next one.
157
			if ( 0 === strpos( $entry, '.' ) ) {
158
				continue;
159
			}
160
161
			// Set the full path to the entry.
162
			$entry_path = $path . DIRECTORY_SEPARATOR . $entry;
163
164
			// Handle directories.
165
			if ( is_dir( $entry_path ) ) {
166
				$accumulator = $this->reduce( $accumulator, $entry_path );
167
168
				continue;
169
			}
170
171
			// Store the file data.
172
			$accumulator[] = array(
173
				$entry_path,
174
				filemtime( $entry_path ),
175
				filesize( $entry_path ),
176
			);
177
		}
178
179
		return $accumulator;
180
	}
181
182
}
183