Overview
Comment: | Partie fonctionnelle de la gestion des sauvegardes |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
3006216dd11947a968b48c9a252003b2 |
User & Date: | bohwaz on 2013-02-11 17:07:22 |
Other Links: | manifest | tags |
Context
2013-02-11
| ||
17:22 | Sauvegarde et restauration par fichier local OK check-in: 435b8438fc user: bohwaz tags: trunk | |
17:07 | Partie fonctionnelle de la gestion des sauvegardes check-in: 3006216dd1 user: bohwaz tags: trunk | |
2013-02-01
| ||
00:25 | Fix [072d4d7af7] saisie de décimales dans chrome check-in: 24bcfcad7b user: bohwaz tags: trunk | |
Changes
Modified include/class.config.php from [5bd2fdc1a1] to [08109e603f].
︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 'categorie_membres' => $int, 'categorie_dons' => $int, 'categorie_cotisations' => $int, 'accueil_wiki' => $string, 'accueil_connexion' => $string, 'version' => $string, ); $db = DB::getInstance(); $this->config = $db->simpleStatementFetchAssoc('SELECT cle, valeur FROM config ORDER BY cle;'); | > > > | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 'categorie_membres' => $int, 'categorie_dons' => $int, 'categorie_cotisations' => $int, 'accueil_wiki' => $string, 'accueil_connexion' => $string, 'frequence_sauvegardes' => $int, 'nombre_sauvegardes' => $int, 'version' => $string, ); $db = DB::getInstance(); $this->config = $db->simpleStatementFetchAssoc('SELECT cle, valeur FROM config ORDER BY cle;'); |
︙ | ︙ |
Modified include/class.sauvegarde.php from [be28d1c4fd] to [1b29bf08f0].
1 2 3 4 5 6 7 8 | <?php namespace Garradin; class Sauvegarde { const NEED_UPGRADE = 'nu'; | > > > > > | > > | > | > > > > > > > > | < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > < > > > > > | > > > > > > > > > > > > > > > > > | | > | > > | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | <?php namespace Garradin; class Sauvegarde { const NEED_UPGRADE = 'nu'; /** * Renvoie la liste des fichiers SQLite sauvegardés * @param boolean $auto Si true ne renvoie que la liste des sauvegardes automatiques * @return array Liste des fichiers */ public function getList($auto = false) { $ext = $auto ? '\d+' . preg_quote('.auto.sqlite') : 'sqlite'; $out = array(); $dir = dir(GARRADIN_ROOT); while ($file = $dir->read()) { if ($file[0] != '.' && is_file(GARRADIN_ROOT . '/' . $file) && preg_match('![\w\d._-]+\.' . $ext . '$!i', $file) && $file != basename(GARRADIN_DB_FILE)) { $out[$file] = filemtime(GARRADIN_ROOT . '/' . $file); } } $dir->close(); ksort($out); return $out; } /** * Crée une nouvelle sauvegarde * @param boolean $auto Si true le nom de fichier sera celui de la sauvegarde automatique courante, * sinon le nom sera basé sur la date (sauvegarde manuelle) * @return string Le nom de fichier de la sauvegarde ainsi créée */ public function create($auto = false) { $backup = str_replace('.sqlite', ($auto ? '.1.auto' : date('.Y-m-d-H-i')) . '.sqlite', GARRADIN_DB_FILE); copy(GARRADIN_DB_FILE, $backup); return basename($backup); } /** * Effectue une rotation des sauvegardes automatiques * association.1.auto.sqlite deviendra association.2.auto.sqlite par exemple * @return boolean true */ public function rotate() { $config = Config::getInstance(); $nb = $config->get('nombre_sauvegardes'); $list = $this->getList(true); krsort($list); if (count($list) >= $nb) { $this->remove(key($list)); $list = array_shift($list); } foreach ($list as $f=>$d) { $new = preg_replace_callback('/\.(\d+)\.auto\.sqlite$!', function ($m) { return (int) $m[1] + 1; }, $f); rename(GARRADIN_ROOT . '/' . $f, GARRADIN_ROOT . '/' . $new); } return true; } /** * Crée une sauvegarde automatique si besoin est * @return boolean true */ public function auto() { $config = Config::getInstance(); // Pas besoin d'aller plus loin si on ne fait pas de sauvegarde auto if ($config->get('frequence_sauvegardes') == 0 || $config->get('nombre_sauvegardes') == 0) return true; $list = $this->getList(true); if (count($list) > 0) { $last = current($list); } else { $last = false; } // Test de la date de création de la dernière sauvegarde if ($last >= (time() - ($config->get('frequence_sauvegardes') * 3600 * 24))) { return true; } // Si pas de modif depuis la dernière sauvegarde, ça sert à rien d'en faire if ($last >= filemtime(GARRADIN_DB_FILE)) { return true; } $this->rotate(); $this->create(true); return true; } /** * Efface une sauvegarde locale * @param string $file Nom du fichier à supprimer * @return boolean true si le fichier a bien été supprimé, false sinon */ public function remove($file) { if (preg_match('!\.\.+!', $file) || !preg_match('!^[\w\d._-]+\.sqlite$!i', $file) || $file == basename(GARRADIN_DB_FILE)) { throw new UserException('Nom de fichier non valide.'); } return unlink(GARRADIN_ROOT . '/' . $file); } /** * Renvoie sur la sortie courante le contenu du fichier de base de données courant * @return boolean true */ public function dump() { $in = fopen(GARRADIN_DB_FILE, 'r'); $out = fopen('php://output', 'w'); while (!feof($in)) { fwrite($out, fread($in, 8192)); } fclose($in); fclose($out); return true; } /** * Restaure une sauvegarde locale * @param string $file Le nom de fichier à utiliser comme point de restauration * @return boolean true si la restauration a fonctionné, false sinon */ public function restoreFromLocal($file) { if (preg_match('!\.\.+!', $file) || !preg_match('!^[\w\d._-]+$!i', $file)) { throw new UserException('Nom de fichier non valide.'); } if (!file_exists(GARRADIN_ROOT . '/' . $file)) { throw new UserException('Le fichier fourni n\'existe pas.'); } return $this->restoreDB(GARRADIN_ROOT . '/' . $file); } /** * Restaure une copie distante (fichier envoyé) * @param array $file Tableau provenant de $_FILES * @return boolean true */ public function restoreFromUpload($file) { if (empty($file['size']) || empty($file['tmp_name']) || !empty($file['error'])) { throw new UserException('Le fichier n\'a pas été correctement envoyé. Essayer de le renvoyer à nouveau.'); } $r = $this->restoreDB($file['tmp_name']); if ($r) { unlink($file['tmp_name']); } return $r; } /** * Restauration de base de données, la fonction qui le fait vraiment * @param string $file Chemin absolu vers la base de données à utiliser * @return mixed true si rien ne va plus, ou self::NEED_UPGRADE si la version de la DB * ne correspond pas à la version de Garradin (mise à jour nécessaire). */ protected function restoreDB($file) { // Essayons déjà d'ouvrir la base de données à restaurer en lecture try { $db = new \SQLite3($file, SQLITE3_OPEN_READONLY); } catch (\Exception $e) { throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' . 'Message d\'erreur de SQLite : ' . $e->getMessage()); } // Regardons ensuite si la base de données n'est pas corrompue $check = $db->querySingle('PRAGMA integrity_check;'); if (strtolower(trim($check)) != 'ok') { throw new UserException('Le fichier fourni est corrompu. SQLite a trouvé ' . $check . ' erreurs.'); } // On ne peut pas faire de vérifications très poussées sur la structure de la base de données, // celle-ci pouvant changer d'une version à l'autre et on peut vouloir importer une base // un peu vieille, mais on vérifie quand même que ça ressemble un minimum à une base garradin $table = $db->querySingle('SELECT 1 FROM sqlite_master WHERE type=\'table\' AND tbl_name=\'config\';'); if (!$table) { throw new UserException('Le fichier fourni ne semble pas contenir de données liées à Garradin.'); } // On récupère la version pour plus tard $version = $db->querySingle('SELECT valeur FROM config WHERE cle=\'version\';'); $db->close(); $backup = str_replace('.sqlite', date('.Y-m-d-H-i') . '.pre-restore.sqlite', GARRADIN_DB_FILE); if (!rename(GARRADIN_DB_FILE, $backup)) { throw new \RuntimeException('Unable to backup current DB file.'); } if (!copy($file, GARRADIN_DB_FILE)) { rename($backup, GARRADIN_DB_FILE); throw new \RuntimeException('Unable to copy backup DB to main location.'); } if ($version != garradin_version()) { return self::NEED_UPGRADE; } |
︙ | ︙ |
Modified templates/admin/config/donnees.tpl from [241063d7b5] to [e5cf7f264c].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | {include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"} {include file="admin/config/_menu.tpl" current="donnees"} <form method="post" action="{$self_url|escape}"> <fieldset> <legend>Sauvegarde automatique</legend> <p class="help"> En activant cette option une sauvegarde sera automatiquement créée à chaque intervalle donné. Par exemple en activant une sauvegarde hebdomadaire, une copie des données sera réalisée une fois par semaine, sauf si aucune modification n'a été effectuée sur les données ou que personne ne s'est connecté. </p> <dl> <dt><label for="f_frequency">Intervalle de sauvegarde</label></dt> <dd> | > > > > > > > > > > > > | | | | | | | | | > > > > > > > > > > > > | | | > > > > | > | < < < < < < < < < < > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | {include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"} {include file="admin/config/_menu.tpl" current="donnees"} {if $error} <p class="error">{$error|escape}</p> {elseif $ok} <p class="confirm"> {if $ok == 'config'}La configuration a bien été enregistrée. {elseif $ok == 'create'}Une nouvelle sauvegarde a été créée. {elseif $ok == 'restore'}La restauration a bien été effectuée. Si vous désirez revenir en arrière, vous pouvez utiliser la sauvegarde automatique nommée <em>pre-restore</em>, sinon vous pouvez l'effacer. {elseif $ok == 'remove'}La sauvegarde a été supprimée. {/if} </p> {/if} <form method="post" action="{$self_url|escape}"> <fieldset> <legend>Sauvegarde automatique</legend> <p class="help"> En activant cette option une sauvegarde sera automatiquement créée à chaque intervalle donné. Par exemple en activant une sauvegarde hebdomadaire, une copie des données sera réalisée une fois par semaine, sauf si aucune modification n'a été effectuée sur les données ou que personne ne s'est connecté. </p> <dl> <dt><label for="f_frequency">Intervalle de sauvegarde</label></dt> <dd> <select name="frequence_sauvegardes" id="f_frequency"> <option value="0"{form_field name=frequence_sauvegardes data=$config selected=0}>Aucun — les sauvegardes automatiques sont désactivées</option> <option value="1"{form_field name=frequence_sauvegardes data=$config selected=1}>Quotidien, tous les jours</option> <option value="7"{form_field name=frequence_sauvegardes data=$config selected=7}>Hebdomadaire, tous les 7 jours</option> <option value="15"{form_field name=frequence_sauvegardes data=$config selected=15}>Bimensuel, tous les 15 jours</option> <option value="30"{form_field name=frequence_sauvegardes data=$config selected=30}>Mensuel</option> <option value="90"{form_field name=frequence_sauvegardes data=$config selected=90}>Trimestriel</option> <option value="365{form_field name=frequence_sauvegardes data=$config selected=365}">Annuel</option> </select> </dd> <dt><label for="f_max_backups">Nombre de sauvegardes conservées</label></dt> <dd class="help"> Par exemple avec l'intervalle mensuel, en indiquant de conserver 12 sauvegardes, vous pourrez garder un an d'historique de sauvegardes. </dd> <dd class="help"> <strong>Attention :</strong> si vous choisissez un nombre important et un intervalle réduit, l'espace disque occupé par vos sauvegardes va rapidement augmenter. </dd> <dd><input type="number" name="nombre_sauvegardes" value="{form_field name=nombre_sauvegardes data=$config}" if="f_max_backups" min="1" max="90" /></dd> </dl> <p> {csrf_field key="backup_config"} <input type="submit" name="config" value="Enregistrer →" /> </p> </fieldset> </form> <form method="post" action="{$self_url|escape}"> <fieldset> <legend>Sauvegarde manuelle</legend> <p> {csrf_field key="backup_create"} <input type="submit" name="create" value="Créer une nouvelle sauvegarde des données →" /> </p> </fieldset> </form> <form method="post" action="{$self_url|escape}"> <fieldset> <legend>Copies de sauvegarde disponibles</legend> {if empty($liste)} <p class="help">Aucune copie de sauvegarde disponible.</p> {else} <dl> {foreach from=$liste key="f" item="d"} <dd> <label> <input type="radio" name="file" value="{$f|escape}" /> {$f|escape} — {$d|date_fr:'d/m/Y à H:i'} </label> </dd> {/foreach} </dl> <p class="alert"> Attention, en cas de restauration, l'intégralité des données courantes seront effacées et remplacées par celles contenues dans la sauvegarde sélectionnée. </p> <p> {csrf_field key="backup_manage"} <input type="submit" name="restore" value="Restaurer cette sauvegarde" /> <input type="submit" name="remove" value="Supprimer cette sauvegarde" /> </p> {/if} </fieldset> </form> <form method="post" action="{$self_url|escape}"> <fieldset> <legend>Téléchargement</legend> <p> {csrf_field key="backup_download"} <input type="submit" name="download" value="Télécharger une copie des données sur mon ordinateur" /> </p> </fieldset> </form> <form method="post" action="{$self_url|escape}"> <fieldset> <legend><label for="f_file">Restaurer depuis un fichier</label></legend> <p class="alert"> Attention, l'intégralité des données courantes seront effacées et remplacées par celles contenues dans le fichier fourni. </p> <p class="help"> Une sauvegarde des données courantes sera effectuée avant le remplacement, en cas de besoin d'annuler cette restauration. </p> <p> {csrf_field key="backup_restore"} <input type="file" name="file" id="f_file" /> <input type="submit" name="restore_file" value="Restaurer depuis le fichier sélectionné" /> </p> </fieldset> </form> {include file="admin/_foot.tpl"} |
Modified www/admin/config/donnees.php from [828cf46bd2] to [975fa1c668].
1 2 3 4 5 6 7 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; $s = new Sauvegarde; | > | > | > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; $s = new Sauvegarde; $error = false; if (utils::post('config')) { if (!utils::CSRF_check('backup_config')) { $error = 'Une erreur est survenue, merci de renvoyer le formulaire.'; } else { $config->set('frequence_sauvegardes', utils::post('frequence_sauvegardes')); $config->set('nombre_sauvegardes', utils::post('nombre_sauvegardes')); $config->save(); utils::redirect('/admin/config/donnees.php?ok=config'); } } elseif (utils::post('create')) { if (!utils::CSRF_check('backup_create')) { $error = 'Une erreur est survenue, merci de renvoyer le formulaire.'; } else { $s->create(); utils::redirect('/admin/config/donnees.php?ok=create'); } } elseif (utils::post('download')) { if (!utils::CSRF_check('backup_download')) { $error = 'Une erreur est survenue, merci de renvoyer le formulaire.'; } else { header('Content-type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $config->get('nom_asso') . ' - Sauvegarde données - ' . date('Y-m-d') . '.sqlite"'); $s->dump(); exit; } } elseif (utils::post('restore')) { if (!utils::CSRF_check('backup_manage')) { $error = 'Une erreur est survenue, merci de renvoyer le formulaire.'; } else { $s->restoreFromLocal(utils::post('file')); utils::redirect('/admin/config/donnees.php?ok=restore'); } } elseif (utils::post('remove')) { if (!utils::CSRF_check('backup_manage')) { $error = 'Une erreur est survenue, merci de renvoyer le formulaire.'; } else { $s->remove(utils::post('file')); utils::redirect('/admin/config/donnees.php?ok=remove'); } } $tpl->assign('error', $error); $tpl->assign('ok', utils::get('ok')); $tpl->assign('liste', $s->getList()); $tpl->display('admin/config/donnees.tpl'); ?> |