]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/DynamicFieldsHandle.php
Build statements from own methods, to clarify
[galette.git] / galette / lib / Galette / Entity / DynamicFieldsHandle.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Dynamic fields handle, aggregating field descriptors and values
7 *
8 * PHP version 5
9 *
10 * Copyright © 2011-2014 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
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.
18 *
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.
23 *
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/>.
26 *
27 * @category Entity
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2011-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
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2011-06-20
35 */
36
37 namespace Galette\Entity;
38
39 use Analog\Analog;
40 use Laminas\Db\Adapter\Driver\StatementInterface;
41 use Laminas\Db\Sql\Expression;
42 use Laminas\Db\Sql\Predicate\Expression as PredicateExpression;
43 use Galette\Core\Db;
44 use Galette\Core\Login;
45 use Galette\Core\Authentication;
46 use Galette\DynamicFields\Separator;
47 use Galette\DynamicFields\Text;
48 use Galette\DynamicFields\Line;
49 use Galette\DynamicFields\Choice;
50 use Galette\DynamicFields\Date;
51 use Galette\DynamicFields\Boolean;
52 use Galette\DynamicFields\File;
53 use Galette\DynamicFields\DynamicField;
54 use Galette\Repository\DynamicFieldsSet;
55
56 /**
57 * Dynamic fields handle, aggregating field descriptors and values
58 *
59 * @name DynamicFieldsHandle
60 * @category Entity
61 * @package Galette
62 *
63 * @author Johan Cwiklinski <johan@x-tnd.be>
64 * @copyright 2011-2014 The Galette Team
65 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
66 * @link http://galette.tuxfamily.org
67 */
68
69 class DynamicFieldsHandle
70 {
71 public const TABLE = 'dynamic_fields';
72
73 private $dynamic_fields = [];
74 private $current_values = [];
75 private $form_name;
76 private $item_id;
77
78 private $errors = array();
79
80 private $zdb;
81
82 private $insert_stmt;
83 private $update_stmt;
84 private $delete_stmt;
85
86 private $has_changed = false;
87
88 /**
89 * Default constructor
90 *
91 * @param Db $zdb Database instance
92 * @param Login $login Login instance
93 * @param mixed $instance Object instance
94 */
95 public function __construct(Db $zdb, Login $login, $instance = null)
96 {
97 $this->zdb = $zdb;
98 $this->login = $login;
99 if ($instance !== null) {
100 $this->load($instance);
101 }
102 }
103
104 /**
105 * Load dynamic fields values for specified object
106 *
107 * @param mixed $object Object instance
108 *
109 * @return array|false
110 */
111 public function load($object)
112 {
113 switch (get_class($object)) {
114 case 'Galette\Entity\Adherent':
115 $this->form_name = 'adh';
116 break;
117 case 'Galette\Entity\Contribution':
118 $this->form_name = 'contrib';
119 break;
120 case 'Galette\Entity\Transaction':
121 $this->form_name = 'trans';
122 break;
123 default:
124 throw new \RuntimeException('Class ' . get_class($object) . ' does not handle dynamic fields!');
125 break;
126 }
127
128 try {
129 $this->item_id = $object->id;
130 $fields = new DynamicFieldsSet($this->zdb, $this->login);
131 $this->dynamic_fields = $fields->getList($this->form_name);
132
133 $results = $this->getCurrentFields();
134
135 if ($results->count() > 0) {
136 $dfields = array();
137
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];
144 }
145 $this->current_values[$f->{DynamicField::PK}][] = array_filter(
146 (array)$f,
147 function ($k) {
148 return $k != DynamicField::PK;
149 },
150 ARRAY_FILTER_USE_KEY
151 );
152 } else {
153 Analog::log(
154 'Dynamic values found for ' . get_class($object) . ' #' . $this->item_id .
155 '; but no dynamic field configured!',
156 Analog::WARNING
157 );
158 }
159 }
160 return true;
161 } else {
162 return false;
163 }
164 } catch (\Exception $e) {
165 Analog::log(
166 __METHOD__ . ' | ' . $e->getMessage(),
167 Analog::WARNING
168 );
169 return false;
170 }
171 }
172
173 /**
174 * Get errors
175 *
176 * @return array
177 */
178 public function getErrors()
179 {
180 return $this->errors;
181 }
182
183 /**
184 * Get fields
185 *
186 * @return array
187 */
188 public function getFields()
189 {
190 return $this->dynamic_fields;
191 }
192
193 /**
194 * Get values
195 *
196 * @param integer $field Field ID
197 *
198 * @return array
199 */
200 public function getValues($field)
201 {
202 if (!isset($this->current_values[$field])) {
203 $this->current_values[$field][] = [
204 'item_id' => '',
205 'field_form' => $this->dynamic_fields[$field]->getForm(),
206 'val_index' => '',
207 'field_val' => '',
208 'is_new' => true
209 ];
210 }
211 return $this->current_values[$field];
212 }
213
214 /**
215 * Set field value
216 *
217 * @param integer $item Item ID
218 * @param integer $field Field ID
219 * @param integer $index Value index
220 * @param mixed $value Value
221 *
222 * @return void
223 */
224 public function setValue($item, $field, $index, $value)
225 {
226 $idx = $index - 1;
227 if (isset($this->current_values[$field][$idx])) {
228 $this->current_values[$field][$idx]['field_val'] = $value;
229 } else {
230 $this->current_values[$field][$idx] = [
231 'item_id' => $item,
232 'field_form' => $this->dynamic_fields[$field]->getForm(),
233 'val_index' => $index,
234 'field_val' => $value,
235 'is_new' => true
236 ];
237 }
238 }
239
240 /**
241 * Unset field value
242 *
243 * @param integer $item Item ID
244 * @param integer $field Field ID
245 * @param integer $index Value index
246 *
247 * @return void
248 */
249 public function unsetValue($item, $field, $index)
250 {
251 $idx = $index - 1;
252 if (isset($this->current_values[$field][$idx])) {
253 unset($this->current_values[$field][$idx]);
254 }
255 }
256
257 /**
258 * Store values
259 *
260 * @param integer $item_id Curent item id to use (will be used if current item_id is 0)
261 * @param boolean $transaction True if a transaction already exists
262 *
263 * @return boolean
264 */
265 public function storeValues($item_id = null, $transaction = false)
266 {
267 try {
268 if ($item_id !== null && ($this->item_id == null || $this->item_id == 0)) {
269 $this->item_id = $item_id;
270 }
271 if (!$transaction) {
272 $this->zdb->connection->beginTransaction();
273 }
274
275 $this->handleRemovals();
276
277 foreach ($this->current_values as $field_id => $values) {
278 foreach ($values as $value) {
279 $value[DynamicField::PK] = $field_id;
280 if ($value['item_id'] == 0) {
281 $value['item_id'] = $this->item_id;
282 }
283
284 if (isset($value['is_new'])) {
285 unset($value['is_new']);
286 $this->getInsertStatement()->execute($value);
287 $this->has_changed = true;
288 } else {
289 $params = [
290 'field_val' => $value['field_val'],
291 'val_index' => $value['val_index'],
292 'where1' => $value['item_id'],
293 'where2' => $value['field_id'],
294 'where3' => $value['field_form'],
295 'where4' => $value['old_val_index'] ?? $value['val_index']
296 ];
297 $this->getUpdateStatement()->execute($value);
298 $this->has_changed = true;
299 }
300 }
301 }
302
303 if (!$transaction) {
304 $this->zdb->connection->commit();
305 }
306 return true;
307 } catch (\Exception $e) {
308 if (!$transaction) {
309 $this->zdb->connection->rollBack();
310 } else {
311 throw $e;
312 }
313 Analog::log(
314 'An error occurred storing dynamic field. Form name: ' . $this->form_name .
315 ' | Error was: ' . $e->getMessage(),
316 Analog::ERROR
317 );
318 return false;
319 } finally {
320 unset($this->update_stmt);
321 unset($this->insert_stmt);
322 }
323 }
324
325 /**
326 * Get (and prepare if not done yet) insert statement
327 *
328 * @return StatementInterface
329 */
330 private function getInsertStatement(): StatementInterface
331 {
332 if ($this->insert_stmt === null) {
333 $insert = $this->zdb->insert(self::TABLE);
334 $insert->values([
335 'item_id' => ':item_id',
336 'field_id' => ':field_id',
337 'field_form' => ':field_form',
338 'val_index' => ':val_index',
339 'field_val' => ':field_val'
340 ]);
341 $this->insert_stmt = $this->zdb->sql->prepareStatementForSqlObject($insert);
342 }
343 return $this->insert_stmt;
344 }
345
346 /**
347 * Get (and prepare if not done yet) update statement
348 *
349 * @return StatementInterface
350 */
351 private function getUpdateStatement(): StatementInterface
352 {
353 if ($this->update_stmt === null) {
354 $update = $this->zdb->update(self::TABLE);
355 $update->set([
356 'field_val' => ':field_val',
357 'val_index' => ':val_index'
358 ])->where([
359 'item_id' => ':item_id',
360 'field_id' => ':field_id',
361 'field_form' => ':field_form',
362 'val_index' => ':val_index'
363 ]);
364 $this->update_stmt = $this->zdb->sql->prepareStatementForSqlObject($update);
365 }
366 return $this->update_stmt;
367 }
368
369 /**
370 * Handle values that have been removed
371 *
372 * @return boolean
373 */
374 private function handleRemovals()
375 {
376 $fields = new DynamicFieldsSet($this->zdb, $this->login);
377 $this->dynamic_fields = $fields->getList($this->form_name, $this->login);
378
379 $results = $this->getCurrentFields();
380
381 $fromdb = [];
382 if ($results->count() > 0) {
383 foreach ($results as $result) {
384 $fromdb[$result->field_id . '_' . $result->val_index] = [
385 'where1' => $this->item_id,
386 'where2' => $this->form_name,
387 'where3' => $result->field_id,
388 'where4' => $result->val_index
389 ];
390 }
391 }
392
393 if (!count($fromdb)) {
394 //no entry in database, nothing to do.
395 return;
396 }
397
398 foreach ($this->current_values as $field_id => $values) {
399 foreach ($values as $value) {
400 $key = $field_id . '_' . $value['val_index'];
401 if (isset($fromdb[$key])) {
402 unset($fromdb[$key]);
403 }
404 }
405 }
406
407 if (count($fromdb)) {
408 foreach ($fromdb as $entry) {
409 if ($this->delete_stmt === null) {
410 $delete = $this->zdb->delete(self::TABLE);
411 $delete->where([
412 'item_id' => ':item_id',
413 'field_form' => ':field_form',
414 'field_id' => ':field_id',
415 'val_index' => ':val_index'
416 ]);
417 $this->delete_stmt = $this->zdb->sql->prepareStatementForSqlObject($delete);
418 }
419 $this->delete_stmt->execute($entry);
420 //update val index
421 $field_id = $entry['where3'];
422 if (
423 isset($this->current_values[$field_id])
424 && count($this->current_values[$field_id])
425 ) {
426 $val_index = (int)$entry['where4'];
427 foreach ($this->current_values[$field_id] as &$current) {
428 if ((int)$current['val_index'] === $val_index + 1) {
429 $current['val_index'] = $val_index;
430 ++$val_index;
431 $current['old_val_index'] = $val_index;
432 }
433 }
434 }
435 }
436 $this->has_changed = true;
437 }
438 }
439
440 /**
441 * Is there any change in dynamic filelds?
442 *
443 * @return boolean
444 */
445 public function hasChanged()
446 {
447 return $this->has_changed;
448 }
449
450 /**
451 * Remove values
452 *
453 * @param integer $item_id Curent item id to use (will be used if current item_id is 0)
454 * @param boolean $transaction True if a transaction already exists
455 *
456 * @return boolean
457 */
458 public function removeValues($item_id = null, $transaction = false)
459 {
460 try {
461 if ($item_id !== null && ($this->item_id == null || $this->item_id == 0)) {
462 $this->item_id = $item_id;
463 }
464 if (!$transaction) {
465 $this->zdb->connection->beginTransaction();
466 }
467
468 $delete = $this->zdb->delete(self::TABLE);
469 $delete->where(
470 array(
471 'item_id' => $this->item_id,
472 'field_form' => $this->form_name
473 )
474 );
475 $this->zdb->execute($delete);
476
477 if (!$transaction) {
478 $this->zdb->connection->commit();
479 }
480 return true;
481 } catch (\Exception $e) {
482 if (!$transaction) {
483 $this->zdb->connection->rollBack();
484 } else {
485 throw $e;
486 }
487 Analog::log(
488 'An error occurred removing dynamic field. Form name: ' . $this->form_name .
489 ' | Error was: ' . $e->getMessage(),
490 Analog::ERROR
491 );
492 return false;
493 }
494 }
495
496 /**
497 * Get current fields resultset
498 *
499 * @return ResulSet
500 */
501 protected function getCurrentFields()
502 {
503 $select = $this->zdb->select(self::TABLE, 'd');
504 $select->join(
505 array('t' => PREFIX_DB . DynamicField::TABLE),
506 'd.' . DynamicField::PK . '=t.' . DynamicField::PK,
507 array('field_id')
508 )->where(
509 array(
510 'item_id' => $this->item_id,
511 'd.field_form' => $this->form_name
512 )
513 );
514
515 /** only load values for accessible fields*/
516 $accessible_fields = [];
517 $access_level = $this->login->getAccessLevel();
518
519 foreach ($this->dynamic_fields as $field) {
520 $perm = $field->getPerm();
521 if (
522 ($perm == DynamicField::PERM_MANAGER &&
523 $access_level < Authentication::ACCESS_MANAGER) ||
524 ($perm == DynamicField::PERM_STAFF &&
525 $access_level < Authentication::ACCESS_STAFF) ||
526 ($perm == DynamicField::PERM_ADMIN &&
527 $access_level < Authentication::ACCESS_ADMIN)
528 ) {
529 continue;
530 }
531 $accessible_fields[] = $field->getId();
532 }
533
534 if (count($accessible_fields)) {
535 $select->where->in('d.' . DynamicField::PK, $accessible_fields);
536 }
537
538 $results = $this->zdb->execute($select);
539 return $results;
540 }
541 }