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