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: 004b9a67f66e23dc8c2d6d0713775a0fd46a641beb6b0e693e21a08217678a70
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
17
18
19
20
21

22
23
24
25
26
27
28

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,
    mandatory INTEGER NOT NULL DEFAULT 0,
    private INTEGER NOT NULL DEFAULT 0,
    user_editable INTEGER NOT NULL DEFAULT 1,
    list_row INTEGER NULL,
    options TEXT,

    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







|
|
|

|
>







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
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"
;	mandatory = true
;	user_editable = false
;
;	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
;	user_editable:
;		true = modifiable par le membre
;		false = modifiable uniquement par un admin (défaut)
;	mandatory:
;		true = obligatoire, la fiche membre ne pourra être enregistrée si ce champ est vide
;		false = facultatif (défaut)
;	private:
;		true = non visible par le membre lui-même
;		false = visible par le membre (défaut)

;	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"
mandatory = true
install = true
user_editable = false
list_row = 1

[nom]
type = text
label = "Nom & prénom"
mandatory = true
install = true
user_editable = true
list_row = 2

[email]
; ce champ est facultatif et de type 'email'
type = email
label = "Adresse E-Mail"
mandatory = false
install = true
user_editable = true

[passe]
; ce champ est obligatoirement présent et de type 'password'
; le titre ne peut être modifié
type = password
mandatory = false
install = true
user_editable = true

[adresse]
type = textarea
label = "Adresse postale"
help = "Indiquer ici le numéro, le type de voie, etc."
install = true
user_editable = true

[code_postal]
type = text
label = "Code postal"
install = true
user_editable = true
list_row = 3

[ville]
type = text
label = "Ville"
install = true
user_editable = true
list_row = 4

[pays]
type = country
label = "Pays"
install = true
user_editable = true

[telephone]
type = tel
label = "Numéro de téléphone"
install = true
user_editable = true

[lettre_infos]
type = checkbox
label = "Inscription à la lettre d'information"
install = true
user_editable = true

[groupe_travail]
type = multiple
label = "Groupes de travail"
user_editable = false
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"
user_editable = true

[notes]
type = textarea
label = "Notes"
user_editable = false
private = true

[photo]
type = file
label = "Photo"
user_editable = false
private = false









|
|

















|
|
|
|


|
<
|
>













|

|





|

|






|

|





|

|






|





|






|






|





|





|




|









|




|
|




|
|
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
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
<?php

namespace Garradin\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 $name;


	/**
	 * Order of field in form
	 * @var int
	 */
	protected $sort_order;


	protected $type;
	protected $label;
	protected $help;
	protected $mandatory;



	protected $private;
	protected $user_editable;




	protected $list_row;
	protected $options;




	protected $system;









	protected $_types = [
		'name'          => 'string',
		'sort_order'    => 'int',
		'type'          => 'string',
		'label'         => 'string',



		'help'          => '?string',
		'mandatory'     => 'bool',
		'private'       => 'bool',

		'user_editable' => 'bool',
		'list_row'      => '?int',


		'options'       => '?string',
		'system'        => '?string',
	];




	const TYPES = [
		'email'		=>	'Adresse E-Mail',
		'url'		=>	'Adresse URL',
		'checkbox'	=>	'Case à cocher',
		'date'		=>	'Date',
		'datetime'	=>	'Date et heure',


|











|
>





|

<
|
|
|
|
>
>
>
|
|
>
>
>
>
|
|
>
>
>
>
|
>
>
>
>
>

>
>
>
|
|
<
<
<
>
>
>
|
|
<
>
|
<
>
>
|
<
<

>
>







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
187
188
189
190
191
192
193
194
			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();

				$db->exec('DELETE FROM config WHERE key = \'champs_membres\';');
			}

			// Vérification de la cohérence des clés étrangères
			$db->foreignKeyCheck();

			// Delete local cached files
			Utils::resetCache(USER_TEMPLATES_CACHE_ROOT);







>
>
>
>

>



|







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
16
17
18
19
20
21
22
23
<?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';

	const TABLE = DynamicFields::TABLE;

	protected $_fields;
	protected $_fields_by_type;
	protected $_fields_by_system_use;
	protected $_presets;

	static protected $_instance;











>
>




|







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
97
98

99

100
101
102
103
104
105
106
107
108
109
110
111
112
		$db->exec($sql);

		// Regenerate login index
		$db->exec('DROP INDEX IF EXISTS users_id_field;');
		$this->createIndexes();
	}

	protected function __construct()
	{

		$this->reload();

	}

	protected function reload()
	{
		$db = DB::getInstance();
		$this->_fields = $db->getGrouped('SELECT key, * FROM config_users_fields ORDER BY sort_order;');
		$this->reloadCache();
	}

	protected function reloadCache()
	{
		$this->_fields_by_type = [];
		$this->_fields_by_system_use = [];







|

>
|
>





|







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
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

	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(), function ($row) { return !$row->install; });
























































	}

	public function isText(string $field)
	{
		$type = $this->_fields[$field]->type;
		return self::TYPES[$type] == 'string';
	}







|
















|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
277
278
279
280
281
282
283
284
		];

		end($this->_fields);
		$last_one = key($this->_fields);

		foreach ($this->_fields as $key=>$cfg)
		{
			$type = DynamicField::SQL_TYPES[$field->type];

			if ($type == 'TEXT') {
				$type = 'TEXT COLLATE NOCASE';
			}

			$line = sprintf('%s %s', $db->quoteIdentifier($key), $type);








|







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);