zZzzZ.
home2
/
zrzvbdry
/
wiredsave.com
/
wp-content
/
plugins
/
backup
/
src
/
JetBackup
/
Snapshot
New Folder
Editing: Snapshot.php
Save
Cancel
<?php namespace JetBackup\Snapshot; use Exception; use JetBackup\Archive\Archive; use JetBackup\BackupJob\BackupJob; use JetBackup\CLI\CLI; use JetBackup\Cron\Task\Task; use JetBackup\Data\Engine; use JetBackup\Data\SleekStore; use JetBackup\Destination\Destination; use JetBackup\Destination\Vendors\Imported\Imported; use JetBackup\Entities\Util; use JetBackup\Exception\AjaxException; use JetBackup\Exception\ArchiveException; use JetBackup\Exception\DBException; use JetBackup\Exception\GzipException; use JetBackup\Exception\IOException; use JetBackup\Exception\JBException; use JetBackup\Exception\QueueException; use JetBackup\Exception\SnapshotMetaException; use JetBackup\Export\Vendor\Vendor; use JetBackup\Factory; use JetBackup\JetBackup; use JetBackup\Log\LogController; use JetBackup\Queue\Queue; use JetBackup\Queue\QueueItem; use JetBackup\Queue\QueueItemDownload; use JetBackup\Queue\QueueItemExport; use JetBackup\Queue\QueueItemExtract; use JetBackup\Queue\QueueItemRestore; use JetBackup\ResumableTask\ResumableTask; use JetBackup\Schedule\Schedule; use JetBackup\UserInput\UserInput; use JetBackup\Wordpress\Wordpress; use SleekDB\Exceptions\InvalidArgumentException; use SleekDB\QueryBuilder; if (!defined( '__JETBACKUP__')) die('Direct access is not allowed'); class Snapshot extends Engine { const COLLECTION = 'snapshots'; const UNIQUE_ID = 'unique_id'; const NAME = 'name'; const VERSION = 'version'; const BACKUP_TYPE = 'backup_type'; const NOTES = 'notes'; const CREATED = 'created'; const CONTAINS = 'contains'; const STRUCTURE = 'structure'; const SIZE = 'size'; const REINDEX = 'reindex'; const LOCKED = 'locked'; const DELETED = 'deleted'; const SCHEDULES = 'schedules'; const SCHEDULES_NAMES = 'schedules_names'; const DESTINATION_ID = 'destination_id'; const DESTINATION_NAME = 'destination_name'; const JOB_IDENTIFIER = 'job_identifier'; const ITEMS = 'items'; const PARAMS = 'params'; const SKELETON_DATABASE_DIRNAME = 'database'; const SKELETON_LOG_DIRNAME = 'log'; const SKELETON_LOG_FILENAME = 'task.log.gz'; const SKELETON_TEMP_DIRNAME = 'tmp'; const SKELETON_META_DIRNAME = 'meta'; const SKELETON_CONFIG_DIRNAME = 'config'; const SKELETON_FILES_DIRNAME = 'files'; const SKELETON_FILES_ARCHIVE_NAME = 'files' . Archive::ARCHIVE_EXT; const SKELETON_META_FILENAME = 'meta.json'; const SKELETON_FILES_LIST_FILENAME = 'files.list'; const META_MIN_VERSION = '1.0.3'; const META_VERSION = '1.0.3'; const META_FILEPATH = '%s' . JetBackup::SEP . self::SKELETON_META_DIRNAME . JetBackup::SEP . self::SKELETON_META_FILENAME; const LOG_FILEPATH = '%s' . JetBackup::SEP . self::SKELETON_LOG_DIRNAME . JetBackup::SEP . self::SKELETON_LOG_FILENAME; const SNAPSHOT_NAME_PATTERN = 'snap_%s_%s'; const SNAPSHOT_NAME_REGEX = "/^snap_([\d]{4}-[\d]{2}-[\d]{2})_([\d]{6})_(.*)$/"; const PARAM_MULTISITE = 'multisite'; const PARAM_SITE_URL = 'site_url'; const PARAM_DB_PREFIX = 'db_prefix'; const PARAM_DB_EXCLUDED = 'db_excluded'; const PARAM_WP_CONTENT_ONLY_BACKUP = 'wp_content_only_backup'; /** * @var SnapshotItem[] */ private array $_items=[]; public function __construct($_id=null) { parent::__construct(self::COLLECTION); if($_id) $this->_loadById((int) $_id); } public function setUniqueId($id) { $this->set(self::UNIQUE_ID, $id); } public function getUniqueId():string { return $this->get(self::UNIQUE_ID); } public function setCreated(int $created) { $this->set(self::CREATED, $created); } public function getCreated():int { return $this->get(self::CREATED, 0); } public function setName(string $name) { $this->set(self::NAME, $name); } public function getName():string { return $this->get(self::NAME); } public function setBackupType(int $type) { $this->set(self::BACKUP_TYPE, $type); } public function getBackupType() : int { return $this->get(self::BACKUP_TYPE, 0); } public function setDestinationId(int $_id) { $this->set(self::DESTINATION_ID, $_id); } public function getDestinationId():int { return $this->get(self::DESTINATION_ID, 0); } public function getDestinationName():string {return (new Destination($this->getDestinationId()))->getName();} public function setJobIdentifier(string $identifier) { $this->set(self::JOB_IDENTIFIER, $identifier); } public function getJobIdentifier():string { return $this->get(self::JOB_IDENTIFIER); } public function setSize(int $size) { $this->set(self::SIZE, $size); } public function getSize():int { return (int) $this->get(self::SIZE, 0); } public function setStructure(int $structure) { $this->set(self::STRUCTURE, $structure); } public function getStructure():int { return (int) $this->get(self::STRUCTURE, 0); } public function setContains(int $contains) { $this->set(self::CONTAINS, $contains); } public function getContains():int { return (int) $this->get(self::CONTAINS, 0); } public function setNotes(string $notes) { $this->set(self::NOTES, $notes); } public function getNotes():string { return $this->get(self::NOTES); } public function setSchedules(array $schedules) { $this->set(self::SCHEDULES, $schedules); } public function getSchedules(): array {return $this->get(self::SCHEDULES, []);} /** * @return array */ public function getSchedulesNamesByType(): array { $schedules = $this->getSchedules(); $output = []; if (!empty($schedules)) { foreach ($schedules as $schedule_type) { $output[] = [ 'schedule_id' => $schedule_type, 'schedule_name' => Schedule::TYPE_NAMES[$schedule_type] ?? 'Unknown', ]; } } return $output; } public function setReindex(bool $reindex) { $this->set(self::REINDEX, $reindex); } public function isReindex():bool { return $this->get(self::REINDEX, false); } public function setLocked(bool $locked) { $this->set(self::LOCKED, $locked); } public function isLocked():bool { return $this->get(self::LOCKED, false); } public function setDeleted(int $deleted) { $this->set(self::DELETED, $deleted); } public function getDeleted():int { return $this->get(self::DELETED, 0); } public function isDeleted():bool { return $this->getDeleted() > 0 && $this->getDeleted() <= time(); } public function setParams(array $params) { $this->set(self::PARAMS, $params); } public function getParams():array { return $this->get(self::PARAMS, []); } public function getParam(string $key, $default=null) { $params = $this->getParams(); return $params[$key] ?? $default; } public function addParam(string $key, $value):void { $params = $this->getParams(); $params[$key] = $value; $this->setParams($params); } public function setItems(array $items) { $this->_items = $items; } public function addItem(SnapshotItem $item) { $this->_items[] = $item; } /** * @return SnapshotItem[] * @throws \SleekDB\Exceptions\IOException * @throws InvalidArgumentException|DBException */ public function getItems():array { if($this->_items) return $this->_items; $output = []; $list = SnapshotItem::query() ->where([SnapshotItem::PARENT_ID, '=', $this->getId()]) ->orderBy([SnapshotItem::NAME =>'asc']) ->getQuery() ->fetch(); foreach($list as $item_details) $output[] = new SnapshotItem($item_details[JetBackup::ID_FIELD]); return $output; } /** * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException */ public function removeItems() { SnapshotItem::query() ->where([SnapshotItem::PARENT_ID, '=', $this->getId()]) ->getQuery() ->delete(); } /** * @param $source * @param LogController|null $logController * @param callable|null $callback * @param array $excludes * @param array $includes * * @return void * @throws ArchiveException * @throws DBException * @throws GzipException * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException */ public function extract($source, ?LogController $logController=null, ?callable $callback=null, array $excludes=[], array $includes=[]) { if(!($items = $this->getItems())) return; if(!$logController) $logController = new LogController(); foreach($items as $item) $item->extract($source, $logController, $callback, $excludes, $includes); } /** * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException */ public function delete():void { parent::delete(); SnapshotItem::query() ->where([SnapshotItem::PARENT_ID, '=', $this->getId()]) ->getQuery() ->delete(); } public function isCompressed():bool { return $this->getStructure() == BackupJob::STRUCTURE_COMPRESSED; } public static function generateName(): string { return sprintf(self::SNAPSHOT_NAME_PATTERN, Util::date('Y-m-d_His'), Util::generateUniqueId()); } public function save():void { if(!$this->getUniqueId()) $this->setUniqueId(Util::generateUniqueId()); parent::save(); foreach($this->_items as $item) { $item->setParentId($this->getId()); $item->save(); } } public static function db():SleekStore { return new SleekStore(self::COLLECTION); } public static function query():QueryBuilder { return self::db()->createQueryBuilder(); } /** * @throws \SleekDB\Exceptions\IOException * @throws InvalidArgumentException */ public static function getTotalSnapshots():int { return count(self::query() ->select([JetBackup::ID_FIELD]) ->getQuery() ->fetch()); } /** * @param int $type * * @return void */ public function addScheduleByType(int $type):void { $types = $this->getSchedules(); if(in_array($type, $types)) return; $types[] = $type; $this->setSchedules($types); } /** * @throws \SleekDB\Exceptions\IOException * @throws QueueException * @throws InvalidArgumentException */ public function addToDownloadQueue() { $download = new QueueItemDownload(); $download->setSnapshotId($this->getId()); $queue_item = QueueItem::prepare(); $queue_item->setType(Queue::QUEUE_TYPE_DOWNLOAD); $queue_item->setItemId($this->getId()); $queue_item->setItemData($download); Queue::addToQueue($queue_item); } /** * @throws \SleekDB\Exceptions\IOException * @throws QueueException * @throws InvalidArgumentException */ public function addToDownloadLogQueue() { $download = new QueueItemDownload(); $download->setSnapshotId($this->getId()); $queue_item = QueueItem::prepare(); $queue_item->setType(Queue::QUEUE_TYPE_DOWNLOAD_BACKUP_LOG); $queue_item->setItemId($this->getId()); $queue_item->setItemData($download); Queue::addToQueue($queue_item); } /** * @throws \SleekDB\Exceptions\IOException * @throws QueueException * @throws InvalidArgumentException */ private static function _addToRestoreQueue(int $id=0, string $path='', int $options=0, array $exclude=[], array $include=[], array $exclude_db=[], array $include_db=[], array $filemanager=[]) { $restore = new QueueItemRestore(); // these file cannot be restored, we can cut our own branch $exclude[] = 'wp-config.php'; $exclude[] = '.htaccess'; if($id) $restore->setSnapshotId($id); elseif($path) { if(!file_exists($path) || !is_readable($path)) throw new QueueException("The provided snapshot path not exists"); if( !Archive::isGzCompressed($path) && !Archive::isTar($path)) throw new QueueException("The provided snapshot path is not a tar.gz file"); $restore->setSnapshotPath($path); } else throw new QueueException("You must provide snapshot id or path"); // Check if backup was created with wp-content-only restriction $isWpContentOnlyBackup = false; if ($id) { $snapshot = new Snapshot($id); $isWpContentOnlyBackup = $snapshot->getParam(self::PARAM_WP_CONTENT_ONLY_BACKUP, false); } // Apply wp-content-only restriction if: // 1. Current setting is enabled, OR // 2. Backup was originally created with this restriction (WP Cloud or setting was enabled) // Note: in multisite restore, when restoring subsites $includes will already have values if ((Factory::getSettingsRestore()->isRestoreWpContentOnlyEnabled() || $isWpContentOnlyBackup) && empty($include)) { $exclude = []; $include[] = Wordpress::WP_CONTENT . JetBackup::SEP . '*'; } $restore->setOptions($options); $restore->setExcludes($exclude); $restore->setIncludes($include); $restore->setExcludedDatabases($exclude_db); $restore->setIncludedDatabases($include_db); $restore->setFileManager($filemanager); $queue_item = QueueItem::prepare(); $queue_item->setType(Queue::QUEUE_TYPE_RESTORE); $queue_item->setItemData($restore); Queue::addToQueue($queue_item); } /** * @param $path * @param int $options * @param array $exclude * @param array $exclude_db * @param array $include_db * * @return void * @throws InvalidArgumentException * @throws QueueException * @throws \SleekDB\Exceptions\IOException */ public static function addToRestoreQueueByPath($path, int $options= QueueItemRestore::OPTION_RESTORE_FILES_ENTIRE | QueueItemRestore::OPTION_RESTORE_DATABASE_ENTIRE, array $exclude=[], array $exclude_db=[], array $include_db=[]) { $include = []; // placeholder for future implementation self::_addToRestoreQueue(0, $path, $options, $exclude, $include, $exclude_db, $include_db); } /** * Import a backup file from a given path and save it to the database. * This allows the backup to be indexed and restored later without re-uploading. * * @param string $path Path to the backup file (tar or tar.gz) * @param bool $crossDomain Allow cross-domain restores * * @return Snapshot The imported and saved snapshot * @throws QueueException * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException * @throws DBException * @throws \JetBackup\Exception\DestinationException */ public static function importFromPath(string $path, bool $crossDomain = true): Snapshot { // 1. Validate backup file if (!file_exists($path) || !is_readable($path)) { throw new QueueException("The provided backup file doesn't exist or is not readable"); } $isCompressed = Archive::isGzCompressed($path); if (!$isCompressed && !Archive::isTar($path)) { throw new QueueException("The provided file is not a valid backup file (tar or tar.gz)"); } // 2. Extract only meta.json to a temp location $tempDir = Factory::getLocations()->getTempDir() . JetBackup::SEP . 'import_' . Util::generateUniqueId(); if (!file_exists($tempDir)) { mkdir($tempDir, 0700, true); } $extractPath = $path; // If compressed, decompress to temp location first if ($isCompressed) { $decompressedPath = $tempDir . JetBackup::SEP . basename($path, '.gz'); \JetBackup\Archive\Gzip::decompress($path, $decompressedPath); $extractPath = $decompressedPath; } // Extract only the meta directory from the tar try { $archive = new Archive($extractPath); $archive->setExcludeCallback(function($path, $isDir) { // Exclude everything EXCEPT meta directory files $metaDir = self::SKELETON_META_DIRNAME; return !str_starts_with($path, $metaDir . JetBackup::SEP) && $path !== $metaDir; }); $archive->extract($tempDir); } catch (ArchiveException $e) { Util::rm($tempDir); throw new QueueException("Failed to extract backup meta: " . $e->getMessage()); } // Remove decompressed temp file if we created one if ($isCompressed && file_exists($extractPath)) { unlink($extractPath); } // 3. Parse meta.json $metaFile = sprintf(self::META_FILEPATH, $tempDir); if (!file_exists($metaFile)) { Util::rm($tempDir); throw new QueueException("The provided backup doesn't contain a valid meta file"); } $snapshot = new Snapshot(); try { $snapshot->importMeta($metaFile, $crossDomain); } catch (SnapshotMetaException $e) { Util::rm($tempDir); throw new QueueException("Can't import backup: " . $e->getMessage()); } // Clean up temp meta extraction Util::rm($tempDir); // 4. Get or create Imported destination $destination = Destination::createImportedDestination(); // 5. Generate job identifier for imported backup $jobIdentifier = 'imported_' . Util::generateUniqueId(); // 6. Check for existing snapshot with same name to avoid duplicates $existing = self::query() ->where([self::NAME, '=', $snapshot->getName()]) ->where([self::DESTINATION_ID, '=', $destination->getId()]) ->getQuery() ->first(); if ($existing) { // Return existing snapshot, no need to re-import return new Snapshot($existing[JetBackup::ID_FIELD]); } // 7. Move backup file to permanent location $destination->connect(); $permanentDir = JetBackup::SEP . $jobIdentifier; if (!$destination->dirExists($permanentDir)) { $destination->createDir($permanentDir); } $backupFilename = $snapshot->getName() . ($isCompressed ? '.tar.gz' : '.tar'); $permanentPath = $permanentDir . JetBackup::SEP . $backupFilename; // Copy file to permanent location (use copy to preserve original until confirmed) $destination->getInstance()->copyFileToRemote($path, $permanentPath); $destination->disconnect(); $dataDir = Factory::getLocations()->getDataDir(); $archiveFilename = basename($permanentPath); $extractFolderName = $snapshot->getName(); $archivePath = $dataDir . JetBackup::SEP. Imported::IMPORTS_DIR . JetBackup::SEP.$jobIdentifier. JetBackup::SEP. $archiveFilename; $extractDir = $dataDir . JetBackup::SEP. Imported::IMPORTS_DIR . JetBackup::SEP .$jobIdentifier. JetBackup::SEP. $extractFolderName; if (!is_dir($extractDir)) { mkdir($extractDir, 0755, true); } try { $archive = new Archive($archivePath); $archive->extract($extractDir); } catch (\Exception $e) { throw new QueueException("Failed to extract imported backup: " . $e->getMessage()); } unlink($archivePath); // Remove original uploaded file unlink($path); // If the file was decompressed from a .gz file, the original .gz parent dir may now be empty $parentDir = dirname($path); if (is_dir($parentDir) && count(scandir($parentDir)) == 2) { rmdir($parentDir); } // 8. Set snapshot fields $snapshot->setDestinationId($destination->getId()); $snapshot->setJobIdentifier($jobIdentifier); $snapshot->addParam('imported', true); $snapshot->addParam('import_time', time()); $snapshot->addParam('original_filename', basename($path)); // Add import schedule type for retention tracking $snapshot->addScheduleByType(Schedule::TYPE_IMPORTED); // 9. Save snapshot to database $snapshot->save(); return $snapshot; } /** * @return void * @throws InvalidArgumentException * @throws QueueException * @throws \SleekDB\Exceptions\IOException */ public function addToRestoreQueue(int $options=QueueItemRestore::OPTION_RESTORE_FILES_ENTIRE | QueueItemRestore::OPTION_RESTORE_DATABASE_ENTIRE, array $exclude=[], array $include=[], array $exclude_db=[], array $include_db=[], array $filemanager=[]) { self::_addToRestoreQueue($this->getId(), '', $options, $exclude, $include, $exclude_db, $include_db, $filemanager); } /** * @param int $type * * @return void * @throws InvalidArgumentException * @throws QueueException * @throws \SleekDB\Exceptions\IOException */ public function addToExportQueue(int $type) { if (!$type || !in_array($type, Vendor::ALL_VENDORS)) throw new QueueException("Invalid Panel type"); $export = new QueueItemExport(); $export->setType($type); $export->setSnapshotId($this->getId()); $queue_item = QueueItem::prepare(); $queue_item->setType(Queue::QUEUE_TYPE_EXPORT); $queue_item->setItemId($this->getId()); $queue_item->setItemData($export); Queue::addToQueue($queue_item); } /** * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException * @throws QueueException */ public function addToExtractQueue($extract_path=null): void { $extract = new QueueItemExtract(); $extract->setSnapshotId($this->getId()); if($extract_path) $extract->setExtractPath($extract_path); $queue_item = QueueItem::prepare(); $queue_item->setType(Queue::QUEUE_TYPE_EXTRACT); $queue_item->setItemId($this->getId()); $queue_item->setItemData($extract); Queue::addToQueue($queue_item); } /** * @param int $type * * @return void */ public function removeSchedule(int $type):void { if($type < 1) return; $types = $this->getSchedules(); $offset = array_search($type, $types); if($offset === false) return; array_splice($types, $offset, 1); $this->setSchedules($types); } /** * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException */ public static function getTotalSnapshotsSize():int { $list = self::query() ->where([Engine::ENGINE, '=', Engine::ENGINE_WP]) ->select([self::SIZE]) ->getQuery() ->fetch(); $size = 0; foreach ($list as $item) $size += $item[self::SIZE]; return $size; } /** * @param int $destinationID * * @return void * @throws AjaxException * @throws DBException * @throws InvalidArgumentException * @throws \SleekDB\Exceptions\IOException */ public static function deleteByDestinationID(int $destinationID):void { $snapshots = Snapshot::query() ->select([JetBackup::ID_FIELD]) ->where([Snapshot::DESTINATION_ID, '=', $destinationID]) ->getQuery() ->fetch(); if(empty($snapshots)) return; foreach ($snapshots as $snapshotID) { $snapshot = new Snapshot($snapshotID[JetBackup::ID_FIELD]); if(!$snapshot->getId()) throw new AjaxException("Invalid snapshot id provided"); $snapshot->delete(); } } /** * @return array */ public function getDisplay(): array { return [ JetBackup::ID_FIELD => $this->getId(), self::UNIQUE_ID => $this->getUniqueId(), self::DESTINATION_ID => $this->getDestinationId(), self::DESTINATION_NAME => $this->getDestinationName(), self::NAME => $this->getName(), self::BACKUP_TYPE => $this->getBackupType(), Engine::ENGINE => $this->getEngine(), self::CONTAINS => $this->getContains(), self::STRUCTURE => $this->getStructure(), self::SCHEDULES => $this->getSchedules(), self::SCHEDULES_NAMES => $this->getSchedulesNamesByType(), self::NOTES => $this->getNotes(), self::PARAMS => $this->getParams(), self::CREATED => $this->getCreated(), self::SIZE => Util::bytesToHumanReadable($this->getSize()), self::LOCKED => $this->isLocked() ? 1 : 0, self::DELETED => $this->getDeleted() ]; } /** * @return array */ public function getDisplayCLI(): array { return [ 'ID' => $this->getId(), 'Destination ID' => $this->getDestinationId(), 'Name' => $this->getName(), 'Backup Type' => $this->getBackupType(), 'Engine' => $this->getEngineName(), 'Contains' => $this->getContains(), 'Structure' => $this->getStructure(), 'Schedules' => $this->getSchedules(), 'Notes' => $this->getNotes(), 'Created' => CLI::date($this->getCreated()), 'Size' => $this->getSize(), 'Locked' => $this->isLocked() ? 'Yes' : 'No', 'Deleted' => $this->getDeleted(), ]; } public function exportMeta($path):void { if($this->getEngine() != Engine::ENGINE_WP) throw new SnapshotMetaException("Only snapshot of type WordPress is allowed to be exported"); $items = []; foreach($this->_items as $item) $items[] = $item->exportMeta(); $umask = umask(077); file_put_contents(sprintf(self::META_FILEPATH, $path), json_encode([ self::VERSION => self::META_VERSION, self::NAME => $this->getName(), self::BACKUP_TYPE => $this->getBackupType(), Engine::ENGINE => $this->getEngine(), self::CONTAINS => $this->getContains(), self::STRUCTURE => $this->getStructure(), self::SCHEDULES => $this->getSchedules(), self::JOB_IDENTIFIER => $this->getJobIdentifier(), self::CREATED => $this->getCreated(), self::SIZE => $this->getSize(), self::PARAMS => $this->getParams(), self::ITEMS => $items, ])); umask($umask); } /** * @param string $file * @param bool $cross_domain * * @return void * @throws SnapshotMetaException */ public function importMeta(string $file, bool $cross_domain=false):void { $meta = json_decode(file_get_contents($file)); $current_site_url = Wordpress::getSiteDomain(); if(isset($meta->version) && version_compare($meta->version, self::META_MIN_VERSION, '>=')) { $this->setEngine($meta->{Engine::ENGINE}); $this->setBackupType($meta->{self::BACKUP_TYPE}); $this->setCreated($meta->{self::CREATED}); $this->setName($meta->{self::NAME}); $this->setContains($meta->{self::CONTAINS}); $this->setParams((array) $meta->{self::PARAMS}); $this->setStructure($meta->{self::STRUCTURE}); $this->setSchedules($meta->{self::SCHEDULES}); $this->setSize($meta->{self::SIZE}); $backup_site_url = preg_replace("#^http(s?)://#", "", $this->getParam(self::PARAM_SITE_URL)); if( !$cross_domain && $backup_site_url != $current_site_url) throw new SnapshotMetaException("This snapshot isn't owned by this site, you need to allow cross-domain restores"); foreach($meta->{self::ITEMS} as $item_meta) { $item = new SnapshotItem(); $item->importMeta($item_meta); $this->addItem($item); } } elseif(isset($meta->meta_version)) { // Support meta file with version 1.0.2 and below $backup_site_url = preg_replace("#^http(s?)://#", "", $meta->domain_backup); if( !$cross_domain && $backup_site_url != $current_site_url) throw new SnapshotMetaException("This snapshot isn't owned by this site, you need to allow cross-domain restores"); $contains = 0; if(isset($meta->job_settings->backup_contains)) { $backup_contains = (array) $meta->job_settings->backup_contains; if(in_array('homedir', $backup_contains)) $contains |= BackupJob::BACKUP_ACCOUNT_CONTAINS_HOMEDIR; if(in_array('database', $backup_contains)) $contains |= BackupJob::BACKUP_ACCOUNT_CONTAINS_DATABASE; } $this->setEngine(isset($meta->queue_item->engine) ? (Wordpress::strContains($meta->queue_item->engine, '5.303,13.236,5.301,20.758 C91.384,64.08,82.732,78.016,69.871,85.516z') ? Engine::ENGINE_WP : Engine::ENGINE_JB) : Engine::ENGINE_WP); $this->setBackupType(BackupJob::TYPE_ACCOUNT); $this->setCreated($meta->queue_item->created ?? 0); $this->setName(isset($meta->queue_item->snap) && $meta->queue_item->snap ? 'snap_' . $meta->queue_item->snap : ''); $this->setContains($contains); if(isset($meta->backup_structure_name)) $this->setStructure($meta->backup_structure_name == 'Compressed' ? BackupJob::STRUCTURE_COMPRESSED : BackupJob::STRUCTURE_ARCHIVED); $this->setSize($meta->size ?? 0); $this->addParam(Snapshot::PARAM_MULTISITE, $meta->multisite_details); $this->addParam(Snapshot::PARAM_SITE_URL, $meta->domain_backup); $schedules = []; if(isset($meta->queue_item->parent_schedules)) foreach($meta->queue_item->parent_schedules as $schedule_id) $schedules[] = (int) $schedule_id; $this->setSchedules($schedules); if($this->getContains() & BackupJob::BACKUP_ACCOUNT_CONTAINS_HOMEDIR) { $item = new SnapshotItem(); $item->setBackupType(BackupJob::TYPE_ACCOUNT); $item->setBackupContains(BackupJob::BACKUP_ACCOUNT_CONTAINS_HOMEDIR); $item->setCreated($meta->queue_item->created); $item->setName(''); $item->setPath('files/files.tar' . ($this->isCompressed() ? '.gz' : '')); $item->setSize($meta->size - $meta->db_size); $this->addItem($item); } if(isset($meta->db_items) && is_array($meta->db_items)) { foreach ($meta->db_items as $db_item) { $item = new SnapshotItem(); $item->setName($db_item->name ?? ''); $item->setPath('db_dumps/' . $db_item->name . '.sql' . ($db_item->compressed ? '.gz' : '')); $item->setSize($db_item->size ?? 0); $item->setCreated($db_item->mtime); $item->setBackupType(BackupJob::TYPE_ACCOUNT); $item->setBackupContains(BackupJob::BACKUP_ACCOUNT_CONTAINS_DATABASE); $item->addParam(Snapshot::PARAM_DB_PREFIX, $meta->db_prefix); $item->addParam(Snapshot::PARAM_DB_EXCLUDED, in_array($db_item->name, $meta->job_settings->exDatabase)); $this->addItem($item); } } $item = new SnapshotItem(); $item->setBackupType(BackupJob::TYPE_ACCOUNT); $item->setBackupContains(BackupJob::BACKUP_ACCOUNT_CONTAINS_FULL); $item->setCreated($meta->queue_item->created); $item->setName(''); $item->setPath(''); $item->setSize(0); $item->addParam(Snapshot::PARAM_MULTISITE, $meta->multisite_details); $item->addParam(Snapshot::PARAM_SITE_URL, $meta->domain_backup); $this->addItem($item); } else throw new SnapshotMetaException("Can't find meta file version"); $this->_validateImportMeta(); } /** * @return void * @throws SnapshotMetaException */ private function _validateImportMeta():void { if(!$this->getName()) throw new SnapshotMetaException("Can't find name in meta file"); if(!$this->getEngine()) throw new SnapshotMetaException("Can't find engine in meta file"); if(!$this->getBackupType()) throw new SnapshotMetaException("Can't find backup type in meta file"); if(!$this->getCreated()) throw new SnapshotMetaException("Can't find creation date in meta file"); if(!$this->getContains()) throw new SnapshotMetaException("Can't find backup contains in meta file"); if(!$this->getStructure()) throw new SnapshotMetaException("Can't find backup structure in meta file"); // Schedules are optional - manual/on-demand backups may not have schedules if(!$this->_items) throw new SnapshotMetaException("Can't find backup items in meta file"); } }