]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/Entitled.php
Move trait to Features namespace
[galette.git] / galette / lib / Galette / Entity / Entitled.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Entitleds handling
7 *
8 * PHP version 5
9 *
10 * Copyright © 2007-2021 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 2007-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 - 2007-10-27
35 */
36
37 namespace Galette\Entity;
38
39 use Throwable;
40 use Analog\Analog;
41 use Laminas\Db\Sql\Expression;
42 use Galette\Core\Db;
43 use Galette\Features\I18n;
44
45 /**
46 * Entitled handling. Manage:
47 * - id
48 * - label
49 * - extra (that may differ from one entity to another)
50 *
51 * @category Entity
52 * @name Entitled
53 * @package Galette
54 * @author Johan Cwiklinski <johan@x-tnd.be>
55 * @copyright 2007-2021 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 - 2007-10-27
59 *
60 * @property integer $id
61 * @property string $label
62 * @property string $libelle
63 * @property mixed $third
64 * @property mixed $extension
65 */
66
67 abstract class Entitled
68 {
69 use I18n;
70
71 public const ID_NOT_EXITS = -1;
72
73 private $zdb;
74 private $table;
75 private $fpk;
76 private $flabel;
77 private $fthird;
78 private $used;
79
80 public static $fields;
81 protected static $defaults;
82
83 /** @var string|false */
84 protected $order_field = false;
85
86 private $id;
87 private $label;
88 private $third;
89
90 private $errors = array();
91
92 /**
93 * Default constructor
94 *
95 * @param Db $zdb Database
96 * @param string $table Table name
97 * @param string $fpk Primary key field name
98 * @param string $flabel Label fields name
99 * @param string $fthird The third field name
100 * @param string $used Table name for isUsed function
101 * @param mixed $args Either an int or a resultset to load
102 */
103 public function __construct(Db $zdb, $table, $fpk, $flabel, $fthird, $used, $args = null)
104 {
105 $this->zdb = $zdb;
106 $this->table = $table;
107 $this->fpk = $fpk;
108 $this->flabel = $flabel;
109 $this->fthird = $fthird;
110 $this->used = $used;
111 if (is_int($args)) {
112 $this->load($args);
113 } elseif (is_object($args)) {
114 $this->loadFromRS($args);
115 }
116 }
117
118 /**
119 * Loads an entry from its id
120 *
121 * @param int $id Entry ID
122 *
123 * @return boolean true if query succeed, false otherwise
124 */
125 public function load($id)
126 {
127 try {
128 $select = $this->zdb->select($this->table);
129 $select->where($this->fpk . ' = ' . $id);
130
131 $results = $this->zdb->execute($select);
132 if ($results->count() > 0) {
133 $result = $results->current();
134 $this->loadFromRS($result);
135
136 return true;
137 } else {
138 throw new \RuntimeException(
139 'Unknown ID ' . $id . '!'
140 );
141 }
142 } catch (Throwable $e) {
143 Analog::log(
144 'Cannot load ' . $this->getType() . ' from id `' . $id . '` | ' .
145 $e->getMessage(),
146 Analog::WARNING
147 );
148 return false;
149 }
150 }
151
152 /**
153 * Populate object from a resultset row
154 *
155 * @param ResultSet $r the resultset row
156 *
157 * @return void
158 */
159 private function loadFromRS($r)
160 {
161 $pk = $this->fpk;
162 $this->id = $r->$pk;
163 $flabel = $this->flabel;
164 $this->label = $r->$flabel;
165 $fthird = $this->fthird;
166 $this->third = $r->$fthird;
167 }
168
169 /**
170 * Set defaults at install time
171 *
172 * @return boolean|Exception
173 */
174 public function installInit()
175 {
176 $class = get_class($this);
177
178 try {
179 //first, we drop all values
180 $delete = $this->zdb->delete($this->table);
181 $this->zdb->execute($delete);
182
183 $values = array();
184 foreach ($class::$fields as $key => $f) {
185 $values[$f] = ':' . $key;
186 }
187
188 $insert = $this->zdb->insert($this->table);
189 $insert->values($values);
190 $stmt = $this->zdb->sql->prepareStatementForSqlObject($insert);
191
192 $this->zdb->handleSequence(
193 $this->table,
194 count(static::$defaults)
195 );
196
197 $fnames = array_values($values);
198 foreach ($class::$defaults as $d) {
199 $val = null;
200 if (isset($d['priority'])) {
201 $val = $d['priority'];
202 } else {
203 $val = $d['extension'];
204 }
205
206 $stmt->execute(
207 array(
208 $fnames[0] => $d['id'],
209 $fnames[1] => $d['libelle'],
210 $fnames[2] => $val
211 )
212 );
213 }
214
215 Analog::log(
216 'Defaults (' . $this->getType() .
217 ') were successfully stored into database.',
218 Analog::INFO
219 );
220 return true;
221 } catch (Throwable $e) {
222 Analog::log(
223 'Unable to initialize defaults (' . $this->getType() . ').' .
224 $e->getMessage(),
225 Analog::WARNING
226 );
227 throw $e;
228 }
229 }
230
231 /**
232 * Get list in an array built as:
233 * $array[id] = "translated label"
234 *
235 * @param boolean|null $extent Filter on (non) cotisations types
236 *
237 * @return array|false
238 */
239 public function getList(bool $extent = null)
240 {
241 $list = array();
242
243 try {
244 $select = $this->zdb->select($this->table);
245 $fields = array($this->fpk, $this->flabel);
246 if (
247 $this->order_field !== false
248 && $this->order_field !== $this->fpk
249 && $this->order_field !== $this->flabel
250 ) {
251 $fields[] = $this->order_field;
252 }
253 $select->quantifier('DISTINCT');
254 $select->columns($fields);
255
256 if ($this->order_field !== false) {
257 $select->order($this->order_field, $this->fpk);
258 }
259 if ($extent !== null) {
260 if ($extent === true) {
261 $select->where(array($this->fthird => new Expression('true')));
262 } elseif ($extent === false) {
263 $select->where(array($this->fthird => new Expression('false')));
264 }
265 }
266
267 $results = $this->zdb->execute($select);
268
269 foreach ($results as $r) {
270 $fpk = $this->fpk;
271 $flabel = $this->flabel;
272 $list[$r->$fpk] = _T($r->$flabel);
273 }
274 return $list;
275 } catch (Throwable $e) {
276 Analog::log(
277 __METHOD__ . ' | ' . $e->getMessage(),
278 Analog::ERROR
279 );
280 return false;
281 }
282 }
283
284 /**
285 * Complete list
286 *
287 * @return array of all objects if succeeded, false otherwise
288 */
289 public function getCompleteList()
290 {
291 $list = array();
292
293 try {
294 $select = $this->zdb->select($this->table);
295 if ($this->order_field !== false) {
296 $select->order(array($this->order_field, $this->fpk));
297 }
298
299 $results = $this->zdb->execute($select);
300
301 if ($results->count() == 0) {
302 Analog::log(
303 'No entries (' . $this->getType() . ') defined in database.',
304 Analog::INFO
305 );
306 } else {
307 $pk = $this->fpk;
308 $flabel = $this->flabel;
309 $fprio = $this->fthird;
310
311 foreach ($results as $r) {
312 $list[$r->$pk] = array(
313 'name' => _T($r->$flabel),
314 'extra' => $r->$fprio
315 );
316 }
317 }
318 return $list;
319 } catch (Throwable $e) {
320 Analog::log(
321 'Cannot list entries (' . $this->getType() .
322 ') | ' . $e->getMessage(),
323 Analog::WARNING
324 );
325 return false;
326 }
327 }
328
329 /**
330 * Get a entry
331 *
332 * @param integer $id Entry ID
333 *
334 * @return mixed|false Row if succeed ; false: no such id
335 */
336 public function get($id)
337 {
338 if (!is_numeric($id)) {
339 $this->errors[] = _T("ID must be an integer!");
340 return false;
341 }
342
343 try {
344 $select = $this->zdb->select($this->table);
345 $select->where($this->fpk . '=' . $id);
346
347 $results = $this->zdb->execute($select);
348 $result = $results->current();
349
350 if (!$result) {
351 $this->errors[] = _T("Label does not exist");
352 return false;
353 }
354
355 return $result;
356 } catch (Throwable $e) {
357 Analog::log(
358 __METHOD__ . ' | ' . $e->getMessage(),
359 Analog::WARNING
360 );
361 return false;
362 }
363 }
364
365 /**
366 * Get a label
367 *
368 * @param integer $id Id
369 * @param boolean $translated Do we want translated or original label?
370 * Defaults to true.
371 *
372 * @return string
373 */
374 public function getLabel($id, $translated = true)
375 {
376 $res = $this->get($id);
377 if ($res === false) {
378 //get() alred logged
379 return self::ID_NOT_EXITS;
380 };
381 $field = $this->flabel;
382 return ($translated) ? _T($res->$field) : $res->$field;
383 }
384
385 /**
386 * Get an ID from a label
387 *
388 * @param string $label The label
389 *
390 * @return int|false Return id if it exists false otherwise
391 */
392 public function getIdByLabel($label)
393 {
394 try {
395 $pk = $this->fpk;
396 $select = $this->zdb->select($this->table);
397 $select->columns(array($pk))
398 ->where(array($this->flabel => $label));
399
400 $results = $this->zdb->execute($select);
401 $result = $results->current();
402 if ($result) {
403 return $result->$pk;
404 } else {
405 return false;
406 }
407 } catch (Throwable $e) {
408 Analog::log(
409 'Unable to retrieve ' . $this->getType() . ' from label `' .
410 $label . '` | ' . $e->getMessage(),
411 Analog::ERROR
412 );
413 return false;
414 }
415 }
416
417 /**
418 * Add a new entry
419 *
420 * @param string $label The label
421 * @param integer $extra Extra values (priority for statuses,
422 * extension for contributions types, ...)
423 *
424 * @return integer id if success ; -1 : DB error ; -2 : label already exists
425 */
426 public function add($label, $extra)
427 {
428 // Avoid duplicates.
429 $ret = $this->getIdByLabel($label);
430
431 if ($ret !== false) {
432 Analog::log(
433 $this->getType() . ' with label `' . $label . '` already exists',
434 Analog::WARNING
435 );
436 return -2;
437 }
438
439 try {
440 $this->zdb->connection->beginTransaction();
441 $values = array(
442 $this->flabel => $label,
443 $this->fthird => $extra
444 );
445
446 $insert = $this->zdb->insert($this->table);
447 $insert->values($values);
448
449 $ret = $this->zdb->execute($insert);
450
451 if ($ret->count() > 0) {
452 Analog::log(
453 'New ' . $this->getType() . ' `' . $label .
454 '` added successfully.',
455 Analog::INFO
456 );
457
458 $this->id = $this->zdb->getLastGeneratedValue($this);
459
460 $this->addTranslation($label);
461 } else {
462 throw new \Exception('New ' . $this->getType() . ' not added.');
463 }
464 $this->zdb->connection->commit();
465 return true;
466 } catch (Throwable $e) {
467 $this->zdb->connection->rollBack();
468 Analog::log(
469 'Unable to add new entry `' . $label . '` | ' .
470 $e->getMessage(),
471 Analog::ERROR
472 );
473 throw $e;
474 }
475 }
476
477 /**
478 * Update in database.
479 *
480 * @param integer $id Entry ID
481 * @param string $label The label
482 * @param integer $extra Extra values (priority for statuses,
483 * extension for contributions types, ...)
484 *
485 * @return ID_NOT_EXITS|boolean
486 */
487 public function update($id, $label, $extra)
488 {
489 $ret = $this->get($id);
490 if (!$ret) {
491 /* get() already logged and set $this->error. */
492 return self::ID_NOT_EXITS;
493 }
494
495 $class = get_class($this);
496
497 try {
498 $oldlabel = $ret->{$this->flabel};
499 $this->zdb->connection->beginTransaction();
500 $values = array(
501 $this->flabel => $label,
502 $this->fthird => $extra
503 );
504
505 $update = $this->zdb->update($this->table);
506 $update->set($values);
507 $update->where($this->fpk . ' = ' . $id);
508
509 $ret = $this->zdb->execute($update);
510
511 if ($oldlabel != $label) {
512 $this->deleteTranslation($oldlabel);
513 $this->addTranslation($label);
514 }
515
516 Analog::log(
517 $this->getType() . ' #' . $id . ' updated successfully.',
518 Analog::INFO
519 );
520 $this->zdb->connection->commit();
521 return true;
522 } catch (Throwable $e) {
523 $this->zdb->connection->rollBack();
524 Analog::log(
525 'Unable to update ' . $this->getType() . ' #' . $id . ' | ' .
526 $e->getMessage(),
527 Analog::ERROR
528 );
529 throw $e;
530 }
531 }
532
533 /**
534 * Delete entry
535 *
536 * @param integer $id Entry ID
537 *
538 * @return ID_NOT_EXITS|boolean
539 */
540 public function delete($id)
541 {
542 $ret = $this->get($id);
543 if (!$ret) {
544 /* get() already logged */
545 return self::ID_NOT_EXITS;
546 }
547
548 if ($this->isUsed($id)) {
549 $this->errors[] = _T("Cannot delete this label: it's still used");
550 return false;
551 }
552
553 try {
554 $this->zdb->connection->beginTransaction();
555 $delete = $this->zdb->delete($this->table);
556 $delete->where($this->fpk . ' = ' . $id);
557
558 $this->zdb->execute($delete);
559 $this->deleteTranslation($ret->{$this->flabel});
560
561 Analog::log(
562 $this->getType() . ' ' . $id . ' deleted successfully.',
563 Analog::INFO
564 );
565
566 $this->zdb->connection->commit();
567 return true;
568 } catch (Throwable $e) {
569 $this->zdb->connection->rollBack();
570 Analog::log(
571 'Unable to delete ' . $this->getType() . ' ' . $id .
572 ' | ' . $e->getMessage(),
573 Analog::ERROR
574 );
575 throw $e;
576 }
577 }
578
579 /**
580 * Check if this entry is used.
581 *
582 * @param integer $id Entry ID
583 *
584 * @return boolean
585 */
586 public function isUsed($id)
587 {
588 try {
589 $select = $this->zdb->select($this->used);
590 $select->where($this->fpk . ' = ' . $id);
591
592 $results = $this->zdb->execute($select);
593 $result = $results->current();
594
595 if ($result !== null) {
596 return true;
597 } else {
598 return false;
599 }
600 } catch (Throwable $e) {
601 Analog::log(
602 'Unable to check if ' . $this->getType . ' `' . $id .
603 '` is used. | ' . $e->getMessage(),
604 Analog::ERROR
605 );
606 //in case of error, we consider that it is used, to avoid errors
607 return true;
608 }
609 }
610
611 /**
612 * Get textual type representation
613 *
614 * @return string
615 */
616 abstract protected function getType();
617
618 /**
619 * Get translated textual representation
620 *
621 * @return string
622 */
623 abstract public function getI18nType();
624
625 /**
626 * Global getter method
627 *
628 * @param string $name name of the property we want to retrive
629 *
630 * @return false|object the called property
631 */
632 public function __get($name)
633 {
634 $forbidden = array();
635 $virtuals = array('extension', 'libelle');
636 if (
637 in_array($name, $virtuals)
638 || !in_array($name, $forbidden)
639 && isset($this->$name)
640 ) {
641 switch ($name) {
642 case 'libelle':
643 return _T($this->label);
644 break;
645 case 'extension':
646 return $this->third;
647 break;
648 default:
649 return $this->$name;
650 break;
651 }
652 } else {
653 return false;
654 }
655 }
656
657 /**
658 * Get errors
659 *
660 * @return array
661 */
662 public function getErrors(): array
663 {
664 return $this->errors;
665 }
666 }