Overview
Comment:Implement edit and adding new fields
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA3-256: c2c72edead74bb8ac2f81a68890d3062bc6f93b1686f2b6dac46240dd6d28788
User & Date: bohwaz on 2022-03-16 02:03:12
Other Links: branch diff | manifest | tags
Context
2022-03-16
02:41
Rename config properties to English check-in: 8b6354df02 user: bohwaz tags: dev
02:03
Implement edit and adding new fields check-in: c2c72edead user: bohwaz tags: dev
2022-03-15
00:42
Implement dynamic field delete and edit check-in: 3e93b9feec user: bohwaz tags: dev
Changes

Modified src/include/data/1.2.0_schema.sql from [deb0df031b] to [bbc725b503].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    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 UNIQUE INDEX IF NOT EXISTS config_users_fields_name ON config_users_fields (name);








|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    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_table INTEGER NOT NULL DEFAULT 0,
    options TEXT NULL,
    default_value TEXT NULL,
    system TEXT NULL
);

CREATE UNIQUE INDEX IF NOT EXISTS config_users_fields_name ON config_users_fields (name);

Modified src/include/lib/Garradin/Entities/Users/DynamicField.php from [0e567ce101] to [0e64db5237].

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	/**
	 * 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 ?array $options = [];

	/**







|

|







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	/**
	 * 0 = only admins can write this field
	 * 1 = admins + the user themselves can change it
	 */
	protected int $write_access;

	/**
	 * Use in users list table?
	 */
	protected bool $list_table;

	/**
	 * Multiple options (JSON) for select and multiple fields
	 */
	protected ?array $options = [];

	/**
179
180
181
182
183
184
185
186



187














			$this->assert(!$db->test(self::TABLE, 'name = ?', $this->name), 'Ce nom de champ est déjà utilisé par un autre champ: ' . $this->name);
		}
		else {
			$this->assert(!$db->test(self::TABLE, 'name = ? AND id != ?', $this->name, $this->id()), 'Ce nom de champ est déjà utilisé par un autre champ.');
		}

		$this->assert($this->exists() || $this->system & self::PRESET || !array_key_exists($this->name, DynamicFields::getInstance()->getPresets()), 'Ce nom de champ est déjà utilisé par un champ pré-défini.');
	}



}





















|
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
			$this->assert(!$db->test(self::TABLE, 'name = ?', $this->name), 'Ce nom de champ est déjà utilisé par un autre champ: ' . $this->name);
		}
		else {
			$this->assert(!$db->test(self::TABLE, 'name = ? AND id != ?', $this->name, $this->id()), 'Ce nom de champ est déjà utilisé par un autre champ.');
		}

		$this->assert($this->exists() || $this->system & self::PRESET || !array_key_exists($this->name, DynamicFields::getInstance()->getPresets()), 'Ce nom de champ est déjà utilisé par un champ pré-défini.');

		if ($this->exists()) {
			$this->assert(!isset($this->_modified['type']));
			$this->assert(!isset($this->_modified['name']));
		}
	}

	public function importForm(array $source = null)
	{
		if (null === $source) {
			$source = $_POST;
		}

		$source['required'] = !empty($source['required']) ? true : false;
		$source['list_table'] = !empty($source['list_table']) ? true : false;

		return parent::importForm($source);
	}
}

Modified src/include/lib/Garradin/Upgrade.php from [95ffb05e25] to [439017fa89].

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
				$db->beginSchemaUpdate();

				// Create config_users_fields table
				$db->import(ROOT . '/include/data/1.2.0_schema.sql');

				// Migrate users table
				$df = \Garradin\Users\DynamicFields::fromOldINI($config->champs_membres, $config->champ_identifiant, $config->champ_identite, 'numero');
				$df->save();

				// Migrate other stuff
				$db->import(ROOT . '/include/data/1.2.0_migration.sql');
				$db->commitSchemaUpdate();
			}

			// Réinstaller les plugins système si nécessaire







|







267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
				$db->beginSchemaUpdate();

				// Create config_users_fields table
				$db->import(ROOT . '/include/data/1.2.0_schema.sql');

				// Migrate users table
				$df = \Garradin\Users\DynamicFields::fromOldINI($config->champs_membres, $config->champ_identifiant, $config->champ_identite, 'numero');
				$df->save(false);

				// Migrate other stuff
				$db->import(ROOT . '/include/data/1.2.0_migration.sql');
				$db->commitSchemaUpdate();
			}

			// Réinstaller les plugins système si nécessaire

Modified src/include/lib/Garradin/Users/DynamicFields.php from [4fb2df2bc7] to [a863dfd6c8].

25
26
27
28
29
30
31
32


33
34
35
36
37
38
39
	protected $_fields_by_system_use = [
		'login' => [],
		'password' => [],
		'name' => [],
		'number' => [],
	];

	protected $_presets = [];



	static protected $_instance;

	static public function getInstance()
	{
		if (null === self::$_instance) {
			self::$_instance = new self;







|
>
>







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
	protected $_fields_by_system_use = [
		'login' => [],
		'password' => [],
		'name' => [],
		'number' => [],
	];

	protected array $_presets = [];

	protected array $_deleted = [];

	static protected $_instance;

	static public function getInstance()
	{
		if (null === self::$_instance) {
			self::$_instance = new self;
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
			$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->add($field);

			if ($field->type == 'checkbox' || $field->type == 'multiple') {
				// A checkbox/multiple checkbox can either be 0 or 1, not NULL
				$db->exec(sprintf('UPDATE membres SET %s = 0 WHERE %1$s IS NULL;', $field->name));
			}







|







313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
			$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_table', (bool) $data['list_row']);
			$field->set('sort_order', $i++);
			$self->add($field);

			if ($field->type == 'checkbox' || $field->type == 'multiple') {
				// A checkbox/multiple checkbox can either be 0 or 1, not NULL
				$db->exec(sprintf('UPDATE membres SET %s = 0 WHERE %1$s IS NULL;', $field->name));
			}
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
		$fields = array_filter(
			$this->_fields,
			function ($a, $b) use ($name_fields) {
				if (in_array($b, $name_fields)) {
					return false;
				}

				return empty($a->list_row) ? false : true;
			},
			ARRAY_FILTER_USE_BOTH
		);

		uasort($fields, function ($a, $b) {
			if ($a->list_row == $b->list_row)
				return 0;

			return ($a->list_row > $b->list_row) ? 1 : -1;
		});

		return $fields;
	}

	public function getSQLSchema(string $table_name = User::TABLE): string
	{







|





|


|







389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
		$fields = array_filter(
			$this->_fields,
			function ($a, $b) use ($name_fields) {
				if (in_array($b, $name_fields)) {
					return false;
				}

				return empty($a->list_table) ? false : true;
			},
			ARRAY_FILTER_USE_BOTH
		);

		uasort($fields, function ($a, $b) {
			if ($a->sort_order == $b->sort_order)
				return 0;

			return ($a->sort_order > $b->sort_order) ? 1 : -1;
		});

		return $fields;
	}

	public function getSQLSchema(string $table_name = User::TABLE): string
	{
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
	public function getSQLSearchSchema(string $table_name = User::TABLE): ?string
	{
		$search_table = $table_name . '_search';

		$columns = [];

		foreach ($this->_fields as $key => $cfg) {
			if ($cfg->type == 'text' || $cfg->list_row) {
				$columns[] = $key;
			}
		}

		if (!count($columns)) {
			return null;
		}







|







457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
	public function getSQLSearchSchema(string $table_name = User::TABLE): ?string
	{
		$search_table = $table_name . '_search';

		$columns = [];

		foreach ($this->_fields as $key => $cfg) {
			if ($cfg->type == 'text' || $cfg->list_table) {
				$columns[] = $key;
			}
		}

		if (!count($columns)) {
			return null;
		}
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
	{
		$this->_fields[$df->name] = $df;
		$this->reloadCache();
	}

	public function delete(string $name)
	{
		$db = DB::getInstance();

		$db->begin();

		$this->_fields[$name]->delete();
		unset($this->_fields[$name]);

		$this->reload();
		// FIXME/TODO: use ALTER TABLE ... DROP COLUMN for SQLite 3.35.0+
		$this->rebuildUsersTable();

		$db->commit();
	}

	public function save()
	{
		if (empty($this->_fields_by_system_use['number'])) {
			throw new ValidationException('Aucun champ de numéro de membre n\'existe');
		}

		if (count($this->_fields_by_system_use['number']) != 1) {
			throw new ValidationException('Un seul champ peut être défini comme numéro');







<
<
<
<
|


|
<
<
|
<
|
<
|







594
595
596
597
598
599
600




601
602
603
604


605

606

607
608
609
610
611
612
613
614
	{
		$this->_fields[$df->name] = $df;
		$this->reloadCache();
	}

	public function delete(string $name)
	{




		$this->_deleted[] = $this->_fields[$name];
		unset($this->_fields[$name]);

		$this->reloadCache();


	}



	public function save(bool $allow_rebuild = true)
	{
		if (empty($this->_fields_by_system_use['number'])) {
			throw new ValidationException('Aucun champ de numéro de membre n\'existe');
		}

		if (count($this->_fields_by_system_use['number']) != 1) {
			throw new ValidationException('Un seul champ peut être défini comme numéro');
635
636
637
638
639
640
641
642


643





644























645
646
647
648
649
650
651
652
653
654
655
656
657
658





		if (empty($this->_fields_by_system_use['password'])) {
			throw new ValidationException('Aucun champ de mot de passe n\'existe');
		}

		if (count($this->_fields_by_system_use['password']) != 1) {
			throw new ValidationException('Un seul champ peut être défini comme mot de passe');
		}



		foreach ($this->_fields as $field) {





			$field->save();























		}
	}

	public function setOrderAll(array $order)
	{
		foreach (array_values($order) as $sort => $key) {
			if (!array_key_exists($key, $this->_fields)) {
				throw new \InvalidArgumentException('Unknown field name: ' . $key);
			}

			$this->_fields[$key]->set('sort_order', $sort);
		}
	}
}













>
>

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













|
>
>
>
>
>
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
		if (empty($this->_fields_by_system_use['password'])) {
			throw new ValidationException('Aucun champ de mot de passe n\'existe');
		}

		if (count($this->_fields_by_system_use['password']) != 1) {
			throw new ValidationException('Un seul champ peut être défini comme mot de passe');
		}

		$rebuild = false;

		foreach ($this->_fields as $field) {
			if (!$field->exists()) {
				$rebuild = true;
			}

			if ($field->isModified()) {
				$field->save();
			}
		}


		foreach ($this->_deleted as $f) {
			$f->delete();
			$rebuild = true;
		}

		$this->_deleted = [];

		if ($rebuild && $allow_rebuild) {
			$db = DB::getInstance();

			$db->begin();

			// FIXME/TODO: use ALTER TABLE ... DROP COLUMN for SQLite 3.35.0+
			// some conditions apply
			// https://www.sqlite.org/lang_altertable.html#altertabdropcol
			$this->rebuildUsersTable();

			$db->commit();
			$this->reload();
		}
	}

	public function setOrderAll(array $order)
	{
		foreach (array_values($order) as $sort => $key) {
			if (!array_key_exists($key, $this->_fields)) {
				throw new \InvalidArgumentException('Unknown field name: ' . $key);
			}

			$this->_fields[$key]->set('sort_order', $sort);
		}
	}

	public function getLastOrderIndex()
	{
		return count($this->_fields);
	}
}

Modified src/templates/admin/config/fields/edit.tpl from [4e4c6cc5a2] to [a1b7b7cc77].

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
	{if !$field->exists()}
	<p class="help block">Avant de demander une information personnelle à vos membres… en avez-vous vraiment besoin&nbsp;?<br />
		La loi demande à minimiser au strict minimum les données collectées. Pensez également aux risques de sécurité&nbsp;: si vous demandez la date de naissance complète, cela pourrait être utilisé pour de l'usurpation d'identité, il serait donc plus sage de ne demander que le mois et l'année de naissance, si ces données sont nécessaires afin d'avoir l'âge de la personne.
	</p>
	{/if}
	<dl>
	{if !$field->exists()}
		{input type="select" name="type" options=$field::TYPES source=$field label="Type" help="Il ne sera plus possible de modifier le type une fois le champ créé." required=true}

	{else}

		{input type="select" name="type" options=$field::TYPES source=$field label="Type" help="Il n'est plus possible de modifier le titre." disabled=true}

	{/if}

		{input type="text" name="label" label="Libellé" required=true source=$field}
		{input type="text" name="help" label="Texte d'aide" help="Apparaîtra dans les formulaires comme ce texte." source=$field}

		{input type="checkbox" name="required" value=1 label="Champ obligatoire" help="Si coché, une fiche membre ne pourra pas être enregistrée si ce champ n'est pas renseigné." source=$field}

		{input type="text" name="default" source=$field label="Valeur par défaut" help="Si renseigné, le champ aura cette valeur par défaut lors de l'ajout d'un nouveau membre"}

		<dt>Le champ est visible…</dt>
		{input type="radio" name="read_access" value=$field::ACCESS_ADMIN label="Seulement aux personnes qui gèrent les membres" source=$field}
		{input type="radio" name="read_access" value=$field::ACCESS_USER label="Au membre lui-même, et aux personnes qui gèrent les membres" source=$field help="Le membre pourra voir cette information en se connectant"}

		<dt>Le champ peut être modifié…</dt>
		{input type="radio" name="write_access" value=$field::ACCESS_ADMIN label="Par les personnes qui gèrent les membres" source=$field}
		{input type="radio" name="write_access" value=$field::ACCESS_USER label="Par le membre lui-même, et aux personnes qui gèrent les membres" source=$field help="Le membre pourra modifier cette information en se connectant"}
	</dl>
</fieldset>

<fieldset class="type-select type-multiple">
	<legend>Options possibles</legend>

	<p class="alert block type-select">Attention renommer ou supprimer une option n'affecte pas ce qui a déjà été enregistré dans les fiches des membres.</p>
	<p class="alert block type-multiple">Attention changer l'ordre des options peut avoir des effets indésirables.</p>

	<dl class="type-multiple type-select options">
		{foreach from=$field.options item="option"}
		<dd>{input type="text" name="options[]" default=$option}</dd>
		{/foreach}
		<dd>{input type="text" name="options[]"}</dd>
	</dl>
</fieldset>














<p class="submit">
	{csrf_field key=$csrf_key}
	{linkbutton label="Annuler les changements" shape="left" href="./"}
	{button type="submit" name="save" label="Enregistrer les changements" shape="right" class="main"}
</p>

</form>

<script type="text/javascript">
{literal}
var addBtn = document.createElement('button');
addBtn.type = "button";
addBtn.dataset.icon = "➕";
addBtn.className = "icn add";
addBtn.title = "Ajouter une option";

var delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.dataset.icon = "➖";
delBtn.className = "icn delete";
delBtn.title = "Enlever cette option";

var options = $('.options dd');

options.forEach((o, i) => {
	if (i == 0) {
		return;
	}

	let btn = delBtn.cloneNode(true);
	btn.onclick = delOption;
	o.appendChild(btn);
});

addPlusButton();

function addOption(e) {
	var options = $('.options dd');
	var target = e.target;
	var new_option = target.parentNode.cloneNode(true);
	new_option.querySelector('input').value = '';

	new_option.querySelectorAll('button').forEach((b) => b.remove());

	var btn = delBtn.cloneNode();
	btn.onclick = delOption;
	new_option.appendChild(btn);

	target.parentNode.parentNode.appendChild(new_option);
	target.remove(); // Remove add button from previous line

	addPlusButton();
}

function delOption(e) {
	var options = $('.options dd');
	if (options.length == 1) {
		return;
	}

	e.target.parentNode.remove();
	addPlusButton();
}

function addPlusButton () {
	var options = $('.options dd');
	var btn = addBtn.cloneNode();
	btn.onclick = addOption;

	if (options.length < 30) {
		let last = options[options.length - 1];

		if (last.querySelector('.add')) {
			return;
		}

		last.appendChild(btn);
	}
}
{/literal}
</script>

{include file="admin/_foot.tpl"}







|
>

>
|
>



|





<
<
<
|
<
<
<

















>
>
>
>
>
>
>
>
>
>
>
>
>


|
|




|
<
<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

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
	{if !$field->exists()}
	<p class="help block">Avant de demander une information personnelle à vos membres… en avez-vous vraiment besoin&nbsp;?<br />
		La loi demande à minimiser au strict minimum les données collectées. Pensez également aux risques de sécurité&nbsp;: si vous demandez la date de naissance complète, cela pourrait être utilisé pour de l'usurpation d'identité, il serait donc plus sage de ne demander que le mois et l'année de naissance, si ces données sont nécessaires afin d'avoir l'âge de la personne.
	</p>
	{/if}
	<dl>
	{if !$field->exists()}
		{input type="select" name="type" options=$field::TYPES source=$field label="Type" default="text" help="Il ne sera plus possible de modifier le type une fois le champ créé." required=true}
		{input type="text" name="name" pattern="[a-z](_?[a-z0-9]+)*" label="Nom unique" required=true source=$field help="Ne peut comporter que des lettres minuscules et des tirets bas. Par exemple pour un champ demandant l'adresse, on peut utiliser 'adresse_postale'. Ce nom ne peut plus être modifié ensuite."}
	{else}
		<dd class="help">Le type et le nom unique ne sont pas modifiables.</dd>
		{input type="select" name="type" options=$field::TYPES source=$field label="Type" disabled=true}
		{input type="text" name="name" disabled=true label="Nom unique" source=$field}
	{/if}

		{input type="text" name="label" label="Libellé" required=true source=$field}
		{input type="text" name="help" label="Texte d'aide" help="Apparaîtra dans les formulaires de manière identique à ce texte." source=$field}

		{input type="checkbox" name="required" value=1 label="Champ obligatoire" help="Si coché, une fiche membre ne pourra pas être enregistrée si ce champ n'est pas renseigné." source=$field}

		{input type="text" name="default" source=$field label="Valeur par défaut" help="Si renseigné, le champ aura cette valeur par défaut lors de l'ajout d'un nouveau membre"}




		{input type="checkbox" name="list_table" value=1 label="Afficher dans la liste des membres" source=$field}



	</dl>
</fieldset>

<fieldset class="type-select type-multiple">
	<legend>Options possibles</legend>

	<p class="alert block type-select">Attention renommer ou supprimer une option n'affecte pas ce qui a déjà été enregistré dans les fiches des membres.</p>
	<p class="alert block type-multiple">Attention changer l'ordre des options peut avoir des effets indésirables.</p>

	<dl class="type-multiple type-select options">
		{foreach from=$field.options item="option"}
		<dd>{input type="text" name="options[]" default=$option}</dd>
		{/foreach}
		<dd>{input type="text" name="options[]"}</dd>
	</dl>
</fieldset>

<fieldset>
	<legend>Accès</legend>
	<dl>
		<dt>Le champ est visible…</dt>
		{input type="radio" name="read_access" value=$field::ACCESS_ADMIN label="Seulement aux personnes qui gèrent les membres" source=$field}
		{input type="radio" name="read_access" value=$field::ACCESS_USER label="Au membre lui-même, et aux personnes qui gèrent les membres" source=$field help="Le membre pourra voir cette information en se connectant" default=$field::ACCESS_USER}

		<dt>Le champ peut être modifié…</dt>
		{input type="radio" name="write_access" value=$field::ACCESS_ADMIN label="Par les personnes qui gèrent les membres" source=$field}
		{input type="radio" name="write_access" value=$field::ACCESS_USER label="Par le membre lui-même, et les personnes qui gèrent les membres" source=$field help="Le membre pourra modifier cette information en se connectant" default=$field::ACCESS_USER}
	</dl>
</fieldset>

<p class="submit">
	{csrf_field key=$csrf_key}
	{linkbutton label="Annuler" shape="left" href="./" target="_parent"}
	{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
</p>

</form>

<script type="text/javascript" src="{$admin_url}static/scripts/config_fields.js"></script>









































































{include file="admin/_foot.tpl"}

Modified src/templates/admin/config/fields/index.tpl from [2b943be0f1] to [283d7f900d].

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
<form method="post" action="{$self_url_no_qs}">
	<table class="list">
		<thead>
			<tr>
				<td>Ordre</td>
				<th>Libellé</th>
				<td>Liste des membres</td>



				<td></td>
			</tr>
		</thead>
		<tbody>
		{foreach from=$fields item="field"}
			<tr>
				<td>
					{button shape="menu" title="Cliquer, glisser et déposer pour modifier l'ordre"}
					<input type="hidden" name="sort_order[]" value="{$field.name}" />
				</td>
				<th>{$field.label}</th>
				<td>{if $field.list_row}Oui{else}Non{/if}</td>



				<td class="actions">
					{if !$field.system || ($field.system && !($field.system | $field::PRESET))}
						{linkbutton shape="delete" label="Supprimer" href="delete.php?id=%d"|args:$field.id target="_dialog"}
					{/if}
					{linkbutton shape="edit" label="Modifier" href="edit.php?id=%d"|args:$field.id target="_dialog"}
				</td>
			</tr>







>
>
>











|
>
>
>







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
<form method="post" action="{$self_url_no_qs}">
	<table class="list">
		<thead>
			<tr>
				<td>Ordre</td>
				<th>Libellé</th>
				<td>Liste des membres</td>
				<td>Obligatoire&nbsp;?</td>
				<td>Visibilité</td>
				<td>Modification</td>
				<td></td>
			</tr>
		</thead>
		<tbody>
		{foreach from=$fields item="field"}
			<tr>
				<td>
					{button shape="menu" title="Cliquer, glisser et déposer pour modifier l'ordre"}
					<input type="hidden" name="sort_order[]" value="{$field.name}" />
				</td>
				<th>{$field.label}</th>
				<td>{if $field.list_table}Oui{/if}</td>
				<td>{if $field.required}Obligatoire{/if}</td>
				<td>{if $field.read_access == $field::ACCESS_USER}Membre{else}Gestionnaires{/if}</td>
				<td>{if $field.write_access == $field::ACCESS_USER}Membre{else}Gestionnaires{/if}</td>
				<td class="actions">
					{if !$field.system || ($field.system && !($field.system | $field::PRESET))}
						{linkbutton shape="delete" label="Supprimer" href="delete.php?id=%d"|args:$field.id target="_dialog"}
					{/if}
					{linkbutton shape="edit" label="Modifier" href="edit.php?id=%d"|args:$field.id target="_dialog"}
				</td>
			</tr>

Modified src/www/admin/config/fields/delete.php from [bbd2f22de5] to [9f47781249].

16
17
18
19
20
21
22

23
24
25
26
27

$form->runIf('delete', function () use ($field, $fields) {
	if (!f('confirm_delete')) {
		throw new UserException('Merci de bien vouloir cocher la case pour confirmer la suppression.');
	}

	$fields->delete($field->name);

}, $csrf_key, '!config/fields/?msg=DELETED');

$tpl->assign(compact('csrf_key', 'field'));

$tpl->display('admin/config/fields/delete.tpl');







>





16
17
18
19
20
21
22
23
24
25
26
27
28

$form->runIf('delete', function () use ($field, $fields) {
	if (!f('confirm_delete')) {
		throw new UserException('Merci de bien vouloir cocher la case pour confirmer la suppression.');
	}

	$fields->delete($field->name);
	$fields->save();
}, $csrf_key, '!config/fields/?msg=DELETED');

$tpl->assign(compact('csrf_key', 'field'));

$tpl->display('admin/config/fields/delete.tpl');

Modified src/www/admin/config/fields/edit.php from [4f4bd678e4] to [eeda1a9d98].

16
17
18
19
20
21
22
23
24






25
26
27
28
29
30
	$field = new DynamicField;
}

if (!$field) {
	throw new UserException('Le champ indiqué n\'existe pas.');
}

$form->runIf('save', function () use ($field) {
	$field->importForm();






	$field->save();
}, $csrf_key, '!config/fields/?msg=SAVED');

$tpl->assign(compact('csrf_key', 'field'));

$tpl->display('admin/config/fields/edit.tpl');







|

>
>
>
>
>
>
|





16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	$field = new DynamicField;
}

if (!$field) {
	throw new UserException('Le champ indiqué n\'existe pas.');
}

$form->runIf('save', function () use ($field, $fields) {
	$field->importForm();

	if (!$field->exists()) {
		$field->sort_order = $fields->getLastOrderIndex();
		$fields->add($field);
	}

	$fields->save();
}, $csrf_key, '!config/fields/?msg=SAVED');

$tpl->assign(compact('csrf_key', 'field'));

$tpl->display('admin/config/fields/edit.tpl');

Modified src/www/admin/config/fields/index.php from [87b5ad26e4] to [6e011c28dd].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
namespace Garradin;

use Garradin\Users\DynamicFields;

require_once __DIR__ . '/../_inc.php';

$csrf_key = 'change_fields_order';
$fields = DynamicFields::getInstance();

$form->runIf('save', function () use ($fields) {
    $fields->setOrderAll(f('sort_order'));
    $fields->save();
}, $csrf_key, '!config/fields/?msg=SAVED');

$tpl->assign('fields', $fields->all());
$tpl->assign(compact('csrf_key'));

$tpl->display('admin/config/fields/index.tpl');













|





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
namespace Garradin;

use Garradin\Users\DynamicFields;

require_once __DIR__ . '/../_inc.php';

$csrf_key = 'change_fields_order';
$fields = DynamicFields::getInstance();

$form->runIf('save', function () use ($fields) {
    $fields->setOrderAll(f('sort_order'));
    $fields->save();
}, $csrf_key, '!config/fields/?msg=SAVED_ORDER');

$tpl->assign('fields', $fields->all());
$tpl->assign(compact('csrf_key'));

$tpl->display('admin/config/fields/index.tpl');

Added src/www/admin/static/scripts/config_fields.js version [209338fde7].































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
function changeType() {
	var type = $('#f_type').value;
	g.toggle('.type-select, .type-multiple', false);
	g.toggle('.type-' + type, true);
}

$('#f_type').onchange = changeType;

changeType();

var addBtn = document.createElement('button');
addBtn.type = "button";
addBtn.dataset.icon = "➕";
addBtn.className = "icn add";
addBtn.title = "Ajouter une option";

var delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.dataset.icon = "➖";
delBtn.className = "icn delete";
delBtn.title = "Enlever cette option";

var options = $('.options dd');

options.forEach((o, i) => {
	if (i == 0) {
		return;
	}

	let btn = delBtn.cloneNode(true);
	btn.onclick = delOption;
	o.appendChild(btn);
});

addPlusButton();

function addOption(e) {
	var options = $('.options dd');
	var target = e.target;
	var new_option = target.parentNode.cloneNode(true);
	new_option.querySelector('input').value = '';

	new_option.querySelectorAll('button').forEach((b) => b.remove());

	var btn = delBtn.cloneNode();
	btn.onclick = delOption;
	new_option.appendChild(btn);

	target.parentNode.parentNode.appendChild(new_option);
	target.remove(); // Remove add button from previous line

	addPlusButton();
}

function delOption(e) {
	var options = $('.options dd');
	if (options.length == 1) {
		return;
	}

	e.target.parentNode.remove();
	addPlusButton();
}

function addPlusButton () {
	var options = $('.options dd');
	var btn = addBtn.cloneNode();
	btn.onclick = addOption;

	if (options.length < 30) {
		let last = options[options.length - 1];

		if (last.querySelector('.add')) {
			return;
		}

		last.appendChild(btn);
	}
}