Overview
Comment: | Progress on migration of custom fields |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA3-256: |
004b9a67f66e23dc8c2d6d0713775a0f |
User & Date: | bohwaz on 2021-12-04 02:38:52 |
Other Links: | branch diff | manifest | tags |
Context
2021-12-10
| ||
01:10 | Merge with trunk check-in: 8672b16816 user: bohwaz tags: dev | |
2021-12-04
| ||
02:38 | Progress on migration of custom fields check-in: 004b9a67f6 user: bohwaz tags: dev | |
01:38 | Merge missing: Fix Atom feed validity check-in: 86f887284b user: bohwaz tags: dev | |
Changes
Modified src/include/data/1.2.0_migration.sql from [0ba1653e93] to [45ddb4eb17].
︙ | ︙ | |||
29 30 31 32 33 34 35 | UPDATE searches SET target = 'accounting' WHERE target = 'compta'; UPDATE searches SET target = 'users' WHERE target = 'membres'; DROP TABLE recherches; INSERT INTO config VALUES ('log_retention', 720); INSERT INTO config VALUES ('log_anonymize', 365); | > > > | 29 30 31 32 33 34 35 36 37 38 | UPDATE searches SET target = 'accounting' WHERE target = 'compta'; UPDATE searches SET target = 'users' WHERE target = 'membres'; DROP TABLE recherches; INSERT INTO config VALUES ('log_retention', 720); INSERT INTO config VALUES ('log_anonymize', 365); -- This is now part of the config_users_fields table DELETE FROM config WHERE key IN ('champs_membres', 'champ_identite', 'champ_identifiant'); |
Modified src/include/data/1.2.0_schema.sql from [97ed5bad7c] to [04057102f4].
︙ | ︙ | |||
10 11 12 13 14 15 16 | CREATE TABLE IF NOT EXISTS config_users_fields ( name TEXT PRIMARY KEY NOT NULL, sort_order INTEGER NOT NULL, type TEXT NOT NULL, label TEXT NOT NULL, help TEXT NULL, | | | | | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | CREATE TABLE IF NOT EXISTS config_users_fields ( name TEXT PRIMARY KEY NOT NULL, sort_order INTEGER NOT NULL, type TEXT NOT NULL, label TEXT NOT NULL, help TEXT NULL, required INTEGER NOT NULL DEFAULT 0, read_access INTEGER NOT NULL DEFAULT 0, write_access INTEGER NOT NULL DEFAULT 1, list_row INTEGER NULL, options TEXT NULL, default_value TEXT NULL, system TEXT NULL ); CREATE TABLE IF NOT EXISTS plugins ( id TEXT NOT NULL PRIMARY KEY, official INTEGER NOT NULL DEFAULT 0, -- 1 if plugin is official |
︙ | ︙ |
Modified src/include/data/users_fields_presets.ini from [e7b6acb07b] to [4bd1f93e35].
1 2 3 4 5 6 7 8 9 | ; Ce fichier contient la configuration par défaut des champs des fiches membres. ; La configuration est ensuite enregistrée au format INI dans la table ; config de la base de données. ; ; Syntaxe : ; ; [nom_du_champ] ; Nom unique du champ, ne peut contenir que des lettres et des tirets bas ; type = text ; label = "Super champ trop cool" | | | | | | | | < | > | | | | | | | | | | | | | | | | | | | | | 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 | ; Ce fichier contient la configuration par défaut des champs des fiches membres. ; La configuration est ensuite enregistrée au format INI dans la table ; config de la base de données. ; ; Syntaxe : ; ; [nom_du_champ] ; Nom unique du champ, ne peut contenir que des lettres et des tirets bas ; type = text ; label = "Super champ trop cool" ; required = true ; write_access = 0 ; ; Description des options possibles pour chaque champ : ; ; type: (défaut: text) OBLIGATOIRE ; certains types gérés par <input type> de HTML5 : ; text, number, date, datetime, url, email, checkbox, file, password, tel ; champs spécifiques : ; - country = sélecteur de pays ; - textarea = texte multi lignes ; - multiple = multiples cases à cocher (jusqu'à 32, binaire) ; - select = un choix parmis plusieurs ; label: OBLIGATOIRE ; Titre du champ ; help: ; Texte d'aide sur les fiches membres ; options[]: ; pour définir les options d'un champ de type select ou multiple ; write_access: ; 1 = modifiable par le membre ; 0 = modifiable uniquement par un admin (défaut) ; required: ; true = obligatoire, la fiche membre ne pourra être enregistrée si ce champ est vide ; false = facultatif (défaut) ; read_access: ; 1 = visible par le membre (défaut) ; 0 = visible uniquement par un admin ; list_row: ; Si absent ou zéro ('0') ou false, ce champ n'apparaîtra pas dans la liste des membres ; 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 label = "Numéro de membre" help = "Doit être unique, laisser vide pour que le numéro soit attribué automatiquement" required = true install = true write_access = 0 list_row = 1 [nom] type = text label = "Nom & prénom" required = true install = true write_access = 1 list_row = 2 [email] ; ce champ est facultatif et de type 'email' type = email label = "Adresse E-Mail" required = false install = true write_access = 1 [passe] ; ce champ est obligatoirement présent et de type 'password' ; le titre ne peut être modifié type = password required = false install = true write_access = 1 [adresse] type = textarea label = "Adresse postale" help = "Indiquer ici le numéro, le type de voie, etc." install = true write_access = 1 [code_postal] type = text label = "Code postal" install = true write_access = 1 list_row = 3 [ville] type = text label = "Ville" install = true write_access = 1 list_row = 4 [pays] type = country label = "Pays" install = true write_access = 1 [telephone] type = tel label = "Numéro de téléphone" install = true write_access = 1 [lettre_infos] type = checkbox label = "Inscription à la lettre d'information" install = true write_access = 1 [groupe_travail] type = multiple label = "Groupes de travail" write_access = 0 options[] = "Télécoms" options[] = "Trésorerie" options[] = "Relations publiques" options[] = "Communication presse" options[] = "Organisation d'événements" [date_naissance] type = date label = "Date de naissance" write_access = 1 [notes] type = textarea label = "Notes" write_access = 0 read_access = 0 [photo] type = file label = "Photo" write_access = 0 read_access = 0 |
Modified src/include/lib/Garradin/Entities/Users/DynamicField.php from [d371040f81] to [d75a3b7c32].
1 2 | <?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 | <?php namespace Garradin\Entities\Users; use Garradin\Config; use Garradin\DB; use Garradin\Entity; use Garradin\Utils; use Garradin\Users\DynamicFields; class DynamicField extends Entity { const TABLE = 'config_users_fields'; protected int $id; protected string $name; /** * Order of field in form * @var int */ protected int $sort_order; protected string $type; protected string $label; protected ?string $help; /** * TRUE if the field is required */ protected bool $required; /** * 0 = only admins can read this field (private) * 1 = admins + the user themselves can read it */ protected int $read_access; /** * 0 = only admins can write this field * 1 = admins + the user themselves can change it */ protected int $write_access; /** * Index of row in users list */ protected ?int $list_row; /** * Multiple options (JSON) for select and multiple fields */ protected ?string $options; /** * Default value */ protected ?string $default_value; /** * System use: * password, number, name, login */ protected ?string $system; const ACCESS_ADMIN = 0; const ACCESS_USER = 1; const TYPES = [ 'email' => 'Adresse E-Mail', 'url' => 'Adresse URL', 'checkbox' => 'Case à cocher', 'date' => 'Date', 'datetime' => 'Date et heure', |
︙ | ︙ | |||
121 122 123 124 125 126 127 128 129 130 131 132 133 134 | parent::delete(); } public function selfCheck(): void { $this->name = strtolower($this->name); $this->assert(!array_key_exists($this->name, self::SYSTEM_FIELDS), 'Ce nom de champ est déjà utilisé par un champ système, merci d\'en choisir un autre.'); $this->assert(preg_match('!^[a-z][a-z0-9]*(_[a-z0-9]+)*$!', $this->name), 'Le nom du champ est invalide : ne sont acceptés que les lettres minuscules et les chiffres (éventuellement séparés par un underscore).'); $this->assert(trim($this->label) != '', 'Le libellé est obligatoire.'); $this->assert($this->type && array_key_exists($this->type, self::TYPES), 'Type de champ invalide.'); $this->assert($this->system != 'password' || $this->type == 'password', 'Le champ mot de passe ne peut être d\'un type différent de mot de passe.'); | > > > | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | parent::delete(); } public function selfCheck(): void { $this->name = strtolower($this->name); $this->assert($this->read_access == self::ACCESS_ADMIN || $this->read_access == self::ACCESS_USER); $this->assert($this->write_access == self::ACCESS_ADMIN || $this->write_access == self::ACCESS_USER); $this->assert(!array_key_exists($this->name, self::SYSTEM_FIELDS), 'Ce nom de champ est déjà utilisé par un champ système, merci d\'en choisir un autre.'); $this->assert(preg_match('!^[a-z][a-z0-9]*(_[a-z0-9]+)*$!', $this->name), 'Le nom du champ est invalide : ne sont acceptés que les lettres minuscules et les chiffres (éventuellement séparés par un underscore).'); $this->assert(trim($this->label) != '', 'Le libellé est obligatoire.'); $this->assert($this->type && array_key_exists($this->type, self::TYPES), 'Type de champ invalide.'); $this->assert($this->system != 'password' || $this->type == 'password', 'Le champ mot de passe ne peut être d\'un type différent de mot de passe.'); |
︙ | ︙ |
Modified src/include/lib/Garradin/Upgrade.php from [696b44337b] to [27203f7683].
︙ | ︙ | |||
176 177 178 179 180 181 182 183 184 185 186 | if (version_compare($v, '1.1.15', '<')) { $db->begin(); $db->import(ROOT . '/include/data/1.1.15_migration.sql'); $db->commit(); } if (version_compare($v, '1.2.0', '<')) { $db->begin(); $db->import(ROOT . '/include/data/1.2.0_migration.sql'); $db->commit(); | > > > > > | | 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | if (version_compare($v, '1.1.15', '<')) { $db->begin(); $db->import(ROOT . '/include/data/1.1.15_migration.sql'); $db->commit(); } if (version_compare($v, '1.2.0', '<')) { $config = (object) $db->getAssoc('SELECT key, value FROM config WHERE key IN (\'champs_membres\', \'champ_identifiant\', \'champ_identite\');'); $df = \Garradin\Users\DynamicFields::fromOldINI($config->champs_membres, $config->champ_identifiant, $config->champ_identite, 'numero'); $sql = $df->getSQLSchema(); $db->begin(); $db->exec($sql); $db->import(ROOT . '/include/data/1.2.0_migration.sql'); $db->commit(); $db->exec('DELETE FROM config WHERE key IN (\'champs_membres\', \'champ_identite\', \'champ_identifiant\');'); } // Vérification de la cohérence des clés étrangères $db->foreignKeyCheck(); // Delete local cached files Utils::resetCache(USER_TEMPLATES_CACHE_ROOT); |
︙ | ︙ |
Modified src/include/lib/Garradin/Users/DynamicFields.php from [0cdd23dbc3] to [e59c711051].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php namespace Garradin\Users; use Garradin\Config; use Garradin\DB; use Garradin\Utils; use Garradin\Entities\Users\DynamicField; use Garradin\Entities\Users\User; class DynamicFields { const PRESETS_FILE = ROOT . '/include/data/users_fields_presets.ini'; | > > | | 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 | <?php namespace Garradin\Users; use Garradin\Config; use Garradin\DB; use Garradin\Utils; use Garradin\Entities\Users\DynamicField; use Garradin\Entities\Users\User; use const Garradin\ROOT; class DynamicFields { const PRESETS_FILE = ROOT . '/include/data/users_fields_presets.ini'; const TABLE = DynamicField::TABLE; protected $_fields; protected $_fields_by_type; protected $_fields_by_system_use; protected $_presets; static protected $_instance; |
︙ | ︙ | |||
90 91 92 93 94 95 96 | $db->exec($sql); // Regenerate login index $db->exec('DROP INDEX IF EXISTS users_id_field;'); $this->createIndexes(); } | | > | > | | 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 | $db->exec($sql); // Regenerate login index $db->exec('DROP INDEX IF EXISTS users_id_field;'); $this->createIndexes(); } protected function __construct(bool $load = true) { if ($load) { $this->reload(); } } protected function reload() { $db = DB::getInstance(); $this->_fields = $db->getGrouped(sprintf('SELECT name, * FROM %s ORDER BY sort_order;', self::TABLE)); $this->reloadCache(); } protected function reloadCache() { $this->_fields_by_type = []; $this->_fields_by_system_use = []; |
︙ | ︙ | |||
153 154 155 156 157 158 159 | public function getPresets(): array { if (null === $this->_presets) { $this->_presets = parse_ini_file(self::PRESETS_FILE, true); | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | public function getPresets(): array { if (null === $this->_presets) { $this->_presets = parse_ini_file(self::PRESETS_FILE, true); foreach ($this->_presets as &$preset) { $preset = (object) $preset; } unset($preset); } return $this->_presets; } public function listUnusedPresets(): array { return array_diff_key(self::getPresets(), (array) $this->_fields); } public function getInstallPresets() { return array_filter($this->getPresets(), fn ($row) => !$row->install ); } /** * Import from old INI config */ static public function fromOldINI(string $config, string $login_field, string $name_field, string $number_field) { $config = parse_ini_string($config, true); $i = 0; $self = new self(false); $defaults = [ 'help' => null, 'private' => false, 'editable' => true, 'mandatory' => false, 'list_row' => null, ]; foreach ($config as $name => $data) { $field = new DynamicField; if ($name == 'passe') { $name = 'password'; $data['title'] = 'Mot de passe'; $field->system = 'password'; } elseif ($name == $login_field) { $field->system = 'login'; } elseif ($name == $name_field) { $field->system = 'name'; } elseif ($name == $number_field) { $field->system = 'number'; } $data = array_merge($defaults, $data); $field->set('name', $name); $field->set('label', $data['title']); $field->set('type', $data['type']); $field->set('help', empty($data['help']) ? null : $data['help']); $field->set('read_access', $data['private'] ? $field::ACCESS_ADMIN : $field::ACCESS_USER); $field->set('write_access', $data['editable'] ? $field::ACCESS_ADMIN : $field::ACCESS_USER); $field->set('required', (bool) $data['mandatory']); $field->set('list_row', isset($data['list_row']) ? (int)$data['list_row'] : null); $field->set('sort_order', $i++); $self->_fields[$name] = $field; } self::$_instance = $self; return $self; } public function isText(string $field) { $type = $this->_fields[$field]->type; return self::TYPES[$type] == 'string'; } |
︙ | ︙ | |||
270 271 272 273 274 275 276 | ]; end($this->_fields); $last_one = key($this->_fields); foreach ($this->_fields as $key=>$cfg) { | | | 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | ]; end($this->_fields); $last_one = key($this->_fields); foreach ($this->_fields as $key=>$cfg) { $type = DynamicField::SQL_TYPES[$cfg->type]; if ($type == 'TEXT') { $type = 'TEXT COLLATE NOCASE'; } $line = sprintf('%s %s', $db->quoteIdentifier($key), $type); |
︙ | ︙ |