]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/DynamicFields/DynamicField.php
Use same permissions in dynamic and core fields
[galette.git] / galette / lib / Galette / DynamicFields / DynamicField.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\DynamicFields;
23
24 use ArrayObject;
25 use Galette\Features\Permissions;
26 use Throwable;
27 use Analog\Analog;
28 use Galette\Core\Db;
29 use Galette\Entity\DynamicFieldsHandle;
30 use Galette\Features\Translatable;
31 use Galette\Features\I18n;
32 use Laminas\Db\Sql\Expression;
33 use Laminas\Db\Sql\Predicate\Expression as PredicateExpression;
34
35 /**
36 * Abstract dynamic field
37 *
38 * @author Johan Cwiklinski <johan@x-tnd.be>
39 */
40
41 abstract class DynamicField
42 {
43 use Translatable;
44 use I18n;
45 use Permissions;
46
47 public const TABLE = 'field_types';
48 public const PK = 'field_id';
49
50 /** Separator field */
51 public const SEPARATOR = 0;
52 /** Simple text field */
53 public const TEXT = 1;
54 /** Line field */
55 public const LINE = 2;
56 /** Choice field (listbox) */
57 public const CHOICE = 3;
58 /** Date field */
59 public const DATE = 4;
60 /** Boolean field (checkbox) */
61 public const BOOLEAN = 5;
62 /** File field (upload) */
63 public const FILE = 6;
64
65 public const MOVE_UP = 'up';
66 public const MOVE_DOWN = 'down';
67
68 public const DEFAULT_MAX_FILE_SIZE = 1024;
69 public const VALUES_FIELD_LENGTH = 100;
70
71 protected bool $has_data = false;
72 protected bool $has_width = false;
73 protected bool $has_height = false;
74 protected bool $has_size = false;
75 protected bool $has_min_size = false;
76 protected bool $multi_valued = false;
77 protected bool $fixed_values = false;
78 protected bool $has_permissions = true;
79
80 protected ?int $id = null;
81 protected ?int $index = null;
82 protected bool $required = false;
83 protected ?int $width_in_forms = 1;
84 protected bool $information_above = false;
85 protected ?int $width = null;
86 protected ?int $height = null;
87 protected ?int $repeat = null;
88 protected ?int $min_size = null;
89 protected ?int $size = null;
90 protected ?int $old_size = null;
91 /** @var string|array<string>|false */
92 protected string|array|false $values = false;
93 protected string $form;
94 protected ?string $information = null;
95 protected ?string $name = null;
96 protected ?string $old_name = null;
97
98 /** @var array<string> */
99 protected array $errors = [];
100
101 protected Db $zdb;
102
103 /**
104 * Default constructor
105 *
106 * @param Db $zdb Database instance
107 * @param mixed $args Arguments
108 */
109 public function __construct(Db $zdb, $args = null)
110 {
111 $this->zdb = $zdb;
112
113 if (is_int($args)) {
114 $this->load($args);
115 } elseif (is_object($args)) {
116 $this->loadFromRs($args);
117 }
118 }
119
120 /**
121 * Load field from its id
122 *
123 * @param Db $zdb Database instance
124 * @param int $id Field id
125 *
126 * @return DynamicField|false
127 */
128 public static function loadFieldType(Db $zdb, int $id): DynamicField|false
129 {
130 try {
131 $select = $zdb->select(self::TABLE);
132 $select->where(['field_id' => $id]);
133
134 $results = $zdb->execute($select);
135 if ($results->count() > 0) {
136 /** @var ArrayObject<string, int|string> $result */
137 $result = $results->current();
138 $field_type = $result->field_type;
139 $field_type = self::getFieldType($zdb, $field_type);
140 $field_type->loadFromRs($result);
141 return $field_type;
142 }
143 } catch (Throwable $e) {
144 Analog::log(
145 __METHOD__ . ' | Unable to retrieve field `' . $id .
146 '` information | ' . $e->getMessage(),
147 Analog::ERROR
148 );
149 return false;
150 }
151 return false;
152 }
153
154 /**
155 * Get correct field type instance
156 *
157 * @param Db $zdb Database instance
158 * @param int $t Field type
159 * @param int|null $id Optional dynamic field id (to load data)
160 *
161 * @return DynamicField
162 */
163 public static function getFieldType(Db $zdb, int $t, int $id = null): DynamicField
164 {
165 $df = null;
166 switch ($t) {
167 case self::SEPARATOR:
168 $df = new Separator($zdb, $id);
169 break;
170 case self::TEXT:
171 $df = new Text($zdb, $id);
172 break;
173 case self::LINE:
174 $df = new Line($zdb, $id);
175 break;
176 case self::CHOICE:
177 $df = new Choice($zdb, $id);
178 break;
179 case self::DATE:
180 $df = new Date($zdb, $id);
181 break;
182 case self::BOOLEAN:
183 $df = new Boolean($zdb, $id);
184 break;
185 case self::FILE:
186 $df = new File($zdb, $id);
187 break;
188 default:
189 throw new \Exception('Unknown field type ' . $t . '!');
190 }
191 return $df;
192 }
193
194 /**
195 * Load field
196 *
197 * @param integer $id Id
198 *
199 * @return void
200 */
201 public function load(int $id): void
202 {
203 try {
204 $select = $this->zdb->select(self::TABLE);
205 $select->where([self::PK => $id]);
206
207 $results = $this->zdb->execute($select);
208 if ($results->count() > 0) {
209 /** @var ArrayObject<string, int|string> $result */
210 $result = $results->current();
211 $this->loadFromRs($result);
212 }
213 } catch (Throwable $e) {
214 Analog::log(
215 'Unable to retrieve field type for field ' . $id . ' | ' .
216 $e->getMessage(),
217 Analog::ERROR
218 );
219 }
220 }
221
222 /**
223 * Load field type from a db ResultSet
224 *
225 * @param ArrayObject<string, int|string> $rs ResultSet
226 * @param bool $values Whether to load values. Defaults to true
227 *
228 * @return void
229 */
230 public function loadFromRs(ArrayObject $rs, bool $values = true): void
231 {
232 $this->id = (int)$rs->field_id;
233 $this->name = $rs->field_name;
234 $this->index = (int)$rs->field_index;
235 $this->permission = (int)$rs->field_perm;
236 $this->required = $rs->field_required == 1;
237 $this->min_size = $rs->field_min_size;
238 $this->width_in_forms = (int)$rs->field_width_in_forms;
239 $this->width = $rs->field_width;
240 $this->height = $rs->field_height;
241 $this->repeat = (int)$rs->field_repeat;
242 $this->size = $rs->field_size;
243 $this->form = $rs->field_form;
244 $this->information = $rs->field_information;
245 $this->information_above = $rs->field_information_above == 1;
246 if ($values && $this->hasFixedValues()) {
247 $this->loadFixedValues();
248 }
249 }
250
251 /**
252 * Retrieve fixed values table name
253 *
254 * @param integer $id Field ID
255 * @param bool $prefixed Whether table name should be prefixed
256 *
257 * @return string
258 */
259 public static function getFixedValuesTableName(int $id, bool $prefixed = false): string
260 {
261 $name = 'field_contents_' . $id;
262 if ($prefixed === true) {
263 $name = PREFIX_DB . $name;
264 }
265 return $name;
266 }
267
268 /**
269 * Returns an array of fixed valued for a field of type 'choice'.
270 *
271 * @return void
272 */
273 private function loadFixedValues(): void
274 {
275 try {
276 $val_select = $this->zdb->select(
277 self::getFixedValuesTableName($this->id)
278 );
279
280 $val_select->columns(
281 array(
282 'val'
283 )
284 )->order('id');
285
286 $results = $this->zdb->execute($val_select);
287 $this->values = array();
288 if ($results->count() > 0) {
289 foreach ($results as $val) {
290 $this->values[] = $val->val;
291 }
292 }
293 } catch (Throwable $e) {
294 Analog::log(
295 __METHOD__ . ' | ' . $e->getMessage(),
296 Analog::WARNING
297 );
298 }
299 }
300
301 /**
302 * Get field type
303 *
304 * @return integer
305 */
306 abstract public function getType(): int;
307
308 /**
309 * Get field type name
310 *
311 * @return String
312 */
313 public function getTypeName(): string
314 {
315 $types = $this->getFieldsTypesNames();
316 if (isset($types[$this->getType()])) {
317 return $types[$this->getType()];
318 } else {
319 throw new \RuntimeException(
320 'Unknown type ' . $this->getType()
321 );
322 }
323 }
324
325 /**
326 * Does the field handle data?
327 *
328 * @return bool
329 */
330 public function hasData(): bool
331 {
332 return $this->has_data;
333 }
334
335 /**
336 * Does the field has width?
337 *
338 * @return bool
339 */
340 public function hasWidth(): bool
341 {
342 return $this->has_width;
343 }
344
345 /**
346 * Does the field has height?
347 *
348 * @return bool
349 */
350 public function hasHeight(): bool
351 {
352 return $this->has_height;
353 }
354
355 /**
356 * Does the field has min size?
357 *
358 * @return bool
359 */
360 public function hasMinSize(): bool
361 {
362 return $this->has_min_size;
363 }
364
365 /**
366 * Does the field has a size?
367 *
368 * @return bool
369 */
370 public function hasSize(): bool
371 {
372 return $this->has_size;
373 }
374
375 /**
376 * Is the field multivalued?
377 *
378 * @return bool
379 */
380 public function isMultiValued(): bool
381 {
382 return $this->multi_valued;
383 }
384
385 /**
386 * Does the field has fixed values?
387 *
388 * @return bool
389 */
390 public function hasFixedValues(): bool
391 {
392 return $this->fixed_values;
393 }
394
395 /**
396 * Does the field require permissions?
397 *
398 * @return bool
399 */
400 public function hasPermissions(): bool
401 {
402 return $this->has_permissions;
403 }
404
405 /**
406 * Get field id
407 *
408 * @return integer|null
409 */
410 public function getId(): ?int
411 {
412 return $this->id;
413 }
414
415 /**
416 * Is field required?
417 *
418 * @return bool
419 */
420 public function isRequired(): bool
421 {
422 return $this->required;
423 }
424
425 /**
426 * Get field's width in forms
427 *
428 * @return integer|null
429 */
430 public function getWidthInForms(): ?int
431 {
432 return $this->width_in_forms;
433 }
434
435 /**
436 * Get field width
437 *
438 * @return integer|null
439 */
440 public function getWidth(): ?int
441 {
442 return $this->width;
443 }
444
445 /**
446 * Get field height
447 *
448 * @return integer|null
449 */
450 public function getHeight(): ?int
451 {
452 return $this->height;
453 }
454
455 /**
456 * Is current field repeatable?
457 *
458 * @return bool
459 */
460 public function isRepeatable(): bool
461 {
462 return $this->repeat != null && $this->repeat >= 0;
463 }
464
465 /**
466 * Get fields repetitions
467 *
468 * @return integer|null
469 */
470 public function getRepeat(): ?int
471 {
472 return $this->repeat;
473 }
474
475 /**
476 * Get field min size
477 *
478 * @return integer|null
479 */
480 public function getMinSize(): ?int
481 {
482 return $this->min_size;
483 }
484
485 /**
486 * Get field size
487 *
488 * @return integer|null
489 */
490 public function getSize(): ?int
491 {
492 return $this->size;
493 }
494
495 /**
496 * Get field index
497 *
498 * @return integer|null
499 */
500 public function getIndex(): ?int
501 {
502 return $this->index;
503 }
504
505 /**
506 * Get field information
507 *
508 * @return string
509 */
510 public function getInformation(): string
511 {
512 return $this->information ?? '';
513 }
514
515 /**
516 * Does the field information have to be displayed above input?
517 *
518 * @return bool
519 */
520 public function hasInformationAbove(): bool
521 {
522 return $this->information_above;
523 }
524
525 /**
526 * Retrieve forms names
527 *
528 * @return array<string,string>
529 */
530 public static function getFormsNames(): array
531 {
532 return [
533 'adh' => _T("Members"),
534 'contrib' => _T("Contributions"),
535 'trans' => _T("Transactions")
536 ];
537 }
538
539 /**
540 * Retrieve form name
541 *
542 * @param string $form_name Form name
543 *
544 * @return string
545 */
546 public static function getFormTitle(string $form_name): string
547 {
548 $names = self::getFormsNames();
549 return $names[$form_name];
550 }
551
552 /**
553 * Get form
554 *
555 * @return string
556 */
557 public function getForm(): string
558 {
559 return $this->form;
560 }
561
562 /**
563 * Get field values
564 *
565 * @param bool $imploded Whether to implode values
566 *
567 * @return array<string>|string|false
568 */
569 public function getValues(bool $imploded = false): array|string|false
570 {
571 if (!is_array($this->values)) {
572 return false;
573 }
574 if ($imploded === true) {
575 return implode("\n", $this->values);
576 } else {
577 return $this->values;
578 }
579 }
580
581 /**
582 * Check posted values validity
583 *
584 * @param array<string,mixed> $values All values to check, basically the $_POST array
585 * after sending the form
586 *
587 * @return bool
588 */
589 public function check(array $values): bool
590 {
591 $this->errors = [];
592 $this->warnings = [];
593
594 if (
595 (!isset($values['field_name']) || $values['field_name'] == '')
596 && !$this instanceof Separator
597 ) {
598 $this->errors[] = _T('Missing required field name!');
599 } else {
600 if ($this->old_name === null && $this->name !== null && $this->name != $values['field_name']) {
601 $this->old_name = $this->name;
602 }
603 $this->name = $values['field_name'];
604 }
605
606 if (!isset($values['field_perm']) || $values['field_perm'] === '') {
607 $this->errors[] = _T('Missing required field permissions!');
608 } else {
609 if (in_array($values['field_perm'], array_keys(self::getPermissionsList()))) {
610 $this->permission = $values['field_perm'];
611 } else {
612 $this->errors[] = _T('Unknown permission!');
613 }
614 }
615
616 if (!isset($this->id)) {
617 if (!isset($values['form_name']) || $values['form_name'] == '') {
618 $this->errors[] = _T('Missing required form!');
619 } else {
620 if (in_array($values['form_name'], array_keys(self::getFormsNames()))) {
621 $this->form = $values['form_name'];
622 } else {
623 $this->errors[] = _T('Unknown form!');
624 }
625 }
626 }
627
628 $this->required = $values['field_required'] ?? false;
629
630 $this->width_in_forms = $values['field_width_in_forms'] ?? 1;
631
632 if (count($this->errors) === 0 && $this->isDuplicate()) {
633 $this->errors[] = _T("- Field name already used.");
634 }
635
636 if ($this->hasWidth() && isset($values['field_width']) && trim($values['field_width']) != '') {
637 if (!is_numeric($values['field_width']) || $values['field_width'] <= 0) {
638 $this->errors[] = _T("- Width must be a positive integer!");
639 } else {
640 $this->width = $values['field_width'];
641 }
642 }
643
644 if ($this->hasHeight() && isset($values['field_height']) && trim($values['field_height']) != '') {
645 if (!is_numeric($values['field_height']) || $values['field_height'] <= 0) {
646 $this->errors[] = _T("- Height must be a positive integer!");
647 } else {
648 $this->height = $values['field_height'];
649 }
650 }
651
652 if ($this->hasSize() && isset($values['field_size']) && trim($values['field_size']) != '') {
653 if (!is_numeric($values['field_size']) || $values['field_size'] <= 0) {
654 $this->errors[] = _T("- Size must be a positive integer!");
655 } else {
656 $this->size = $values['field_size'];
657 }
658 }
659
660 if ($this->hasMinSize() && isset($values['field_min_size']) && trim($values['field_min_size']) != '') {
661 if (!is_numeric($values['field_min_size']) || $values['field_min_size'] <= 0) {
662 $this->errors[] = _T("- Min size must be a positive integer!");
663 } else {
664 $this->min_size = $values['field_min_size'];
665 }
666 }
667
668 if (
669 $this->hasMinSize()
670 && $this->min_size !== null
671 && $this->hasSize()
672 && $this->size !== null
673 ) {
674 if ($this->min_size > $this->size) {
675 $this->errors[] = _T("- Min size must be lower than size!");
676 }
677 }
678
679 if (isset($values['field_repeat']) && trim($values['field_repeat']) != '') {
680 if (!is_numeric($values['field_repeat'])) {
681 $this->errors[] = _T("- Repeat must be an integer!");
682 } else {
683 $this->repeat = $values['field_repeat'];
684 }
685 }
686
687 if (isset($values['field_information']) && trim($values['field_information']) != '') {
688 global $preferences;
689 $this->information = $preferences->cleanHtmlValue($values['field_information']);
690 }
691
692 $this->information_above = $values['field_information_above'] ?? false;
693
694 if ($this->hasFixedValues() && isset($values['fixed_values'])) {
695 $fixed_values = [];
696 foreach (explode("\n", $values['fixed_values']) as $val) {
697 $val = trim($val);
698 $len = mb_strlen($val);
699 if ($len > 0) {
700 $fixed_values[] = $val;
701 if ($len > $this->size) {
702 if ($this->old_size === null) {
703 $this->old_size = $this->size;
704 }
705 $this->size = $len;
706 }
707 }
708 }
709
710 $this->values = $fixed_values;
711 }
712
713 if (!isset($this->id)) {
714 $this->index = $this->getNewIndex();
715 }
716
717 if (count($this->errors) === 0) {
718 return true;
719 } else {
720 return false;
721 }
722 }
723
724 /**
725 * Store the field type
726 *
727 * @param array<string,mixed> $values All values to check, basically the $_POST array
728 * after sending the form
729 *
730 * @return bool
731 */
732 public function store(array $values): bool
733 {
734 if (!$this->check($values)) {
735 return false;
736 }
737
738 $isnew = (!isset($this->id));
739 if ($this->old_name !== null) {
740 $this->deleteTranslation($this->old_name);
741 $this->addTranslation($this->name);
742 }
743
744 try {
745 $values = array(
746 'field_name' => strip_tags($this->name),
747 'field_perm' => $this->permission,
748 'field_required' => $this->required,
749 'field_width_in_forms' => $this->width_in_forms,
750 'field_width' => ($this->width === null ? new Expression('NULL') : $this->width),
751 'field_height' => ($this->height === null ? new Expression('NULL') : $this->height),
752 'field_min_size' => ($this->min_size === null ? new Expression('NULL') : $this->min_size),
753 'field_size' => ($this->size === null ? new Expression('NULL') : $this->size),
754 'field_repeat' => ($this->repeat === null ? new Expression('NULL') : $this->repeat),
755 'field_form' => $this->form,
756 'field_index' => $this->index,
757 'field_information' => ($this->information === null ? new Expression('NULL') : $this->information),
758 'field_information_above' => $this->information_above,
759 );
760
761 if ($this->required === false) {
762 //Handle booleans for postgres ; bugs #18899 and #19354
763 $values['field_required'] = $this->zdb->isPostgres() ? 'false' : 0;
764 }
765
766 if ($this->information_above === false) {
767 //Handle booleans for postgres ; bugs #18899 and #19354
768 $values['field_information_above'] = $this->zdb->isPostgres() ? 'false' : 0;
769 }
770
771 if (!$isnew) {
772 $update = $this->zdb->update(self::TABLE);
773 $update->set($values)->where([self::PK => $this->id]);
774 $this->zdb->execute($update);
775 } else {
776 $values['field_type'] = $this->getType();
777 $insert = $this->zdb->insert(self::TABLE);
778 $insert->values($values);
779 $this->zdb->execute($insert);
780
781 $this->id = $this->zdb->getLastGeneratedValue($this);
782
783 if ($this->name != '') {
784 $this->addTranslation($this->name);
785 }
786 }
787 } catch (Throwable $e) {
788 Analog::log(
789 'An error occurred storing field | ' . $e->getMessage(),
790 Analog::ERROR
791 );
792 $this->errors[] = _T("An error occurred storing the field.");
793 }
794
795 if (count($this->errors) === 0 && $this->hasFixedValues()) {
796 $contents_table = self::getFixedValuesTableName($this->id, true);
797
798 try {
799 $this->zdb->drop(str_replace(PREFIX_DB, '', $contents_table), true);
800 $field_size = ((int)$this->size > 0) ? $this->size : 1;
801 $this->zdb->db->query(
802 'CREATE TABLE ' . $contents_table .
803 ' (id INTEGER NOT NULL,val varchar(' . $field_size .
804 ') NOT NULL)',
805 \Laminas\Db\Adapter\Adapter::QUERY_MODE_EXECUTE
806 );
807 } catch (Throwable $e) {
808 Analog::log(
809 'Unable to manage fields values table ' .
810 $contents_table . ' | ' . $e->getMessage(),
811 Analog::ERROR
812 );
813 $this->errors[] = _T("An error occurred creating field values table");
814 }
815
816 if (count($this->errors) == 0 && is_array($this->values)) {
817 $contents_table = self::getFixedValuesTableName($this->id);
818 try {
819 $this->zdb->connection->beginTransaction();
820
821 $insert = $this->zdb->insert($contents_table);
822 $insert->values(
823 array(
824 'id' => ':id',
825 'val' => ':val'
826 )
827 );
828 $stmt = $this->zdb->sql->prepareStatementForSqlObject($insert);
829
830 $cnt_values = count($this->values);
831 for ($i = 0; $i < $cnt_values; $i++) {
832 $stmt->execute(
833 array(
834 'id' => $i,
835 'val' => $this->values[$i]
836 )
837 );
838 }
839 $this->zdb->connection->commit();
840 } catch (Throwable $e) {
841 $this->zdb->connection->rollBack();
842 Analog::log(
843 'Unable to store field ' . $this->id . ' values (' .
844 $e->getMessage() . ')',
845 Analog::ERROR
846 );
847 $this->warnings[] = _T('An error occurred storing dynamic field values :(');
848 }
849 }
850 }
851
852 if (count($this->errors) === 0) {
853 return true;
854 } else {
855 return false;
856 }
857 }
858
859 /**
860 * Get new index
861 *
862 * @return integer
863 */
864 protected function getNewIndex(): int
865 {
866 $select = $this->zdb->select(self::TABLE);
867 $select->columns(
868 array(
869 'idx' => new \Laminas\Db\Sql\Expression('COUNT(*) + 1')
870 )
871 );
872 $select->where(['field_form' => $this->form]);
873 $results = $this->zdb->execute($select);
874 $result = $results->current();
875 $idx = $result->idx;
876 return (int)$idx;
877 }
878
879 /**
880 * Is field duplicated?
881 *
882 * @return bool
883 */
884 public function isDuplicate(): bool
885 {
886 //let's consider field is duplicated, in case of future errors
887 $duplicated = true;
888 try {
889 $select = $this->zdb->select(self::TABLE);
890 $select->columns(
891 array(
892 'cnt' => new \Laminas\Db\Sql\Expression('COUNT(' . self::PK . ')')
893 )
894 )->where(
895 array(
896 'field_form' => $this->form,
897 'field_name' => $this->name
898 )
899 );
900
901 if (isset($this->id)) {
902 $select->where->addPredicate(
903 new PredicateExpression(
904 'field_id NOT IN (?)',
905 array($this->id)
906 )
907 );
908 }
909
910 $results = $this->zdb->execute($select);
911 $result = $results->current();
912 $dup = $result->cnt;
913 if (!$dup > 0) {
914 $duplicated = false;
915 }
916 } catch (Throwable $e) {
917 Analog::log(
918 'An error occurred checking field duplicity' . $e->getMessage(),
919 Analog::ERROR
920 );
921 throw $e;
922 }
923 return $duplicated;
924 }
925
926 /**
927 * Move a dynamic field
928 *
929 * @param string $action What to do (one of self::MOVE_*)
930 *
931 * @return bool
932 */
933 public function move(string $action): bool
934 {
935 if ($action !== self::MOVE_UP && $action !== self::MOVE_DOWN) {
936 throw new \RuntimeException(('Unknown action ' . $action));
937 }
938
939 try {
940 $this->zdb->connection->beginTransaction();
941
942 $old_rank = $this->index;
943
944 $direction = $action == self::MOVE_UP ? -1 : 1;
945 $new_rank = $old_rank + $direction;
946 $update = $this->zdb->update(self::TABLE);
947 $update->set([
948 'field_index' => $old_rank
949 ])->where([
950 'field_index' => $new_rank,
951 'field_form' => $this->form
952 ]);
953 $this->zdb->execute($update);
954
955 $update = $this->zdb->update(self::TABLE);
956 $update->set(
957 array(
958 'field_index' => $new_rank
959 )
960 )->where(
961 array(
962 self::PK => $this->id
963 )
964 );
965 $this->zdb->execute($update);
966 $this->zdb->connection->commit();
967
968 return true;
969 } catch (Throwable $e) {
970 $this->zdb->connection->rollBack();
971 Analog::log(
972 'Unable to change field ' . $this->id . ' rank | ' .
973 $e->getMessage(),
974 Analog::ERROR
975 );
976 return false;
977 }
978 }
979
980 /**
981 * Delete a dynamic field
982 *
983 * @return bool
984 */
985 public function remove(): bool
986 {
987 try {
988 if ($this->hasFixedValues()) {
989 $contents_table = self::getFixedValuesTableName($this->id);
990 $this->zdb->drop($contents_table);
991 }
992
993 $this->zdb->connection->beginTransaction();
994 $old_rank = $this->index;
995
996 $update = $this->zdb->update(self::TABLE);
997 $update->set(
998 array(
999 'field_index' => new \Laminas\Db\Sql\Expression('field_index-1')
1000 )
1001 )->where
1002 ->greaterThan('field_index', $old_rank)
1003 ->equalTo('field_form', $this->form);
1004 $this->zdb->execute($update);
1005
1006 //remove associated values
1007 try {
1008 $delete = $this->zdb->delete(DynamicFieldsHandle::TABLE);
1009 $delete->where(
1010 array(
1011 'field_id' => $this->id,
1012 'field_form' => $this->form
1013 )
1014 );
1015 $this->zdb->execute($delete);
1016 } catch (Throwable $e) {
1017 throw new \RuntimeException('Unable to remove associated values for field ' . $this->id . '!');
1018 }
1019
1020 //remove field type
1021 try {
1022 $delete = $this->zdb->delete(self::TABLE);
1023 $delete->where(
1024 array(
1025 'field_id' => $this->id,
1026 'field_form' => $this->form
1027 )
1028 );
1029 $this->zdb->execute($delete);
1030 } catch (Throwable $e) {
1031 throw new \RuntimeException('Unable to remove field type ' . $this->id . '!');
1032 }
1033
1034 $this->deleteTranslation($this->name);
1035
1036 $this->zdb->connection->commit();
1037
1038 return true;
1039 } catch (Throwable $e) {
1040 if ($this->zdb->connection->inTransaction()) {
1041 //because of DROP autocommit on mysql...
1042 $this->zdb->connection->rollBack();
1043 }
1044 Analog::log(
1045 'An error occurred deleting field | ' . $e->getMessage(),
1046 Analog::ERROR
1047 );
1048 return false;
1049 }
1050 }
1051
1052 /**
1053 * Retrieve fields types names
1054 *
1055 * @return array<int, string>
1056 */
1057 public static function getFieldsTypesNames(): array
1058 {
1059 $names = [
1060 self::SEPARATOR => _T("separator"),
1061 self::TEXT => _T("free text"),
1062 self::LINE => _T("single line"),
1063 self::CHOICE => _T("choice"),
1064 self::DATE => _T("date"),
1065 self::BOOLEAN => _T("boolean"),
1066 self::FILE => _T("file")
1067 ];
1068 return $names;
1069 }
1070
1071 /**
1072 * Get errors
1073 *
1074 * @return array<string>
1075 */
1076 public function getErrors(): array
1077 {
1078 return $this->errors;
1079 }
1080
1081 /**
1082 * Get warnings
1083 *
1084 * @return array<string>
1085 */
1086 public function getWarnings(): array
1087 {
1088 return $this->warnings;
1089 }
1090 }