Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup\Adapter;
|
||||
|
||||
use Xentral\Components\Database\DatabaseConfig;
|
||||
|
||||
/**
|
||||
* Interface AdapterInterface
|
||||
*
|
||||
* @package Xentral\Components\Backup\Adapter
|
||||
*/
|
||||
interface AdapterInterface
|
||||
{
|
||||
/** @var string STATUS_WORKING */
|
||||
const STATUS_WORKING = 'working';
|
||||
|
||||
/** @var string STATUS_WAIT */
|
||||
const STATUS_WAITING = 'waiting';
|
||||
|
||||
/**
|
||||
* Makes MySQL DUMP
|
||||
*
|
||||
* @param DatabaseConfig $config
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @param null|string|array $sTable
|
||||
*
|
||||
* @param null|string $where
|
||||
*
|
||||
* @param bool $quickMode Without SET INNODB_STRICT_MODE=0; Advantage quick and space-saving
|
||||
*
|
||||
* @return int PidFile
|
||||
*/
|
||||
public function createDump(DatabaseConfig $config, $file, $sTable = null, $where = null, $quickMode=true);
|
||||
|
||||
/**
|
||||
* Makes Backup or System template recovery
|
||||
*
|
||||
* @param DatabaseConfig $config
|
||||
* @param string $file
|
||||
*
|
||||
* @return int pidFile
|
||||
*/
|
||||
public function restoreDump(DatabaseConfig $config, $file);
|
||||
|
||||
/**
|
||||
* returns the current status
|
||||
*
|
||||
* @param string $pidFile
|
||||
*
|
||||
* @return string|self::STATUS_WORKING|self::STATUS_WAITING
|
||||
*/
|
||||
public function getStatus($pidFile);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup\Adapter;
|
||||
|
||||
use Xentral\Components\Database\DatabaseConfig;
|
||||
|
||||
final class ExecAdapter implements AdapterInterface
|
||||
{
|
||||
|
||||
/** @var DatabaseConfig $config */
|
||||
private $config;
|
||||
|
||||
/** @var int timeout */
|
||||
const TIME_OUT = 3600;
|
||||
|
||||
/**
|
||||
* @param DatabaseConfig $config
|
||||
* @param string $file
|
||||
*
|
||||
* @param null|string|array $tables
|
||||
*
|
||||
* @param null|string $where
|
||||
*
|
||||
* @param bool $quickMode Without SET INNODB_STRICT_MODE=0; Advantage quick and space-saving
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createDump(DatabaseConfig $config, $file, $tables = null, $where = null, $quickMode = true)
|
||||
{
|
||||
$this->config = $config;
|
||||
$sAsBackup = $this->config->getDatabase();
|
||||
if ($tables !== null) {
|
||||
if (is_array($tables)) {
|
||||
$tables = implode(' ', $tables);
|
||||
}
|
||||
$sAsBackup .= ' --tables ' . $tables;
|
||||
}
|
||||
if ($where !== null) {
|
||||
$sAsBackup .= " --where=\"$where\"";
|
||||
}
|
||||
if ($quickMode !== true) {
|
||||
$cmd = "echo 'SET INNODB_STRICT_MODE=0;' > {$file} && mysqldump --no-tablespaces --extended-insert {$sAsBackup} --no-create-db -h{$this->config->getHostname()} -u{$this->config->getUsername()} -p'{$this->config->getPassword()}' >> {$file} && gzip -c {$file} > " . $file . '.gz && rm -f' . $file;
|
||||
} else {
|
||||
$cmd = "mysqldump --no-tablespaces --extended-insert {$sAsBackup} --no-create-db -h{$this->config->getHostname()} -u{$this->config->getUsername()} -p'{$this->config->getPassword()}' | gzip > " . $file . '.gz';
|
||||
}
|
||||
$this->execute($cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DatabaseConfig $config
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restoreDump(DatabaseConfig $config, $file)
|
||||
{
|
||||
$this->config = $config;
|
||||
$cmd = "gunzip < {$file} | mysql -D{$this->config->getDatabase()} -h{$this->config->getHostname()} -u{$this->config->getUsername()} -p'{$this->config->getPassword()}'";
|
||||
$this->execute($cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pidFile
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus($pidFile)
|
||||
{
|
||||
if (file_exists($pidFile) && ($time = file_get_contents($pidFile)) && (time() - (int)$time) < static::TIME_OUT) {
|
||||
return AdapterInterface::STATUS_WORKING;
|
||||
}
|
||||
|
||||
return AdapterInterface::STATUS_WAITING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cmd
|
||||
*/
|
||||
protected function execute($cmd)
|
||||
{
|
||||
@exec($cmd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup;
|
||||
|
||||
use ApplicationCore;
|
||||
use Xentral\Components\Backup\Exception\BackupException;
|
||||
use Xentral\Components\Backup\Adapter\ExecAdapter;
|
||||
use Xentral\Components\Backup\Logger\BackupLog;
|
||||
use Xentral\Core\DependencyInjection\ContainerInterface;
|
||||
|
||||
final class Bootstrap
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function registerServices()
|
||||
{
|
||||
return [
|
||||
'DatabaseBackup' => 'onInitDatabaseBackup',
|
||||
'FileBackup' => 'onInitFileBackup',
|
||||
'BackupLog' => 'onInitBackupLogger',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return DatabaseBackup
|
||||
*/
|
||||
public static function onInitDatabaseBackup(ContainerInterface $container)
|
||||
{
|
||||
//@codeCoverageIgnoreStart
|
||||
if (!function_exists('exec')) {
|
||||
throw new BackupException(sprintf('function "%s" is missing!', 'exec'));
|
||||
}
|
||||
//@codeCoverageIgnoreEnd
|
||||
/** @var ApplicationCore $app */
|
||||
$app = $container->get('LegacyApplication');
|
||||
|
||||
return new DatabaseBackup(new ExecAdapter(), $app->erp->getTMP());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return FileBackup
|
||||
*/
|
||||
public static function onInitFileBackup(ContainerInterface $container)
|
||||
{
|
||||
/** @var ApplicationCore $app */
|
||||
$app = $container->get('LegacyApplication');
|
||||
|
||||
return new FileBackup($container->get('BackupLog'), $app->erp->getTMP());
|
||||
}
|
||||
|
||||
public static function onInitBackupLogger(ContainerInterface $container)
|
||||
{
|
||||
/** @var ApplicationCore $app */
|
||||
$app = $container->get('LegacyApplication');
|
||||
|
||||
/** @var string $path */
|
||||
$path = $app->erp->GetRootPath() . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR;
|
||||
|
||||
return new BackupLog($path, 'status.txt');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup;
|
||||
|
||||
use Xentral\Components\Backup\Adapter\AdapterInterface;
|
||||
use Xentral\Components\Database\DatabaseConfig;
|
||||
use Xentral\Components\Backup\Exception\BackupException;
|
||||
|
||||
final class DatabaseBackup
|
||||
{
|
||||
/**
|
||||
* @var AdapterInterface
|
||||
*/
|
||||
private $adapter;
|
||||
|
||||
/** @var string $tmpPath */
|
||||
private $tmpPath;
|
||||
|
||||
/** @var string lock */
|
||||
const PID_NAME = 'backup.lock';
|
||||
|
||||
/**
|
||||
* DatabaseBackup constructor.
|
||||
*
|
||||
* @param AdapterInterface $adapter
|
||||
*
|
||||
* @param string $tmpPath
|
||||
*/
|
||||
public function __construct(AdapterInterface $adapter, $tmpPath)
|
||||
{
|
||||
$this->adapter = $adapter;
|
||||
$this->tmpPath = $tmpPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates MySQL Dump
|
||||
*
|
||||
* @param DatabaseConfig $config
|
||||
* @param string $file
|
||||
* @param null|string|array $sTable
|
||||
*
|
||||
* @param null|string $where
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createDump(DatabaseConfig $config, $file, $sTable = null, $where = null)
|
||||
{
|
||||
$sPidFile = $this->getLockFile();
|
||||
file_put_contents($sPidFile, time());
|
||||
$this->adapter->createDump($config, $file, $sTable, $where);
|
||||
@unlink($sPidFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores Database DUMP
|
||||
*
|
||||
* @param DatabaseConfig $config
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restoreDump(DatabaseConfig $config, $file)
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
throw new BackupException(sprintf('Database Dump %s not found!', $file));
|
||||
}
|
||||
$sPidFile = $this->getLockFile();
|
||||
file_put_contents($sPidFile, time());
|
||||
$this->adapter->restoreDump($config, $file);
|
||||
@unlink($sPidFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $metaFile
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMetaInfo($metaFile)
|
||||
{
|
||||
if (!empty($metaFile) && file_exists($metaFile) && ($sMetaEnc = file_get_contents($metaFile))) {
|
||||
return $this->decodeJson(base64_decode($sMetaEnc), true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sJSON
|
||||
* @param bool $bAsHash
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function decodeJson($sJSON, $bAsHash = false)
|
||||
{
|
||||
if (($xData = json_decode($sJSON, $bAsHash)) !== null
|
||||
&& (json_last_error() === JSON_ERROR_NONE)) {
|
||||
return $xData;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string|AdapterInterface
|
||||
*/
|
||||
public function getLockStatus()
|
||||
{
|
||||
return $this->adapter->getStatus($this->getLockFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getLockFile()
|
||||
{
|
||||
return rtrim($this->tmpPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . static::PID_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $tables
|
||||
* @param array $excludeKeys
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function excludeCheckSumTables($tables, $excludeKeys = [])
|
||||
{
|
||||
$default = [
|
||||
'backup',
|
||||
'useronline',
|
||||
'logfile',
|
||||
'cronjob_starter_running',
|
||||
'wiki',
|
||||
'protokoll',
|
||||
'cronjob_log',
|
||||
'module_stat',
|
||||
'checkaltertable',
|
||||
'konfiguration',
|
||||
'permissionhistory',
|
||||
'adapterbox_request_log',
|
||||
'hook',
|
||||
'module_action',
|
||||
'prozessstarter',
|
||||
'sqlcache',
|
||||
'systemhealth',
|
||||
'userkonfiguration',
|
||||
'artikel',
|
||||
'shopimport_amazon_throttling',
|
||||
'lieferschein',
|
||||
'report_column',
|
||||
'report_parameter',
|
||||
'notification_message',
|
||||
'module_stat_detail',
|
||||
];
|
||||
$excludeKeys = array_merge($default, $excludeKeys);
|
||||
foreach ($excludeKeys as $key) {
|
||||
if (!array_key_exists($key, $tables)) {
|
||||
continue;
|
||||
}
|
||||
unset($tables[$key]);
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $backupFile
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMetaFileName($backupFile)
|
||||
{
|
||||
$asFile = explode('.', $backupFile);
|
||||
array_pop($asFile);
|
||||
$filename = implode('.', $asFile) . '.meta';
|
||||
|
||||
return str_replace('.backup', 'backup/snapshots', $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDumpMetaData($filePath = null)
|
||||
{
|
||||
return $this->getMetaInfo($this->getMetaFileName($filePath));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class BackupException extends RuntimeException implements BackupExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup\Exception;
|
||||
|
||||
use Xentral\Core\Exception\ComponentExceptionInterface;
|
||||
|
||||
/**
|
||||
* Interface BackupExceptionInterface
|
||||
*
|
||||
* @package Xentral\Components\Backup\Exception
|
||||
*/
|
||||
interface BackupExceptionInterface extends ComponentExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Components\Backup\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
|
||||
class LogException extends RuntimeException implements BackupExceptionInterface
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Components\Backup;
|
||||
|
||||
use PHPUnit\Runner\Exception;
|
||||
use Xentral\Components\Backup\Logger\BackupLog;
|
||||
use Xentral\Components\Backup\Exception\BackupException;
|
||||
use ZipArchive;
|
||||
|
||||
final class FileBackup implements FileBackupInterface
|
||||
{
|
||||
|
||||
/** @var string backup path */
|
||||
private $sUserPath;
|
||||
|
||||
/** @var BackupLog $logger */
|
||||
private $logger;
|
||||
|
||||
/** @var string $cacheTmp */
|
||||
private $cacheTmp;
|
||||
|
||||
/**
|
||||
* @param BackupLog $logger
|
||||
* @param string $cacheTmp
|
||||
*/
|
||||
public function __construct(BackupLog $logger, $cacheTmp)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->cacheTmp = $cacheTmp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getMainPath()
|
||||
{
|
||||
$asPath = explode(DIRECTORY_SEPARATOR, $this->sUserPath);
|
||||
array_pop($asPath);
|
||||
|
||||
return implode(DIRECTORY_SEPARATOR, $asPath) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the full file name path
|
||||
*
|
||||
* @param string $filename
|
||||
* @param bool $bIsSnapshots
|
||||
* @param null $userPath
|
||||
*
|
||||
* @throws BackupException
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalPath($filename, $userPath = null, $bIsSnapshots = true)
|
||||
{
|
||||
if (null !== $userPath) {
|
||||
$this->sUserPath = $userPath;
|
||||
}
|
||||
$path = $this->getMainPath();
|
||||
if ($bIsSnapshots === true) {
|
||||
$path .= FileBackupInterface::SNAPSHOTS_FOLDER . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
if (!file_exists($path) && !@mkdir($path) && !is_dir($path)) {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $path));
|
||||
throw new BackupException(sprintf('Directory "%s" was not created', $path));
|
||||
}
|
||||
|
||||
return $path . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function tmpDir()
|
||||
{
|
||||
return $this->getMainPath() . 'backup/.backup' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSnapshotsDir()
|
||||
{
|
||||
return $this->getMainPath() . FileBackupInterface::SNAPSHOTS_FOLDER . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected function addLock($path)
|
||||
{
|
||||
return $this->logger->write(time(), $path, FileBackupInterface::PID_FILE, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function tryPurgePidFile()
|
||||
{
|
||||
$pidFile = $this->tmpDir() . FileBackupInterface::PID_FILE;
|
||||
|
||||
$time = file_get_contents($pidFile);
|
||||
|
||||
if ((time() - (int)$time > FileBackupInterface::TIME_OUT)) {
|
||||
return unlink($pidFile);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $userPath
|
||||
*
|
||||
* @throws BackupException
|
||||
* @return string|null
|
||||
*/
|
||||
public function begin($userPath = null)
|
||||
{
|
||||
if (null !== $userPath) {
|
||||
$this->sUserPath = $userPath;
|
||||
}
|
||||
$path = $this->tmpDir();
|
||||
|
||||
if (is_dir($path)) {
|
||||
@exec('rm -rf ' . $path);
|
||||
}
|
||||
|
||||
$backupDir = $this->getMainPath() . 'backup';
|
||||
if (file_exists($backupDir . DIRECTORY_SEPARATOR . 'status.txt')) {
|
||||
@unlink($backupDir . DIRECTORY_SEPARATOR . 'status.txt');
|
||||
}
|
||||
|
||||
if (file_exists($backupDir . DIRECTORY_SEPARATOR . 'session.txt')) {
|
||||
@unlink($backupDir . DIRECTORY_SEPARATOR . 'session.txt');
|
||||
}
|
||||
|
||||
if (!file_exists($path) && !@mkdir($path, 0777, true) && !is_dir($path)) {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $path));
|
||||
throw new BackupException(sprintf('Directory "%s" was not created', $path));
|
||||
}
|
||||
|
||||
if ($this->getLockStatus() === FileBackupInterface::STATUS_WORKING && $this->tryPurgePidFile() === false) {
|
||||
return null;
|
||||
//throw new BackupException(sprintf('Backup is Running'));
|
||||
}
|
||||
|
||||
if ($this->addLock($path) === false) {
|
||||
$this->logger->writePersistent('Failed start backup');
|
||||
throw new BackupException('Failed start backup');
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function cleanUp($file)
|
||||
{
|
||||
$path = $this->tmpDir();
|
||||
if ($this->moveDir($path . $file, $this->getLocalPath($file)) === true) {
|
||||
return $this->deleteDir($path);
|
||||
}
|
||||
$this->logger->writePersistent(sprintf('Clean Up of %s failed', $path));
|
||||
throw new BackupException(sprintf('Clean Up of %s failed', $path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class_name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function classExists($class_name)
|
||||
{
|
||||
return class_exists($class_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipArchive $oZip
|
||||
* @param string $fileName
|
||||
* @param int $flags
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function openZipObject($oZip, $fileName, $flags = 0)
|
||||
{
|
||||
return $oZip->open($fileName, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $oldDir
|
||||
* @param string $newDir
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function moveDir($oldDir, $newDir)
|
||||
{
|
||||
return @rename($oldDir, $newDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDir($dir)
|
||||
{
|
||||
return @mkdir($dir) || is_dir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBackupExtension()
|
||||
{
|
||||
return FileBackupInterface::COMPRESS_EXTENSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename Zipped file name
|
||||
* @param string $userPath local directory to backup
|
||||
* @param string|null $sMySQLFile MySQL Backup file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function createBackup($filename, $userPath, $sMySQLFile = null)
|
||||
{
|
||||
$this->sUserPath = $userPath;
|
||||
$rootPath = realpath($userPath);
|
||||
|
||||
if (!file_exists($userPath)) {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" was not found', $userPath));
|
||||
throw new BackupException(sprintf('Directory "%s" was not found', $userPath));
|
||||
}
|
||||
|
||||
$tmpFilename = $this->tmpDir() . $filename;
|
||||
$sMySQLFullPath = $this->tmpDir() . $sMySQLFile;
|
||||
|
||||
if (null !== $sMySQLFile && is_file($sMySQLFullPath) && filesize($sMySQLFullPath) > 1024) {
|
||||
$this->logger->write('Add MySQL file to Zip');
|
||||
exec('cd ' . $rootPath . ' && mv ' . $sMySQLFullPath . ' ' . $sMySQLFile);
|
||||
}
|
||||
|
||||
exec('cd ' . $rootPath . ' && zip -r -9 ' . $tmpFilename . ' * .[^.]* -x "wiki/*"');
|
||||
|
||||
if (null !== $sMySQLFile) {
|
||||
exec('cd ' . $rootPath . ' && rm -f ' . $sMySQLFile);
|
||||
}
|
||||
|
||||
return $this->cleanUp($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dirPath
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function deleteDir($dirPath)
|
||||
{
|
||||
if (is_dir($dirPath)) {
|
||||
if (substr($dirPath, strlen($dirPath) - 1, 1) !== '/') {
|
||||
$dirPath .= '/';
|
||||
}
|
||||
$files = glob($dirPath . '*', GLOB_MARK);
|
||||
foreach ($files as $file) {
|
||||
if (is_dir($file)) {
|
||||
$this->deleteDir($file);
|
||||
} else {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($dirPath);
|
||||
}
|
||||
$this->logger->writePersistent(sprintf('Deleted DIR %s failed', $dirPath));
|
||||
throw new BackupException(sprintf('Deleted DIR %s failed', $dirPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $backupFile
|
||||
* @param string $userPath
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function restoreFileSystem($backupFile, $userPath, $options = [])
|
||||
{
|
||||
$default = ['template_file_dir' => null, 'exclude_dir' => ['wiki']];
|
||||
$options = array_merge($default, $options);
|
||||
$templateFileDir = $options['template_file_dir'];
|
||||
$this->sUserPath = $userPath;
|
||||
$bIsSnapshots = null === $templateFileDir;
|
||||
|
||||
$this->sUserPath = null === $templateFileDir ? $userPath : $templateFileDir;
|
||||
|
||||
if (file_exists($file = $this->getLocalPath($backupFile, null, $bIsSnapshots))) {
|
||||
$userDataPath = realpath($userPath);
|
||||
$tmpExtract = $this->tmpDir() . FileBackupInterface::LOCAL_FILES_DIR_NAME . 'tmp';
|
||||
if (!file_exists($tmpExtract) && !@mkdir($tmpExtract) && !is_dir($tmpExtract)) {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $tmpExtract));
|
||||
throw new BackupException(sprintf('Directory "%s" was not created', $tmpExtract));
|
||||
}
|
||||
|
||||
if (!$this->classExists('ZipArchive')) {
|
||||
$this->logger->writePersistent('Class ZipArchive is missing!');
|
||||
throw new BackupException('Class ZipArchive is missing!');
|
||||
}
|
||||
|
||||
$oZip = new ZipArchive();
|
||||
|
||||
if ($this->openZipObject($oZip, $file, ZipArchive::CHECKCONS) !== true) {
|
||||
$this->logger->writePersistent(sprintf('Failure to open file in "%s"', $file));
|
||||
throw new BackupException(sprintf('Failure to open file in "%s"', $file));
|
||||
}
|
||||
|
||||
$oZip->extractTo($tmpExtract);
|
||||
$oZip->close();
|
||||
|
||||
// move user data
|
||||
$shortTmp = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.rmTmp';
|
||||
$sBeforeTmp = $shortTmp . uniqid('', true) . 'before';
|
||||
|
||||
if (!$this->isDir($sBeforeTmp)) {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $sBeforeTmp));
|
||||
throw new BackupException(sprintf('Directory "%s" was not created', $sBeforeTmp));
|
||||
}
|
||||
|
||||
$this->logger->write('Moving userdata away');
|
||||
|
||||
if (!$this->moveDir($userDataPath, $sBeforeTmp)) {
|
||||
$this->logger->writePersistent(sprintf('Moving %s into %s failed! ', $userDataPath, $sBeforeTmp));
|
||||
throw new BackupException(sprintf('Moving %s into %s failed! ', $userDataPath, $sBeforeTmp));
|
||||
}
|
||||
|
||||
if (array_key_exists('exclude_dir', $options) && is_array($options['exclude_dir'])) {
|
||||
$this->excludeDirectory($options['exclude_dir'], $tmpExtract, $sBeforeTmp);
|
||||
}
|
||||
|
||||
$this->logger->write('Recovering userData');
|
||||
if (!$this->moveDir($tmpExtract, $userDataPath)) {
|
||||
$this->logger->writePersistent(sprintf('Moving %s into %s failed!', $tmpExtract, $userDataPath));
|
||||
throw new BackupException(sprintf('Moving %s into %s failed!', $tmpExtract, $userDataPath));
|
||||
}
|
||||
|
||||
// FIX TMP ISSUE
|
||||
if (!empty($this->cacheTmp) && is_dir($this->cacheTmp)) {
|
||||
$this->logger->write('Delete DB tmp');
|
||||
$this->deleteDir($this->cacheTmp);
|
||||
}
|
||||
|
||||
// remove DB if exists
|
||||
$backupFileExploded = explode('.', $backupFile);
|
||||
array_pop($backupFileExploded);
|
||||
$tmpSql = implode('.', $backupFileExploded) . '.sql.gz';
|
||||
if (is_file($userDataPath . DIRECTORY_SEPARATOR . $tmpSql)) {
|
||||
exec('cd ' . $userDataPath . ' && rm -f ' . $tmpSql);
|
||||
}
|
||||
|
||||
return $this->deleteDir($this->tmpDir()) && $this->deleteDir($sBeforeTmp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $excludeDir
|
||||
* @param string $tmpDir extracted temporally directory
|
||||
* @param string $oldUserDataDir
|
||||
*/
|
||||
protected function excludeDirectory($excludeDir = [], $tmpDir, $oldUserDataDir)
|
||||
{
|
||||
foreach ($excludeDir as $directory) {
|
||||
// EXCLUDE WIKI DIRECTORY
|
||||
$keepPath = $tmpDir . DIRECTORY_SEPARATOR . $directory;
|
||||
$keepDirTmp = $oldUserDataDir . DIRECTORY_SEPARATOR . $directory;
|
||||
|
||||
if (!is_dir($keepPath) && $directory !== 'wiki') {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" cannot be skipped', $keepPath));
|
||||
throw new BackupException(sprintf('Directory "%s" cannot be skipped', $keepPath));
|
||||
}
|
||||
|
||||
if ($directory !== 'wiki') {
|
||||
$this->deleteDir($keepPath);
|
||||
}
|
||||
|
||||
if (file_exists($keepDirTmp)) {
|
||||
$oldDirTmp = rtrim(
|
||||
sys_get_temp_dir(),
|
||||
DIRECTORY_SEPARATOR
|
||||
) . DIRECTORY_SEPARATOR . '.' . $directory . 'Tmp' . uniqid('', true);
|
||||
if (!$this->isDir($oldDirTmp)) {
|
||||
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $oldDirTmp));
|
||||
throw new BackupException(sprintf('Directory "%s" was not created', $oldDirTmp));
|
||||
}
|
||||
$oldDir = $oldDirTmp . DIRECTORY_SEPARATOR . $directory;
|
||||
if (!$this->moveDir($keepDirTmp, $oldDir)) {
|
||||
$this->logger->writePersistent(sprintf('Could not move %s directory into "%s"', $directory,
|
||||
$oldDir));
|
||||
throw new BackupException(sprintf('Could not move %s directory into "%s"', $directory,
|
||||
$oldDir));
|
||||
}
|
||||
}
|
||||
|
||||
// Reset Latest WIKI Directory
|
||||
if (isset($oldDir) && is_dir($oldDir)) {
|
||||
$this->logger->write('Reset Wiki Directory');
|
||||
|
||||
if (!$this->moveDir($oldDir, $keepPath)) {
|
||||
$this->logger->writePersistent(sprintf('Could not move %s directory into "%s"', $oldDir,
|
||||
$keepPath));
|
||||
throw new BackupException(sprintf('Could not move %s directory into "%s"', $oldDir, $keepPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $userDataDir
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLockStatus($userDataDir = null)
|
||||
{
|
||||
if (null !== $userDataDir) {
|
||||
$this->sUserPath = $userDataDir;
|
||||
}
|
||||
$path = $this->tmpDir();
|
||||
if (file_exists($path . FileBackupInterface::PID_FILE) &&
|
||||
($time = file_get_contents($path . FileBackupInterface::PID_FILE)) &&
|
||||
(time() - (int)$time < FileBackupInterface::TIME_OUT)
|
||||
) {
|
||||
return FileBackupInterface::STATUS_WORKING;
|
||||
}
|
||||
|
||||
return FileBackupInterface::STATUS_WAITING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean everything without files move. This might be used, when breaking started backup job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function breakCleanUp()
|
||||
{
|
||||
return $this->deleteDir($this->tmpDir());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Components\Backup;
|
||||
|
||||
|
||||
use Xentral\Components\Backup\Exception\BackupException;
|
||||
|
||||
interface FileBackupInterface
|
||||
{
|
||||
/** @var string STATUS_WAIT */
|
||||
const STATUS_WAITING = 'waiting';
|
||||
/** @var string STATUS_WORKING */
|
||||
const STATUS_WORKING = 'working';
|
||||
/** @var string Extension for the whole backup */
|
||||
const COMPRESS_EXTENSION = 'zip';
|
||||
/** @var string pid file */
|
||||
const PID_FILE = 'backup.lock';
|
||||
/** @var int Timeout */
|
||||
const TIME_OUT = 3600;
|
||||
/** @var string snapshots folder */
|
||||
const SNAPSHOTS_FOLDER = 'backup/snapshots';
|
||||
/** @var string user data directory */
|
||||
const LOCAL_FILES_DIR_NAME = 'userdata';
|
||||
|
||||
/**
|
||||
* @param string|null $userPath
|
||||
*
|
||||
* @throws BackupException
|
||||
* @return string|null
|
||||
*/
|
||||
public function begin($userPath = null);
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $userPath
|
||||
* @param string|null $sMySQLFile
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function createBackup($filename, $userPath, $sMySQLFile = null);
|
||||
|
||||
/**
|
||||
* @param string $backupFile
|
||||
* @param string $userPath
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function restoreFileSystem($backupFile, $userPath, $options = []);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLockStatus();
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Components\Backup\Logger;
|
||||
|
||||
|
||||
use Xentral\Components\Backup\Exception\LogException;
|
||||
|
||||
final class BackupLog
|
||||
{
|
||||
/** @var string|null $path */
|
||||
private $fullPath;
|
||||
|
||||
/** @var string|null $storagePath */
|
||||
private $storagePath;
|
||||
|
||||
/** @var string|null $fileName */
|
||||
private $fileName;
|
||||
|
||||
/** @var string $persistentFile */
|
||||
private static $persistentFile = 'backup_logger.txt';
|
||||
|
||||
public function __construct($path = null, $fileName = null)
|
||||
{
|
||||
if (null !== $path && null !== $fileName) {
|
||||
$this->fullPath = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
|
||||
}
|
||||
$this->storagePath = $path;
|
||||
$this->fileName = $fileName;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @param string|null $path
|
||||
* @param string|null $fileName
|
||||
*
|
||||
* @param bool $withDate
|
||||
*
|
||||
* @param bool $append
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
public function write($message, $path = null, $fileName = null, $withDate = true, $append = true)
|
||||
{
|
||||
$flag = FILE_APPEND | LOCK_EX;
|
||||
|
||||
$path = $this->getFullPath($path, $fileName);
|
||||
|
||||
if (null === $path || (!file_exists($path) && !@touch($path))) {
|
||||
throw new LogException(sprintf('cannot access or create file %s', $path));
|
||||
}
|
||||
|
||||
$message = $withDate === true ? time() . ': ' . $message : $message;
|
||||
if ($append === false) {
|
||||
$flag &= ~FILE_APPEND;
|
||||
}
|
||||
|
||||
return file_put_contents($path, $message . "\n", $flag);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $linePosition
|
||||
*
|
||||
* @param string|null $path
|
||||
*
|
||||
* @param string|null $fileName
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function tail($linePosition = 0, $path = null, $fileName = null)
|
||||
{
|
||||
$path = $this->getFullPath($path, $fileName);
|
||||
|
||||
if (null === $path || !file_exists($path)) {
|
||||
throw new LogException(sprintf('File %s cannot be found!', $path));
|
||||
}
|
||||
$output = '';
|
||||
if (($xData = file($path, FILE_SKIP_EMPTY_LINES)) && count($xData) > 0) {
|
||||
$key = (int)$linePosition === 0 ? count($xData) - 1 : $linePosition;
|
||||
if (!array_key_exists($key, $xData)) {
|
||||
throw new LogException(sprintf('Offset %d is missing', (int)$linePosition));
|
||||
}
|
||||
$output = $xData[$key];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $path
|
||||
*
|
||||
* @param string|null $fileName
|
||||
*
|
||||
* @return bool|false|string
|
||||
*/
|
||||
public function getContent($path = null, $fileName = null)
|
||||
{
|
||||
$path = $this->getFullPath($path, $fileName);
|
||||
|
||||
if (null === $path || !file_exists($path)) {
|
||||
throw new LogException(sprintf('File %s cannot be found!', $path));
|
||||
}
|
||||
|
||||
return file_get_contents($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $path
|
||||
*
|
||||
* @param string|null $fileName
|
||||
*
|
||||
* @throws LogException
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($path = null, $fileName = null)
|
||||
{
|
||||
$path = $this->getFullPath($path, $fileName);
|
||||
if (null !== $path && !file_exists($path)) {
|
||||
return false;
|
||||
//throw new LogException(sprintf('File %s cannot be deleted!', $path));
|
||||
}
|
||||
|
||||
return unlink($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $path
|
||||
* @param null $fileName
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function getFullPath($path = null, $fileName = null)
|
||||
{
|
||||
if ($path === null && $fileName === null) {
|
||||
return $this->fullPath;
|
||||
}
|
||||
if ($path !== null && $fileName !== null) {
|
||||
return rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
|
||||
}
|
||||
|
||||
if ($fileName !== null && $path === null && $this->storagePath !== null) {
|
||||
return rtrim($this->storagePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
|
||||
}
|
||||
|
||||
if ($path !== null && $fileName === null && $this->fileName !== null) {
|
||||
return rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->fileName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writePersistent($message)
|
||||
{
|
||||
$this->write($message, null, self::$persistentFile, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPersistentFileName()
|
||||
{
|
||||
return self::$persistentFile;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user