PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / DirIterator / DirIterator.php
backup / src / JetBackup / DirIterator Last commit date
.htaccess 1 year ago DirIterator.php 5 months ago DirIteratorExcludes.php 1 year ago DirIteratorFile.php 1 year ago index.html 1 year ago web.config 1 year ago
DirIterator.php
317 lines
1 <?php
2
3 namespace JetBackup\DirIterator;
4
5 use JetBackup\Destination\Integration\DestinationDirIterator;
6 use JetBackup\Destination\Integration\DestinationFile;
7 use JetBackup\Entities\Util;
8 use JetBackup\Exception\DirIteratorException;
9 use JetBackup\Exception\DirIteratorFileVanishedException;
10 use JetBackup\Filesystem\File;
11 use JetBackup\JetBackup;
12 use JetBackup\Log\LogController;
13 use JetBackup\Wordpress\Init;
14 use JetBackup\Wordpress\Wordpress;
15
16
17 if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');
18
19 class DirIterator implements DestinationDirIterator {
20
21 const POSITION_EOL_STRING = "{eol:%s}";
22 const POSITION_EOL_REGEX = "\{eol:([r|n]{1})\}";
23 const POSITION_ARCHIVE_STRING = "{pos:%d}";
24 const POSITION_ARCHIVE_REGEX = "\{pos:([\d]+)\}$";
25
26 const PHP_EXIT = "<?php exit; ?>";
27 private $_source;
28 private $_exclude;
29 private LogController $_logController;
30
31 private $_tree_filename;
32 private $_tree_filesize;
33 private $_tree_fd;
34 private $_tree_fd_position;
35 private $_current_file;
36 private $_current_file_length;
37 private $_prev_file_length;
38 private $_total_file;
39 private $_callback;
40 /**
41 * @throws DirIteratorException
42 */
43 public function __construct($tree_filename) {
44 if(!$tree_filename) throw new DirIteratorException("No tree file was provided");
45 $this->_tree_filename = $tree_filename;
46 $this->_tree_fd_position = 0;
47 $this->_current_file_length = 0;
48 $this->_prev_file_length = 0;
49 $this->_total_file = 0;
50 $this->_exclude = [];
51 $this->_logController = new LogController();
52 }
53
54 public function setCallBack(callable $callback): void {
55 $this->_callback = $callback;
56 }
57
58 public function setSource($source) { $this->_source = $source; }
59 public function getSource() { return $this->_source; }
60
61 /**
62 * @param LogController $logController
63 *
64 * @return void
65 */
66 public function setLogController(LogController $logController) { $this->_logController = $logController; }
67
68 /**
69 * @return LogController
70 */
71 public function getLogController():LogController { return $this->_logController; }
72
73 /**
74 * Should always create new tree, tree file should not be present at this point
75 * @throws DirIteratorException
76 */
77 public function getTotalFiles(): int {
78 $this->_buildTree();
79 return $this->_total_file;
80 }
81
82 public function setExcludes($excludes) { $this->_exclude = $excludes; }
83 public function getExcludes(): array {return $this->_exclude;}
84
85 /**
86 * @throws DirIteratorException
87 */
88 public function hasNext(): bool {
89 $this->_current_file = $this->_calculateNextFile();
90 return !!$this->_current_file;
91 }
92
93 /**
94 * @param $archive_position
95 *
96 * @return DirIteratorFile
97 * @throws DirIteratorException
98 * @throws DirIteratorFileVanishedException
99 */
100 public function next($archive_position): DirIteratorFile {
101 $this->_tree_fd_position += $this->_prev_file_length;
102 $this->_prev_file_length = $this->_current_file_length;
103 $this->save($archive_position);
104 return new DirIteratorFile($this->_current_file);
105
106 }
107
108
109 /**
110 * @throws DirIteratorException
111 */
112 private function _calculateNextFile(): ?string {
113 $this->getLogController()->logDebug("[DirIterator] [_calculateNextFile]");
114 $this->_buildTree();
115 if(!$this->_tree_fd) $this->_tree_fd = fopen($this->_tree_filename, 'a+');
116 if(!$this->_tree_filesize) $this->_tree_filesize = DirIteratorFile::safe_filesize($this->_tree_filename);
117
118 $delimiter_length = strlen(PHP_EOL);
119 $this->_current_file_length = $delimiter_length + 1;
120 $currentLine = '';
121
122 while (-1 !== fseek($this->_tree_fd, -($this->_current_file_length + $this->_prev_file_length + $this->_tree_fd_position), SEEK_END)) {
123 $char = fgetc($this->_tree_fd);
124 $currentLine = $char . $currentLine;
125 $this->_current_file_length++;
126
127 if(substr($currentLine, 0, $delimiter_length) == PHP_EOL) {
128 $currentLine = substr($currentLine, $delimiter_length);
129 $this->_current_file_length -= $delimiter_length;
130 break;
131 }
132 }
133
134 if($currentLine) {
135 $this->_current_file_length--;
136 if (substr($currentLine, 0, strlen(self::PHP_EXIT)) == self::PHP_EXIT) return null;
137 return $currentLine;
138 }
139
140 return null;
141 }
142
143 public function save($archive_position) {
144 if(!file_exists($this->_tree_filename)) return;
145
146 $eol_size = strlen(PHP_EOL);
147 $new_size = $this->_tree_filesize - $this->_tree_fd_position;
148
149 if($this->_current_file && !preg_match("/" . self::POSITION_ARCHIVE_REGEX . "/", $this->_current_file)) {
150 $pos = sprintf(self::POSITION_ARCHIVE_STRING, $archive_position);
151 $this->_current_file .= $pos;
152
153 ftruncate($this->_tree_fd, $new_size-$this->_current_file_length);
154 fseek($this->_tree_fd, $new_size-$this->_current_file_length);
155 fwrite($this->_tree_fd, $this->_current_file);
156
157 $this->_current_file_length += strlen($pos);
158 $this->_prev_file_length = $this->_current_file_length;
159 $new_size += strlen($pos);
160 }
161
162 $new_size -= $eol_size;
163
164 ftruncate($this->_tree_fd, $new_size);
165 fseek($this->_tree_fd, $new_size);
166 fwrite($this->_tree_fd, PHP_EOL);
167 $new_size += $eol_size;
168
169 $this->_tree_filesize = $new_size;
170 $this->_tree_fd_position = 0;
171 }
172
173 public function isBuildDone() {
174 if(
175 !file_exists($this->_tree_filename) ||
176 filesize($this->_tree_filename) == 0
177 ) return false;
178
179 $file = fopen($this->_tree_filename, "r");
180 fseek($file, strlen(self::PHP_EXIT));
181 $status = fread($file, 1);
182 fclose($file);
183 return $status == '1';
184 }
185
186 private function _countFiles():int {
187 if(!file_exists($this->_tree_filename)) return 0;
188 if(filesize($this->_tree_filename) == 0) return 0;
189 $file = fopen($this->_tree_filename, "r");
190 $total = 0;
191 while (fgets($file) !== false) $total++;
192 fclose($file);
193 // Remove the first line from counting
194 return $total-1;
195 }
196
197 private function _buildTree() {
198
199 if($this->isBuildDone()) return;
200
201 $this->getLogController()->logDebug("[DirIterator] [_buildTree]");
202 $this->getLogController()->logDebug("\t[_buildTree] Tree File name: " . $this->_tree_filename);
203
204
205 if(!$this->getSource()) throw new DirIteratorException("No source was provided");
206 $source = new File($this->getSource());
207
208 if(!$source->exists()) throw new DirIteratorException("The provided source not exists");
209 if(!$source->isDir() || $source->isLink()) throw new DirIteratorException("The provided source isn't directory");
210
211 $completed = $this->_countFiles();
212
213 $old_umask = umask(0177);
214 if(!file_exists($this->_tree_filename)) file_put_contents($this->_tree_filename, self::PHP_EXIT."0".PHP_EOL);
215 umask($old_umask); // Reset to previous umask value
216
217 $this->_tree_fd = fopen($this->_tree_filename, 'r+');
218 fseek($this->_tree_fd, 0, SEEK_END);
219
220 $queue = [dir($source->path())];
221
222 while($queue) {
223
224 $dir = array_pop($queue);
225 $dir_path = $dir->path;
226
227 while(($entry = $dir->read()) !== false) {
228 if($entry == '.' || $entry == '..') continue;
229
230 $file = new File($dir_path . JetBackup::SEP . $entry);
231
232 if (!$file->isReadable()) {
233 // In WP Cloud/Atomic environments, some wp-content files are read-only (managed by the platform)
234 // Don't treat these as errors to avoid alarming users
235 $isWpContentPath = strpos($file->path(), JetBackup::SEP . Wordpress::WP_CONTENT . JetBackup::SEP) !== false;
236 if (Init::isWpCloudAtomic() && $isWpContentPath) {
237 $this->getLogController()->logMessage("The file {$file->path()} is not readable (WP Cloud managed), Skipping");
238 } else {
239 $this->getLogController()->logError("The file {$file->path()} is not readable, Skipping");
240 if ($this->_callback) call_user_func($this->_callback, 'error', $file->path(), $this->_total_file);
241 }
242 continue;
243 }
244
245 // check if this entry is excluded
246 $clean_path = JetBackup::SEP . trim(substr($file->path(), strlen($this->getSource())), JetBackup::SEP);
247 $clean_path = str_replace('\\', '/', $clean_path);
248 foreach($this->getExcludes() as $exclude) {
249 // fnmatch() would fail to match paths correctly because Windows uses backslashes
250 $exclude = Util::normalizePath($exclude);
251 //$this->getLogController()->logDebug("\t[_buildTree] analyzing exclude: [$exclude] - [$clean_path] ");
252 if(fnmatch($exclude, $clean_path, FNM_CASEFOLD) || ($file->isDir() && fnmatch($exclude, $clean_path . JetBackup::SEP, FNM_CASEFOLD))) {
253 $this->getLogController()->logDebug("\t[_buildTree] Exclude HIT: '$exclude' for '$clean_path'");
254 continue 2;
255 }
256 }
257
258 // Handle wp-content: If using an alternate content dir, treat it as regular 'wp-content'
259 $alternateWpContent = Wordpress::getAlternateContentDir() && basename(Wordpress::getAlternateContentDir()) == basename($file->path()) && $file->isLink();
260
261 // Add regular directories (not symlinks) to the queue for further traversal.
262 $realDir = $file->isDir() && !$file->isLink();
263
264 if ($alternateWpContent || $realDir) {
265 $queue[] = $dir;
266 $queue[] = dir( $file->path() );
267 continue 2;
268 } else {
269 $this->_total_file++;
270 if($this->_total_file <= $completed) continue;
271 $filename = self::_escapeEOL($file->path());
272 fwrite($this->_tree_fd, $filename . PHP_EOL);
273 if ($this->_callback) call_user_func($this->_callback, 'file', $filename, $this->_total_file);
274 }
275 }
276
277 $this->_total_file++;
278 if($this->_total_file <= $completed) continue;
279 $filename = self::_escapeEOL($dir_path);
280 fwrite($this->_tree_fd, $filename . PHP_EOL);
281 if ($this->_callback) call_user_func($this->_callback, 'dir', $filename, $this->_total_file);
282
283 $dir->close();
284 }
285 $this->getLogController()->logDebug("\t[_buildTree] Total files in tree: " . $this->_total_file);
286
287 fseek($this->_tree_fd, strlen(self::PHP_EXIT));
288 fwrite($this->_tree_fd, '1');
289 fseek($this->_tree_fd, 0, SEEK_END);
290
291 //copy ($this->_tree_filename, $this->_tree_filename.'_original.php');
292 //chmod($this->_tree_filename.'_original.php', 0600);
293 }
294
295 private static function _escapeEOL($string):string {
296 return trim(str_replace(["\r","\n"], [sprintf(self::POSITION_EOL_STRING, "r"), sprintf(self::POSITION_EOL_STRING, "n")], $string));
297 }
298
299 public function __destruct() {
300 //$this->save();
301 if($this->_tree_fd) fclose($this->_tree_fd);
302 }
303
304 public function done() {
305 if(file_exists($this->_tree_filename)) unlink($this->_tree_filename);
306 }
307
308 /**
309 * @inheritDoc
310 */
311 public function rewind():void {}
312
313 /**
314 * @inheritDoc
315 */
316 public function getNext(): ?DestinationFile { return null; }
317 }