3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Fields config handling
10 * Copyright © 2009-2020 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2020 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-03-26
37 namespace Galette\Entity
;
41 use Laminas\Db\Adapter\Adapter
;
43 use Galette\Core\Login
;
44 use Galette\Core\Authentication
;
47 * Fields config class for galette :
48 * defines fields visibility for lists and forms
49 * defines fields order and requirement flag for forms
54 * @author Johan Cwiklinski <johan@x-tnd.be>
55 * @copyright 2009-2020 The Galette Team
56 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
57 * @link http://galette.tuxfamily.org
58 * @since Available since 0.7dev - 2009-03-26
70 const TYPE_HIDDEN
= 1;
79 const TYPE_RADIO
= 10;
80 const TYPE_SELECT
= 11;
83 protected $core_db_fields = array();
84 protected $all_required = array();
85 protected $all_visibles = array();
86 protected $categorized_fields = array();
88 protected $defaults = null;
89 protected $cats_defaults = null;
91 private $staff_fields = array(
98 private $admin_fields = array(
102 const TABLE
= 'fields_config';
105 * Fields that are not visible in the
106 * form should not be visible here.
108 private $non_required = array(
117 //Fields we do not want to be set as required
125 private $non_form_elements = array(
130 private $non_display_elements = array(
140 * Default constructor
142 * @param Db $zdb Database
143 * @param string $table the table for which to get fields configuration
144 * @param array $defaults default values
145 * @param array $cats_defaults default categories values
146 * @param boolean $install Are we calling from installer?
148 public function __construct(Db
$zdb, $table, $defaults, $cats_defaults, $install = false)
151 $this->table
= $table;
152 $this->defaults
= $defaults;
153 $this->cats_defaults
= $cats_defaults;
154 //prevent check at install time...
157 $this->checkUpdate();
162 * Load current fields configuration from database.
166 public function load()
169 $select = $this->zdb
->select(self
::TABLE
);
171 ->where(array('table_name' => $this->table
))
172 ->order(array(FieldsCategories
::PK
, 'position ASC'));
174 $results = $this->zdb
->execute($select);
175 $this->core_db_fields
= [];
177 foreach ($results as $k) {
178 $field = $this->buildField($k);
179 $this->core_db_fields
[$k->field_id
] = $field;
184 } catch (\Exception
$e) {
186 'Fields configuration cannot be loaded!',
194 * Prepare a field (required data, automation)
196 * @param ArrayObject $rset DB ResultSet row
198 * @return ArrayObject
200 protected function prepareField(ArrayObject
$rset): ArrayObject
202 if ($rset->field_id
=== 'parent_id') {
203 $rset->readonly
= true;
204 $rset->required
= false;
210 * Prepare a field (required data, automation)
212 * @param ArrayObject $rset DB ResultSet row
216 protected function buildField(ArrayObject
$rset): array
218 $rset = $this->prepareField($rset);
220 'field_id' => $rset->field_id
,
221 'label' => $this->defaults
[$rset->field_id
]['label'],
222 'category' => (int)$rset->id_field_category
,
223 'visible' => (int)$rset->visible
,
224 'required' => (bool)$rset->required
,
225 'propname' => $this->defaults
[$rset->field_id
]['propname'],
226 'position' => (int)$rset->position
,
233 * Create field array configuration,
234 * Several lists of fields are kept (visible, requireds, etc), build them.
238 protected function buildLists()
241 $this->categorized_fields
= [];
242 $this->all_required
= [];
243 $this->all_visibles
= [];
245 foreach ($this->core_db_fields
as $field) {
246 $this->addToLists($field);
251 * Adds a field to lists
253 * @param array $field Field values
257 protected function addToLists(array $field)
259 if ($field['position'] >= 0) {
260 $this->categorized_fields
[$field['category']][] = $field;
263 //array of all required fields
264 if ($field['required']) {
265 $this->all_required
[$field['field_id']] = $field['required'];
268 //array of all fields visibility
269 $this->all_visibles
[$field['field_id']] = $field['visible'];
273 * Is a field set as required?
275 * @param string $field Field name
279 public function isRequired($field)
281 return isset($this->all_required
[$field]);
285 * Temporary set a field as not required
286 * (password for existing members for example)
288 * @param string $field Field name
292 public function setNotRequired($field)
294 if (isset($this->all_required
[$field])) {
295 unset($this->all_required
[$field]);
298 foreach ($this->categorized_fields
as &$cat) {
299 foreach ($cat as &$f) {
300 if ($f['field_id'] === $field) {
301 $f['required'] = false;
309 * Checks if all fields are present in the database.
311 * For now, this function only checks if count matches.
315 private function checkUpdate()
317 $class = get_class($this);
320 $_all_fields = array();
321 if (count($this->core_db_fields
)) {
323 $this->core_db_fields
,
324 function ($field) use (&$_all_fields) {
325 $_all_fields[$field['field_id']] = $field;
329 //hum... no records. Let's check if any category exists
330 $select = $this->zdb
->select(FieldsCategories
::TABLE
);
331 $results = $this->zdb
->execute($select);
333 if ($results->count() == 0) {
334 //categories are missing, add them
335 $categories = new FieldsCategories($this->zdb
, $this->cats_defaults
);
336 $categories->installInit();
340 if (count($this->defaults
) != count($_all_fields)) {
342 'Fields configuration count for `' . $this->table
.
343 '` columns does not match records. Is : ' .
344 count($_all_fields) . ' and should be ' .
345 count($this->defaults
),
350 foreach ($this->defaults
as $k => $f) {
351 if (!isset($_all_fields[$k])) {
353 'Missing field configuration for field `' . $k . '`',
358 'table_name' => $this->table
,
359 'required' => $f['required'],
360 'visible' => $f['visible'],
361 'position' => $f['position'],
362 'category' => $f['category'],
363 'list_visible' => $f['list_visible'] ??
false,
364 'list_position' => $f['list_position'] ??
null
369 if (count($params) > 0) {
370 $this->insert($params);
374 } catch (\Exception
$e) {
376 '[' . $class . '] An error occurred while checking update for ' .
377 'fields configuration for table `' . $this->table
. '`. ' .
386 * Set default fields configuration at install time. All previous
387 * existing values will be dropped first, including fields categories.
389 * @return boolean|\Exception
391 public function installInit()
394 $fields = array_keys($this->defaults
);
395 $categories = new FieldsCategories($this->zdb
, $this->cats_defaults
);
397 //first, we drop all values
398 $delete = $this->zdb
->delete(self
::TABLE
);
400 array('table_name' => $this->table
)
402 $this->zdb
->execute($delete);
403 //take care of fields categories, for db relations
404 $categories->installInit($this->zdb
);
406 $fields = array_keys($this->defaults
);
407 foreach ($fields as $f) {
408 //build default config for each field
411 'table_name' => $this->table
,
412 'required' => $this->defaults
[$f]['required'],
413 'visible' => $this->defaults
[$f]['visible'],
414 'position' => (int)$this->defaults
[$f]['position'],
415 'category' => $this->defaults
[$f]['category'],
416 'list_visible' => $this->defaults
[$f]['list_visible'] ??
false,
417 'list_position' => $this->defaults
[$f]['list_position'] ??
-1
420 $this->insert($params);
423 'Default fields configuration were successfully stored.',
427 } catch (\Exception
$e) {
429 'Unable to initialize default fields configuration.' . $e->getMessage(),
433 /*$messages = array();
435 $messages[] = $e->getMessage();
436 } while ($e = $e->getPrevious());
439 'Unable to initialize default fields configuration.' .
440 implode("\n", $messages),
448 * Get non required fields
452 public function getNonRequired()
454 return $this->non_required
;
458 * Retrieve form elements
460 * @param Login $login Login instance
461 * @param boolean $new True when adding a new member
462 * @param boolean $selfs True if we're called from self subscription page
466 public function getFormElements(Login
$login, $new, $selfs = false)
470 $hidden_elements = [];
473 //get columns descriptions
474 $columns = $this->zdb
->getColumns($this->table
);
476 $access_level = $login->getAccessLevel();
477 $categories = FieldsCategories
::getList($this->zdb
);
479 foreach ($categories as $c) {
480 $cpk = FieldsCategories
::PK
;
482 foreach ($this->cats_defaults
as $conf_cat) {
483 if ($conf_cat['id'] == $c->$cpk) {
484 $cat_label = $conf_cat['category'];
488 if ($cat_label === null) {
489 $cat_label = $c->category
;
491 $cat = (object)array(
492 'id' => (int)$c->$cpk,
493 'label' => $cat_label,
494 'elements' => array()
497 $elements = $this->categorized_fields
[$c->$cpk];
498 $cat->elements
= array();
500 foreach ($elements as $elt) {
502 $o->readonly
= false;
504 if ($o->field_id
== 'id_adh') {
505 // ignore access control, as member ID is always needed
506 if (!$preferences->pref_show_id ||
$new === true) {
507 $hidden_elements[] = $o;
509 $o->type
= self
::TYPE_STR
;
511 $cat->elements
[$o->field_id
] = $o;
513 } elseif ($o->field_id
== 'parent_id') {
514 $hidden_elements[] = $o;
516 // skip fields blacklisted for edition
518 in_array($o->field_id
, $this->non_form_elements
)
519 ||
$selfs && $this->isSelfExcluded($o->field_id
)
524 // skip fields according to access control
526 $o->visible
== self
::NOBODY ||
527 ($o->visible
== self
::ADMIN
&&
528 $access_level < Authentication
::ACCESS_ADMIN
) ||
529 ($o->visible
== self
::STAFF
&&
530 $access_level < Authentication
::ACCESS_STAFF
) ||
531 ($o->visible
== self
::MANAGER
&&
532 $access_level < Authentication
::ACCESS_MANAGER
)
537 if (preg_match('/date/', $o->field_id
)) {
538 $o->type
= self
::TYPE_DATE
;
539 } elseif (preg_match('/bool/', $o->field_id
)) {
540 $o->type
= self
::TYPE_BOOL
;
542 $o->field_id
== 'titre_adh'
543 ||
$o->field_id
== 'pref_lang'
544 ||
$o->field_id
== 'id_statut'
546 $o->type
= self
::TYPE_SELECT
;
547 } elseif ($o->field_id
== 'sexe_adh') {
548 $o->type
= self
::TYPE_RADIO
;
550 $o->type
= self
::TYPE_STR
;
553 //retrieve field information from DB
554 foreach ($columns as $column) {
555 if ($column->getName() === $o->field_id
) {
557 = $column->getCharacterMaximumLength();
558 $o->default = $column->getColumnDefault();
559 $o->datatype
= $column->getDataType();
564 // disabled field according to access control
566 $o->visible
== self
::USER_READ
&&
567 $access_level == Authentication
::ACCESS_USER
571 $o->disabled
= false;
574 $cat->elements
[$o->field_id
] = $o;
578 if (count($cat->elements
) > 0) {
579 $form_elements[] = $cat;
583 'fieldsets' => $form_elements,
584 'hiddens' => $hidden_elements
586 } catch (\Exception
$e) {
588 'An error occurred getting form elements',
596 * Retrieve display elements
598 * @param Login $login Login instance
602 public function getDisplayElements(Login
$login)
606 $display_elements = [];
607 $access_level = $login->getAccessLevel();
608 $categories = FieldsCategories
::getList($this->zdb
);
610 foreach ($categories as $c) {
611 $cpk = FieldsCategories
::PK
;
613 foreach ($this->cats_defaults
as $conf_cat) {
614 if ($conf_cat['id'] == $c->$cpk) {
615 $cat_label = $conf_cat['category'];
619 if ($cat_label === null) {
620 $cat_label = $c->category
;
622 $cat = (object)array(
623 'id' => (int)$c->$cpk,
624 'label' => $cat_label,
625 'elements' => array()
628 $elements = $this->categorized_fields
[$c->$cpk];
629 $cat->elements
= array();
631 foreach ($elements as $elt) {
634 if ($o->field_id
== 'id_adh') {
635 // ignore access control, as member ID is always needed
636 if (!isset($preferences) ||
!$preferences->pref_show_id
) {
637 $hidden_elements[] = $o;
639 $o->type
= self
::TYPE_STR
;
640 $cat->elements
[$o->field_id
] = $o;
643 // skip fields blacklisted for display
644 if (in_array($o->field_id
, $this->non_display_elements
)) {
648 // skip fields according to access control
650 $o->visible
== self
::NOBODY ||
651 ($o->visible
== self
::ADMIN
&&
652 $access_level < Authentication
::ACCESS_ADMIN
) ||
653 ($o->visible
== self
::STAFF
&&
654 $access_level < Authentication
::ACCESS_STAFF
) ||
655 ($o->visible
== self
::MANAGER
&&
656 $access_level < Authentication
::ACCESS_MANAGER
)
661 $cat->elements
[$o->field_id
] = $o;
665 if (count($cat->elements
) > 0) {
666 $display_elements[] = $cat;
669 return $display_elements;
670 } catch (\Exception
$e) {
672 'An error occurred getting display elements',
680 * Get required fields
682 * @return array of all required fields. Field names = keys
684 public function getRequired()
686 return $this->all_required
;
692 * @return array of all visibles fields
694 public function getVisibilities()
696 return $this->all_visibles
;
700 * Get visibility for specified field
702 * @param string $field The requested field
706 public function getVisibility($field)
708 return $this->all_visibles
[$field];
712 * Get all fields with their categories
716 public function getCategorizedFields()
718 return $this->categorized_fields
;
724 * @param array $fields categorized fields array
728 public function setFields($fields)
730 $this->categorized_fields
= $fields;
731 return $this->store();
735 * Store config in database
739 private function store()
741 $class = get_class($this);
744 $this->zdb
->connection
->beginTransaction();
746 $update = $this->zdb
->update(self
::TABLE
);
749 'required' => ':required',
750 'visible' => ':visible',
751 'position' => ':position',
752 FieldsCategories
::PK
=> ':category'
756 'field_id' => ':field_id',
757 'table_name' => $this->table
760 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($update);
763 foreach ($this->categorized_fields
as $cat) {
764 foreach ($cat as $pos => $field) {
765 if (in_array($field['field_id'], $this->non_required
)) {
766 $field['required'] = $this->zdb
->isPostgres() ?
'false' : 0;
769 if ($field['field_id'] === 'parent_id') {
770 $field['visible'] = 0;
774 'required' => $field['required'],
775 'visible' => $field['visible'],
777 FieldsCategories
::PK
=> $field['category'],
778 'where1' => $field['field_id']
781 $stmt->execute($params);
786 '[' . $class . '] Fields configuration stored successfully! ',
793 '[' . $class . '] Fields configuration for table %s stored ' .
799 $this->zdb
->connection
->commit();
800 return $this->load();
801 } catch (\Exception
$e) {
802 $this->zdb
->connection
->rollBack();
804 '[' . $class . '] An error occurred while storing fields ' .
805 'configuration for table `' . $this->table
. '`.' .
810 $e->getTraceAsString(),
818 * Migrate old required fields configuration
819 * Only needeed for 0.7.4 upgrade
820 * (should have been 0.7.3 - but I missed that.)
824 public function migrateRequired()
826 $old_required = null;
829 $select = $this->zdb
->select('required');
830 $select->from(PREFIX_DB
. 'required');
832 $old_required = $this->zdb
->execute($select);
833 } catch (\Exception
$pe) {
835 'Unable to retrieve required fields_config. Maybe ' .
836 'the table does not exists?',
843 $this->zdb
->connection
->beginTransaction();
845 $update = $this->zdb
->update(self
::TABLE
);
848 'required' => ':required'
852 'field_id' => ':field_id',
853 'table_name' => $this->table
857 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($update);
859 foreach ($old_required as $or) {
860 /** Why where parameter is named where1 ?? */
863 'required' => ($or->required
=== false) ?
864 ($this->zdb
->isPostgres() ?
'false' : 0) : true,
865 'where1' => $or->field_id
870 $class = get_class($this);
875 '[' . $class . '] Required fields for table %s upgraded ' .
881 $this->zdb
->db
->query(
882 'DROP TABLE ' . PREFIX_DB
. 'required',
883 Adapter
::QUERY_MODE_EXECUTE
886 $this->zdb
->connection
->commit();
888 } catch (\Exception
$e) {
889 $this->zdb
->connection
->rollBack();
891 'An error occurred migrating old required fields. | ' .
900 * Insert values in database
902 * @param array $values Values to insert
906 private function insert($values)
908 $insert = $this->zdb
->insert(self
::TABLE
);
911 'field_id' => ':field_id',
912 'table_name' => ':table_name',
913 'required' => ':required',
914 'visible' => ':visible',
915 FieldsCategories
::PK
=> ':category',
916 'position' => ':position',
917 'list_visible' => ':list_visible',
918 'list_position' => ':list_position'
921 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($insert);
922 foreach ($values as $d) {
923 $required = $d['required'];
924 if ($required === false) {
925 $required = $this->zdb
->isPostgres() ?
'false' : 0;
928 $list_visible = $f['list_visible'] ??
false;
929 if ($list_visible === false) {
930 $list_visible = $this->zdb
->isPostgres() ?
'false' : 0;
935 'field_id' => $d['field_id'],
936 'table_name' => $d['table_name'],
937 'required' => $required,
938 'visible' => $d['visible'],
939 FieldsCategories
::PK
=> $d['category'],
940 'position' => $d['position'],
941 'list_visible' => $list_visible,
942 'list_position' => $d['list_position'] ??
-1
949 * Does field should be displayed in self subscription page
951 * @param string $name Field name
955 public function isSelfExcluded($name)
967 * Get fields for massive changes
968 * @see FieldsConfig::getFormElements
970 * @param array $fields Member fields
971 * @param Login $login Login instance
975 public function getMassiveFormElements(array $fields, Login
$login)
977 $visibles = $this->getVisibilities();
978 $access_level = $login->getAccessLevel();
980 //remove not searchable fields
981 unset($fields['mdp_adh']);
983 foreach ($fields as $k => $f) {
985 $visibles[$k] == FieldsConfig
::NOBODY ||
986 ($visibles[$k] == FieldsConfig
::ADMIN
&&
987 $access_level < Authentication
::ACCESS_ADMIN
) ||
988 ($visibles[$k] == FieldsConfig
::STAFF
&&
989 $access_level < Authentication
::ACCESS_STAFF
) ||
990 ($visibles[$k] == FieldsConfig
::MANAGER
&&
991 $access_level < Authentication
::ACCESS_MANAGER
)
1004 'bool_display_info',
1010 $mass_fields = array_intersect(array_keys($fields), $mass_fields);
1012 foreach ($mass_fields as $mass_field) {
1013 $this->setNotRequired($mass_field);
1015 $form_elements = $this->getFormElements($login, false);
1016 unset($form_elements['hiddens']);
1018 foreach ($form_elements['fieldsets'] as &$form_element) {
1019 $form_element->elements
= array_intersect_key($form_element->elements
, array_flip($mass_fields));
1021 return $form_elements;
1025 * Get field configuration
1027 * @param string $name Field name
1031 public function getField($name): array
1033 if (!isset($this->core_db_fields
[$name])) {
1034 throw new \
UnexpectedValueException("$name fied does not exists");
1036 return $this->core_db_fields
[$name];