340 lines
8.5 KiB
PHP
340 lines
8.5 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Classe per la gestione dei backup.
|
||
|
*
|
||
|
* @since 2.4
|
||
|
*/
|
||
|
class Backup
|
||
|
{
|
||
|
/** @var string Pattern per i nomi dei backup */
|
||
|
const PATTERN = 'OSM backup Y-m-d H_i_s';
|
||
|
|
||
|
/** @var array Elenco delle varabili da sostituire nel pattern */
|
||
|
protected static $replaces = [
|
||
|
'Y' => [
|
||
|
'regex' => '([0-9]{4})',
|
||
|
],
|
||
|
'm' => [],
|
||
|
'd' => [],
|
||
|
'H' => [],
|
||
|
'i' => [],
|
||
|
's' => [],
|
||
|
];
|
||
|
|
||
|
/** @var array Elenco delle varabili che identificano i backup giornalieri */
|
||
|
protected static $daily_replaces = [
|
||
|
'Y', 'm', 'd',
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* Restituisce il percorso su previsto per il salvataggio dei backup.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function getDirectory()
|
||
|
{
|
||
|
$result = App::getConfig()['backup_dir'];
|
||
|
|
||
|
$result = rtrim($result, '/');
|
||
|
|
||
|
if (!is_writable($result) || !directory($result)) {
|
||
|
throw new UnexpectedValueException();
|
||
|
}
|
||
|
|
||
|
return realpath($result);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restituisce il percorso su cui salvare temporeneamente il dump del database.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
protected static function getDatabaseDirectory()
|
||
|
{
|
||
|
$result = self::getDirectory().'/database';
|
||
|
|
||
|
if (!directory($result)) {
|
||
|
throw new UnexpectedValueException();
|
||
|
}
|
||
|
|
||
|
return realpath($result);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restituisce l'elenco dei backup disponibili.
|
||
|
*
|
||
|
* @param string $pattern Eventuale pattern alternativo
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public static function getList($pattern = null)
|
||
|
{
|
||
|
// Costruzione del pattern
|
||
|
if (empty($pattern)) {
|
||
|
$replaces = self::getReplaces();
|
||
|
$regexs = array_column($replaces, 'regex');
|
||
|
|
||
|
$pattern = str_replace(array_keys($replaces), array_values($regexs), self::PATTERN);
|
||
|
}
|
||
|
|
||
|
// Individuazione dei backup
|
||
|
$backups = Symfony\Component\Finder\Finder::create()
|
||
|
->name('/^'.$pattern.'/')
|
||
|
->sortByName()
|
||
|
->in(self::getDirectory());
|
||
|
|
||
|
$results = [];
|
||
|
foreach ($backups as $backup) {
|
||
|
$results[] = $backup->getRealPath();
|
||
|
}
|
||
|
|
||
|
return $results;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restituisce l'elenco delle variabili da sostituire normalizzato per l'utilizzo.
|
||
|
*/
|
||
|
protected static function getReplaces()
|
||
|
{
|
||
|
$replaces = self::$replaces;
|
||
|
|
||
|
foreach ($replaces as $key => $value) {
|
||
|
if (!isset($replaces[$key]['value'])) {
|
||
|
$replaces[$key]['value'] = date($key);
|
||
|
}
|
||
|
|
||
|
if (!isset($replaces[$key]['regex'])) {
|
||
|
$replaces[$key]['regex'] = '(.{'.strlen($replaces[$key]['value']).'})';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $replaces;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restituisce il nome previsto per il backup successivo.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
protected static function getNextName()
|
||
|
{
|
||
|
// Costruzione del pattern
|
||
|
$replaces = self::getReplaces();
|
||
|
$values = array_column($replaces, 'value');
|
||
|
|
||
|
$pattern = str_replace(array_keys($replaces), array_values($values), self::PATTERN);
|
||
|
|
||
|
return $pattern;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restituisce i valori utilizzati sulle variabili sostituite.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public static function readName($string)
|
||
|
{
|
||
|
// Costruzione del pattern
|
||
|
$replaces = self::getReplaces();
|
||
|
$values = array_column($replaces, 'regex');
|
||
|
|
||
|
$pattern = str_replace(array_keys($replaces), array_values($values), self::PATTERN);
|
||
|
|
||
|
// Individuazione dei valori
|
||
|
preg_match('/^'.$pattern.'/', basename($string), $m);
|
||
|
|
||
|
$keys = array_keys($replaces);
|
||
|
|
||
|
$results = [];
|
||
|
for ($i = 1; $i < count($m); ++$i) {
|
||
|
$results[$keys[$i - 1]] = $m[$i];
|
||
|
}
|
||
|
|
||
|
return $results;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Effettua il backup giornaliero.
|
||
|
*
|
||
|
* @return null|bool
|
||
|
*/
|
||
|
public static function daily()
|
||
|
{
|
||
|
// Costruzione del pattern
|
||
|
$replaces = self::getReplaces();
|
||
|
|
||
|
foreach ($replaces as $key => $replace) {
|
||
|
if (in_array($key, self::$daily_replaces)) {
|
||
|
$replaces[$key] = $replace['value'];
|
||
|
} else {
|
||
|
$replaces[$key] = $replace['regex'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$pattern = str_replace(array_keys($replaces), array_values($replaces), self::PATTERN);
|
||
|
|
||
|
// Individuazione dei backup
|
||
|
$backups = self::getList($pattern);
|
||
|
|
||
|
// Creazione del backup eventuale
|
||
|
if (empty($backups)) {
|
||
|
return self::create();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Esegue il backup del progetto.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function create()
|
||
|
{
|
||
|
$backup_dir = self::getDirectory();
|
||
|
$backup_name = self::getNextName();
|
||
|
|
||
|
set_time_limit(0);
|
||
|
|
||
|
// Backup del database
|
||
|
$database_file = self::getDatabaseDirectory().'/database.sql';
|
||
|
self::database($database_file);
|
||
|
|
||
|
// Percorsi da ignorare di default
|
||
|
$ignores = [
|
||
|
'files' => [
|
||
|
'config.inc.php',
|
||
|
],
|
||
|
'dirs' => [
|
||
|
basename($backup_dir),
|
||
|
'.couscous',
|
||
|
'node_modules',
|
||
|
'tests',
|
||
|
],
|
||
|
];
|
||
|
|
||
|
// Lista dei file da inserire nel backup
|
||
|
$files = Symfony\Component\Finder\Finder::create()
|
||
|
->files()
|
||
|
->exclude((array) $ignores['dirs'])
|
||
|
->ignoreDotFiles(true)
|
||
|
->ignoreVCS(true)
|
||
|
->in(DOCROOT)
|
||
|
->in(self::getDatabaseDirectory());
|
||
|
|
||
|
foreach ((array) $ignores['files'] as $value) {
|
||
|
$files->notName($value);
|
||
|
}
|
||
|
|
||
|
// Creazione backup in formato ZIP
|
||
|
if (extension_loaded('zip')) {
|
||
|
$result = self::zipBackup($files, $backup_dir.'/'.$backup_name.'.zip');
|
||
|
}
|
||
|
|
||
|
// Creazione backup attraverso la copia dei file
|
||
|
else {
|
||
|
$result = self::folderBackup($files, $backup_dir.'/'.$backup_name);
|
||
|
}
|
||
|
|
||
|
// Rimozione cartella temporanea
|
||
|
delete($database_file);
|
||
|
|
||
|
self::cleanup();
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Effettua il backup in formato ZIP.
|
||
|
*
|
||
|
* @param array $files Elenco dei file da includere
|
||
|
* @param string $destination Nome del file ZIP
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected static function zipBackup($files, $destination)
|
||
|
{
|
||
|
if (!directory(dirname($destination))) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$zip = new ZipArchive();
|
||
|
|
||
|
$result = $zip->open($destination, ZIPARCHIVE::CREATE);
|
||
|
if ($result === true) {
|
||
|
foreach ($files as $file) {
|
||
|
$zip->addFile($file, $file->getRelativePathname());
|
||
|
}
|
||
|
|
||
|
$zip->close();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Effettua il backup attraverso la copia dei file.
|
||
|
*
|
||
|
* @param array $files Elenco dei file da includere
|
||
|
* @param string $destination Nome della cartella
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected static function folderBackup($files, $destination)
|
||
|
{
|
||
|
if (!directory($destination)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$result = true;
|
||
|
|
||
|
// Filesystem Symfony
|
||
|
$fs = new Symfony\Component\Filesystem\Filesystem();
|
||
|
foreach ($files as $file) {
|
||
|
$filename = $destination.DIRECTORY_SEPARATOR.$file->getRelativePathname();
|
||
|
|
||
|
// Copia
|
||
|
try {
|
||
|
$fs->copy($file, $filename);
|
||
|
} catch (Symfony\Component\Filesystem\Exception\IOException $e) {
|
||
|
$result = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Effettua il dump del database.
|
||
|
*
|
||
|
* @param string $file
|
||
|
*/
|
||
|
public static function database($file)
|
||
|
{
|
||
|
$config = App::getConfig();
|
||
|
|
||
|
$dump = new Ifsnop\Mysqldump\Mysqldump('mysql:host='.$config['db_host'].';dbname='.$config['db_name'], $config['db_username'], $config['db_password'], [
|
||
|
'add-drop-table' => true,
|
||
|
]);
|
||
|
|
||
|
$dump->start($file);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rimuove i backup più vecchi.
|
||
|
*/
|
||
|
public static function cleanup()
|
||
|
{
|
||
|
$max_backups = intval(Settings::get('Numero di backup da mantenere'));
|
||
|
|
||
|
$backups = self::getList();
|
||
|
$count = count($backups);
|
||
|
|
||
|
// Rimozione dei backup più vecchi
|
||
|
for ($i = 0; $i < $count - $max_backups; ++$i) {
|
||
|
delete($backups[$i]);
|
||
|
}
|
||
|
}
|
||
|
}
|