"; private $_source; private $_exclude; private LogController $_logController; private $_tree_filename; private $_tree_filesize; private $_tree_fd; private $_tree_fd_position; private $_current_file; private $_current_file_length; private $_prev_file_length; private $_total_file; private $_callback; /** * @throws DirIteratorException */ public function __construct($tree_filename) { if(!$tree_filename) throw new DirIteratorException("No tree file was provided"); $this->_tree_filename = $tree_filename; $this->_tree_fd_position = 0; $this->_current_file_length = 0; $this->_prev_file_length = 0; $this->_total_file = 0; $this->_exclude = []; $this->_logController = new LogController(); } public function setCallBack(callable $callback): void { $this->_callback = $callback; } public function setSource($source) { $this->_source = $source; } public function getSource() { return $this->_source; } /** * @param LogController $logController * * @return void */ public function setLogController(LogController $logController) { $this->_logController = $logController; } /** * @return LogController */ public function getLogController():LogController { return $this->_logController; } /** * Should always create new tree, tree file should not be present at this point * @throws DirIteratorException */ public function getTotalFiles(): int { $this->_buildTree(); return $this->_total_file; } public function setExcludes($excludes) { $this->_exclude = $excludes; } public function getExcludes(): array {return $this->_exclude;} /** * @throws DirIteratorException */ public function hasNext(): bool { $this->_current_file = $this->_calculateNextFile(); return !!$this->_current_file; } /** * @param $archive_position * * @return DirIteratorFile * @throws DirIteratorException * @throws DirIteratorFileVanishedException */ public function next($archive_position): DirIteratorFile { $this->_tree_fd_position += $this->_prev_file_length; $this->_prev_file_length = $this->_current_file_length; $this->save($archive_position); return new DirIteratorFile($this->_current_file); } /** * @throws DirIteratorException */ private function _calculateNextFile(): ?string { $this->getLogController()->logDebug("[DirIterator] [_calculateNextFile]"); $this->_buildTree(); if(!$this->_tree_fd) $this->_tree_fd = fopen($this->_tree_filename, 'a+'); if(!$this->_tree_filesize) $this->_tree_filesize = DirIteratorFile::safe_filesize($this->_tree_filename); $delimiter_length = strlen(PHP_EOL); $this->_current_file_length = $delimiter_length + 1; $currentLine = ''; while (-1 !== fseek($this->_tree_fd, -($this->_current_file_length + $this->_prev_file_length + $this->_tree_fd_position), SEEK_END)) { $char = fgetc($this->_tree_fd); $currentLine = $char . $currentLine; $this->_current_file_length++; if(substr($currentLine, 0, $delimiter_length) == PHP_EOL) { $currentLine = substr($currentLine, $delimiter_length); $this->_current_file_length -= $delimiter_length; break; } } if($currentLine) { $this->_current_file_length--; if (substr($currentLine, 0, strlen(self::PHP_EXIT)) == self::PHP_EXIT) return null; return $currentLine; } return null; } public function save($archive_position) { if(!file_exists($this->_tree_filename)) return; $eol_size = strlen(PHP_EOL); $new_size = $this->_tree_filesize - $this->_tree_fd_position; if($this->_current_file && !preg_match("/" . self::POSITION_ARCHIVE_REGEX . "/", $this->_current_file)) { $pos = sprintf(self::POSITION_ARCHIVE_STRING, $archive_position); $this->_current_file .= $pos; ftruncate($this->_tree_fd, $new_size-$this->_current_file_length); fseek($this->_tree_fd, $new_size-$this->_current_file_length); fwrite($this->_tree_fd, $this->_current_file); $this->_current_file_length += strlen($pos); $this->_prev_file_length = $this->_current_file_length; $new_size += strlen($pos); } $new_size -= $eol_size; ftruncate($this->_tree_fd, $new_size); fseek($this->_tree_fd, $new_size); fwrite($this->_tree_fd, PHP_EOL); $new_size += $eol_size; $this->_tree_filesize = $new_size; $this->_tree_fd_position = 0; } public function isBuildDone() { if( !file_exists($this->_tree_filename) || filesize($this->_tree_filename) == 0 ) return false; $file = fopen($this->_tree_filename, "r"); fseek($file, strlen(self::PHP_EXIT)); $status = fread($file, 1); fclose($file); return $status == '1'; } private function _countFiles():int { if(!file_exists($this->_tree_filename)) return 0; if(filesize($this->_tree_filename) == 0) return 0; $file = fopen($this->_tree_filename, "r"); $total = 0; while (fgets($file) !== false) $total++; fclose($file); // Remove the first line from counting return $total-1; } private function _buildTree() { if($this->isBuildDone()) return; $this->getLogController()->logDebug("[DirIterator] [_buildTree]"); $this->getLogController()->logDebug("\t[_buildTree] Tree File name: " . $this->_tree_filename); if(!$this->getSource()) throw new DirIteratorException("No source was provided"); $source = new File($this->getSource()); if(!$source->exists()) throw new DirIteratorException("The provided source not exists"); if(!$source->isDir() || $source->isLink()) throw new DirIteratorException("The provided source isn't directory"); $completed = $this->_countFiles(); $old_umask = umask(0177); if(!file_exists($this->_tree_filename)) file_put_contents($this->_tree_filename, self::PHP_EXIT."0".PHP_EOL); umask($old_umask); // Reset to previous umask value $this->_tree_fd = fopen($this->_tree_filename, 'r+'); fseek($this->_tree_fd, 0, SEEK_END); $queue = [dir($source->path())]; while($queue) { $dir = array_pop($queue); $dir_path = $dir->path; while(($entry = $dir->read()) !== false) { if($entry == '.' || $entry == '..') continue; $file = new File($dir_path . JetBackup::SEP . $entry); if (!$file->isReadable()) { // In WP Cloud/Atomic environments, some wp-content files are read-only (managed by the platform) // Don't treat these as errors to avoid alarming users $isWpContentPath = strpos($file->path(), JetBackup::SEP . Wordpress::WP_CONTENT . JetBackup::SEP) !== false; if (Init::isWpCloudAtomic() && $isWpContentPath) { $this->getLogController()->logMessage("The file {$file->path()} is not readable (WP Cloud managed), Skipping"); } else { $this->getLogController()->logError("The file {$file->path()} is not readable, Skipping"); if ($this->_callback) call_user_func($this->_callback, 'error', $file->path(), $this->_total_file); } continue; } // check if this entry is excluded $clean_path = JetBackup::SEP . trim(substr($file->path(), strlen($this->getSource())), JetBackup::SEP); $clean_path = str_replace('\\', '/', $clean_path); foreach($this->getExcludes() as $exclude) { // fnmatch() would fail to match paths correctly because Windows uses backslashes $exclude = Util::normalizePath($exclude); //$this->getLogController()->logDebug("\t[_buildTree] analyzing exclude: [$exclude] - [$clean_path] "); if(fnmatch($exclude, $clean_path, FNM_CASEFOLD) || ($file->isDir() && fnmatch($exclude, $clean_path . JetBackup::SEP, FNM_CASEFOLD))) { $this->getLogController()->logDebug("\t[_buildTree] Exclude HIT: '$exclude' for '$clean_path'"); continue 2; } } // Handle wp-content: If using an alternate content dir, treat it as regular 'wp-content' $alternateWpContent = Wordpress::getAlternateContentDir() && basename(Wordpress::getAlternateContentDir()) == basename($file->path()) && $file->isLink(); // Add regular directories (not symlinks) to the queue for further traversal. $realDir = $file->isDir() && !$file->isLink(); if ($alternateWpContent || $realDir) { $queue[] = $dir; $queue[] = dir( $file->path() ); continue 2; } else { $this->_total_file++; if($this->_total_file <= $completed) continue; $filename = self::_escapeEOL($file->path()); fwrite($this->_tree_fd, $filename . PHP_EOL); if ($this->_callback) call_user_func($this->_callback, 'file', $filename, $this->_total_file); } } $this->_total_file++; if($this->_total_file <= $completed) continue; $filename = self::_escapeEOL($dir_path); fwrite($this->_tree_fd, $filename . PHP_EOL); if ($this->_callback) call_user_func($this->_callback, 'dir', $filename, $this->_total_file); $dir->close(); } $this->getLogController()->logDebug("\t[_buildTree] Total files in tree: " . $this->_total_file); fseek($this->_tree_fd, strlen(self::PHP_EXIT)); fwrite($this->_tree_fd, '1'); fseek($this->_tree_fd, 0, SEEK_END); //copy ($this->_tree_filename, $this->_tree_filename.'_original.php'); //chmod($this->_tree_filename.'_original.php', 0600); } private static function _escapeEOL($string):string { return trim(str_replace(["\r","\n"], [sprintf(self::POSITION_EOL_STRING, "r"), sprintf(self::POSITION_EOL_STRING, "n")], $string)); } public function __destruct() { //$this->save(); if($this->_tree_fd) fclose($this->_tree_fd); } public function done() { if(file_exists($this->_tree_filename)) unlink($this->_tree_filename); } /** * @inheritDoc */ public function rewind():void {} /** * @inheritDoc */ public function getNext(): ?DestinationFile { return null; } }