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