3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Abstract dynamic field
10 * Copyright © 2012-2014 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/>.
27 * @category DynamicFields
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2012-2014 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
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.7.1dev - 2012-07-28
38 namespace Galette\DynamicFields
;
42 use Galette\Entity\DynamicFieldsHandle
;
43 use Galette\Entity\TranslatableTrait
;
44 use Galette\Entity\I18nTrait
;
45 use Laminas\Db\Sql\Expression
;
46 use Laminas\Db\Sql\Predicate\Expression
as PredicateExpression
;
49 * Abstract dynamic field
52 * @category DynamicFields
55 * @author Johan Cwiklinski <johan@x-tnd.be>
56 * @copyright 2012-2014 The Galette Team
57 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
58 * @link http://galette.tuxfamily.org
61 abstract class DynamicField
63 use TranslatableTrait
;
66 const TABLE
= 'field_types';
67 const PK
= 'field_id';
69 /** Separator field */
71 /** Simple text field */
75 /** Choice field (listbox) */
79 /** Boolean field (checkbox) */
81 /** File field (upload) */
84 const PERM_USER_WRITE
= 0;
87 const PERM_MANAGER
= 3;
88 const PERM_USER_READ
= 4;
90 const DEFAULT_MAX_FILE_SIZE
= 1024;
91 const VALUES_FIELD_LENGTH
= 100;
93 protected $has_data = false;
94 protected $has_width = false;
95 protected $has_height = false;
96 protected $has_size = false;
97 protected $multi_valued = false;
98 protected $fixed_values = false;
99 protected $has_permissions = true;
118 * Default constructor
120 * @param Db $zdb Database instance
121 * @param mixed $args Arguments
123 public function __construct(Db
$zdb, $args = null)
129 } elseif ($args !== null && is_object($args)) {
130 $this->loadFromRs($args);
135 * Load field from its id
137 * @param Db $zdb Database instance
138 * @param int $id Field id
140 * @return DynamicField|false
142 public static function loadFieldType(Db
$zdb, $id)
145 $select = $zdb->select(self
::TABLE
);
146 $select->where('field_id = ' . $id);
148 $results = $zdb->execute($select);
149 $result = $results->current();
151 $field_type = $result->field_type
;
152 $field_type = self
::getFieldType($zdb, $field_type);
153 $field_type->loadFromRs($result);
156 } catch (\Exception
$e) {
158 __METHOD__
. ' | Unable to retrieve field `' . $id .
159 '` information | ' . $e->getMessage(),
168 * Get correct field type instance
170 * @param Db $zdb Database instance
171 * @param int $t Field type
172 * @param int $id Optional dynamic field id (to load data)
174 * @return DynamicField
176 public static function getFieldType(Db
$zdb, $t, $id = null)
180 case self
::SEPARATOR
:
181 $df = new Separator($zdb, $id);
184 $df = new Text($zdb, $id);
187 $df = new Line($zdb, $id);
190 $df = new Choice($zdb, $id);
193 $df = new Date($zdb, $id);
196 $df = new Boolean($zdb, $id);
199 $df = new File($zdb, $id);
202 throw new \
Exception('Unknown field type ' . $t . '!');
211 * @param integer $id Id
215 public function load($id)
218 $select = $this->zdb
->select(self
::TABLE
);
219 $select->where(self
::PK
. ' = ' . $id);
221 $results = $this->zdb
->execute($select);
222 $result = $results->current();
225 $this->loadFromRs($result);
227 } catch (Exception
$e) {
229 'Unable to retrieve field type for field ' . $id . ' | ' .
237 * Load field type from a db ResultSet
239 * @param ResultSet $rs ResultSet
240 * @param boolean $values Whether to load values. Defaults to true
244 public function loadFromRs($rs, $values = true)
246 $this->id
= (int)$rs->field_id
;
247 $this->name
= $rs->field_name
;
248 $this->index
= (int)$rs->field_index
;
249 $this->perm
= (int)$rs->field_perm
;
250 $this->required
= ($rs->field_required
== 1 ?
true : false);
251 $this->width
= $rs->field_width
;
252 $this->height
= $rs->field_height
;
253 $this->repeat
= $rs->field_repeat
;
254 $this->size
= $rs->field_size
;
255 $this->form
= $rs->field_form
;
256 if ($values && $this->hasFixedValues()) {
257 $this->loadFixedValues();
262 * Retrieve fixed values table name
264 * @param integer $id Field ID
265 * @param boolean $prefixed Whether table name should be prefixed
269 public static function getFixedValuesTableName($id, $prefixed = false)
271 $name = 'field_contents_' . $id;
272 if ($prefixed === true) {
273 $name = PREFIX_DB
. $name;
279 * Returns an array of fixed valued for a field of type 'choice'.
283 private function loadFixedValues()
286 $val_select = $this->zdb
->select(
287 self
::getFixedValuesTableName($this->id
)
290 $val_select->columns(
296 $results = $this->zdb
->execute($val_select);
297 $this->values
= array();
299 foreach ($results as $val) {
300 $this->values
[] = $val->val
;
303 } catch (\Exception
$e) {
305 __METHOD__
. ' | ' . $e->getMessage(),
316 abstract public function getType();
319 * Get field type name
323 public function getTypeName()
325 $types = $this->getFieldsTypesNames();
326 if (isset($types[$this->getType()])) {
327 return $types[$this->getType()];
329 throw new \
RuntimeException(
330 'Unknow type ' . $this->getType()
336 * Does the field handle data?
340 public function hasData()
342 return $this->has_data
;
346 * Does the field has width?
350 public function hasWidth()
352 return $this->has_width
;
356 * Does the field has height?
360 public function hasHeight()
362 return $this->has_height
;
366 * Does the field has a size?
370 public function hasSize()
372 return $this->has_size
;
376 * Is the field multi valued?
380 public function isMultiValued()
382 return $this->multi_valued
;
386 * Does the field has fixed values?
390 public function hasFixedValues()
392 return $this->fixed_values
;
396 * Does the field require permissions?
400 public function hasPermissions()
402 return $this->has_permissions
;
411 public function getId()
417 * Get field Permissions
421 public function getPerm()
432 public function isRequired()
434 return $this->required
;
442 public function getWidth()
452 public function getHeight()
454 return $this->height
;
458 * Is current field repeatable?
462 public function isRepeatable()
464 return $this->repeat
!= null && trim($this->repeat
) != '' && (int)$this->repeat
> 1;
468 * Get fields repetitions
470 * @return integer|boolean
472 public function getRepeat()
474 return $this->repeat
;
482 public function getSize()
492 public function getIndex()
498 * Retrieve permissions names for display
502 public static function getPermsNames()
505 self
::PERM_USER_WRITE
=> _T("User, read/write"),
506 self
::PERM_STAFF
=> _T("Staff member"),
507 self
::PERM_ADMIN
=> _T("Administrator"),
508 self
::PERM_MANAGER
=> _T("Group manager"),
509 self
::PERM_USER_READ
=> _T("User, read only")
514 * Retrieve forms names
518 public static function getFormsNames()
521 'adh' => _T("Members"),
522 'contrib' => _T("Contributions"),
523 'trans' => _T("Transactions")
530 * @param string $form_name Form name
534 public static function getFormTitle($form_name)
536 $names = self
::getFormsNames();
537 return $names[$form_name];
541 * Get permission name
545 public function getPermName()
547 $perms = self
::getPermsNames();
548 return $perms[$this->getPerm()];
556 public function getForm()
564 * @param boolean $imploded Whether to implode values
568 public function getValues($imploded = false)
570 if (!is_array($this->values
)) {
573 if ($imploded === true) {
574 return implode("\n", $this->values
);
576 return $this->values
;
581 * Check posted values validity
583 * @param array $values All values to check, basically the $_POST array
584 * after sending the form
588 public function check($values)
591 $this->warnings
= [];
593 if ((!isset($values['field_name']) ||
$values['field_name'] == '')
594 && get_class($this) != '\Galette\DynamicField\Separator'
596 $this->errors
[] = _T('Missing required field name!');
598 if ($this->old_name
=== null && $this->name
!== null && $this->name
!= $values['field_name']) {
599 $this->old_name
= $this->name
;
601 $this->name
= $values['field_name'];
604 if (!isset($values['field_perm']) ||
$values['field_perm'] === '') {
605 $this->errors
[] = _T('Missing required field permissions!');
607 if (in_array($values['field_perm'], array_keys(self
::getPermsNames()))) {
608 $this->perm
= $values['field_perm'];
610 $this->errors
[] = _T('Unknown permission!');
614 if ($this->id
=== null) {
615 if (!isset($values['form']) ||
$values['form'] == '') {
616 $this->errors
[] = _T('Missing required form!');
618 if (in_array($values['form'], array_keys(self
::getFormsNames()))) {
619 $this->form
= $values['form'];
621 $this->errors
[] = _T('Unknown form!');
626 $this->required
= $values['field_required'];
628 if (count($this->errors
) === 0 && $this->isDuplicate($values['form'], $this->name
, $this->id
)) {
629 $this->errors
[] = _T("- Field name already used.");
632 if ($this->hasWidth() && isset($values['field_width']) && trim($values['field_width']) != '') {
633 $this->width
= $values['field_width'];
636 if ($this->hasHeight() && isset($values['field_height']) && trim($values['field_height']) != '') {
637 $this->height
= $values['field_height'];
640 if ($this->hasSize() && isset($values['field_size']) && trim($values['field_size']) != '') {
641 $this->size
= $values['field_size'];
644 if (isset($values['field_repeat']) && trim($values['field_repeat']) != '') {
645 $this->repeat
= $values['field_repeat'];
648 if ($this->hasFixedValues() && isset($values['fixed_values'])) {
650 foreach (explode("\n", $values['fixed_values']) as $val) {
652 $len = mb_strlen($val);
654 $fixed_values[] = $val;
655 if ($len > $this->size
) {
656 if ($this->old_size
=== null) {
657 $this->old_size
= $this->size
;
664 $this->values
= $fixed_values;
667 if ($this->id
== null) {
668 $this->index
= $this->getNewIndex();
671 if (count($this->errors
) === 0) {
679 * Store the field type
681 * @param array $values All values to check, basically the $_POST array
682 * after sending the form
686 public function store($values)
688 if (!$this->check($values)) {
692 $isnew = ($this->id
=== null);
693 if ($this->old_name
!== null) {
694 $this->deleteTranslation($this->old_name
);
695 $this->addTranslation($this->name
);
700 'field_name' => $this->name
,
701 'field_perm' => $this->perm
,
702 'field_required' => $this->required
,
703 'field_width' => ($this->width
=== null ?
new Expression('NULL') : $this->width
),
704 'field_height' => ($this->height
=== null ?
new Expression('NULL') : $this->height
),
705 'field_size' => ($this->size
=== null ?
new Expression('NULL') : $this->size
),
706 'field_repeat' => ($this->repeat
=== null ?
new Expression('NULL') : $this->repeat
),
707 'field_form' => $this->form
,
708 'field_index' => $this->index
711 if ($this->required
=== false) {
712 //Handle booleans for postgres ; bugs #18899 and #19354
713 $values['field_required'] = $this->zdb
->isPostgres() ?
'false' : 0;
717 $update = $this->zdb
->update(self
::TABLE
);
718 $update->set($values)->where(
719 self
::PK
. ' = ' . $this->id
721 $this->zdb
->execute($update);
723 $values['field_type'] = $this->getType();
724 $insert = $this->zdb
->insert(self
::TABLE
);
725 $insert->values($values);
726 $this->zdb
->execute($insert);
728 if ($this->zdb
->isPostgres()) {
729 $this->id
= $this->zdb
->driver
->getLastGeneratedValue(
730 PREFIX_DB
. 'field_types_id_seq'
733 $this->id
= $this->zdb
->driver
->getLastGeneratedValue();
736 if ($this->name
!= '') {
737 $this->addTranslation($this->name
);
740 } catch (Exception
$e) {
742 'An error occurred storing field | ' . $e->getMessage(),
745 $this->errors
[] = _T("An error occurred storing the field.");
748 if (count($this->errors
) === 0 && $this->hasFixedValues()) {
749 $contents_table = self
::getFixedValuesTableName($this->id
, true);
752 $this->zdb
->connection
->beginTransaction();
753 $this->zdb
->db
->query(
754 'DROP TABLE IF EXISTS ' . $contents_table,
755 \Laminas\Db\Adapter\Adapter
::QUERY_MODE_EXECUTE
757 $field_size = ((int)$this->size
> 0) ?
$this->size
: 1;
758 $this->zdb
->db
->query(
759 'CREATE TABLE ' . $contents_table .
760 ' (id INTEGER NOT NULL,val varchar(' . $field_size .
762 \Laminas\Db\Adapter\Adapter
::QUERY_MODE_EXECUTE
764 $this->zdb
->connection
->commit();
765 } catch (\Exception
$e) {
766 $this->zdb
->connection
->rollBack();
768 'Unable to manage fields values table ' .
769 $contents_table . ' | ' . $e->getMessage(),
772 $this->errors
[] = _T("An error occurred creating field values table");
775 if (count($this->errors
) == 0 && is_array($this->values
)) {
776 $contents_table = self
::getFixedValuesTableName($this->id
);
778 $this->zdb
->connection
->beginTransaction();
780 $insert = $this->zdb
->insert($contents_table);
787 $stmt = $this->zdb
->sql
->prepareStatementForSqlObject($insert);
789 for ($i = 0; $i < count($this->values
); $i++
) {
793 'val' => $this->values
[$i]
797 $this->zdb
->connection
->commit();
798 } catch (\Exception
$e) {
799 $this->zdb
->connection
->rollBack();
801 'Unable to store field ' . $this->id
. ' values (' .
802 $e->getMessage() . ')',
805 $this->warnings
[] = _T('An error occurred storing dynamic field values :(');
810 if (count($this->errors
) === 0) {
822 protected function getNewIndex()
824 $select = $this->zdb
->select(self
::TABLE
);
827 'idx' => new \Laminas\Db\Sql\
Expression('COUNT(*) + 1')
830 $select->where(['field_form' => $this->form
]);
831 $results = $this->zdb
->execute($select);
832 $result = $results->current();
838 * Is field duplicated?
842 public function isDuplicate()
844 //let's consider field is duplicated, in case of future errors
847 $select = $this->zdb
->select(self
::TABLE
);
850 'cnt' => new \Laminas\Db\Sql\
Expression('COUNT('. self
::PK
.')')
854 'field_form' => $this->form
,
855 'field_name' => $this->name
859 if ($this->id
!== null) {
860 $select->where
->addPredicate(
861 new PredicateExpression(
862 'field_id NOT IN (?)',
868 $results = $this->zdb
->execute($select);
869 $result = $results->current();
874 } catch (\Exception
$e) {
876 'An error occurred checking field duplicity' . $e->getMessage(),
883 * Move a dynamic field
885 * @param string $action What to do (either 'up' or 'down' localized)
889 public function move($action)
892 $this->zdb
->connection
->beginTransaction();
894 $old_rank = $this->index
;
896 $direction = $action == 'up' ?
-1: 1;
897 $new_rank = $old_rank +
$direction;
898 $update = $this->zdb
->update(self
::TABLE
);
900 'field_index' => $old_rank
902 'field_index' => $new_rank,
903 'field_form' => $this->form
905 $this->zdb
->execute($update);
907 $update = $this->zdb
->update(self
::TABLE
);
910 'field_index' => $new_rank
914 self
::PK
=> $this->id
917 $this->zdb
->execute($update);
918 $this->zdb
->connection
->commit();
921 } catch (\Exception
$e) {
922 $this->zdb
->connection
->rollBack();
924 'Unable to change field ' . $this->id
. ' rank | ' .
933 * Delete a dynamic field
937 public function remove()
940 $this->zdb
->connection
->beginTransaction();
941 $old_rank = $this->index
;
943 $update = $this->zdb
->update(self
::TABLE
);
946 'field_index' => new \Laminas\Db\Sql\
Expression('field_index-1')
949 ->greaterThan('field_index', $old_rank)
950 ->equalTo('field_form', $this->form
);
951 $this->zdb
->execute($update);
953 //remove associated values
954 $delete = $this->zdb
->delete(DynamicFieldsHandle
::TABLE
);
957 'field_id' => $this->id
,
958 'field_form' => $this->form
961 $result = $this->zdb
->execute($delete);
963 throw new \
RuntimeException('Unable to remove associated values for field ' . $this->id
. '!');
967 $delete = $this->zdb
->delete(self
::TABLE
);
970 'field_id' => $this->id
,
971 'field_form' => $this->form
974 $result = $this->zdb
->execute($delete);
976 throw new \
RuntimeException('Unable to remove field ' . $this->id
. '!');
979 if ($this->hasFixedValues()) {
980 $contents_table = self
::getFixedValuesTableName($this->id
);
981 $this->zdb
->db
->query(
982 'DROP TABLE IF EXISTS ' . $contents_table,
983 \Laminas\Db\Adapter\Adapter
::QUERY_MODE_EXECUTE
986 $this->deleteTranslation($this->name
);
988 $this->zdb
->connection
->commit();
990 } catch (\Exception
$e) {
991 $this->zdb
->connection
->rollBack();
993 'An error occurred deleting field | ' . $e->getMessage(),
1001 * Retrieve fields types names
1005 public static function getFieldsTypesNames()
1008 self
::SEPARATOR
=> _T("separator"),
1009 self
::TEXT
=> _T("free text"),
1010 self
::LINE
=> _T("single line"),
1011 self
::CHOICE
=> _T("choice"),
1012 self
::DATE
=> _T("date"),
1013 self
::BOOLEAN
=> _T("boolean"),
1014 self
::FILE
=> _T("file")
1024 public function getErrors()
1026 return $this->errors
;
1034 public function getWarnings()
1036 return $this->warnings
;