From f75a8a066bba260674eadc4b41cd388d7d110423 Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Sat, 3 Mar 2018 15:03:28 +0100 Subject: [PATCH] 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. --- bug.php | 2 +- index.php | 23 +-- lib/functions.php | 113 +------------ modules/backup/actions.php | 2 +- modules/backup/edit.php | 36 ++-- src/Backup.php | 339 +++++++++++++++++++++++++++++++++++++ 6 files changed, 367 insertions(+), 148 deletions(-) create mode 100644 src/Backup.php diff --git a/bug.php b/bug.php index db2fc7e73..f6ab7a75d 100644 --- a/bug.php +++ b/bug.php @@ -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); diff --git a/index.php b/index.php index c20536fb9..fe90cc4c0 100644 --- a/index.php +++ b/index.php @@ -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'); diff --git a/lib/functions.php b/lib/functions.php index 62f211e9a..fe3e99b34 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -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. * diff --git a/modules/backup/actions.php b/modules/backup/actions.php index 1ca36b553..98715b207 100644 --- a/modules/backup/actions.php +++ b/modules/backup/actions.php @@ -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!')); diff --git a/modules/backup/edit.php b/modules/backup/edit.php index 4888be610..ca7eb7017 100644 --- a/modules/backup/edit.php +++ b/modules/backup/edit.php @@ -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...').' '; } else { - // Ordino i backup dal più recente al più vecchio - arsort($backups_zip); - arsort($backups_file); - echo '
@@ -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 '

'.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), ]).'

'.tr('Nome del file').': '.$name.'
@@ -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 '

'.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), ]).'

'.tr('Nome del file').': '.$name.'
diff --git a/src/Backup.php b/src/Backup.php new file mode 100644 index 000000000..302f0482a --- /dev/null +++ b/src/Backup.php @@ -0,0 +1,339 @@ + [ + '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]); + } + } +}