3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Dynamic fields handle, aggregating field descriptors and values
10 * Copyright © 2011-2021 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 2011-2021 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 - 2011-06-20
37 namespace Galette\Entity
;
41 use Laminas\Db\Adapter\Driver\StatementInterface
;
42 use Laminas\Db\Sql\Expression
;
43 use Laminas\Db\Sql\Predicate\Expression
as PredicateExpression
;
45 use Galette\Core\Login
;
46 use Galette\Core\Authentication
;
47 use Galette\DynamicFields\Separator
;
48 use Galette\DynamicFields\Text
;
49 use Galette\DynamicFields\Line
;
50 use Galette\DynamicFields\Choice
;
51 use Galette\DynamicFields\Date
;
52 use Galette\DynamicFields\Boolean
;
53 use Galette\DynamicFields\File
;
54 use Galette\DynamicFields\DynamicField
;
55 use Galette\Repository\DynamicFieldsSet
;
58 * Dynamic fields handle, aggregating field descriptors and values
60 * @name DynamicFieldsHandle
64 * @author Johan Cwiklinski <johan@x-tnd.be>
65 * @copyright 2011-2021 The Galette Team
66 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
67 * @link http://galette.tuxfamily.org
70 class DynamicFieldsHandle
72 public const TABLE
= 'dynamic_fields';
74 private $dynamic_fields = [];
75 private $current_values = [];
79 private $errors = array();
88 private $has_changed = false;
93 * @param Db $zdb Database instance
94 * @param Login $login Login instance
95 * @param mixed $instance Object instance
97 public function __construct(Db
$zdb, Login
$login, $instance = null)
100 $this->login
= $login;
101 if ($instance !== null) {
102 $this->load($instance);
107 * Load dynamic fields values for specified object
109 * @param mixed $object Object instance
111 * @return array|false
113 public function load($object)
115 switch (get_class($object)) {
116 case 'Galette\Entity\Adherent':
117 $this->form_name
= 'adh';
119 case 'Galette\Entity\Contribution':
120 $this->form_name
= 'contrib';
122 case 'Galette\Entity\Transaction':
123 $this->form_name
= 'trans';
126 throw new \
RuntimeException('Class ' . get_class($object) . ' does not handle dynamic fields!');
131 $this->item_id
= $object->id
;
132 $fields = new DynamicFieldsSet($this->zdb
, $this->login
);
133 $this->dynamic_fields
= $fields->getList($this->form_name
);
135 $results = $this->getCurrentFields();
137 if ($results->count() > 0) {
138 foreach ($results as $f) {
139 if (isset($this->dynamic_fields
[$f->{DynamicField
::PK
}])) {
140 $field = $this->dynamic_fields
[$f->{DynamicField
::PK
}];
141 if ($field->hasFixedValues()) {
142 $choices = $field->getValues();
143 $f->text_val
= $choices[$f->field_val
];
145 $this->current_values
[$f->{DynamicField
::PK
}][] = array_filter(
147 static function ($k) {
148 return $k != DynamicField
::PK
;
154 'Dynamic values found for ' . get_class($object) . ' #' . $this->item_id
.
155 '; but no dynamic field configured!',
164 } catch (Throwable
$e) {
166 __METHOD__
. ' | ' . $e->getMessage(),
178 public function getErrors(): array
180 return $this->errors
;
188 public function getFields(): array
190 return $this->dynamic_fields
;
196 * @param integer $field Field ID
200 public function getValues($field): array
202 if (!isset($this->current_values
[$field])) {
203 $this->current_values
[$field][] = [
204 'item_id' => $this->item_id
,
205 'field_form' => $this->dynamic_fields
[$field]->getForm(),
211 return $this->current_values
[$field];
217 * @param integer $item Item ID
218 * @param integer $field Field ID
219 * @param integer $index Value index
220 * @param mixed $value Value
224 public function setValue($item, $field, $index, $value)
229 'field_form' => $this->dynamic_fields
[$field]->getForm(),
230 'val_index' => $index,
231 'field_val' => $value,
234 if (!isset($this->current_values
[$field][$idx])) {
235 $input['is_new'] = true;
238 $this->current_values
[$field][$idx] = $input;
244 * @param integer $item Item ID
245 * @param integer $field Field ID
246 * @param integer $index Value index
250 public function unsetValue($item, $field, $index)
253 if (isset($this->current_values
[$field][$idx])) {
254 unset($this->current_values
[$field][$idx]);
261 * @param integer $item_id Curent item id to use (will be used if current item_id is 0)
262 * @param boolean $transaction True if a transaction already exists
266 public function storeValues($item_id = null, $transaction = false)
269 if ($item_id !== null && ($this->item_id
== null ||
$this->item_id
== 0)) {
270 $this->item_id
= $item_id;
273 $this->zdb
->connection
->beginTransaction();
276 $this->handleRemovals();
278 foreach ($this->current_values
as $field_id => $values) {
279 foreach ($values as $value) {
280 $value[DynamicField
::PK
] = $field_id;
281 if ($value['item_id'] == 0) {
282 $value['item_id'] = $this->item_id
;
285 if (isset($value['is_new'])) {
286 unset($value['is_new']);
287 $this->getInsertStatement()->execute($value);
288 $this->has_changed
= true;
291 'field_val' => $value['field_val'],
292 'val_index' => $value['val_index'],
293 'item_id' => $value['item_id'],
294 'field_id' => $value['field_id'],
295 'field_form' => $value['field_form'],
296 'old_val_index' => $value['old_val_index'] ??
$value['val_index'] //:old_val_index
298 $this->getUpdateStatement()->execute($params);
299 $this->has_changed
= true;
305 $this->zdb
->connection
->commit();
308 } catch (Throwable
$e) {
310 $this->zdb
->connection
->rollBack();
313 'An error occurred storing dynamic field. Form name: ' . $this->form_name
.
314 ' | Error was: ' . $e->getMessage(),
327 * Get (and prepare if not done yet) insert statement
329 * @return StatementInterface
331 private function getInsertStatement(): StatementInterface
333 if (!isset($this->insert_stmt
)) {
334 $insert = $this->zdb
->insert(self
::TABLE
);
336 'item_id' => ':item_id',
337 'field_id' => ':field_id',
338 'field_form' => ':field_form',
339 'val_index' => ':val_index',
340 'field_val' => ':field_val'
342 $this->insert_stmt
= $this->zdb
->sql
->prepareStatementForSqlObject($insert);
344 return $this->insert_stmt
;
348 * Get (and prepare if not done yet) update statement
350 * @return StatementInterface
352 private function getUpdateStatement(): StatementInterface
354 if (!isset($this->update_stmt
)) {
355 $update = $this->zdb
->update(self
::TABLE
);
357 'field_val' => ':field_val',
358 'val_index' => ':val_index'
360 'item_id' => ':item_id',
361 'field_id' => ':field_id',
362 'field_form' => ':field_form',
363 'val_index' => ':old_val_index'
365 $this->update_stmt
= $this->zdb
->sql
->prepareStatementForSqlObject($update);
367 return $this->update_stmt
;
371 * Handle values that have been removed
375 private function handleRemovals()
377 $fields = new DynamicFieldsSet($this->zdb
, $this->login
);
378 $this->dynamic_fields
= $fields->getList($this->form_name
, $this->login
);
380 $results = $this->getCurrentFields();
383 if ($results->count() > 0) {
384 foreach ($results as $result) {
385 $fromdb[$result->field_id
. '_' . $result->val_index
] = [
386 'item_id' => $this->item_id
,
387 'field_form' => $this->form_name
,
388 'field_id' => $result->field_id
,
389 'val_index' => $result->val_index
394 if (!count($fromdb)) {
395 //no entry in database, nothing to do.
399 foreach ($this->current_values
as $field_id => $values) {
400 foreach ($values as $value) {
401 $key = $field_id . '_' . $value['val_index'];
402 if (isset($fromdb[$key])) {
403 unset($fromdb[$key]);
408 if (count($fromdb)) {
409 foreach ($fromdb as $entry) {
410 if ($this->delete_stmt
=== null) {
411 $delete = $this->zdb
->delete(self
::TABLE
);
413 'item_id' => ':item_id',
414 'field_form' => ':field_form',
415 'field_id' => ':field_id',
416 'val_index' => ':val_index'
418 $this->delete_stmt
= $this->zdb
->sql
->prepareStatementForSqlObject($delete);
420 $this->delete_stmt
->execute($entry);
422 $field_id = $entry['field_id'];
424 isset($this->current_values
[$field_id])
425 && count($this->current_values
[$field_id])
427 $val_index = (int)$entry['val_index'];
428 foreach ($this->current_values
[$field_id] as &$current) {
429 if ((int)$current['val_index'] === $val_index +
1) {
430 $current['val_index'] = $val_index;
432 $current['old_val_index'] = $val_index;
437 $this->has_changed
= true;
442 * Is there any change in dynamic fields?
446 public function hasChanged()
448 return $this->has_changed
;
454 * @param integer $item_id Curent item id to use (will be used if current item_id is 0)
455 * @param boolean $transaction True if a transaction already exists
459 public function removeValues($item_id = null, $transaction = false)
462 if ($item_id !== null && ($this->item_id
== null ||
$this->item_id
== 0)) {
463 $this->item_id
= $item_id;
466 $this->zdb
->connection
->beginTransaction();
469 $delete = $this->zdb
->delete(self
::TABLE
);
472 'item_id' => $this->item_id
,
473 'field_form' => $this->form_name
476 $this->zdb
->execute($delete);
479 $this->zdb
->connection
->commit();
482 } catch (Throwable
$e) {
484 $this->zdb
->connection
->rollBack();
487 'An error occurred removing dynamic field. Form name: ' . $this->form_name
.
488 ' | Error was: ' . $e->getMessage(),
496 * Get current fields resultset
500 protected function getCurrentFields()
502 $select = $this->zdb
->select(self
::TABLE
, 'd');
504 array('t' => PREFIX_DB
. DynamicField
::TABLE
),
505 'd.' . DynamicField
::PK
. '=t.' . DynamicField
::PK
,
509 'item_id' => $this->item_id
,
510 'd.field_form' => $this->form_name
514 /** only load values for accessible fields*/
515 $accessible_fields = [];
516 $access_level = $this->login
->getAccessLevel();
518 foreach ($this->dynamic_fields
as $field) {
519 $perm = $field->getPerm();
521 ($perm == DynamicField
::PERM_MANAGER
&&
522 $access_level < Authentication
::ACCESS_MANAGER
) ||
523 ($perm == DynamicField
::PERM_STAFF
&&
524 $access_level < Authentication
::ACCESS_STAFF
) ||
525 ($perm == DynamicField
::PERM_ADMIN
&&
526 $access_level < Authentication
::ACCESS_ADMIN
)
530 $accessible_fields[] = $field->getId();
533 if (count($accessible_fields)) {
534 $select->where
->in('d.' . DynamicField
::PK
, $accessible_fields);
537 $results = $this->zdb
->execute($select);