3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright U.S. PIRG Education Fund (c) 2007 |
7 | Licensed to CiviCRM under the Academic Free License version 3.0. |
8 +--------------------------------------------------------------------+
9 | This file is a part of CiviCRM. |
11 | CiviCRM is free software; you can copy, modify, and distribute it |
12 | under the terms of the GNU Affero General Public License |
13 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
15 | CiviCRM is distributed in the hope that it will be useful, but |
16 | WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
18 | See the GNU Affero General Public License for more details. |
20 | You should have received a copy of the GNU Affero General Public |
21 | License and the CiviCRM Licensing Exception along |
22 | with this program; if not, contact CiviCRM LLC |
23 | at info[AT]civicrm[DOT]org. If you have questions about the |
24 | GNU Affero General Public License or the licensing of CiviCRM, |
25 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
26 +--------------------------------------------------------------------+
32 * @copyright U.S. PIRG 2007
36 class CRM_Contact_BAO_GroupNesting
extends CRM_Contact_DAO_GroupNesting
implements Iterator
{
38 static $_sortOrder = 'ASC';
42 private $_parentStack = array();
44 private $_lastParentlessGroup;
46 private $_styleLabels;
48 private $_styleIndent;
50 private $_alreadyStyled = FALSE;
55 function __construct($styleLabels = FALSE, $styleIndent = " -- ") {
56 parent
::__construct();
57 $this->_styleLabels
= $styleLabels;
58 $this->_styleIndent
= $styleIndent;
61 function setSortOrder($sortOrder) {
65 if ($sortOrder != self
::$_sortOrder) {
66 self
::$_sortOrder = $sortOrder;
72 // spit out some error, someday
76 function getSortOrder() {
77 return self
::$_sortOrder;
80 function getCurrentNestingLevel() {
81 return count($this->_parentStack
);
85 * Go back to the first element in the group nesting graph,
86 * which is the first group (according to _sortOrder) that
87 * has no parent groups
90 $this->_parentStack
= array();
91 // calling _getNextParentlessGroup w/ no arguments
92 // makes it return the first parentless group
93 $firstGroup = $this->_getNextParentlessGroup();
94 $this->_current
= $firstGroup;
95 $this->_lastParentlessGroup
= $firstGroup;
96 $this->_alreadyStyled
= FALSE;
100 if ($this->_styleLabels
&&
102 !$this->_alreadyStyled
104 $styledGroup = clone($this->_current
);
105 $nestingLevel = $this->getCurrentNestingLevel();
107 while ($nestingLevel--) {
108 $indent .= $this->_styleIndent
;
110 $styledGroup->title
= $indent . $styledGroup->title
;
112 $this->_current
= &$styledGroup;
113 $this->_alreadyStyled
= TRUE;
115 return $this->_current
;
119 $group = &$this->_current
;
121 foreach ($this->_parentStack
as $parentGroup) {
122 $ids[] = $parentGroup->id
;
124 $key = implode('-', $ids);
125 if (strlen($key) > 0) {
133 $currentGroup = &$this->_current
;
134 $childGroup = $this->_getNextChildGroup($currentGroup);
136 $nextGroup = &$childGroup;
137 $this->_parentStack
[] = &$this->_current
;
140 $nextGroup = $this->_getNextSiblingGroup($currentGroup);
142 // no sibling, find an ancestor w/ a sibling
144 // since we pop this array everytime, we should be
145 // reasonably safe from infinite loops, I think :)
146 $ancestor = array_pop($this->_parentStack
);
147 $this->_current
= &$ancestor;
148 if ($ancestor == NULL) {
151 $nextGroup = $this->_getNextSiblingGroup($ancestor);
158 $this->_current
= &$nextGroup;
159 $this->_alreadyStyled
= FALSE;
164 if ($this->_current
) {
172 function _getNextParentlessGroup(&$group = NULL) {
173 $lastParentlessGroup = $this->_lastParentlessGroup
;
174 $nextGroup = new CRM_Contact_BAO_Group();
175 $nextGroup->order_by
= 'title ' . self
::$_sortOrder;
177 if ($group == NULL) {
183 while ($nextGroup->fetch()) {
184 if (!self
::hasParentGroups($nextGroup->id
) && $sawLast) {
187 elseif ($lastParentlessGroup->id
== $nextGroup->id
) {
194 function _getNextChildGroup(&$parentGroup, &$group = NULL) {
195 $children = self
::getChildGroupIds($parentGroup->id
);
196 if (count($children) > 0) {
197 // we have child groups, so get the first one based on _sortOrder
198 $childGroup = new CRM_Contact_BAO_Group();
199 $cgQuery = "SELECT * FROM civicrm_group WHERE id IN (" . implode(',', $children) . ") ORDER BY title " . self
::$_sortOrder;
200 $childGroup->query($cgQuery);
201 $currentGroup = &$this->_current
;
202 if ($group == NULL) {
208 while ($childGroup->fetch()) {
212 elseif ($currentGroup->id
=== $childGroup->id
) {
220 function _getNextSiblingGroup(&$group) {
221 $parentGroup = end($this->_parentStack
);
223 $nextGroup = $this->_getNextChildGroup($parentGroup, $group);
227 /* if we get here, it could be because we're out of siblings
228 * (in which case we return null) or because we're at the
229 * top level groups which do not have parents but may still
230 * have siblings, so check for that first.
233 $nextGroup = $this->_getNextParentlessGroup($group);
235 $this->_lastParentlessGroup
= $nextGroup;
243 * Adds a new child group identified by $childGroupId to the group
244 * identified by $groupId
249 * @internal param \The $groupId id of the group to add the child to
250 * @internal param \The $childGroupId id of the new child group
256 static function add($parentID, $childID) {
257 // TODO: Add checks here to make sure invalid nests can't be created
258 $dao = new CRM_Contact_DAO_GroupNesting();
259 $query = "REPLACE INTO civicrm_group_nesting (child_group_id, parent_group_id) VALUES ($childID,$parentID);";
264 * Removes a child group identified by $childGroupId from the group
265 * identified by $groupId; does not delete child group, just the
266 * association between the two
268 * @param $parentID The id of the group to remove the child from
269 * @param $childID The id of the child group being removed
275 static function remove($parentID, $childID) {
276 $dao = new CRM_Contact_DAO_GroupNesting();
277 $query = "DELETE FROM civicrm_group_nesting WHERE child_group_id = $childID AND parent_group_id = $parentID";
282 * Removes associations where a child group is identified by $childGroupId from the group
283 * identified by $groupId; does not delete child group, just the
284 * association between the two
286 * @param $childID The id of the child group being removed
288 * @internal param \The $parentID id of the group to remove the child from
293 static function removeAllParentForChild($childID) {
294 $dao = new CRM_Contact_DAO_GroupNesting();
295 $query = "DELETE FROM civicrm_group_nesting WHERE child_group_id = $childID";
300 * Returns true if the association between parent and child is present,
303 * @param $parentID The parent id of the association
304 * @param $childID The child id of the association
306 * @return boolean True if association is found, false otherwise.
310 static function isParentChild($parentID, $childID) {
311 $dao = new CRM_Contact_DAO_GroupNesting();
312 $query = "SELECT id FROM civicrm_group_nesting WHERE child_group_id = $childID AND parent_group_id = $parentID";
321 * Returns true if if the given groupId has 1 or more child groups,
324 * @param $groupId The id of the group to check for child groups
326 * @return boolean True if 1 or more child groups are found, false otherwise.
330 static function hasChildGroups($groupId) {
331 $dao = new CRM_Contact_DAO_GroupNesting();
332 $query = "SELECT child_group_id FROM civicrm_group_nesting WHERE parent_group_id = $groupId LIMIT 1";
333 //print $query . "\n<br><br>";
342 * Returns true if the given groupId has 1 or more parent groups,
345 * @param $groupId The id of the group to check for parent groups
347 * @return boolean True if 1 or more parent groups are found, false otherwise.
351 static function hasParentGroups($groupId) {
352 $dao = new CRM_Contact_DAO_GroupNesting();
353 $query = "SELECT parent_group_id FROM civicrm_group_nesting WHERE child_group_id = $groupId LIMIT 1";
362 * Returns true if checkGroupId is a parent of one of the groups in
363 * groupIds, false otherwise.
365 * @param $groupIds Array of group ids (or one group id) to serve as the starting point
366 * @param $checkGroupId The group id to check if it is a parent of the $groupIds group(s)
368 * @return boolean True if $checkGroupId points to a group that is a parent of one of the $groupIds groups, false otherwise.
372 static function isParentGroup($groupIds, $checkGroupId) {
373 if (!is_array($groupIds)) {
374 $groupIds = array($groupIds);
376 $dao = new CRM_Contact_DAO_GroupNesting();
377 $query = "SELECT parent_group_id FROM civicrm_group_nesting WHERE child_group_id IN (" . implode(',', $groupIds) . ")";
379 while ($dao->fetch()) {
380 $parentGroupId = $dao->parent_group_id
;
381 if ($parentGroupId == $checkGroupId) {
382 /* print "One of these: <pre>";
384 print "</pre> has groupId $checkGroupId as an ancestor.<br/>"; */
393 * Returns true if checkGroupId is a child of one of the groups in
394 * groupIds, false otherwise.
396 * @param $groupIds Array of group ids (or one group id) to serve as the starting point
397 * @param $checkGroupId The group id to check if it is a child of the $groupIds group(s)
399 * @return boolean True if $checkGroupId points to a group that is a child of one of the $groupIds groups, false otherwise.
403 static function isChildGroup($groupIds, $checkGroupId) {
405 if (!is_array($groupIds)) {
406 $groupIds = array($groupIds);
408 $dao = new CRM_Contact_DAO_GroupNesting();
409 $query = "SELECT child_group_id FROM civicrm_group_nesting WHERE parent_group_id IN (" . implode(',', $groupIds) . ")";
412 while ($dao->fetch()) {
413 $childGroupId = $dao->child_group_id
;
414 if ($childGroupId == $checkGroupId) {
415 /* print "One of these: <pre>";
417 print "</pre> has groupId $checkGroupId as a descendent.<br/><br/>"; */
426 * Returns true if checkGroupId is an ancestor of one of the groups in
427 * groupIds, false otherwise.
429 * @param $groupIds Array of group ids (or one group id) to serve as the starting point
430 * @param $checkGroupId The group id to check if it is an ancestor of the $groupIds group(s)
432 * @return boolean True if $checkGroupId points to a group that is an ancestor of one of the $groupIds groups, false otherwise.
436 static function isAncestorGroup($groupIds, $checkGroupId) {
437 if (!is_array($groupIds)) {
438 $groupIds = array($groupIds);
440 $dao = new CRM_Contact_DAO_GroupNesting();
441 $query = "SELECT parent_group_id FROM civicrm_group_nesting WHERE child_group_id IN (" . implode(',', $groupIds) . ")";
443 $nextGroupIds = array();
444 $gotAtLeastOneResult = FALSE;
445 while ($dao->fetch()) {
446 $gotAtLeastOneResult = TRUE;
447 $parentGroupId = $dao->parent_group_id
;
448 if ($parentGroupId == $checkGroupId) {
449 /* print "One of these: <pre>";
451 print "</pre> has groupId $checkGroupId as an ancestor.<br/>"; */
455 $nextGroupIds[] = $parentGroupId;
457 if ($gotAtLeastOneResult) {
458 return self
::isAncestorGroup($nextGroupIds, $checkGroupId);
466 * Returns true if checkGroupId is a descendent of one of the groups in
467 * groupIds, false otherwise.
469 * @param $groupIds Array of group ids (or one group id) to serve as the starting point
470 * @param $checkGroupId The group id to check if it is a descendent of the $groupIds group(s)
472 * @return boolean True if $checkGroupId points to a group that is a descendent of one of the $groupIds groups, false otherwise.
476 static function isDescendentGroup($groupIds, $checkGroupId) {
477 if (!is_array($groupIds)) {
478 $groupIds = array($groupIds);
480 $dao = new CRM_Contact_DAO_GroupNesting();
481 $query = "SELECT child_group_id FROM civicrm_group_nesting WHERE parent_group_id IN (" . implode(',', $groupIds) . ")";
483 $nextGroupIds = array();
484 $gotAtLeastOneResult = FALSE;
485 while ($dao->fetch()) {
486 $gotAtLeastOneResult = TRUE;
487 $childGroupId = $dao->child_group_id
;
488 if ($childGroupId == $checkGroupId) {
489 /* print "One of these: <pre>";
491 print "</pre> has groupId $checkGroupId as a descendent.<br/><br/>"; */
495 $nextGroupIds[] = $childGroupId;
497 if ($gotAtLeastOneResult) {
498 return self
::isDescendentGroup($nextGroupIds, $checkGroupId);
506 * Returns array of group ids of ancestor groups of the specified group.
508 * @param $groupIds An array of valid group ids (passed by reference)
510 * @param bool $includeSelf
512 * @return array $groupIdArray List of groupIds that represent the requested group and its ancestors@access public
514 static function getAncestorGroupIds($groupIds, $includeSelf = TRUE) {
515 if (!is_array($groupIds)) {
516 $groupIds = array($groupIds);
518 $dao = new CRM_Contact_DAO_GroupNesting();
519 $query = "SELECT parent_group_id, child_group_id
520 FROM civicrm_group_nesting
521 WHERE child_group_id IN (" . implode(',', $groupIds) . ")";
523 $tmpGroupIds = array();
524 $parentGroupIds = array();
526 $parentGroupIds = $groupIds;
528 while ($dao->fetch()) {
529 // make sure we're not following any cyclical references
530 if (!array_key_exists($dao->child_group_id
, $parentGroupIds) && $dao->parent_group_id
!= $groupIds[0]) {
531 $tmpGroupIds[] = $dao->parent_group_id
;
534 if (!empty($tmpGroupIds)) {
535 $newParentGroupIds = self
::getAncestorGroupIds($tmpGroupIds);
536 $parentGroupIds = array_merge($parentGroupIds, $newParentGroupIds);
538 return $parentGroupIds;
542 * Returns array of ancestor groups of the specified group.
544 * @param $groupIds An array of valid group ids (passed by reference)
546 * @param bool $includeSelf
547 * @return \An $groupArray List of ancestor groups@access public
549 static function getAncestorGroups($groupIds, $includeSelf = TRUE) {
550 $groupIds = self
::getAncestorGroupIds($groupIds, $includeSelf);
551 $params['id'] = $groupIds;
552 return CRM_Contact_BAO_Group
::getGroups($params);
556 * Returns array of group ids of child groups of the specified group.
558 * @param $groupIds An array of valid group ids (passed by reference)
560 * @return array $groupIdArray List of groupIds that represent the requested group and its children@access public
562 static function getChildGroupIds($groupIds) {
563 if (!is_array($groupIds)) {
564 $groupIds = array($groupIds);
566 $dao = new CRM_Contact_DAO_GroupNesting();
567 $query = "SELECT child_group_id FROM civicrm_group_nesting WHERE parent_group_id IN (" . implode(',', $groupIds) . ")";
569 $childGroupIds = array();
570 while ($dao->fetch()) {
571 $childGroupIds[] = $dao->child_group_id
;
573 return $childGroupIds;
577 * Returns array of group ids of parent groups of the specified group.
579 * @param $groupIds An array of valid group ids (passed by reference)
581 * @return array $groupIdArray List of groupIds that represent the requested group and its parents@access public
583 static function getParentGroupIds($groupIds) {
584 if (!is_array($groupIds)) {
585 $groupIds = array($groupIds);
587 $dao = new CRM_Contact_DAO_GroupNesting();
588 $query = "SELECT parent_group_id FROM civicrm_group_nesting WHERE child_group_id IN (" . implode(',', $groupIds) . ")";
590 $parentGroupIds = array();
591 while ($dao->fetch()) {
592 $parentGroupIds[] = $dao->parent_group_id
;
594 return $parentGroupIds;
598 * Returns array of group ids of descendent groups of the specified group.
600 * @param $groupIds An array of valid group ids (passed by reference)
602 * @param bool $includeSelf
603 * @return array $groupIdArray List of groupIds that represent the requested group and its descendents@access public
605 static function getDescendentGroupIds($groupIds, $includeSelf = TRUE) {
606 if (!is_array($groupIds)) {
607 $groupIds = array($groupIds);
609 $dao = new CRM_Contact_DAO_GroupNesting();
610 $query = "SELECT child_group_id, parent_group_id FROM civicrm_group_nesting WHERE parent_group_id IN (" . implode(',', $groupIds) . ")";
612 $tmpGroupIds = array();
613 $childGroupIds = array();
615 $childGroupIds = $groupIds;
617 while ($dao->fetch()) {
618 // make sure we're not following any cyclical references
619 if (!array_key_exists($dao->parent_group_id
, $childGroupIds) && $dao->child_group_id
!= $groupIds[0]) {
620 $tmpGroupIds[] = $dao->child_group_id
;
623 if (!empty($tmpGroupIds)) {
624 $newChildGroupIds = self
::getDescendentGroupIds($tmpGroupIds);
625 $childGroupIds = array_merge($childGroupIds, $newChildGroupIds);
627 return $childGroupIds;
631 * Returns array of descendent groups of the specified group.
633 * @param $groupIds An array of valid group ids (passed by reference)
635 * @param bool $includeSelf
636 * @return \An $groupArray List of descendent groups@access public
638 static function getDescendentGroups($groupIds, $includeSelf = TRUE) {
639 $groupIds = self
::getDescendentGroupIds($groupIds, $includeSelf);
640 $params['id'] = $groupIds;
641 return CRM_Contact_BAO_Group
::getGroups($params);
645 * Returns array of group ids of valid potential child groups of the specified group.
647 * @param $groupId The group id to get valid potential children for
649 * @return array $groupIdArray List of groupIds that represent the valid potential children of the group@access public
651 static function getPotentialChildGroupIds($groupId) {
652 $groups = CRM_Contact_BAO_Group
::getGroups();
653 $potentialChildGroupIds = array();
654 foreach ($groups as $group) {
655 $potentialChildGroupId = $group->id
;
656 // print "Checking if $potentialChildGroupId is a descendent/ancestor of $groupId<br/><br/>";
657 if (!self
::isDescendentGroup($groupId, $potentialChildGroupId) &&
658 !self
::isAncestorGroup($groupId, $potentialChildGroupId) &&
659 $potentialChildGroupId != $groupId
661 $potentialChildGroupIds[] = $potentialChildGroupId;
664 return $potentialChildGroupIds;
667 static function getContainingGroups($contactId, $parentGroupId) {
668 $groups = CRM_Contact_BAO_Group
::getGroups();
669 $containingGroups = array();
670 foreach ($groups as $group) {
671 if (self
::isDescendentGroup($parentGroupId, $group->id
)) {
672 $members = CRM_Contact_BAO_Group
::getMember($group->id
);
673 if ($members[$contactId]) {
674 $containingGroups[] = $group->title
;
679 return $containingGroups;