init
[garradin.git] / include / class.champs_membres.php
1 <?php
2
3 namespace Garradin;
4
5 class Champs_Membres
6 {
7 protected $champs = null;
8
9 protected $types = [
10 'email' => 'Adresse E-Mail',
11 'url' => 'Adresse URL',
12 'checkbox' => 'Case à cocher',
13 'date' => 'Date',
14 'datetime' => 'Date et heure',
15 //'file' => 'Fichier',
16 'password' => 'Mot de passe',
17 'number' => 'Numéro',
18 'tel' => 'Numéro de téléphone',
19 'select' => 'Sélecteur à choix unique',
20 'multiple' => 'Sélecteur à choix multiple',
21 'country' => 'Sélecteur de pays',
22 'text' => 'Texte',
23 'textarea' => 'Texte multi-lignes',
24 ];
25
26 protected $text_types = [
27 'email',
28 'text',
29 'select',
30 'textarea',
31 'url',
32 'password',
33 'country'
34 ];
35
36 protected $config_fields = [
37 'type',
38 'title',
39 'help',
40 'editable',
41 'list_row',
42 'mandatory',
43 'private',
44 'options'
45 ];
46
47 static protected $presets = null;
48
49 public function __toString()
50 {
51 return utils::write_ini_string($this->champs);
52 }
53
54 public function toString()
55 {
56 return utils::write_ini_string($this->champs);
57 }
58
59 static public function importInstall()
60 {
61 $champs = parse_ini_file(ROOT . '/include/data/champs_membres.ini', true);
62 $champs = array_filter($champs, function ($row) { return !empty($row['install']); });
63 return new Champs_Membres($champs);
64 }
65
66 static public function importPresets()
67 {
68 if (is_null(self::$presets))
69 {
70 self::$presets = parse_ini_file(ROOT . '/include/data/champs_membres.ini', true);
71 }
72
73 return self::$presets;
74 }
75
76 static public function listUnusedPresets(Champs_Membres $champs)
77 {
78 return array_diff_key(self::importPresets(), $champs->getAll());
79 }
80
81 public function __construct($champs)
82 {
83 if ($champs instanceOf Champs_Membres)
84 {
85 $this->champs = $champs->getAll();
86 }
87 elseif (is_array($champs))
88 {
89 foreach ($champs as $key=>&$config)
90 {
91 $this->_checkField($key, $config);
92 }
93
94 $this->champs = $champs;
95 }
96 else
97 {
98 $champs = parse_ini_string((string)$champs, true);
99
100 foreach ($champs as $key=>&$config)
101 {
102 $this->_checkField($key, $config);
103 }
104
105 $this->champs = $champs;
106 }
107 }
108
109 public function getTypes()
110 {
111 return $this->types;
112 }
113
114 public function get($champ, $key = null)
115 {
116 if ($champ == 'id')
117 {
118 return ['title' => 'Numéro unique', 'type' => 'number'];
119 }
120
121 if (!array_key_exists($champ, $this->champs))
122 return null;
123
124 if ($key !== null)
125 {
126 if (array_key_exists($key, $this->champs[$champ]))
127 return $this->champs[$champ][$key];
128 else
129 return null;
130 }
131
132 return $this->champs[$champ];
133 }
134
135 public function isText($champ)
136 {
137 if (!array_key_exists($champ, $this->champs))
138 return null;
139
140 if (in_array($this->champs[$champ]['type'], $this->text_types))
141 return true;
142 else
143 return false;
144 }
145
146 public function getAll()
147 {
148 $this->champs['passe']['title'] = 'Mot de passe';
149 return $this->champs;
150 }
151
152 public function getList()
153 {
154 $champs = $this->champs;
155 unset($champs['passe']);
156 return $champs;
157 }
158
159 public function getFirst()
160 {
161 reset($this->champs);
162 return key($this->champs);
163 }
164
165 public function getListedFields()
166 {
167 $champs = $this->champs;
168
169 $champs = array_filter($champs, function ($a) {
170 return empty($a['list_row']) ? false : true;
171 });
172
173 uasort($champs, function ($a, $b) {
174 if ($a['list_row'] == $b['list_row'])
175 return 0;
176
177 return ($a['list_row'] > $b['list_row']) ? 1 : -1;
178 });
179
180 return $champs;
181 }
182
183 /**
184 * Vérifie la cohérence et la présence des bons éléments pour un champ
185 * @param string $name Nom du champ
186 * @param array $config Configuration du champ
187 * @return boolean true
188 */
189 protected function _checkField($name, &$config)
190 {
191 if (!preg_match('!^\w+(_\w+)*$!', $name))
192 {
193 throw new UserException('Le nom du champ est invalide.');
194 }
195
196 foreach ($config as $key=>&$value)
197 {
198 // Champ install non pris en compte
199 if ($key == 'install')
200 {
201 unset($config[$key]);
202 continue;
203 }
204
205 if (!in_array($key, $this->config_fields))
206 {
207 throw new \BadMethodCallException('Champ '.$key.' non valide.');
208 }
209
210 if ($key == 'editable' || $key == 'private' || $key == 'mandatory')
211 {
212 $value = (bool) (int) $value;
213 }
214 elseif ($key == 'list_row')
215 {
216 $value = (int) $value;
217 }
218 elseif ($key == 'help' || $key == 'title')
219 {
220 $value = trim((string) $value);
221 }
222 elseif ($key == 'options')
223 {
224 $value = (array) $value;
225
226 foreach ($value as $option_key=>$option_value)
227 {
228 if (trim($option_value) == '')
229 {
230 unset($value[$option_key]);
231 }
232 }
233 }
234 }
235
236 if (empty($config['title']) && $name != 'passe')
237 {
238 throw new UserException('Champ "'.$name.'" : Le titre est obligatoire.');
239 }
240
241 if (empty($config['type']) || !array_key_exists($config['type'], $this->types))
242 {
243 throw new UserException('Champ "'.$name.'" : Le type est vide ou non valide.');
244 }
245
246 if ($name == 'email' && $config['type'] != 'email')
247 {
248 throw new UserException('Le champ email ne peut être d\'un type différent de email.');
249 }
250
251 if ($name == 'passe' && $config['type'] != 'password')
252 {
253 throw new UserException('Le champ mot de passe ne peut être d\'un type différent de mot de passe.');
254 }
255
256 if (($config['type'] == 'multiple' || $config['type'] == 'select') && empty($config['options']))
257 {
258 throw new UserException('Le champ "'.$name.'" nécessite de comporter au moins une option possible.');
259 }
260
261 if (!array_key_exists('editable', $config))
262 {
263 $config['editable'] = false;
264 }
265
266 if (!array_key_exists('mandatory', $config))
267 {
268 $config['mandatory'] = false;
269 }
270
271 if (!array_key_exists('private', $config))
272 {
273 $config['private'] = false;
274 }
275
276 return true;
277 }
278
279 /**
280 * Ajouter un nouveau champ
281 * @param string $name Nom du champ
282 * @param array $config Configuration du champ
283 * @return boolean true
284 */
285 public function add($name, $config)
286 {
287 if (!preg_match('!^[a-z0-9]+(_[a-z0-9]+)*$!', $name))
288 {
289 throw new UserException('Le nom du champ est invalide : ne sont acceptés que des lettres minuscules et chiffres.');
290 }
291
292 $this->_checkField($name, $config);
293
294 $this->champs[$name] = $config;
295
296 return true;
297 }
298
299 /**
300 * Modifie un champ particulier
301 * @param string $champ Nom du champ
302 * @param string $key Nom de la clé à modifier
303 * @param mixed $value Valeur à affecter
304 * @return boolean true
305 */
306 public function set($champ, $key, $value)
307 {
308 if (!isset($this->champs[$champ]))
309 {
310 throw new \LogicException('Champ "'.$champ.'" inconnu.');
311 }
312
313 // Vérification
314 $config = $this->champs[$champ];
315 $config[$key] = $value;
316 $this->_checkField($champ, $config);
317
318 $this->champs[$champ] = $config;
319 return true;
320 }
321
322 /**
323 * Modifie les champs en interne en vérifiant que tout va bien
324 * @param array $champs Liste des champs
325 * @return boolean true
326 */
327 public function setAll($champs)
328 {
329 if (!array_key_exists('email', $champs))
330 {
331 throw new UserException('Le champ E-Mail ne peut être supprimé des fiches membres.');
332 }
333
334 if (!array_key_exists('passe', $champs))
335 {
336 throw new UserException('Le champ Mot de passe ne peut être supprimé des fiches membres.');
337 }
338
339 $config = Config::getInstance();
340
341 if (!array_key_exists($config->get('champ_identite'), $champs))
342 {
343 throw new UserException('Le champ '.$config->get('champ_identite')
344 .' est défini comme identité des membres et ne peut donc être supprimé des fiches membres.');
345 }
346
347 if (!array_key_exists($config->get('champ_identifiant'), $champs))
348 {
349 throw new UserException('Le champ '.$config->get('champ_identifiant')
350 .' est défini comme identifiant à la connexion et ne peut donc être supprimé des fiches membres.');
351 }
352
353 foreach ($champs as $name=>&$config)
354 {
355 $this->_checkField($name, $config);
356 }
357
358 $this->champs = $champs;
359
360 return true;
361 }
362
363 /**
364 * Enregistre les changements de champs en base de données
365 * @param boolean $enable_copy Recopier les anciennes champs dans les nouveaux ?
366 * @return boolean true
367 */
368 public function save($enable_copy = true)
369 {
370 $db = DB::getInstance();
371 $config = Config::getInstance();
372
373 // Champs à créer
374 $create = [
375 'id INTEGER PRIMARY KEY, -- Numéro attribué automatiquement',
376 'id_categorie INTEGER NOT NULL, -- Numéro de catégorie',
377 'date_connexion TEXT NULL, -- Date de dernière connexion',
378 'date_inscription TEXT NOT NULL DEFAULT CURRENT_DATE, -- Date d\'inscription',
379 ];
380
381 $create_keys = [
382 'FOREIGN KEY (id_categorie) REFERENCES membres_categories (id)'
383 ];
384
385 // Champs à recopier
386 $copy = [
387 'id',
388 'id_categorie',
389 'date_connexion',
390 'date_inscription',
391 ];
392
393 $anciens_champs = $config->get('champs_membres');
394 $anciens_champs = is_null($anciens_champs) ? $this->champs : $anciens_champs->getAll();
395
396 foreach ($this->champs as $key=>$cfg)
397 {
398 if ($cfg['type'] == 'number')
399 $type = 'FLOAT';
400 elseif ($cfg['type'] == 'multiple' || $cfg['type'] == 'checkbox')
401 $type = 'INTEGER';
402 elseif ($cfg['type'] == 'file')
403 $type = 'BLOB';
404 else
405 $type = 'TEXT';
406
407 $line = $key . ' ' . $type . ',';
408
409 if (!empty($cfg['title']))
410 {
411 $line .= ' -- ' . str_replace(["\n", "\r"], '', $cfg['title']);
412 }
413
414 $create[] = $line;
415
416 if (array_key_exists($key, $anciens_champs))
417 {
418 $copy[] = $key;
419 }
420 }
421
422 $create = array_merge($create, $create_keys);
423
424 $create = 'CREATE TABLE membres_tmp (' . "\n\t" . implode("\n\t", $create) . "\n);";
425 $copy = 'INSERT INTO membres_tmp (' . implode(', ', $copy) . ') SELECT ' . implode(', ', $copy) . ' FROM membres;';
426
427 $db->exec('PRAGMA foreign_keys = OFF;');
428 $db->exec('BEGIN;');
429 $db->exec($create);
430
431 if ($enable_copy) {
432 $db->exec($copy);
433 }
434
435 $db->exec('DROP TABLE IF EXISTS membres;');
436 $db->exec('ALTER TABLE membres_tmp RENAME TO membres;');
437 $db->exec('CREATE INDEX membres_id_categorie ON membres (id_categorie);'); // Index
438
439 if ($config->get('champ_identifiant'))
440 {
441 // Mettre les champs identifiant vides à NULL pour pouvoir créer un index unique
442 $db->exec('UPDATE membres SET '.$config->get('champ_identifiant').' = NULL
443 WHERE '.$config->get('champ_identifiant').' = "";');
444
445 // Création de l'index unique
446 $db->exec('CREATE UNIQUE INDEX membres_identifiant ON membres ('.$config->get('champ_identifiant').');');
447 }
448
449 // Création des index pour les champs affichés dans la liste des membres
450 $listed_fields = array_keys($this->getListedFields());
451 foreach ($listed_fields as $field)
452 {
453 if ($field === $config->get('champ_identifiant'))
454 {
455 // Il y a déjà un index
456 continue;
457 }
458
459 $db->exec('CREATE INDEX membres_liste_' . $field . ' ON membres (' . $field . ');');
460 }
461
462 $db->exec('END;');
463 $db->exec('PRAGMA foreign_keys = ON;');
464
465 $config->set('champs_membres', $this);
466 $config->save();
467
468 return true;
469 }
470 }