]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/FieldsConfig.php
Use same permissions in dynamic and core fields
[galette.git] / galette / lib / Galette / Entity / FieldsConfig.php
1 <?php
2
3 /**
4 * Copyright © 2003-2024 The Galette Team
5 *
6 * This file is part of Galette (https://galette.eu).
7 *
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 namespace Galette\Entity;
23
24 use ArrayObject;
25 use Galette\Features\Permissions;
26 use Throwable;
27 use Analog\Analog;
28 use Laminas\Db\Adapter\Adapter;
29 use Galette\Core\Db;
30 use Galette\Core\Login;
31 use Galette\Core\Authentication;
32
33 /**
34 * Fields config class for galette :
35 * defines fields visibility for lists and forms
36 * defines fields order and requirement flag for forms
37 *
38 * @author Johan Cwiklinski <johan@x-tnd.be>
39 */
40 class FieldsConfig
41 {
42 use Permissions;
43
44 public const NOBODY = 0;
45 public const USER_WRITE = 1;
46 public const ADMIN = 2;
47 public const STAFF = 3;
48 public const MANAGER = 4;
49 public const USER_READ = 5;
50 public const ALL = 10;
51
52 public const TYPE_STR = 0;
53 public const TYPE_HIDDEN = 1;
54 public const TYPE_BOOL = 2;
55 public const TYPE_INT = 3;
56 public const TYPE_DEC = 4;
57 public const TYPE_DATE = 5;
58 public const TYPE_TXT = 6;
59 public const TYPE_PASS = 7;
60 public const TYPE_EMAIL = 8;
61 public const TYPE_URL = 9;
62 public const TYPE_RADIO = 10;
63 public const TYPE_SELECT = 11;
64
65 protected Db $zdb;
66 /** @var array<string, array<string, mixed>> */
67 protected array $core_db_fields = array();
68 /** @var array<string, bool> */
69 protected array $all_required = array();
70 /** @var array<string, int> */
71 protected array $all_visibles = array();
72 /** @var array<int, array<int, array<string, mixed>>> */
73 protected array $categorized_fields = array();
74 protected string $table;
75 /** @var array<string, mixed>|null */
76 protected ?array $defaults = null;
77 /** @var array<string, mixed>|null */
78 protected ?array $cats_defaults = null;
79
80 /** @var array<string> */
81 private array $staff_fields = array(
82 'activite_adh',
83 'id_statut',
84 'bool_exempt_adh',
85 'date_crea_adh',
86 'info_adh'
87 );
88 /** @var array<string> */
89 private array $admin_fields = array(
90 'bool_admin_adh'
91 );
92
93 public const TABLE = 'fields_config';
94
95 /**
96 * Fields that are not visible in the
97 * form should not be visible here.
98 *
99 * @var array<string>
100 */
101 private array $non_required = array(
102 'id_adh',
103 'date_echeance',
104 'bool_display_info',
105 'bool_exempt_adh',
106 'bool_admin_adh',
107 'activite_adh',
108 'date_crea_adh',
109 'date_modif_adh',
110 //Fields we do not want to be set as required
111 'societe_adh',
112 'id_statut',
113 'pref_lang',
114 'sexe_adh',
115 'parent_id'
116 );
117
118 /** @var array<string> */
119 private array $non_form_elements = array(
120 'date_echeance',
121 'date_modif_adh'
122 );
123
124 /** @var array<string> */
125 private array $non_display_elements = array(
126 'date_echeance',
127 'mdp_adh',
128 'titre_adh',
129 'sexe_adh',
130 'prenom_adh'
131 );
132
133 /**
134 * Default constructor
135 *
136 * @param Db $zdb Database
137 * @param string $table the table for which to get fields configuration
138 * @param array<string, mixed> $defaults default values
139 * @param array<string, mixed> $cats_defaults default categories values
140 * @param boolean $install Are we calling from installer?
141 */
142 public function __construct(Db $zdb, string $table, array $defaults, array $cats_defaults, bool $install = false)
143 {
144 $this->zdb = $zdb;
145 $this->table = $table;
146 $this->defaults = $defaults;
147 $this->cats_defaults = $cats_defaults;
148 //prevent check at install time...
149 if (!$install) {
150 $this->load();
151 $this->checkUpdate();
152 }
153 }
154
155 /**
156 * Load current fields configuration from database.
157 *
158 * @return boolean
159 */
160 public function load(): bool
161 {
162 try {
163 $select = $this->zdb->select(self::TABLE);
164 $select
165 ->where(array('table_name' => $this->table))
166 ->order(array(FieldsCategories::PK, 'position ASC'));
167
168 $results = $this->zdb->execute($select);
169 $this->core_db_fields = [];
170
171 foreach ($results as $k) {
172 /** @var ArrayObject<string, int|string> $k */
173 $field = $this->buildField($k);
174 $this->core_db_fields[$k->field_id] = $field;
175 }
176
177 $this->buildLists();
178 return true;
179 } catch (Throwable $e) {
180 Analog::log(
181 'Fields configuration cannot be loaded!',
182 Analog::URGENT
183 );
184 throw $e;
185 }
186 }
187
188 /**
189 * Prepare a field (required data, automation)
190 *
191 * @param ArrayObject<string, int|string> $rset DB ResultSet row
192 *
193 * @return ArrayObject<string, int|string>
194 */
195 protected function prepareField(ArrayObject $rset): ArrayObject
196 {
197 if ($rset->field_id === 'parent_id') {
198 $rset->readonly = true;
199 $rset->required = false;
200 }
201 return $rset;
202 }
203
204 /**
205 * Prepare a field (required data, automation)
206 *
207 * @param ArrayObject<string, int|string> $rset DB ResultSet row
208 *
209 * @return array<string, mixed>
210 */
211 protected function buildField(ArrayObject $rset): array
212 {
213 $rset = $this->prepareField($rset);
214 $f = array(
215 'field_id' => $rset->field_id,
216 'label' => $this->defaults[$rset->field_id]['label'],
217 'category' => (int)$rset->id_field_category,
218 'visible' => (int)$rset->visible,
219 'required' => (bool)$rset->required,
220 'propname' => $this->defaults[$rset->field_id]['propname'],
221 'position' => (int)$rset->position,
222 'disabled' => false,
223 'width_in_forms' => (int)$rset->width_in_forms,
224 );
225 return $f;
226 }
227
228 /**
229 * Create field array configuration,
230 * Several lists of fields are kept (visible, requireds, etc), build them.
231 *
232 * @return void
233 */
234 protected function buildLists(): void
235 {
236 $this->categorized_fields = [];
237 $this->all_required = [];
238 $this->all_visibles = [];
239
240 foreach ($this->core_db_fields as $field) {
241 $this->addToLists($field);
242 }
243 }
244
245 /**
246 * Adds a field to lists
247 *
248 * @param array<string,mixed> $field Field values
249 *
250 * @return void
251 */
252 protected function addToLists(array $field): void
253 {
254 if ($field['position'] >= 0) {
255 $this->categorized_fields[$field['category']][] = $field;
256 }
257
258 //array of all required fields
259 if ($field['required']) {
260 $this->all_required[$field['field_id']] = $field['required'];
261 }
262
263 //array of all fields visibility
264 $this->all_visibles[$field['field_id']] = $field['visible'];
265 }
266
267 /**
268 * Is a field set as required?
269 *
270 * @param string $field Field name
271 *
272 * @return boolean
273 */
274 public function isRequired(string $field): bool
275 {
276 return isset($this->all_required[$field]);
277 }
278
279 /**
280 * Temporary set a field as not required
281 * (password for existing members for example)
282 *
283 * @param string $field Field name
284 *
285 * @return void
286 */
287 public function setNotRequired(string $field): void
288 {
289 if (isset($this->all_required[$field])) {
290 unset($this->all_required[$field]);
291 }
292
293 foreach ($this->categorized_fields as &$cat) {
294 foreach ($cat as &$f) {
295 if ($f['field_id'] === $field) {
296 $f['required'] = false;
297 return;
298 }
299 }
300 }
301 }
302
303 /**
304 * Checks if all fields are present in the database.
305 *
306 * For now, this function only checks if count matches.
307 *
308 * @return void
309 */
310 private function checkUpdate(): void
311 {
312 $class = get_class($this);
313
314 try {
315 $_all_fields = array();
316 if (count($this->core_db_fields)) {
317 array_walk(
318 $this->core_db_fields,
319 function ($field) use (&$_all_fields) {
320 $_all_fields[$field['field_id']] = $field;
321 }
322 );
323 } else {
324 //hum... no records. Let's check if any category exists
325 $select = $this->zdb->select(FieldsCategories::TABLE);
326 $results = $this->zdb->execute($select);
327
328 if ($results->count() == 0) {
329 //categories are missing, add them
330 $categories = new FieldsCategories($this->zdb, $this->cats_defaults);
331 $categories->installInit();
332 }
333 }
334
335 if (count($this->defaults) != count($_all_fields)) {
336 Analog::log(
337 'Fields configuration count for `' . $this->table .
338 '` columns does not match records. Is : ' .
339 count($_all_fields) . ' and should be ' .
340 count($this->defaults),
341 Analog::WARNING
342 );
343
344 $params = array();
345 foreach ($this->defaults as $k => $f) {
346 if (!isset($_all_fields[$k])) {
347 Analog::log(
348 'Missing field configuration for field `' . $k . '`',
349 Analog::INFO
350 );
351 $params[] = array(
352 'field_id' => $k,
353 'table_name' => $this->table,
354 'required' => $f['required'],
355 'visible' => $f['visible'],
356 'position' => $f['position'],
357 'category' => $f['category'],
358 'list_visible' => $f['list_visible'] ?? false,
359 'list_position' => $f['list_position'] ?? null,
360 'width_in_forms' => $f['width_in_forms'] ?? 1
361 );
362 }
363 }
364
365 if (count($params) > 0) {
366 $this->insert($params);
367 $this->load();
368 }
369 }
370 } catch (Throwable $e) {
371 Analog::log(
372 '[' . $class . '] An error occurred while checking update for ' .
373 'fields configuration for table `' . $this->table . '`. ' .
374 $e->getMessage(),
375 Analog::ERROR
376 );
377 throw $e;
378 }
379 }
380
381 /**
382 * Set default fields configuration at install time. All previous
383 * existing values will be dropped first, including fields categories.
384 *
385 * @return boolean
386 * @throws Throwable
387 */
388 public function installInit(): bool
389 {
390 try {
391 $fields = array_keys($this->defaults);
392 $categories = new FieldsCategories($this->zdb, $this->cats_defaults);
393
394 //first, we drop all values
395 $delete = $this->zdb->delete(self::TABLE);
396 $delete->where(
397 array('table_name' => $this->table)
398 );
399 $this->zdb->execute($delete);
400 //take care of fields categories, for db relations
401 $categories->installInit();
402
403 $params = [];
404 foreach ($fields as $f) {
405 //build default config for each field
406 $params[] = array(
407 'field_id' => $f,
408 'table_name' => $this->table,
409 'required' => $this->defaults[$f]['required'],
410 'visible' => $this->defaults[$f]['visible'],
411 'position' => (int)$this->defaults[$f]['position'],
412 'category' => $this->defaults[$f]['category'],
413 'list_visible' => $this->defaults[$f]['list_visible'] ?? false,
414 'list_position' => $this->defaults[$f]['list_position'] ?? -1,
415 'width_in_forms' => $this->defaults[$f]['width_in_forms'] ?? 1
416 );
417 }
418 $this->insert($params);
419
420 Analog::log(
421 'Default fields configuration were successfully stored.',
422 Analog::INFO
423 );
424 return true;
425 } catch (Throwable $e) {
426 Analog::log(
427 'Unable to initialize default fields configuration.' . $e->getMessage(),
428 Analog::ERROR
429 );
430
431 throw $e;
432 }
433 }
434
435 /**
436 * Get non required fields
437 *
438 * @return array<string>
439 */
440 public function getNonRequired(): array
441 {
442 return $this->non_required;
443 }
444
445 /**
446 * Retrieve form elements
447 *
448 * @param Login $login Login instance
449 * @param boolean $new True when adding a new member
450 * @param boolean $selfs True if we're called from self subscription page
451 *
452 * @return array<string, array<int,object>>
453 */
454 public function getFormElements(Login $login, bool $new, bool $selfs = false): array
455 {
456 global $preferences;
457
458 $hidden_elements = [];
459 $form_elements = [];
460
461 //get columns descriptions
462 $columns = $this->zdb->getColumns($this->table);
463
464 $access_level = $login->getAccessLevel();
465 $categories = FieldsCategories::getList($this->zdb);
466 try {
467 foreach ($categories as $c) {
468 $cpk = FieldsCategories::PK;
469 $cat_label = null;
470 foreach ($this->cats_defaults as $conf_cat) {
471 if ($conf_cat['id'] == $c->$cpk) {
472 $cat_label = $conf_cat['category'];
473 break;
474 }
475 }
476 if ($cat_label === null) {
477 $cat_label = $c->category;
478 }
479 $cat = (object)array(
480 'id' => (int)$c->$cpk,
481 'label' => $cat_label,
482 'elements' => array()
483 );
484
485 $elements = $this->categorized_fields[$c->$cpk];
486 $cat->elements = array();
487
488 foreach ($elements as $elt) {
489 $o = (object)$elt;
490 $o->readonly = false;
491
492 if ($o->field_id == 'id_adh') {
493 // ignore access control, as member ID is always needed
494 if (!$preferences->pref_show_id || $new === true) {
495 $hidden_elements[] = $o;
496 } else {
497 $o->type = self::TYPE_STR;
498 $o->readonly = true;
499 $cat->elements[$o->field_id] = $o;
500 }
501 } elseif ($o->field_id == 'parent_id') {
502 $hidden_elements[] = $o;
503 } else {
504 // skip fields blacklisted for edition
505 if (
506 in_array($o->field_id, $this->non_form_elements)
507 || $selfs && $this->isSelfExcluded($o->field_id)
508 ) {
509 continue;
510 }
511
512 // skip fields according to access control
513 if (
514 $o->visible == self::NOBODY ||
515 ($o->visible == self::ADMIN &&
516 $access_level < Authentication::ACCESS_ADMIN) ||
517 ($o->visible == self::STAFF &&
518 $access_level < Authentication::ACCESS_STAFF) ||
519 ($o->visible == self::MANAGER &&
520 $access_level < Authentication::ACCESS_MANAGER)
521 ) {
522 continue;
523 }
524
525 if (preg_match('/date/', $o->field_id)) {
526 $o->type = self::TYPE_DATE;
527 } elseif (preg_match('/bool/', $o->field_id)) {
528 $o->type = self::TYPE_BOOL;
529 } elseif (
530 $o->field_id == 'titre_adh'
531 || $o->field_id == 'pref_lang'
532 || $o->field_id == 'id_statut'
533 ) {
534 $o->type = self::TYPE_SELECT;
535 } elseif ($o->field_id == 'sexe_adh') {
536 $o->type = self::TYPE_RADIO;
537 } else {
538 $o->type = self::TYPE_STR;
539 }
540
541 //retrieve field information from DB
542 foreach ($columns as $column) {
543 if ($column->getName() === $o->field_id) {
544 $o->max_length
545 = $column->getCharacterMaximumLength();
546 $o->default = $column->getColumnDefault();
547 $o->datatype = $column->getDataType();
548 break;
549 }
550 }
551
552 // disabled field according to access control
553 if (
554 $o->visible == self::USER_READ &&
555 $access_level == Authentication::ACCESS_USER
556 ) {
557 $o->disabled = true;
558 } else {
559 $o->disabled = false;
560 }
561
562 if ($selfs === true) {
563 //email, login and password are always required for self subscription
564 $srequireds = ['email_adh', 'login_adh'];
565 if (in_array($o->field_id, $srequireds)) {
566 $o->required = true;
567 }
568 }
569 $cat->elements[$o->field_id] = $o;
570 }
571 }
572
573 if (count($cat->elements) > 0) {
574 $form_elements[] = $cat;
575 }
576 }
577 return array(
578 'fieldsets' => $form_elements,
579 'hiddens' => $hidden_elements
580 );
581 } catch (Throwable $e) {
582 Analog::log(
583 'An error occurred getting form elements',
584 Analog::ERROR
585 );
586 throw $e;
587 }
588 }
589
590 /**
591 * Retrieve display elements
592 *
593 * @param Login $login Login instance
594 *
595 * @return array<int,object>
596 */
597 public function getDisplayElements(Login $login): array
598 {
599 global $preferences;
600
601 $display_elements = [];
602 $access_level = $login->getAccessLevel();
603 $categories = FieldsCategories::getList($this->zdb);
604 try {
605 foreach ($categories as $c) {
606 $cpk = FieldsCategories::PK;
607 $cat_label = null;
608 foreach ($this->cats_defaults as $conf_cat) {
609 if ($conf_cat['id'] == $c->$cpk) {
610 $cat_label = $conf_cat['category'];
611 break;
612 }
613 }
614 if ($cat_label === null) {
615 $cat_label = $c->category;
616 }
617 $cat = (object)array(
618 'id' => (int)$c->$cpk,
619 'label' => $cat_label,
620 'elements' => array()
621 );
622
623 $elements = $this->categorized_fields[$c->$cpk];
624 $cat->elements = array();
625
626 foreach ($elements as $elt) {
627 $o = (object)$elt;
628
629 if ($o->field_id == 'id_adh') {
630 // ignore access control, as member ID is always needed
631 if (!isset($preferences) || !$preferences->pref_show_id) {
632 $hidden_elements[] = $o;
633 } else {
634 $o->type = self::TYPE_STR;
635 $cat->elements[$o->field_id] = $o;
636 }
637 } else {
638 // skip fields blacklisted for display
639 if (in_array($o->field_id, $this->non_display_elements)) {
640 continue;
641 }
642
643 // skip fields according to access control
644 if (
645 $o->visible == self::NOBODY ||
646 ($o->visible == self::ADMIN &&
647 $access_level < Authentication::ACCESS_ADMIN) ||
648 ($o->visible == self::STAFF &&
649 $access_level < Authentication::ACCESS_STAFF) ||
650 ($o->visible == self::MANAGER &&
651 $access_level < Authentication::ACCESS_MANAGER)
652 ) {
653 continue;
654 }
655
656 $cat->elements[$o->field_id] = $o;
657 }
658 }
659
660 if (count($cat->elements) > 0) {
661 $display_elements[] = $cat;
662 }
663 }
664 return $display_elements;
665 } catch (Throwable $e) {
666 Analog::log(
667 'An error occurred getting display elements',
668 Analog::ERROR
669 );
670 throw $e;
671 }
672 }
673
674 /**
675 * Get required fields
676 *
677 * @return array<string, bool> of all required fields. Field names = keys
678 */
679 public function getRequired(): array
680 {
681 return $this->all_required;
682 }
683
684 /**
685 * Get visible fields
686 *
687 * @return array<string,int> of all visibles fields
688 */
689 public function getVisibilities(): array
690 {
691 return $this->all_visibles;
692 }
693
694 /**
695 * Get visibility for specified field
696 *
697 * @param string $field The requested field
698 *
699 * @return integer
700 */
701 public function getVisibility(string $field): int
702 {
703 return $this->all_visibles[$field];
704 }
705
706 /**
707 * Get all fields with their categories
708 *
709 * @return array<int, array<int, array<string, mixed>>>
710 */
711 public function getCategorizedFields(): array
712 {
713 return $this->categorized_fields;
714 }
715
716 /**
717 * Set fields
718 *
719 * @param array<int, array<int, array<string, mixed>>> $fields categorized fields array
720 *
721 * @return boolean
722 */
723 public function setFields(array $fields): bool
724 {
725 $this->categorized_fields = $fields;
726 return $this->store();
727 }
728
729 /**
730 * Store config in database
731 *
732 * @return boolean
733 */
734 private function store(): bool
735 {
736 $class = get_class($this);
737
738 try {
739 $this->zdb->connection->beginTransaction();
740
741 $update = $this->zdb->update(self::TABLE);
742 $update->set(
743 array(
744 'required' => ':required',
745 'visible' => ':visible',
746 'position' => ':position',
747 FieldsCategories::PK => ':' . FieldsCategories::PK,
748 'width_in_forms' => ':width_in_forms'
749 )
750 )->where(
751 array(
752 'field_id' => ':field_id',
753 'table_name' => $this->table
754 )
755 );
756 $stmt = $this->zdb->sql->prepareStatementForSqlObject($update);
757
758 foreach ($this->categorized_fields as $cat) {
759 foreach ($cat as $pos => $field) {
760 if (in_array($field['field_id'], $this->non_required)) {
761 $field['required'] = $this->zdb->isPostgres() ? 'false' : 0;
762 }
763
764 if ($field['field_id'] === 'parent_id') {
765 $field['visible'] = 0;
766 }
767
768 $params = array(
769 'required' => $field['required'],
770 'visible' => $field['visible'],
771 'position' => $pos,
772 FieldsCategories::PK => $field['category'],
773 'field_id' => $field['field_id'],
774 'width_in_forms' => $field['width_in_forms']
775 );
776
777 $stmt->execute($params);
778 }
779 }
780
781 Analog::log(
782 '[' . $class . '] Fields configuration stored successfully! ',
783 Analog::DEBUG
784 );
785 Analog::log(
786 str_replace(
787 '%s',
788 $this->table,
789 '[' . $class . '] Fields configuration for table %s stored ' .
790 'successfully.'
791 ),
792 Analog::INFO
793 );
794
795 $this->zdb->connection->commit();
796 return $this->load();
797 } catch (Throwable $e) {
798 $this->zdb->connection->rollBack();
799 Analog::log(
800 '[' . $class . '] An error occurred while storing fields ' .
801 'configuration for table `' . $this->table . '`.' .
802 $e->getMessage(),
803 Analog::ERROR
804 );
805 throw $e;
806 }
807 }
808
809 /**
810 * Migrate old required fields configuration
811 * Only needeed for 0.7.4 upgrade
812 * (should have been 0.7.3 - but I missed that.)
813 *
814 * @return boolean
815 */
816 public function migrateRequired(): bool
817 {
818 try {
819 $select = $this->zdb->select('required');
820 $select->from(PREFIX_DB . 'required');
821
822 $old_required = $this->zdb->execute($select);
823 } catch (\Exception $pe) {
824 Analog::log(
825 'Unable to retrieve required fields_config. Maybe ' .
826 'the table does not exists?',
827 Analog::WARNING
828 );
829 //not a blocker
830 return true;
831 }
832
833 $this->zdb->connection->beginTransaction();
834 try {
835 $update = $this->zdb->update(self::TABLE);
836 $update->set(
837 array(
838 'required' => ':required'
839 )
840 )->where(
841 array(
842 'field_id' => ':field_id',
843 'table_name' => $this->table
844 )
845 );
846
847 $stmt = $this->zdb->sql->prepareStatementForSqlObject($update);
848
849 foreach ($old_required as $or) {
850 $stmt->execute(
851 array(
852 'required' => ($or->required === false) ?
853 ($this->zdb->isPostgres() ? 'false' : 0) : true,
854 'field_id' => $or->field_id
855 )
856 );
857 }
858
859 $class = get_class($this);
860 Analog::log(
861 str_replace(
862 '%s',
863 $this->table,
864 '[' . $class . '] Required fields for table %s upgraded ' .
865 'successfully.'
866 ),
867 Analog::INFO
868 );
869
870 $this->zdb->db->query(
871 'DROP TABLE ' . PREFIX_DB . 'required',
872 Adapter::QUERY_MODE_EXECUTE
873 );
874
875 $this->zdb->connection->commit();
876 return true;
877 } catch (Throwable $e) {
878 $this->zdb->connection->rollBack();
879 Analog::log(
880 'An error occurred migrating old required fields. | ' .
881 $e->getMessage(),
882 Analog::ERROR
883 );
884 throw $e;
885 }
886 }
887
888 /**
889 * Insert values in database
890 *
891 * @param array<int,mixed> $values Values to insert
892 *
893 * @return void
894 */
895 private function insert(array $values): void
896 {
897 $insert = $this->zdb->insert(self::TABLE);
898 $insert->values(
899 array(
900 'field_id' => ':field_id',
901 'table_name' => ':table_name',
902 'required' => ':required',
903 'visible' => ':visible',
904 FieldsCategories::PK => ':category',
905 'position' => ':position',
906 'list_visible' => ':list_visible',
907 'list_position' => ':list_position',
908 'width_in_forms' => ':width_in_forms'
909 )
910 );
911 $stmt = $this->zdb->sql->prepareStatementForSqlObject($insert);
912 foreach ($values as $d) {
913 $required = $d['required'];
914 if ($required === false) {
915 $required = $this->zdb->isPostgres() ? 'false' : 0;
916 }
917
918 $list_visible = $d['list_visible'] ?? false;
919 if ($list_visible === false) {
920 $list_visible = $this->zdb->isPostgres() ? 'false' : 0;
921 }
922
923 $stmt->execute(
924 array(
925 'field_id' => $d['field_id'],
926 'table_name' => $d['table_name'],
927 'required' => $required,
928 'visible' => $d['visible'],
929 'category' => $d['category'],
930 'position' => $d['position'],
931 'list_visible' => $list_visible,
932 'list_position' => $d['list_position'] ?? -1,
933 'width_in_forms' => $d['width_in_forms'] ?? 1
934 )
935 );
936 }
937 }
938
939 /**
940 * Does field should be displayed in self subscription page
941 *
942 * @param string $name Field name
943 *
944 * @return boolean
945 */
946 public function isSelfExcluded(string $name): bool
947 {
948 return in_array(
949 $name,
950 array_merge(
951 $this->staff_fields,
952 $this->admin_fields
953 )
954 );
955 }
956
957 /**
958 * Filter visible fields
959 *
960 * @param Login $login Login instance
961 * @param array<string,mixed> $fields Fields list
962 *
963 * @return void
964 */
965 public function filterVisible(Login $login, array &$fields): void
966 {
967 $access_level = $login->getAccessLevel();
968 $visibles = $this->getVisibilities();
969
970 //remove not searchable fields
971 unset($fields['mdp_adh']);
972
973 foreach ($fields as $k => $f) {
974 if (
975 $visibles[$k] == FieldsConfig::NOBODY ||
976 ($visibles[$k] == FieldsConfig::ADMIN &&
977 $access_level < Authentication::ACCESS_ADMIN) ||
978 ($visibles[$k] == FieldsConfig::STAFF &&
979 $access_level < Authentication::ACCESS_STAFF) ||
980 ($visibles[$k] == FieldsConfig::MANAGER &&
981 $access_level < Authentication::ACCESS_MANAGER)
982 ) {
983 unset($fields[$k]);
984 }
985 }
986 }
987
988 /**
989 * Get fields for massive changes
990 * @see FieldsConfig::getFormElements
991 *
992 * @param array<string,mixed> $fields Member fields
993 * @param Login $login Login instance
994 *
995 * @return array<string,mixed>
996 */
997 public function getMassiveFormElements(array $fields, Login $login): array
998 {
999 $this->filterVisible($login, $fields);
1000
1001 $mass_fields = [
1002 'titre_adh',
1003 'sexe_adh',
1004 'pref_lang',
1005 'cp_adh',
1006 'ville_adh',
1007 'pays_adh',
1008 'bool_display_info',
1009 'activite_adh',
1010 Status::PK,
1011 'bool_admin_adh',
1012 'bool_exempt_adh',
1013 ];
1014 $mass_fields = array_intersect(array_keys($fields), $mass_fields);
1015
1016 foreach ($mass_fields as $mass_field) {
1017 $this->setNotRequired($mass_field);
1018 }
1019 $form_elements = $this->getFormElements($login, false);
1020 unset($form_elements['hiddens']);
1021
1022 foreach ($form_elements['fieldsets'] as &$form_element) {
1023 $form_element->elements = array_intersect_key($form_element->elements, array_flip($mass_fields));
1024 }
1025 return $form_elements;
1026 }
1027
1028 /**
1029 * Get field configuration
1030 *
1031 * @param string $name Field name
1032 *
1033 * @return array<string,mixed>
1034 */
1035 public function getField(string $name): array
1036 {
1037 if (!isset($this->core_db_fields[$name])) {
1038 throw new \UnexpectedValueException("$name field does not exists");
1039 }
1040 return $this->core_db_fields[$name];
1041 }
1042 }