<?php namespace JetBackup\JetBackupLinux; use JetBackup\BackupJob\BackupJob; use JetBackup\Config\System; use JetBackup\Data\Engine; use JetBackup\Entities\Util; use JetBackup\Exception\DBException; use JetBackup\Exception\JetBackupLinuxException; use JetBackup\Exception\QueueException; use JetBackup\Factory; use JetBackup\JetBackup; use JetBackup\Queue\Queue; use JetBackup\Queue\QueueItem; use JetBackup\Queue\QueueItemReindex; use JetBackup\Snapshot\Snapshot; use JetBackup\SocketAPI\Client\Client; use JetBackup\SocketAPI\Exception\SocketAPIException; use JetBackup\SocketAPI\SocketAPI; use JetBackup\Wordpress\Wordpress; use SleekDB\Exceptions\InvalidArgumentException; use SleekDB\Exceptions\IOException; if (!defined( '__JETBACKUP__')) die('Direct access is not allowed'); class JetBackupLinux { const BACKUP_TYPE_ACCOUNT = 1; const BACKUP_TYPE_ACCOUNT_CONFIG = 1<<0; const BACKUP_TYPE_ACCOUNT_HOMEDIR = 1<<1; const BACKUP_TYPE_ACCOUNT_DATABASES = 1<<2; const BACKUP_TYPE_ACCOUNT_EMAILS = 1<<3; const BACKUP_TYPE_ACCOUNT_CRON_JOBS = 1<<4; const BACKUP_TYPE_ACCOUNT_DOMAINS = 1<<5; const BACKUP_TYPE_ACCOUNT_CERTIFICATES = 1<<6; const BACKUP_TYPE_ACCOUNT_DATABASE_USERS = 1<<7; const BACKUP_TYPE_ACCOUNT_FTP = 1<<8; const BACKUP_TYPE_ACCOUNT_FULL = self::BACKUP_TYPE_ACCOUNT_CONFIG | self::BACKUP_TYPE_ACCOUNT_HOMEDIR | self::BACKUP_TYPE_ACCOUNT_DATABASES | self::BACKUP_TYPE_ACCOUNT_EMAILS | self::BACKUP_TYPE_ACCOUNT_CRON_JOBS | self::BACKUP_TYPE_ACCOUNT_DOMAINS | self::BACKUP_TYPE_ACCOUNT_CERTIFICATES | self::BACKUP_TYPE_ACCOUNT_DATABASE_USERS | self::BACKUP_TYPE_ACCOUNT_FTP; const BACKUP_STRUCTURE_INCREMENTAL = 1; const BACKUP_STRUCTURE_ARCHIVED = 2; const BACKUP_STRUCTURE_COMPRESSED = 4; const QUEUE_STATUS_RESTORE_ACCOUNT_CONFIG = 30; const QUEUE_STATUS_RESTORE_ACCOUNT_DOMAINS = 31; const QUEUE_STATUS_RESTORE_ACCOUNT_CERTIFICATES = 32; const QUEUE_STATUS_RESTORE_ACCOUNT_FTP = 33; const QUEUE_STATUS_RESTORE_ACCOUNT_CRON_JOBS = 34; const QUEUE_STATUS_RESTORE_ACCOUNT_IMPORTING_DATABASES = 35; const QUEUE_STATUS_RESTORE_ACCOUNT_DATABASES = 36; const QUEUE_STATUS_RESTORE_ACCOUNT_DATABASE_USERS = 37; const QUEUE_STATUS_RESTORE_ACCOUNT_HOMEDIR = 38; const QUEUE_STATUS_RESTORE_ACCOUNT_EMAILS = 39; const QUEUE_STATUS_RESTORE_ACCOUNT_POST_RESTORE = 40; const QUEUE_STATUS_RESTORE_ACCOUNT_PRE_RESTORE = 41; const QUEUE_STATUS_RESTORE_MAPPING = [ self::QUEUE_STATUS_RESTORE_ACCOUNT_IMPORTING_DATABASES => Queue::STATUS_RESTORE_JB_IMPORTING_DB, self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASES => Queue::STATUS_RESTORE_JB_DATABASES, self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASE_USERS => Queue::STATUS_RESTORE_JB_DATABASE_USERS, self::QUEUE_STATUS_RESTORE_ACCOUNT_HOMEDIR => Queue::STATUS_RESTORE_JB_HOMEDIR, self::QUEUE_STATUS_RESTORE_ACCOUNT_POST_RESTORE => Queue::STATUS_RESTORE_JB_POST, self::QUEUE_STATUS_RESTORE_ACCOUNT_PRE_RESTORE => Queue::STATUS_RESTORE_JB_PRE, ]; const QUEUE_STATUS_RESTORE_ACCOUNT_NAMES = [ self::QUEUE_STATUS_RESTORE_ACCOUNT_CONFIG => "Restoring Panel Configurations", self::QUEUE_STATUS_RESTORE_ACCOUNT_DOMAINS => "Restoring Domains and DNS", self::QUEUE_STATUS_RESTORE_ACCOUNT_CERTIFICATES => "Restoring SSL Certificates", self::QUEUE_STATUS_RESTORE_ACCOUNT_FTP => "Restoring FTP Accounts", self::QUEUE_STATUS_RESTORE_ACCOUNT_CRON_JOBS => "Restoring Cron Jobs", self::QUEUE_STATUS_RESTORE_ACCOUNT_IMPORTING_DATABASES => "Importing databases data", self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASES => "Restoring Databases", self::QUEUE_STATUS_RESTORE_ACCOUNT_DATABASE_USERS => "Restoring Database Users", self::QUEUE_STATUS_RESTORE_ACCOUNT_HOMEDIR => "Restoring Home Directory files", self::QUEUE_STATUS_RESTORE_ACCOUNT_EMAILS => "Restoring Email Accounts", self::QUEUE_STATUS_RESTORE_ACCOUNT_POST_RESTORE => "Performing post restore actions", self::QUEUE_STATUS_RESTORE_ACCOUNT_PRE_RESTORE => "Performing pre restore actions", ]; const QUEUE_STATUS_COMPLETED = 100; const QUEUE_STATUS_PARTIALLY_COMPLETED = 101; const QUEUE_STATUS_FAILED = 102; const QUEUE_STATUS_ABORTED = 103; const QUEUE_STATUS_NEVER_FINISHED = 104; const QUEUE_STATUS_MAPPING = [ self::QUEUE_STATUS_COMPLETED => Queue::STATUS_DONE, self::QUEUE_STATUS_PARTIALLY_COMPLETED => Queue::STATUS_PARTIALLY, self::QUEUE_STATUS_FAILED => Queue::STATUS_FAILED, self::QUEUE_STATUS_ABORTED => Queue::STATUS_ABORTED, self::QUEUE_STATUS_NEVER_FINISHED => Queue::STATUS_NEVER_FINISHED, ]; const QUEUE_TYPE_RESTORE = 1<<1; const MINIMUM_VERSION = '5.3.15'; private function __construct() {} public static function isEnabled():bool { return Factory::getSettingsGeneral()->isJBIntegrationEnabled() && self::isInstalled(); } /** * Return true will allow to check if socket file exist, we will not continue to check for socket file is this return false * @return bool */ private static function isOpenBaseDir(): bool { $openBasedir = ini_get('open_basedir'); if (!$openBasedir) return true; $socket = realpath(Client::SOCKET_FILE) ?: Client::SOCKET_FILE; foreach (explode(PATH_SEPARATOR, $openBasedir) as $allowed) { $allowed = trim($allowed); if ($allowed === '') continue; // Normalize to "dir/" form $allowedReal = realpath($allowed) ?: $allowed; $allowedReal = rtrim($allowedReal, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; if (str_starts_with($socket, $allowedReal)) return true; } return false; } private static function isSocket(): bool { return file_exists(Client::SOCKET_FILE); } /** * Using this in the background for different implementations, just to verify if JBLinux is installed * @return bool */ public static function isInstalled():bool { return !System::isWindowsOS() && self::isOpenBaseDir() && self::isSocket(); } /** * Using this for Ajax validation * @return void * @throws JetBackupLinuxException */ public static function checkRequirements():void { // Check for blocking requirements first (these prevent further checks) if(System::isWindowsOS()) throw new JetBackupLinuxException("This feature is not supported in Windows operating system"); if(!self::isOpenBaseDir()) throw new JetBackupLinuxException("'open_basedir' restriction is enabled, make sure " . dirname(Client::SOCKET_FILE) . " is available"); // Collect all missing function errors at once for better user experience $missingFunctions = []; if(!Util::has_posix_getpwuid()) $missingFunctions[] = 'posix_getpwuid'; if(!Util::has_posix_getgrgid()) $missingFunctions[] = 'posix_getgrgid'; if(!Util::has_posix_geteuid()) $missingFunctions[] = 'posix_geteuid'; if(!function_exists('socket_connect')) $missingFunctions[] = 'socket_connect'; if(!empty($missingFunctions)) { $functionList = implode(', ', $missingFunctions); $plural = count($missingFunctions) > 1 ? 's' : ''; throw new JetBackupLinuxException("Required function{$plural} '{$functionList}' disabled or not installed. Enable to allow Socket API."); } if (!self::isSocket()) throw new JetBackupLinuxException("Cannot find JetBackup Linux socket"); try { $response = SocketAPI::api('getMyAccount')->execute(); } catch(SocketAPIException $e) { throw new JetBackupLinuxException($e->getMessage()); } if(!isset($response['system']['version'])) throw new JetBackupLinuxException("Failed fetching JetBackup linux version"); if(version_compare(self::MINIMUM_VERSION, $response['system']['version'], '>')) throw new JetBackupLinuxException("JetBackup linux version is not compatible ({$response['system']['version']}), Minimum required version is " . self::MINIMUM_VERSION); } /** * @return array|mixed * @throws JetBackupLinuxException * @throws SocketAPIException */ public static function getAccountInfo() { return Query::api('getMyAccount')->execute(); } /** * @param string $id * @param string $path * @param int $limit * @param int $skip * @param array $sort * * @return array|mixed * @throws JetBackupLinuxException * @throws SocketAPIException */ public static function fileManager(string $id, string $path, int $limit=0, int $skip=0, array $sort=[]) { $query = Query::api('fileManager') ->arg('type', 'backup') ->arg('_id', $id) ->arg('path', $path); if($limit || $skip) $query->limit($limit, $skip); foreach($sort as $key => $direction) { if($direction > 0) $query->sortAsc($key); else if($direction < 0) $query->sortDesc($key); } return $query->execute(); } /** * @param array $items * @param array $files * @param array $options * * @return array|mixed * @throws JetBackupLinuxException * @throws SocketAPIException */ public static function addQueueItems(array $items, array $files=[], array $options=[]) { // Force exclude plugin's main folder and datadir if(!isset($options['exclude'])) $options['exclude'] = []; $options['exclude'][] = Factory::getWPHelper()->getWordPressRelativePublicDir() . JetBackup::SEP . Wordpress::WP_CONTENT . JetBackup::SEP . Wordpress::WP_PLUGINS . JetBackup::SEP . JetBackup::PLUGIN_NAME; if (Factory::getLocations()->getPublicDataDir()) { $options['exclude'][] = Factory::getWPHelper()->getWordPressRelativePublicDir() . JetBackup::SEP . Factory::getLocations()->getPublicDataDir(); } else { $options['exclude'][] = Factory::getLocations()->getRelativeDataDir(); } return Query::api('addQueueItems') ->arg('type', self::QUEUE_TYPE_RESTORE) ->arg('items', $items) ->arg('files', $files) ->arg('options', $options) ->execute(); } /** * @param array $sort * * @return array * @throws JetBackupLinuxException|SocketAPIException */ public static function listQueueGroups(array $sort=[]):array { $query = Query::api('listQueueGroups') ->arg('type', self::QUEUE_TYPE_RESTORE) ->limit(9999999); foreach($sort as $key => $direction) { if($direction > 0) $query->sortAsc($key); else if($direction < 0) $query->sortDesc($key); } $response = $query->execute(); return $response['groups']; } /** * @param string $group_id * * @return array * @throws JetBackupLinuxException|SocketAPIException */ public static function getQueueGroup(string $group_id) { return Query::api('getQueueGroup')->arg('_id', $group_id)->execute(); } /** * @param string $group_id * @param array $sort * * @return array * @throws JetBackupLinuxException|SocketAPIException */ public static function listQueueItems(string $group_id, array $sort=[]):array { $query = Query::api('listQueueItems') ->arg('group_id', $group_id) ->limit(9999999); foreach($sort as $key => $direction) { if($direction > 0) $query->sortAsc($key); else if($direction < 0) $query->sortDesc($key); } $response = $query->execute(); return $response['items']; } /** * @param array $sort * * @return array * @throws JetBackupLinuxException * @throws SocketAPIException */ public static function listBackups(array $sort=[], int $limit=25, int $skip=0):array { $query = Query::api('listBackupsWithItems') ->arg('type', self::BACKUP_TYPE_ACCOUNT) ->arg('structure', self::BACKUP_STRUCTURE_INCREMENTAL) ->limit($limit, $skip); foreach($sort as $key => $direction) { if($direction > 0) $query->sortAsc($key); else if($direction < 0) $query->sortDesc($key); } $response = $query->execute(); return $response['backups']; } /** * @return void * @throws IOException * @throws InvalidArgumentException * @throws DBException */ public static function deleteSnapshots():void { $list = Snapshot::query() ->where([Engine::ENGINE, '=', Engine::ENGINE_JB]) ->getQuery() ->fetch(); if (empty($list)) return; foreach ($list as $item) { $snapshot = new Snapshot($item[JetBackup::ID_FIELD]); if(!$snapshot->getId()) continue; $snapshot->delete(); } } /** * @return void * @throws IOException * @throws InvalidArgumentException * @throws QueueException * @throws JetBackupLinuxException */ public static function addToQueue():void { if(!Factory::getSettingsGeneral()->isJBIntegrationEnabled()) throw new JetBackupLinuxException("JetBackup Linux integration is disabled"); if(!self::isInstalled()) throw new JetBackupLinuxException("JetBackup Linux integration is not installed"); $reindex = new QueueItemReindex(); $queue_item = QueueItem::prepare(); $queue_item->setType(Queue::QUEUE_TYPE_REINDEX); $queue_item->setItemId(0); $queue_item->setItemData($reindex); Queue::addToQueue($queue_item); } public static function prepareFilesForRestore($homedir_snapID, $fileManger): array { $public_dir = trim(Factory::getWPHelper()->getWordPressHomedir(true), JetBackup::SEP); $files = []; if(!sizeof($fileManger)) return $files; foreach ($fileManger as $file) { $path = JetBackup::SEP . $public_dir . JetBackup::SEP . trim($file['path'], JetBackup::SEP); $files[$homedir_snapID][$path] = $file['type']; } return $files; } }