Comment: | Dédoublement du champ ID en deux champs : ID (interne) et numéro de membre (champ membre modifiable) |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA1: |
3dfb698790ba0063bba7346aa1a6a3cb |
User & Date: | bohwaz on 2017-08-09 06:59:58 |
Other Links: | branch diff | manifest | tags |
2017-08-09
| ||
07:19 | Utilisation du numéro plutôt que l'ID pour import/export de membre check-in: 6877253ad2 user: bohwaz tags: dev | |
06:59 | Dédoublement du champ ID en deux champs : ID (interne) et numéro de membre (champ membre modifiable) check-in: 3dfb698790 user: bohwaz tags: dev | |
05:02 | Modernisation installation, renomme repasse en passe_confirmed check-in: d71d56990e user: bohwaz tags: dev | |
Modified src/include/data/champs_membres.ini from [c3b46c317e] to [1bb1e7c845].
︙ | ︙ | |||
40 41 42 43 44 45 46 47 48 49 50 51 52 | ; Si présent et un chiffre supérieur à 0, alors le champ apparaîtra dans la liste des membres ; dans l'ordre défini par le chiffre (si nom est à 2 et email à 1, alors email sera ; la première colonne et nom la seconde) ; install: ; true = sera ajouté aux fiches membres à l'installation ; false = sera seulement présent dans les champs supplémentaires possibles (défaut) [nom] type = text title = "Nom & prénom" mandatory = true install = true editable = true | > > > > > > > > | | 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 | ; Si présent et un chiffre supérieur à 0, alors le champ apparaîtra dans la liste des membres ; dans l'ordre défini par le chiffre (si nom est à 2 et email à 1, alors email sera ; la première colonne et nom la seconde) ; install: ; true = sera ajouté aux fiches membres à l'installation ; false = sera seulement présent dans les champs supplémentaires possibles (défaut) [numero] type = number title = "Numéro de membre" mandatory = true install = true editable = false list_row = 1 [nom] type = text title = "Nom & prénom" mandatory = true install = true editable = true list_row = 2 [email] ; ce champ est obligatoirement présent et de type 'email' type = email title = "Adresse E-Mail" mandatory = true install = true |
︙ | ︙ | |||
76 77 78 79 80 81 82 | editable = true [code_postal] type = text title = "Code postal" install = true editable = true | | | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | editable = true [code_postal] type = text title = "Code postal" install = true editable = true list_row = 3 [ville] type = text title = "Ville" install = true editable = true list_row = 4 [pays] type = country title = "Pays" install = true editable = true |
︙ | ︙ |
Modified src/include/lib/Garradin/Membres.php from [29f4b66141] to [5324be994b].
︙ | ︙ | |||
139 140 141 142 143 144 145 146 | public function add($data = []) { $this->_checkFields($data); $db = DB::getInstance(); $config = Config::getInstance(); $id = $config->get('champ_identifiant'); | > | < > > > > > > > > > > > > > | 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 | public function add($data = []) { $this->_checkFields($data); $db = DB::getInstance(); $config = Config::getInstance(); $id = $config->get('champ_identifiant'); $champs = $config->get('champs_membres'); if (!empty($data[$id]) && $db->test('membres', $db->where($id, $data[$id]))) { throw new UserException('La valeur du champ '.$id.' est déjà utilisée par un autre membre, hors ce champ doit être unique à chaque membre.'); } // Numéro de membre if ($champs->get('numero')) { if (empty($data['numero'])) { $data['numero'] = $db->firstColumn('SELECT MAX(numero) + 1 FROM membres;'); } elseif ($db->test('membres', $db->where('numero', $data['numero']))) { throw new UserException('Ce numéro de membre est déjà attribué à un autre membre.'); } } if (isset($data['passe']) && trim($data['passe']) != '') { $data['passe'] = self::hashPassword($data['passe']); } else { |
︙ | ︙ | |||
173 174 175 176 177 178 179 | } public function edit($id, $data = [], $check_editable = true) { $db = DB::getInstance(); $config = Config::getInstance(); | < < | < | | | < < < < < < < < < < < < < < < < | 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 | } public function edit($id, $data = [], $check_editable = true) { $db = DB::getInstance(); $config = Config::getInstance(); unset($data['id']); $this->_checkFields($data, $check_editable, false); $champ_id = $config->get('champ_identifiant'); if (!empty($data[$champ_id]) && $db->firstColumn('SELECT 1 FROM membres WHERE '.$champ_id.' = ? AND id != ? LIMIT 1;', $data[$champ_id], (int)$id)) { throw new UserException('La valeur du champ '.$champ_id.' est déjà utilisée par un autre membre, hors ce champ doit être unique à chaque membre.'); } if (!empty($data['numero'])) { if (!preg_match('/^\d+$/', $data['numero'])) { throw new UserException('Le numéro de membre ne doit contenir que des chiffres.'); } if ($db->test('membres', 'numero = ? AND id != ?', (int)$data['numero'], $id)) { throw new UserException('Ce numéro est déjà attribué à un autre membre.'); } } if (!empty($data['passe']) && trim($data['passe'])) { $data['passe'] = self::hashPassword($data['passe']); } else |
︙ | ︙ | |||
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | public function getNom($id) { $db = DB::getInstance(); $config = Config::getInstance(); return $db->firstColumn('SELECT '.$config->get('champ_identite').' FROM membres WHERE id = ? LIMIT 1;', (int)$id); } public function search($field, $query) { $db = DB::getInstance(); $config = Config::getInstance(); $champs = $config->get('champs_membres'); | > > > > > | | 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | public function getNom($id) { $db = DB::getInstance(); $config = Config::getInstance(); return $db->firstColumn('SELECT '.$config->get('champ_identite').' FROM membres WHERE id = ? LIMIT 1;', (int)$id); } public function getIDWithNumero($numero) { return DB::getInstance()->firstColumn('SELECT id FROM membres WHERE numero = ?;', (int) $numero); } public function search($field, $query) { $db = DB::getInstance(); $config = Config::getInstance(); $champs = $config->get('champs_membres'); if (!$champs->get($field)) { throw new \UnexpectedValueException($field . ' is not a valid field'); } $champ = $champs->get($field); if ($champ['type'] == 'multiple') |
︙ | ︙ |
Modified src/include/lib/Garradin/Membres/Champs.php from [72f212aeb0] to [6b0d0f9ef0].
︙ | ︙ | |||
87 88 89 90 91 92 93 94 95 96 | { if ($champs instanceOf Champs) { $this->champs = $champs->getAll(); } elseif (is_array($champs)) { foreach ($champs as $key=>&$config) { $this->_checkField($key, $config); | > > > > > > > > > > > > > | > | < | 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 | { if ($champs instanceOf Champs) { $this->champs = $champs->getAll(); } elseif (is_array($champs)) { $presets = self::importPresets(); $this->champs = new \stdClass; foreach ($champs as $key=>&$config) { if (is_array($config)) { $config = (object) $config; } if (isset($presets[$key])) { $config->type = $presets[$key]['type']; } $this->_checkField($key, $config); $this->champs->$key = $config; } } else { $champs = parse_ini_string((string)$champs, true); foreach ($champs as $key=>&$config) { |
︙ | ︙ | |||
142 143 144 145 146 147 148 | return true; else return false; } public function getAll() { | < | 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | return true; else return false; } public function getAll() { return $this->champs; } public function getList($with_id = false) { $champs = clone $this->champs; unset($champs->passe); |
︙ | ︙ | |||
421 422 423 424 425 426 427 428 429 430 431 432 433 434 | throw new UserException('Le champ E-Mail ne peut être supprimé des fiches membres.'); } if (!array_key_exists('passe', $champs)) { throw new UserException('Le champ Mot de passe ne peut être supprimé des fiches membres.'); } $config = Config::getInstance(); $identite = $config->get('champ_identite'); if ($identite != 'id' && !array_key_exists($identite, $champs)) { | > > > > > | 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | throw new UserException('Le champ E-Mail ne peut être supprimé des fiches membres.'); } if (!array_key_exists('passe', $champs)) { throw new UserException('Le champ Mot de passe ne peut être supprimé des fiches membres.'); } if (!array_key_exists('numero', $champs)) { throw new UserException('Le champ numéro de membre ne peut être supprimé des fiches membres.'); } $config = Config::getInstance(); $identite = $config->get('champ_identite'); if ($identite != 'id' && !array_key_exists($identite, $champs)) { |
︙ | ︙ | |||
477 478 479 480 481 482 483 | $create_keys = [ 'FOREIGN KEY (id_categorie) REFERENCES membres_categories (id)' ]; // Champs à recopier $copy = [ | | | | | | | | < < | > > > > > | | 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | $create_keys = [ 'FOREIGN KEY (id_categorie) REFERENCES membres_categories (id)' ]; // Champs à recopier $copy = [ 'id' => 'id', 'id_categorie' => 'id_categorie', 'date_connexion' => 'date_connexion', 'date_inscription' => 'date_inscription', 'secret_otp' => 'secret_otp', 'clef_pgp' => 'clef_pgp', ]; $anciens_champs = $config->get('champs_membres'); $anciens_champs = is_null($anciens_champs) ? $this->champs : $anciens_champs->getAll(); foreach ($this->champs as $key=>$cfg) { if ($cfg->type == 'number' || $cfg->type == 'multiple' || $cfg->type == 'checkbox') $type = 'INTEGER'; elseif ($cfg->type == 'file') $type = 'BLOB'; else $type = 'TEXT'; $line = $key . ' ' . $type . ','; if (!empty($cfg->title)) { $line .= ' -- ' . str_replace(["\n", "\r"], '', $cfg->title); } $create[] = $line; if (property_exists($anciens_champs, $key)) { $copy[$key] = $key; } elseif ($key == 'numero') { // Copie des numéros de membre à partir du champ ID $copy[$key] = 'id'; } } $create = array_merge($create, $create_keys); $create = 'CREATE TABLE membres_tmp (' . "\n\t" . implode("\n\t", $create) . "\n);"; $copy = 'INSERT INTO membres_tmp (' . implode(', ', array_keys($copy)) . ') SELECT ' . implode(', ', $copy) . ' FROM membres;'; $db->exec('PRAGMA foreign_keys = OFF;'); $db->begin(); $db->exec($create); if ($enable_copy) { $db->exec($copy); |
︙ | ︙ | |||
540 541 542 543 544 545 546 547 548 549 550 551 552 553 | // Mettre les champs identifiant vides à NULL pour pouvoir créer un index unique $db->exec('UPDATE membres SET '.$config->get('champ_identifiant').' = NULL WHERE '.$config->get('champ_identifiant').' = "";'); // Création de l'index unique $db->exec('CREATE UNIQUE INDEX membres_identifiant ON membres ('.$config->get('champ_identifiant').');'); } // Création des index pour les champs affichés dans la liste des membres $listed_fields = array_keys((array) $this->getListedFields()); foreach ($listed_fields as $field) { if ($field === $config->get('champ_identifiant')) { | > > > > > | 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 | // Mettre les champs identifiant vides à NULL pour pouvoir créer un index unique $db->exec('UPDATE membres SET '.$config->get('champ_identifiant').' = NULL WHERE '.$config->get('champ_identifiant').' = "";'); // Création de l'index unique $db->exec('CREATE UNIQUE INDEX membres_identifiant ON membres ('.$config->get('champ_identifiant').');'); } if (isset($this->champs->numero)) { $db->exec('CREATE UNIQUE INDEX membres_numero ON membres (numero);'); } // Création des index pour les champs affichés dans la liste des membres $listed_fields = array_keys((array) $this->getListedFields()); foreach ($listed_fields as $field) { if ($field === $config->get('champ_identifiant')) { |
︙ | ︙ |
Modified src/include/lib/Garradin/Sauvegarde.php from [31ac9b4e77] to [5f30e3fbad].
︙ | ︙ | |||
299 300 301 302 303 304 305 | { throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' . 'Message d\'erreur de SQLite : ' . $e->getMessage(), self::NOT_A_DB); } try { // Regardons ensuite si la base de données n'est pas corrompue | | | | | | | | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | { throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' . 'Message d\'erreur de SQLite : ' . $e->getMessage(), self::NOT_A_DB); } try { // Regardons ensuite si la base de données n'est pas corrompue $check = $db->querySingle('PRAGMA integrity_check;', false); } catch (\Exception $e) { // Ici SQLite peut rejeter un message type "file is encrypted or is not a db" throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' . 'Message d\'erreur de SQLite : ' . $e->getMessage(), self::NOT_A_DB); } 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 $version = $db->querySingle('SELECT valeur FROM config WHERE cle=\'version\';'); // Vérification de l'AppID pour les versions récentes if (version_compare($version, '0.8.0', '>=')) { $appid = $db->querySingle('PRAGMA application_id;', false); if ($appid !== DB::APPID) { throw new UserException('Ce fichier n\'est pas une sauvegarde Garradin (application_id ne correspond pas).', self::NO_APP_ID); } } if ($user_id) { // Empêchons l'admin de se tirer une balle dans le pied $is_still_admin = $db->querySingle('SELECT 1 FROM membres_categories WHERE id = (SELECT id_categorie FROM membres WHERE id = ' . (int) $user_id . ') AND droit_config >= ' . Membres::DROIT_ADMIN . ' AND droit_connexion >= ' . Membres::DROIT_ACCES); if (!$is_still_admin) { $return |= self::NOT_AN_ADMIN; } |
︙ | ︙ |
Modified src/include/lib/Garradin/Template.php from [44be2cd115] to [6ee6d1d6a7].
︙ | ︙ | |||
482 483 484 485 486 487 488 489 490 491 492 493 494 495 | $params['pattern'] = '\d{4}-\d{2}-\d{2}'; } $field = ''; $value = tpl_form_field($params); $attributes = 'name="' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" '; $attributes .= 'id="f_' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" '; if (!empty($params['disabled'])) { $attributes .= 'disabled="disabled" '; } if (!empty($config->mandatory)) | > > > > > | 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 | $params['pattern'] = '\d{4}-\d{2}-\d{2}'; } $field = ''; $value = tpl_form_field($params); $attributes = 'name="' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" '; $attributes .= 'id="f_' . htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '" '; if ($params['name'] == 'numero' && $config->type == 'number' && !$value) { $value = DB::getInstance()->firstColumn('SELECT MAX(numero) + 1 FROM membres;'); } if (!empty($params['disabled'])) { $attributes .= 'disabled="disabled" '; } if (!empty($config->mandatory)) |
︙ | ︙ |
Modified src/templates/admin/config/donnees.tpl from [815a847b92] to [396a0ea87b].
1 2 3 4 | {include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"} {include file="admin/config/_menu.tpl" current="donnees"} | | | | | | | > | | | 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 | {include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"} {include file="admin/config/_menu.tpl" current="donnees"} {form_errors} {if $code == Garradin\Sauvegarde::INTEGRITY_FAIL && Garradin\ALLOW_MODIFIED_IMPORT} <p class="alert">Pour passer outre, renvoyez le fichier en cochant la case « Ignorer les erreurs ». Attention, si vous avez effectué des modifications dans la base de données, cela peut créer des bugs !</p> {/if} {if $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>date-du-jour.avant_restauration.sqlite</em>, sinon vous pouvez l'effacer. {if $ok_code & Garradin\Sauvegarde::NOT_AN_ADMIN} </p> <p class="alert"> <strong>Vous n'êtes pas administrateur dans cette sauvegarde.</strong> Garradin a donné les droits d'administration à toutes les catégories afin d'empêcher de ne plus pouvoir se connecter. Merci de corriger les droits des catégories maintenant. {/if} {elseif $ok == 'remove'}La sauvegarde a été supprimée. {/if} </p> {/if} <form method="post" action="{$self_url_no_qs}"> <p class="help"> Info : la base de données fait actuellement {$db_size|format_bytes} (dont {$files_size|format_bytes} pour les documents et images). </p> <fieldset> <legend>Sauvegarde automatique</legend> |
︙ | ︙ | |||
65 66 67 68 69 70 71 | <p> {csrf_field key="backup_config"} <input type="submit" name="config" value="Enregistrer →" /> </p> </fieldset> </form> | | | | 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 | <p> {csrf_field key="backup_config"} <input type="submit" name="config" value="Enregistrer →" /> </p> </fieldset> </form> <form method="post" action="{$self_url_no_qs}"> <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_no_qs}"> <fieldset> <legend>Copies de sauvegarde disponibles</legend> {if empty($liste)} <p class="help">Aucune copie de sauvegarde disponible.</p> {else} <dl> |
︙ | ︙ | |||
106 107 108 109 110 111 112 | <input type="submit" name="restore" value="Restaurer cette sauvegarde" /> <input type="submit" name="remove" value="Supprimer cette sauvegarde" /> </p> {/if} </fieldset> </form> | | | | | 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 | <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_no_qs}"> <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_no_qs}" enctype="multipart/form-data"> <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="hidden" name="MAX_FILE_SIZE" value="{$max_file_size}" /> <input type="file" name="file" id="f_file" required="required" /> (maximum {$max_file_size|format_bytes}) <input type="submit" name="restore_file" value="Restaurer depuis le fichier sélectionné →" /> </p> {if $code && ($code == Garradin\Sauvegarde::INTEGRITY_FAIL && Garradin\ALLOW_MODIFIED_IMPORT)} <p> <label><input type="checkbox" name="force_import" value="1" /> Ignorer les erreurs, je sais ce que je fait</label> </p> {/if} </fieldset> </form> {include file="admin/_foot.tpl"} |
Modified src/templates/admin/config/membres.tpl from [fae223707c] to [731fd23cc8].
1 2 3 4 | {include file="admin/_head.tpl" current="config" js=1} {include file="admin/config/_menu.tpl" current="membres"} | < | | < < < < | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | {include file="admin/_head.tpl" current="config" js=1} {include file="admin/config/_menu.tpl" current="membres"} {if isset($status) && $status == 'OK'} <p class="confirm"> La configuration a bien été enregistrée. </p> {elseif isset($status) && $status == 'ADDED'} <p class="confirm"> Le champ a été ajouté à la fin de la liste. </p> {/if} {form_errors} {if $review} <p class="help"> Voici ce à quoi ressemblera la nouvelle fiche de membre, vérifiez vos modifications avant d'enregistrer les changements. </p> <p class="alert"> Attention ! Si vous avez supprimé un champ, les données liées à celui-ci seront supprimées de toutes les fiches de tous les membres. |
︙ | ︙ | |||
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | {/if} </dl> </fieldset> <form method="post" action="{$admin_url}config/membres.php"> <p class="submit"> {csrf_field key="config_membres"} <input type="submit" name="back" value="← Retour à l'édition" class="minor" /> <input type="submit" name="reset" value="Annuler les changements" class="minor" /> <input type="submit" name="save" value="Enregistrer →" /> </p> </form> {else} <p class="help"> Cette page vous permet de personnaliser les fiches d'information des membres de l'association.<br /> <strong>Attention :</strong> Les champs supprimés de la fiche seront effacés de toutes les fiches de tous les membres, et les données qu'ils contenaient seront perdues. </p> | > < < < < < < < < < < < < < < < | | 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 | {/if} </dl> </fieldset> <form method="post" action="{$admin_url}config/membres.php"> <p class="submit"> {csrf_field key="config_membres"} <input type="hidden" name="champs" value="{$champs|escape:json|escape}" /> <input type="submit" name="back" value="← Retour à l'édition" class="minor" /> <input type="submit" name="reset" value="Annuler les changements" class="minor" /> <input type="submit" name="save" value="Enregistrer →" /> </p> </form> {else} <p class="help"> Cette page vous permet de personnaliser les fiches d'information des membres de l'association.<br /> <strong>Attention :</strong> Les champs supprimés de la fiche seront effacés de toutes les fiches de tous les membres, et les données qu'ils contenaient seront perdues. </p> {if !empty($presets)} <form method="post" action="{$self_url}"> <fieldset> <legend>Ajouter un champ pré-défini</legend> <p> <select name="preset" required="required"> <option></option> {foreach from=$presets key="name" item="preset"} <option value="{$name}">{$name} — {$preset.title}</option> {/foreach} </select> {csrf_field key="config_membres"} <input type="submit" name="add" value="Ajouter ce champ à la fiche membre" /> </p> </fieldset> </form> {/if} <form method="post" action="{$self_url}"> |
︙ | ︙ | |||
123 124 125 126 127 128 129 | {foreach from=$types key="type" item="nom"} <option value="{$type}" {form_field name=new_type selected=$type}>{$nom}</option> {/foreach} </select> </dd> </dl> <p> | | | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | {foreach from=$types key="type" item="nom"} <option value="{$type}" {form_field name=new_type selected=$type}>{$nom}</option> {/foreach} </select> </dd> </dl> <p> {csrf_field key="config_membres"} <input type="submit" name="add" value="Ajouter ce champ à la fiche membre" /> </p> </fieldset> </form> <form method="post" action="{$self_url}"> <div id="orderFields"> |
︙ | ︙ | |||
188 189 190 191 192 193 194 | <dd class="help">Si coché, ce champ ne pourra rester vide.</dd> <dt><label><input type="checkbox" name="champs[passe][private]" value="1" {form_field data=$champs.passe name=private checked="1"} /> Champ privé</label></dt> <dd class="help">Si coché, ce champ ne sera visible et modifiable que par les personnes pouvant gérer les membres, mais pas les membres eux-même.</dd> </dl> </fieldset> <p class="submit"> | | | 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | <dd class="help">Si coché, ce champ ne pourra rester vide.</dd> <dt><label><input type="checkbox" name="champs[passe][private]" value="1" {form_field data=$champs.passe name=private checked="1"} /> Champ privé</label></dt> <dd class="help">Si coché, ce champ ne sera visible et modifiable que par les personnes pouvant gérer les membres, mais pas les membres eux-même.</dd> </dl> </fieldset> <p class="submit"> {csrf_field key="config_membres"} <input type="submit" name="reset" value="Annuler les changements" class="minor" /> <input type="submit" name="review" value="Enregistrer →" /> (un récapitulatif sera présenté et une confirmation sera demandée) </p> </form> <script type="text/javascript"> |
︙ | ︙ | |||
272 273 274 275 276 277 278 | edit.title = 'Modifier ce champ'; edit.onclick = function (e) { this.parentNode.parentNode.querySelector('dl').classList.toggle('hidden'); return false; }; actions.appendChild(edit); | | | | 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | edit.title = 'Modifier ce champ'; edit.onclick = function (e) { this.parentNode.parentNode.querySelector('dl').classList.toggle('hidden'); return false; }; actions.appendChild(edit); if (field.id != champ_identifiant && field.id != 'f_passe' && field.id != champ_identite && field.id != 'f_numero') { var rem = document.createElement('a'); rem.className = 'icn remove'; rem.innerHTML = '✘'; rem.title = 'Enlever ce champ de la fiche'; rem.onclick = function (e) { if (!window.confirm('Êtes-vous sûr de supprimer ce champ des fiches de membre ?')) { return false; } var field = this.parentNode.parentNode; this.parentNode.parentNode.querySelector('dl').classList.add('hidden'); field.classList.toggle('removed'); window.setTimeout(function () { field.parentNode.removeChild(field); }, 800); return false; }; actions.appendChild(rem); } if (field.querySelector('.options')) { |
︙ | ︙ |
Modified src/templates/admin/config/plugins.tpl from [1d5abf639f] to [f3dbb74805].
1 2 3 4 | {include file="admin/_head.tpl" title="Extensions" current="config"} {include file="admin/config/_menu.tpl" current="plugins"} | < < | < < | 1 2 3 4 5 6 7 8 9 10 11 12 | {include file="admin/_head.tpl" title="Extensions" current="config"} {include file="admin/config/_menu.tpl" current="plugins"} {form_errors} {if !empty($delete)} <form method="post" action="{$self_url}"> <fieldset> <legend>Désinstaller une extension</legend> <h3 class="warning"> |
︙ | ︙ | |||
77 78 79 80 81 82 83 | <fieldset> <legend>Extensions à installer</legend> <dl> {foreach from=$liste_telecharges item="plugin" key="id"} <dt> <label> | | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | <fieldset> <legend>Extensions à installer</legend> <dl> {foreach from=$liste_telecharges item="plugin" key="id"} <dt> <label> <input type="radio" name="plugin" value="{$id}" /> {$plugin.nom} </label> (version {$plugin.version}) </dt> <dd>[<a href="{$plugin.url}" onclick="return !window.open(this.href);">{$plugin.auteur}</a>] {$plugin.description}</dd> {/foreach} </dl> |
︙ | ︙ |
Modified src/templates/admin/membres/cotisations/ajout.tpl from [d289eec67b] to [aaf9e77610].
︙ | ︙ | |||
70 71 72 73 74 75 76 | <option value="{$compte.id}"{if $compte.id == $banque} selected="selected"{/if}>{$compte.libelle} - {$compte.banque}</option> {/foreach} </select> </dd> <dt><label for="f_date">Date</label> <b title="(Champ obligatoire)">obligatoire</b></dt> <dd><input type="date" name="date" id="f_date" value="{form_field name=date default=$default_date}" required="required" /></dd> {if !$membre} | | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <option value="{$compte.id}"{if $compte.id == $banque} selected="selected"{/if}>{$compte.libelle} - {$compte.banque}</option> {/foreach} </select> </dd> <dt><label for="f_date">Date</label> <b title="(Champ obligatoire)">obligatoire</b></dt> <dd><input type="date" name="date" id="f_date" value="{form_field name=date default=$default_date}" required="required" /></dd> {if !$membre} <dt><label for="f_numero_membre">Numéro de membre</label> <b title="(Champ obligatoire)">obligatoire</b></dt> <dd><input type="number" name="numero_membre" id="f_numero_membre" value="{form_field name=numero_membre}" step="1" min="1" required="required" /></dd> {/if} </dl> </fieldset> <p class="submit"> {csrf_field key="add_cotisation"} {if $membre}<input type="hidden" name="id_membre" value="{$membre.id}" />{/if} |
︙ | ︙ |
Modified src/templates/admin/membres/fiche.tpl from [ce083161b7] to [6c7c3b67dd].
︙ | ︙ | |||
52 53 54 55 56 57 58 | <dd>{$nb_operations} écritures comptables — <a href="{$admin_url}compta/operations/membre.php?id={$membre.id}">Voir la liste des écritures ajoutées par ce membre</a> </dd> {/if} </dl> <dl class="describe"> | < < | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <dd>{$nb_operations} écritures comptables — <a href="{$admin_url}compta/operations/membre.php?id={$membre.id}">Voir la liste des écritures ajoutées par ce membre</a> </dd> {/if} </dl> <dl class="describe"> <dt>Catégorie</dt> <dd>{$categorie.nom} <span class="droits">{format_droits droits=$categorie}</span></dd> <dt>Inscription</dt> <dd>{$membre.date_inscription|date_fr:'d/m/Y'}</dd> <dt>Dernière connexion</dt> <dd>{if empty($membre.date_connexion)}Jamais{else}{$membre.date_connexion|date_fr:'d/m/Y à H:i'}{/if}</dd> {foreach from=$champs key="c" item="config"} |
︙ | ︙ |
Modified src/templates/admin/membres/index.tpl from [a7b4f693eb] to [3953d5a24d].
︙ | ︙ | |||
46 47 48 49 50 51 52 | <form method="post" action="action.php" class="memberList"> {if !empty($liste)} <table class="list"> <thead class="userOrder"> <tr> {if $user.droits.membres == Garradin\Membres::DROIT_ADMIN}<td class="check"><input type="checkbox" title="Tout cocher / décocher" /></td>{/if} | < | < > > | > > < | 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 | <form method="post" action="action.php" class="memberList"> {if !empty($liste)} <table class="list"> <thead class="userOrder"> <tr> {if $user.droits.membres == Garradin\Membres::DROIT_ADMIN}<td class="check"><input type="checkbox" title="Tout cocher / décocher" /></td>{/if} {foreach from=$champs key="c" item="champ"} <td class="{if $order == $c} cur {if $desc}desc{else}asc{/if}{/if}">{if $c == "numero"}#{else}{$champ.title}{/if} <a href="?o={$c}&a&cat={$current_cat}" class="icn up">↑</a><a href="?o={$c}&d&cat={$current_cat}" class="icn dn">↓</a></td> {/foreach} <td></td> </tr> </thead> <tbody> {foreach from=$liste item="membre"} <tr> {if $user.droits.membres == Garradin\Membres::DROIT_ADMIN}<td class="check"><input type="checkbox" name="selected[]" value="{$membre.id}" /></td>{/if} {foreach from=$champs key="c" item="cfg"} <td> {if $c == $config.champ_identite}<a href="{$admin_url}membres/fiche.php?id={$membre.id}">{/if} {$membre->$c|raw|display_champ_membre:$cfg} {if $c == $config.champ_identite}</a>{/if} </td> {/foreach} <td class="actions"> <a class="icn" href="{$admin_url}membres/fiche.php?id={$membre.id}" title="Fiche membre">👤</a> <a class="icn" href="{$admin_url}membres/modifier.php?id={$membre.id}" title="Modifier la fiche membre">✎</a> </td> </tr> {/foreach} </tbody> </table> |
︙ | ︙ |
Modified src/templates/admin/membres/modifier.tpl from [77d2626485] to [45f1ca0f6f].
︙ | ︙ | |||
12 13 14 15 16 17 18 | {form_errors} <form method="post" action="{$self_url}"> <fieldset> <legend>Informations personnelles</legend> <dl> | < < < < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | {form_errors} <form method="post" action="{$self_url}"> <fieldset> <legend>Informations personnelles</legend> <dl> {foreach from=$champs item="champ" key="nom"} {html_champ_membre config=$champ name=$nom data=$membre} {/foreach} </dl> </fieldset> <fieldset> |
︙ | ︙ |
Modified src/www/admin/compta/pie.php from [485262b8ec] to [f0385456c4].
︙ | ︙ | |||
36 37 38 39 40 41 42 | { if ($i++ >= $max) { $others += $row['somme']; } else { | | | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | { if ($i++ >= $max) { $others += $row['somme']; } else { $cat = $categories[$row->id_categorie]; $pie->add(new \KD2\SVGPie_Data($row->somme, substr($cat->intitule, 0, 50), $colors[$i-1])); } } if ($others > 0) { $pie->add(new \KD2\SVGPie_Data($others, 'Autres', '#ccc')); } Static_Cache::store('pie_' . $graph, $pie->output()); } header('Content-Type: image/svg+xml'); Static_Cache::display('pie_' . $graph); |
Modified src/www/admin/config/donnees.php from [b1d0abed2c] to [fd934fbfc9].
1 2 3 4 5 6 | <?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 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 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; $s = new Sauvegarde; $code = false; if (f('config')) { $form->check('backup_config', [ 'frequence_sauvegardes' => 'present|numeric|min:0|max:365', 'nombre_sauvegardes' => 'present|numeric|min:1|max:90', ]); if (!$form->hasErrors()) { try { $config->set('frequence_sauvegardes', f('frequence_sauvegardes')); $config->set('nombre_sauvegardes', f('nombre_sauvegardes')); $config->save(); Utils::redirect('/admin/config/donnees.php?ok=config'); } catch (UserException $e) { $form->addError($e->getMessage()); } } } elseif (f('create')) { $form->check('backup_create'); if (!$form->hasErrors()) { try { $s->create(); Utils::redirect('/admin/config/donnees.php?ok=create'); } catch (UserException $e) { $form->addError($e->getMessage()); } } } elseif (f('download')) { $form->check('backup_download'); if (!$form->hasErrors()) { header('Content-type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $config->get('nom_asso') . ' - Sauvegarde données - ' . date('Y-m-d') . '.sqlite"'); header('Content-Length: ' . $s->getDBSize()); $s->dump(); exit; } } elseif (f('restore')) { $form->check('backup_manage'); if (!$form->hasErrors()) { try { $r = $s->restoreFromLocal(f('file')); Utils::redirect('/admin/config/donnees.php?ok=restore&code=' . (int)$r); } catch (UserException $e) { $form->addError($e->getMessage()); } } } elseif (f('remove')) { $form->check('backup_manage'); if (!$form->hasErrors()) { try { $s->remove(f('file')); Utils::redirect('/admin/config/donnees.php?ok=remove'); } catch (UserException $e) { $form->addError($e->getMessage()); } } } elseif (f('restore_file')) { $form->check('backup_restore'); if (!$form->hasErrors()) { // Ignorer la vérification d'intégrité si autorisé et demandé $check = (ALLOW_MODIFIED_IMPORT && f('force_import')) ? false : true; try { $r = $s->restoreFromUpload($_FILES['file'], $user->id, $check); Utils::redirect('/admin/config/donnees.php?ok=restore&code=' . (int)$r); } catch (UserException $e) { $form->addError($e->getMessage()); $code = $e->getCode(); } } } $tpl->assign('code', $code); $tpl->assign('ok_code', qg('code')); $tpl->assign('ok', qg('ok')); $tpl->assign('liste', $s->getList()); $tpl->assign('max_file_size', Utils::getMaxUploadSize()); $tpl->assign('db_size', $s->getDBSize()); $tpl->assign('files_size', $s->getDBFilesSize()); $tpl->display('admin/config/donnees.tpl'); |
Modified src/www/admin/config/membres.php from [8a6c35e7ca] to [5ecc83e171].
1 2 3 4 5 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; | < < | | > > > > > > | > > > > > > > < < < < | < | | < < < < < < < < < < < < < | < | < < | < < | | | < < < < < | | | | | | | | < | < | | < | > | | > > | | < < < | 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 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; $membres = new Membres; // Restauration de ce qui était if ($champs = f('champs')) { if (is_string($champs)) { $champs = json_decode($champs, true); } try { $champs = new Membres\Champs($champs); } catch (UserException $e) { $champs = new Membres\Champs($config->get('champs_membres')); unset($_POST['review']); $form->addError($e->getMessage()); } } else { // Il est nécessaire de créer une nouvelle instance ici, sinon // l'enregistrement des modifs ne marchera pas car les deux instances seront identiques. // Càd si on utilise directement l'instance de $config, elle sera modifiée directement // du coup quand on essaiera de comparer si ça a changé ça comparera deux fois la même chose // donc ça n'aura pas changé forcément. $champs = new Membres\Champs($config->get('champs_membres')); } if (f('save') || f('add') || f('review') || f('reset')) { $form->check('config_membres'); if (!$form->hasErrors()) { if (f('reset')) { Utils::redirect('/admin/config/membres.php'); } elseif (f('add')) { try { if (f('preset')) { $presets = Membres\Champs::listUnusedPresets($champs); if (!array_key_exists(f('preset'), $presets)) { throw new UserException('Le champ pré-défini demandé ne fait pas partie des champs disponibles.'); } $champs->add(f('preset'), $presets[f('preset')]); } elseif (f('new')) { $presets = Membres\Champs::importPresets(); $new = f('new'); if (array_key_exists($new, $presets)) { throw new UserException('Le champ personnalisé ne peut avoir le même nom qu\'un champ pré-défini.'); } $config = [ 'type' => f('new_type'), 'title' => f('new_title'), 'editable' => true, 'mandatory' => false, ]; if ($config['type'] == 'select' || $config['type'] == 'multiple') { $config['options'] = ['Première option']; } $champs->add($new, $config); } $tpl->assign('status', 'ADDED'); } catch (UserException $e) { $form->addError($e->getMessage()); } } elseif (f('save')) { try { $champs->save(); Utils::redirect('/admin/config/membres.php?ok'); } catch (UserException $e) { $form->addError($e->getMessage()); } } } } else { $tpl->assign('status', null !== qg('ok')); } $tpl->assign('review', (bool) f('review')); $types = $champs->getTypes(); $tpl->assign('champs', $champs->getAll()); $tpl->assign('types', $types); $tpl->assign('presets', Membres\Champs::listUnusedPresets($champs)); $tpl->assign('new', f('new')); $tpl->register_modifier('get_type', function ($type) use ($types) { return $types[$type]; }); $tpl->assign('title', 'Configuration — ' . (null !== qg('review') ? 'Confirmer les changements' : 'Fiche membres')); $tpl->display('admin/config/membres.tpl'); |
Modified src/www/admin/config/plugins.php from [c5914916c1] to [bbcf5eb592].
1 2 3 4 5 6 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; | < < | | < < < < < | > | < > | < | | | > > | < < | | < < | 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 | <?php namespace Garradin; require_once __DIR__ . '/_inc.php'; if (f('install')) { $form->check('install_plugins', [ 'plugin' => 'required', ]); if (!$form->hasErrors()) { try { Plugin::install(f('to_install'), false); Utils::redirect('/admin/config/plugins.php'); } catch (UserException $e) { $form->addError($e->getMessage()); } } } if (f('delete')) { $form->check('delete_plugin_' . qg('delete'), [ 'plugin' => 'required', ]); if (!$form->hasErrors()) { try { $plugin = new Plugin(qg('delete')); $plugin->uninstall(); Utils::redirect('/admin/config/plugins.php'); } catch (UserException $e) { $form->addError($e->getMessage()); } } } if (qg('delete')) { $plugin = new Plugin(qg('delete')); $tpl->assign('plugin', $plugin->getInfos()); $tpl->assign('delete', true); } else { $tpl->assign('liste_telecharges', Plugin::listDownloaded()); $tpl->assign('liste_installes', Plugin::listInstalled()); } $tpl->display('admin/config/plugins.tpl'); |
Modified src/www/admin/install.php from [58ff9c90a2] to [42229607cb].
︙ | ︙ | |||
105 106 107 108 109 110 111 | { $tpl->assign('disabled', true); } else { $tpl->assign('disabled', false); | | | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | { $tpl->assign('disabled', true); } else { $tpl->assign('disabled', false); if (f('save')) { $form->check('install', [ 'nom_asso' => 'required', 'email_asso' => 'required|email', 'nom_membre' => 'required', 'email_membre' => 'required|email', 'passe' => 'confirmed|required', |
︙ | ︙ |
Modified src/www/admin/membres/cotisations/ajout.php from [f220d5298d] to [4c34f02825].
︙ | ︙ | |||
28 29 30 31 32 33 34 | $m_cotisations = new Membres\Cotisations; $cats = new Compta\Categories; $banques = new Compta\Comptes_Bancaires; $error = false; | | > > > > > > > | | | 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 | $m_cotisations = new Membres\Cotisations; $cats = new Compta\Categories; $banques = new Compta\Comptes_Bancaires; $error = false; if (f('add')) { $form->check('add_cotisation', [ 'date' => 'date_format:Y-m-d|required', 'id_cotisation' => 'numeric|required|in_table:cotisations,id', 'id_membre' => 'numeric|required|in_table:membres,id', ]); if (!$form->hasErrors()) { try { $id_membre = f('id_membre'); if (!$id_membre && f('numero_membre')) { $id_membre = (new Membres)->getIDWithNumero(f('numero_membre')); } $data = [ 'date' => f('date'), 'id_cotisation' => f('id_cotisation'), 'id_membre' => $id_membre, 'id_auteur' => $user->id, 'montant' => f('montant'), 'moyen_paiement' => f('moyen_paiement'), 'numero_cheque' => f('numero_cheque'), 'banque' => f('banque'), ]; $m_cotisations->add($data); Utils::redirect('/admin/membres/cotisations.php?id=' . $id_membre); } catch (UserException $e) { $form->addError($e->getMessage()); } } } |
︙ | ︙ |
Modified src/www/admin/membres/index.php from [1aa05ee07f] to [983da9689e].
︙ | ︙ | |||
45 46 47 48 49 50 51 | if (null !== qg('d')) $desc = true; $fields = $champs->getListedFields(); // Vérifier que le champ de tri existe bien dans la table | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | if (null !== qg('d')) $desc = true; $fields = $champs->getListedFields(); // Vérifier que le champ de tri existe bien dans la table if (!isset($fields->$order)) { // Sinon par défaut c'est le premier champ de la table qui fait le tri $order = $champs->getFirstListed(); } $tpl->assign('order', $order); $tpl->assign('desc', $desc); |
︙ | ︙ |
Modified src/www/admin/upgrade.php from [2713b28f11] to [1d7fc72053].
︙ | ︙ | |||
264 265 266 267 268 269 270 271 272 273 274 275 276 277 | $db->begin(); $db->import(ROOT . '/include/data/0.8.0.sql'); $db->commit(); // Nettoyage de la base de données $db->exec('VACUUM;'); } Utils::clearCaches(); $config->setVersion(garradin_version()); | > > > > > > > > | 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | $db->begin(); $db->import(ROOT . '/include/data/0.8.0.sql'); $db->commit(); // Ajout champ numéro de membre $champs = (array) Config::getInstance()->get('champs_membres')->getAll(); $presets = Membres\Champs::importPresets(); // Ajout du numéro au début $champs = array_merge(['numero' => $presets['numero']], $champs); (new Membres\Champs($champs))->save(); // Nettoyage de la base de données $db->exec('VACUUM;'); } Utils::clearCaches(); $config->setVersion(garradin_version()); |
︙ | ︙ |