]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Entity/Group.php
Ensure user is logged to load subgroups; handle exception for pgsql
[galette.git] / galette / lib / Galette / Entity / Group.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Group entity
7 *
8 * PHP version 5
9 *
10 * Copyright © 2012-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 2012-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 - 2012-01-17
35 */
36
37 namespace Galette\Entity;
38
39 use Throwable;
40 use Galette\Core\Login;
41 use Analog\Analog;
42 use Laminas\Db\Sql\Expression;
43
44 /**
45 * Group entity
46 *
47 * @category Entity
48 * @name Group
49 * @package Galette
50 * @author Johan Cwiklinski <johan@x-tnd.be>
51 * @copyright 2012-2021 The Galette Team
52 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
53 * @link http://galette.tuxfamily.org
54 * @since Available since 0.7dev - 2012-01-17
55 */
56 class Group
57 {
58 public const TABLE = 'groups';
59 public const PK = 'id_group';
60 //relations tables
61 public const GROUPSUSERS_TABLE = 'groups_members';
62 public const GROUPSMANAGERS_TABLE = 'groups_managers';
63
64 public const MEMBER_TYPE = 0;
65 public const MANAGER_TYPE = 1;
66
67 private $id;
68 private $group_name;
69 private $parent_group;
70 private $managers;
71 private $members;
72 private $groups;
73 private $creation_date;
74 private $count_members;
75 private $isempty;
76
77 /**
78 * Default constructor
79 *
80 * @param null|int|ResultSet $args Either a ResultSet row or its id for to load
81 * a specific group, or null to just
82 * instanciate object
83 */
84 public function __construct($args = null)
85 {
86 if ($args == null || is_int($args)) {
87 if (is_int($args) && $args > 0) {
88 $this->load($args);
89 }
90 } elseif (is_object($args)) {
91 $this->loadFromRS($args);
92 }
93 }
94
95 /**
96 * Loads a group from its id
97 *
98 * @param int $id the identifiant for the group to load
99 *
100 * @return bool true if query succeed, false otherwise
101 */
102 public function load($id)
103 {
104 global $zdb;
105
106 try {
107 $select = $zdb->select(self::TABLE);
108 $select->where(array(self::PK => $id));
109
110 $results = $zdb->execute($select);
111
112 if ($results->count() > 0) {
113 $this->loadFromRS($results->current());
114 return true;
115 } else {
116 return false;
117 }
118 } catch (Throwable $e) {
119 Analog::log(
120 'Cannot load group form id `' . $id . '` | ' . $e->getMessage(),
121 Analog::WARNING
122 );
123 throw $e;
124 }
125 }
126
127 /**
128 * Populate object from a resultset row
129 *
130 * @param ResultSet $r the resultset row
131 *
132 * @return void
133 */
134 private function loadFromRS($r)
135 {
136 $this->id = $r->id_group;
137 $this->group_name = $r->group_name;
138 $this->creation_date = $r->creation_date;
139 if ($r->parent_group) {
140 $this->parent_group = new Group((int)$r->parent_group);
141 }
142 $adhpk = Adherent::PK;
143 if (isset($r->members)) {
144 //we're from a list, we just want members count
145 $this->count_members = $r->members;
146 } else {
147 //we're probably from a single group, let's load sub entities
148 //$this->loadPersons(self::MEMBER_TYPE);
149 //$this->loadPersons(self::MANAGER_TYPE);
150 //$this->loadSubGroups();
151 }
152 }
153
154 /**
155 * Loads members for the current group
156 *
157 * @param int $type Either self::MEMBER_TYPE or self::MANAGER_TYPE
158 *
159 * @return void
160 */
161 private function loadPersons($type)
162 {
163 global $zdb;
164
165 if ($this->id) {
166 try {
167 $join = null;
168 switch ($type) {
169 case self::MEMBER_TYPE:
170 $join = PREFIX_DB . self::GROUPSUSERS_TABLE;
171 break;
172 case self::MANAGER_TYPE:
173 $join = PREFIX_DB . self::GROUPSMANAGERS_TABLE;
174 break;
175 }
176
177 $select = $zdb->select(Adherent::TABLE, 'a');
178 $select->join(
179 array('g' => $join),
180 'g.' . Adherent::PK . '=a.' . Adherent::PK,
181 array()
182 )->where([
183 'g.' . self::PK => $this->id
184 ])->order(
185 'nom_adh ASC',
186 'prenom_adh ASC'
187 );
188
189 $results = $zdb->execute($select);
190 $members = array();
191
192 $deps = array(
193 'picture' => false,
194 'groups' => false,
195 'dues' => false
196 );
197
198 foreach ($results as $m) {
199 $members[] = new Adherent($zdb, $m, $deps);
200 }
201
202 if ($type === self::MEMBER_TYPE) {
203 $this->members = $members;
204 } else {
205 $this->managers = $members;
206 }
207 } catch (Throwable $e) {
208 Analog::log(
209 'Cannot get group persons | ' . $e->getMessage(),
210 Analog::WARNING
211 );
212 throw $e;
213 }
214 }
215 }
216
217 /**
218 * Load sub-groups
219 *
220 * @return void
221 */
222 private function loadSubGroups()
223 {
224 global $zdb;
225
226 if (!isset($this->login) || !$this->login->isLogged()) {
227 $this->groups = [];
228 return;
229 }
230
231 try {
232 $select = $zdb->select(self::TABLE, 'a');
233
234 if (!$this->login->isAdmin() && !$this->login->isStaff()) {
235 $select->join(
236 array('b' => PREFIX_DB . self::GROUPSMANAGERS_TABLE),
237 'a.' . self::PK . '=b.' . self::PK,
238 array()
239 )->where(['b.' . Adherent::PK => $this->login->id]);
240 }
241
242 $select->where(['parent_group' => $this->id])
243 ->order('group_name ASC');
244
245 $results = $zdb->execute($select);
246 $groups = array();
247 $grppk = self::PK;
248 foreach ($results as $m) {
249 $group = new Group((int)$m->$grppk);
250 $group->setLogin($this->login);
251 $groups[] = $group;
252 }
253 $this->groups = $groups;
254 } catch (Throwable $e) {
255 Analog::log(
256 'Cannot get subgroup for group ' . $this->group_name .
257 ' (' . $this->id . ')| ' . $e->getMessage(),
258 Analog::WARNING
259 );
260 throw $e;
261 }
262 }
263
264 /**
265 * Remove specified group
266 *
267 * @param boolean $cascade Also remove members and managers
268 *
269 * @return boolean
270 */
271 public function remove($cascade = false)
272 {
273 global $zdb;
274 $transaction = false;
275
276 try {
277 if (!$zdb->connection->inTransaction()) {
278 $zdb->connection->beginTransaction();
279 $transaction = true;
280 }
281
282 if ($cascade === true) {
283 $subgroups = $this->getGroups();
284 if (count($subgroups) > 0) {
285 Analog::log(
286 'Cascading remove ' . $this->group_name .
287 '. Subgroups, their members and managers will be detached.',
288 Analog::INFO
289 );
290 foreach ($subgroups as $subgroup) {
291 $subgroup->remove(true);
292 }
293 }
294
295 Analog::log(
296 'Cascading remove ' . $this->group_name .
297 '. Members and managers will be detached.',
298 Analog::INFO
299 );
300
301 //delete members
302 $delete = $zdb->delete(self::GROUPSUSERS_TABLE);
303 $delete->where([self::PK => $this->id]);
304 $zdb->execute($delete);
305
306 //delete managers
307 $delete = $zdb->delete(self::GROUPSMANAGERS_TABLE);
308 $delete->where([self::PK => $this->id]);
309 $zdb->execute($delete);
310 }
311
312 //delete group itself
313 $delete = $zdb->delete(self::TABLE);
314 $delete->where([self::PK => $this->id]);
315 $zdb->execute($delete);
316
317 //commit all changes
318 if ($transaction) {
319 $zdb->connection->commit();
320 }
321
322 return true;
323 } catch (Throwable $e) {
324 if ($transaction) {
325 $zdb->connection->rollBack();
326 }
327 if (!$zdb->isPostgres() && $e->getCode() == 23000 || $zdb->isPostgres() && $e->getCode() == 23503) {
328 Analog::log(
329 str_replace(
330 '%group',
331 $this->group_name,
332 'Group "%group" still have members!'
333 ),
334 Analog::WARNING
335 );
336 $this->isempty = false;
337 } else {
338 Analog::log(
339 'Unable to delete group ' . $this->group_name .
340 ' (' . $this->id . ') |' . $e->getMessage(),
341 Analog::ERROR
342 );
343 throw $e;
344 }
345 return false;
346 }
347 }
348
349 /**
350 * Is group empty? (after first deletion try)
351 *
352 * @return boolean
353 */
354 public function isEmpty()
355 {
356 return $this->isempty;
357 }
358
359 /**
360 * Detach a group from its parent
361 *
362 * @return boolean
363 */
364 public function detach()
365 {
366 global $zdb, $hist;
367
368 try {
369 $update = $zdb->update(self::TABLE);
370 $update->set(
371 array('parent_group' => new Expression('NULL'))
372 )->where(
373 [self::PK => $this->id]
374 );
375
376 $edit = $zdb->execute($update);
377
378 //edit == 0 does not mean there were an error, but that there
379 //were nothing to change
380 if ($edit->count() > 0) {
381 $this->parent_group = null;
382 $hist->add(
383 _T("Group has been detached from its parent"),
384 $this->group_name
385 );
386 }
387
388 return true;
389 } catch (Throwable $e) {
390 Analog::log(
391 'Something went wrong detaching group `' . $this->group_name .
392 '` (' . $this->id . ') from its parent:\'( | ' .
393 $e->getMessage() . "\n" .
394 $e->getTraceAsString(),
395 Analog::ERROR
396 );
397 throw $e;
398 }
399 }
400
401 /**
402 * Store the group
403 *
404 * @return boolean
405 */
406 public function store()
407 {
408 global $zdb, $hist;
409
410 try {
411 $values = array(
412 self::PK => $this->id,
413 'group_name' => $this->group_name
414 );
415
416 if ($this->parent_group) {
417 $values['parent_group'] = $this->parent_group->getId();
418 }
419
420 if (!isset($this->id) || $this->id == '') {
421 //we're inserting a new group
422 unset($values[self::PK]);
423 $this->creation_date = date("Y-m-d H:i:s");
424 $values['creation_date'] = $this->creation_date;
425
426 $insert = $zdb->insert(self::TABLE);
427 $insert->values($values);
428 $add = $zdb->execute($insert);
429 if ($add->count() > 0) {
430 $this->id = $zdb->getLastGeneratedValue($this);
431
432 // logging
433 $hist->add(
434 _T("Group added"),
435 $this->group_name
436 );
437 return true;
438 } else {
439 $hist->add(_T("Fail to add new group."));
440 throw new \Exception(
441 'An error occurred inserting new group!'
442 );
443 }
444 } else {
445 //we're editing an existing group
446 $update = $zdb->update(self::TABLE);
447 $update
448 ->set($values)
449 ->where([self::PK => $this->id]);
450
451 $edit = $zdb->execute($update);
452
453 //edit == 0 does not mean there were an error, but that there
454 //were nothing to change
455 if ($edit->count() > 0) {
456 $hist->add(
457 _T("Group updated"),
458 $this->group_name
459 );
460 }
461 return true;
462 }
463 /** FIXME: also store members and managers? */
464 } catch (Throwable $e) {
465 Analog::log(
466 'Something went wrong :\'( | ' . $e->getMessage() . "\n" .
467 $e->getTraceAsString(),
468 Analog::ERROR
469 );
470 throw $e;
471 }
472 }
473
474 /**
475 * Is current logged-in user manager of the group?
476 *
477 * @param Login $login Login instance
478 *
479 * @return boolean
480 */
481 public function isManager(Login $login)
482 {
483 if ($login->isAdmin() || $login->isStaff()) {
484 //admins as well as staff members are managers for all groups!
485 return true;
486 } else {
487 //let's check if current logged-in user is part of group managers
488 foreach ($this->managers as $manager) {
489 if ($login->login == $manager->login) {
490 return true;
491 break;
492 }
493 }
494 return false;
495 }
496 }
497
498 /**
499 * Get group id
500 *
501 * @return integer
502 */
503 public function getId()
504 {
505 return $this->id;
506 }
507
508 /**
509 * Get Level of the group
510 *
511 * @return integer
512 */
513 public function getLevel()
514 {
515 if ($this->parent_group) {
516 return $this->parent_group->getLevel() + 1;
517 }
518 return 0;
519 }
520
521 /**
522 * Get the full name of the group "foo / bar"
523 *
524 * @return string
525 */
526 public function getFullName()
527 {
528 if ($this->parent_group) {
529 return $this->parent_group->getFullName() . ' / ' . $this->group_name;
530 }
531 return $this->group_name;
532 }
533
534 /**
535 * Get the indented short name of the group " >> bar"
536 *
537 * @return string
538 */
539 public function getIndentName()
540 {
541 if (($level = $this->getLevel())) {
542 return str_repeat("&nbsp;", 3 * $level) . '&raquo; ' . $this->group_name;
543 }
544 return $this->group_name;
545 }
546
547 /**
548 * Get group name
549 *
550 * @return string
551 */
552 public function getName()
553 {
554 return $this->group_name;
555 }
556
557 /**
558 * Get group members
559 *
560 * @return Adherent[]
561 */
562 public function getMembers()
563 {
564 if (!is_array($this->members)) {
565 $this->loadPersons(self::MEMBER_TYPE);
566 }
567 return $this->members;
568 }
569
570 /**
571 * Get groups managers
572 *
573 * @return Adherent[]
574 */
575 public function getManagers()
576 {
577 if (!is_array($this->managers)) {
578 $this->loadPersons(self::MANAGER_TYPE);
579 }
580 return $this->managers;
581 }
582
583 /**
584 * Get subgroups
585 *
586 * @return Group[]
587 */
588 public function getGroups()
589 {
590 if (!is_array($this->groups)) {
591 $this->loadSubGroups();
592 }
593 return $this->groups;
594 }
595
596 /**
597 * Get parent group
598 *
599 * @return Group
600 */
601 public function getParentGroup()
602 {
603 return $this->parent_group;
604 }
605
606 /**
607 * Get group creation date
608 *
609 * @param boolean $formatted Return date formatted, raw if false
610 *
611 * @return string
612 */
613 public function getCreationDate($formatted = true)
614 {
615 if ($formatted === true) {
616 $date = new \DateTime($this->creation_date);
617 return $date->format(__("Y-m-d"));
618 } else {
619 return $this->creation_date;
620 }
621 }
622
623 /**
624 * Get member count
625 *
626 * @param boolean $force Force members load, defaults to false
627 *
628 * @return int
629 */
630 public function getMemberCount($force = false)
631 {
632 if (isset($this->members) && is_array($this->members)) {
633 return count($this->members);
634 } elseif (isset($this->count_members)) {
635 return $this->count_members;
636 } else {
637 if ($force === true) {
638 return count($this->getMembers());
639 } else {
640 return 0;
641 }
642 }
643 }
644
645 /**
646 * Set name
647 *
648 * @param string $name Group name
649 *
650 * @return Group
651 */
652 public function setName($name)
653 {
654 $this->group_name = $name;
655 return $this;
656 }
657
658 /**
659 * Set all subgroups
660 *
661 * @param array $groups Groups id
662 *
663 * @return Group
664 */
665 public function setSubgroups($groups)
666 {
667 $this->groups = $groups;
668 return $this;
669 }
670
671 /**
672 * check if can Set parent group
673 *
674 * @param Group $group Parent group
675 *
676 * @return boolean
677 */
678 public function canSetParentGroup(Group $group)
679 {
680 do {
681 if ($group->getId() == $this->getId()) {
682 return false;
683 }
684 } while ($group = $group->getParentGroup());
685
686 return true;
687 }
688
689 /**
690 * Set parent group
691 *
692 * @param int $id Parent group identifier
693 *
694 * @return Group
695 */
696 public function setParentGroup($id)
697 {
698 $group = new Group((int)$id);
699
700 if (!$this->canSetParentGroup($group)) {
701 //does not seem to work :/
702 throw new \Exception(
703 sprintf(
704 _T('Group `%1$s` cannot be set as parent!'),
705 $group->getName()
706 )
707 );
708 }
709
710 $this->parent_group = $group;
711 return $this;
712 }
713
714 /**
715 * Set members
716 *
717 * @param Adherent[] $members Members list
718 *
719 * @return void
720 */
721 public function setMembers($members)
722 {
723 global $zdb;
724
725 try {
726 $zdb->connection->beginTransaction();
727
728 //first, remove current groups members
729 $delete = $zdb->delete(self::GROUPSUSERS_TABLE);
730 $delete->where([self::PK => $this->id]);
731 $zdb->execute($delete);
732
733 Analog::log(
734 'Group members has been removed for `' . $this->group_name .
735 '`, we can now store new ones.',
736 Analog::INFO
737 );
738
739 $insert = $zdb->insert(self::GROUPSUSERS_TABLE);
740 $insert->values(
741 array(
742 self::PK => ':group',
743 Adherent::PK => ':adh'
744 )
745 );
746
747 $stmt = $zdb->sql->prepareStatementForSqlObject($insert);
748
749 if (is_array($members)) {
750 foreach ($members as $m) {
751 $result = $stmt->execute(
752 array(
753 'group' => $this->id,
754 'adh' => $m->id
755 )
756 );
757
758 if ($result) {
759 Analog::log(
760 'Member `' . $m->sname . '` attached to group `' .
761 $this->group_name . '`.',
762 Analog::DEBUG
763 );
764 } else {
765 Analog::log(
766 'An error occurred trying to attach member `' .
767 $m->sname . '` to group `' . $this->group_name .
768 '` (' . $this->id . ').',
769 Analog::ERROR
770 );
771 throw new \Exception(
772 'Unable to attach `' . $m->sname . '` ' .
773 'to ' . $this->group_name . '(' . $this->id . ')'
774 );
775 }
776 }
777 }
778 //commit all changes
779 $zdb->connection->commit();
780
781 Analog::log(
782 'Group members updated successfully.',
783 Analog::INFO
784 );
785
786 return true;
787 } catch (Throwable $e) {
788 $zdb->connection->rollBack();
789 $messages = array();
790 do {
791 $messages[] = $e->getMessage();
792 } while ($e = $e->getPrevious());
793 Analog::log(
794 'Unable to attach members to group `' . $this->group_name .
795 '` (' . $this->id . ')|' . implode("\n", $messages),
796 Analog::ERROR
797 );
798 throw $e;
799 }
800 }
801
802 /**
803 * Set managers
804 *
805 * @param Adherent[] $members Managers list
806 *
807 * @return boolean
808 */
809 public function setManagers($members)
810 {
811 global $zdb;
812
813 try {
814 $zdb->connection->beginTransaction();
815
816 //first, remove current groups managers
817 $delete = $zdb->delete(self::GROUPSMANAGERS_TABLE);
818 $delete->where([self::PK => $this->id]);
819 $zdb->execute($delete);
820
821 Analog::log(
822 'Group managers has been removed for `' . $this->group_name .
823 '`, we can now store new ones.',
824 Analog::INFO
825 );
826
827 $insert = $zdb->insert(self::GROUPSMANAGERS_TABLE);
828 $insert->values(
829 array(
830 self::PK => ':group',
831 Adherent::PK => ':adh'
832 )
833 );
834
835 $stmt = $zdb->sql->prepareStatementForSqlObject($insert);
836
837 if (is_array($members)) {
838 foreach ($members as $m) {
839 $result = $stmt->execute(
840 array(
841 'group' => $this->id,
842 'adh' => $m->id
843 )
844 );
845
846 if ($result) {
847 Analog::log(
848 'Manager `' . $m->sname . '` attached to group `' .
849 $this->group_name . '`.',
850 Analog::DEBUG
851 );
852 } else {
853 Analog::log(
854 'An error occurred trying to attach manager `' .
855 $m->sname . '` to group `' . $this->group_name .
856 '` (' . $this->id . ').',
857 Analog::ERROR
858 );
859 throw new \Exception(
860 'Unable to attach `' . $m->sname . '` ' .
861 'to ' . $this->group_name . '(' . $this->id . ')'
862 );
863 }
864 }
865 }
866 //commit all changes
867 $zdb->connection->commit();
868
869 Analog::log(
870 'Groups managers updated successfully.',
871 Analog::INFO
872 );
873
874 return true;
875 } catch (Throwable $e) {
876 $zdb->connection->rollBack();
877 $messages = array();
878 do {
879 $messages[] = $e->getMessage();
880 } while ($e = $e->getPrevious());
881 Analog::log(
882 'Unable to attach managers to group `' . $this->group_name .
883 '` (' . $this->id . ')|' . implode("\n", $messages),
884 Analog::ERROR
885 );
886 throw $e;
887 }
888 }
889
890 /**
891 * Set login instance
892 *
893 * @param Login $login Login instance
894 *
895 * @return Group
896 */
897 public function setLogin(Login $login)
898 {
899 $this->login = $login;
900 return $this;
901 }
902 }