Introduzione classe Backup

Introduzione della classe Backup per la gestione dei backup del gestionale, con relativi miglioramenti nella gestione delle informazioni sui backup e la rispettiva generazione.
This commit is contained in:
Thomas Zilio 2018-03-03 15:03:28 +01:00
parent ab29b5b285
commit f75a8a066b
6 changed files with 367 additions and 148 deletions

View File

@ -25,7 +25,7 @@ if (filter('op') == 'send') {
// Aggiunta della copia del database (facoltativo)
if (!empty($post['sql'])) {
$backup_file = $docroot.'/Backup OSM '.date('Y-m-d').' '.date('H_i_s').'.sql';
backup_tables($backup_file);
Backup::database($backup_file);
$mail->AddAttachment($backup_file);

View File

@ -10,35 +10,24 @@ switch ($op) {
case 'login':
$username = post('username');
$password = post('password');
if ($dbo->isConnected() && $dbo->isInstalled() && Auth::getInstance()->attempt($username, $password)) {
$_SESSION['keep_alive'] = (filter('keep_alive') != null);
// Auto backup del database giornaliero
if (get_var('Backup automatico')) {
$folders = glob($backup_dir.'*');
$regexp = '/'.date('Y\-m\-d').'/';
$result = Backup::daily();
// Controllo se esiste già un backup zip o folder creato per oggi
if (!empty($folders)) {
$found = false;
foreach ($folders as $folder) {
if (preg_match($regexp, $folder, $matches)) {
$found = true;
}
}
}
if ($found) {
if (!isset($result)) {
$_SESSION['infos'][] = tr('Backup saltato perché già esistente!');
} elseif (empty($backup_dir)) {
$_SESSION['errors'][] = tr('Non è possibile eseguire i backup poichè la cartella di backup non è stata impostata!!!');
} elseif (directory($backup_dir) && do_backup()) {
} elseif (!empty($result)) {
$_SESSION['infos'][] = tr('Backup automatico eseguito correttamente!');
} else {
$_SESSION['errors'][] = tr('Errore durante la generazione del backup automatico!');
}
}
}
break;
case 'logout':
@ -62,8 +51,10 @@ if (Auth::check() && isset($dbo) && $dbo->isConnected() && $dbo->isInstalled())
exit();
}
// Procedura di installazione
include_once $docroot.'/include/configuration.php';
// Procedura di aggiornamento
include_once $docroot.'/include/update.php';
$pageTitle = tr('Login');

View File

@ -209,106 +209,6 @@ function checkZip($zip_file)
}
}
/**
* Esegue il backup dell'intero progetto.
*
* @return bool
*/
function do_backup($path = null)
{
global $backup_dir;
set_time_limit(0);
$path = !empty($path) ? $path : DOCROOT;
$backup_name = 'OSM backup '.date('Y-m-d').' '.date('H_i_s');
if (
(extension_loaded('zip') && directory($backup_dir.'tmp')) ||
(!extension_loaded('zip') && directory($backup_dir.$backup_name))
) {
// Backup del database
$database_file = $backup_dir.(extension_loaded('zip') ? 'tmp' : $backup_name).'/database.sql';
backup_tables($database_file);
// Percorsi da ignorare di default
$ignores = [
'files' => [
'config.inc.php',
],
'dirs' => [
basename($backup_dir),
'.couscous',
'node_modules',
'tests',
],
];
// Creazione dello zip
if (extension_loaded('zip')) {
$result = create_zip([$path, dirname($database_file)], $backup_dir.$backup_name.'.zip', $ignores);
// Rimozione cartella temporanea
delete($database_file);
}
// Copia dei file di OSM
else {
$result = copyr($path, $backup_dir.$backup_name, $ignores);
}
// Eliminazione vecchi backup se ce ne sono
$max_backups = intval(get_var('Numero di backup da mantenere'));
// Lettura file di backup
if ($handle = opendir($backup_dir)) {
$backups = [];
while (false !== ($file = readdir($handle))) {
// I nomi dei file di backup hanno questa forma:
// OSM backup yyyy-mm-dd HH_ii_ss.zip (oppure solo cartella senza zip)
if (preg_match('/^OSM backup ([0-9\-]{10}) ([0-9_]{8})/', $file, $m)) {
$backups[] = $file;
}
}
closedir($handle);
if (count($backups) > $max_backups) {
// Fondo e ordino i backup dal più recente al più vecchio
arsort($backups);
$cont = 1;
foreach ($backups as $backup) {
if ($cont > $max_backups) {
delete($backup_dir.'/'.$backup);
}
++$cont;
}
}
}
return $result;
}
return false;
}
/**
* Funzione per fare il dump del database.
*
* @see http://davidwalsh.name/backup-mysql-database-php
*
* @param string $tables
*
* @return string
*/
function backup_tables($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);
}
/**
* Individua la differenza tra le date indicate.
* $interval può essere:
@ -427,6 +327,7 @@ function getOS()
'OS/2' => 'OS/2',
'AIX' => 'AIX',
];
foreach ($os as $key => $value) {
if (strpos($_SERVER['HTTP_USER_AGENT'], $key)) {
return $value;
@ -859,18 +760,6 @@ function prepareToField($string)
return str_replace('"', '"', $string);
}
/**
* Restituisce la configurazione dell'installazione.
*
* @return array
*/
function getConfig()
{
include DOCROOT.'/config.inc.php';
return get_defined_vars();
}
/**
* Restituisce se l'user-agent (browser web) è una versione mobile.
*

View File

@ -28,7 +28,7 @@ switch (filter('op')) {
break;
case 'backup':
if (do_backup()) {
if (Backup::create()) {
$_SESSION['infos'][] = tr('Nuovo backup creato correttamente!');
} else {
$_SESSION['errors'][] = tr('Errore durante la creazione del backup!').' '.tr_replace('_DIR_', '"'.$backup_dir.'"', tr('Verifica che la cartella _DIR_ abbia i permessi di scrittura!'));

View File

@ -60,14 +60,12 @@ if (file_exists($backup_dir)) {
$backups_zip = [];
$backups_file = [];
$files = glob($backup_dir.'*');
foreach ($files as $file) {
//I nomi dei file di backup hanno questa forma:
// OSM backup yyyy-mm-dd HH_ii_ss.zip (oppure solo cartella senza zip)
if (preg_match('/^OSM backup ([0-9\-]{10}) ([0-9_]{8})\.zip$/', basename($file), $m)) {
$backups_zip[] = $file;
} elseif (preg_match('/^OSM backup ([0-9\-]{10}) ([0-9_]{8})$/', basename($file), $m)) {
$backups_file[] = $file;
$backups = Backup::getList();
foreach ($backups as $backup) {
if (ends_with($backup, '.zip')) {
$backups_zip[] = $backup;
} else {
$backups_file[] = $backup;
}
}
@ -78,10 +76,6 @@ if (file_exists($backup_dir)) {
'.tr('Se hai già inserito dei dati su OSM crealo il prima possibile...').'
</div>';
} else {
// Ordino i backup dal più recente al più vecchio
arsort($backups_zip);
arsort($backups_file);
echo '
<div class="row">
<div class="col-xs-12 col-md-6">
@ -90,13 +84,16 @@ if (file_exists($backup_dir)) {
if (!empty($backups_zip)) {
foreach ($backups_zip as $backup) {
$name = basename($backup);
preg_match('/^OSM backup ([0-9\-]{10}) ([0-9_]{8})\.zip$/', $name, $m);
$info = Backup::readName($backup);
$data = $info['Y'].'-'.$info['m'].'-'.$info['d'];
$ora = $info['H'].':'.$info['i'].':'.$info['s'];
echo '
<div class="callout callout-info">
<h4>'.tr('Backup del _DATE_ alle _TIME_', [
'_DATE_' => Translator::dateToLocale($m[1]),
'_TIME_' => Translator::timeToLocale(str_replace('_', ':', $m[2])),
'_DATE_' => Translator::dateToLocale($data),
'_TIME_' => Translator::timeToLocale($ora),
]).'</h4>
<p><small>
'.tr('Nome del file').': '.$name.'<br>
@ -127,13 +124,16 @@ if (file_exists($backup_dir)) {
if (!empty($backups_file)) {
foreach ($backups_file as $backup) {
$name = basename($backup);
preg_match('/^OSM backup ([0-9\-]{10}) ([0-9_]{8})$/', $name, $m);
$info = Backup::readName($backup);
$data = $info['Y'].'-'.$info['m'].'-'.$info['d'];
$ora = $info['H'].':'.$info['i'].':'.$info['s'];
echo '
<div class="callout callout-warning">
<h4>'.tr('Backup del _DATE_ alle _TIME_', [
'_DATE_' => Translator::dateToLocale($m[1]),
'_TIME_' => Translator::timeToLocale(str_replace('_', ':', $m[2])),
'_DATE_' => Translator::dateToLocale($data),
'_TIME_' => Translator::timeToLocale($ora),
]).'</h4>
<p><small>
'.tr('Nome del file').': '.$name.'<br>

339
src/Backup.php Normal file
View File

@ -0,0 +1,339 @@
<?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]);
}
}
}