Merge pull request #1373 from ravishnair/CRM-13037
[civicrm-core.git] / CRM / Contact / BAO / GroupNestingCache.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2013
32 * $Id$
33 *
34 */
35 class CRM_Contact_BAO_GroupNestingCache {
36 static public function update() {
37 // lets build the tree in memory first
38
39 $sql = "
40 SELECT n.child_group_id as child ,
41 n.parent_group_id as parent
42 FROM civicrm_group_nesting n,
43 civicrm_group gc,
44 civicrm_group gp
45 WHERE n.child_group_id = gc.id
46 AND n.parent_group_id = gp.id
47 ";
48
49 $dao = CRM_Core_DAO::executeQuery($sql);
50
51 $tree = array();
52 while ($dao->fetch()) {
53 if (!array_key_exists($dao->child, $tree)) {
54 $tree[$dao->child] = array('children' => array(),
55 'parents' => array(),
56 );
57 }
58
59 if (!array_key_exists($dao->parent, $tree)) {
60 $tree[$dao->parent] = array('children' => array(),
61 'parents' => array(),
62 );
63 }
64
65 $tree[$dao->child]['parents'][] = $dao->parent;
66 $tree[$dao->parent]['children'][] = $dao->child;
67 }
68
69 if (self::checkCyclicGraph($tree)) {
70 CRM_Core_Error::fatal(ts('We detected a cycle which we cant handle. aborting'));
71 }
72
73 // first reset the current cache entries
74 $sql = "
75 UPDATE civicrm_group
76 SET parents = null,
77 children = null
78 ";
79 CRM_Core_DAO::executeQuery($sql);
80
81 $values = array();
82 foreach (array_keys($tree) as $id) {
83 $parents = implode(',', $tree[$id]['parents']);
84 $children = implode(',', $tree[$id]['children']);
85 $parents = $parents == NULL ? 'null' : "'$parents'";
86 $children = $children == NULL ? 'null' : "'$children'";
87 $sql = "
88 UPDATE civicrm_group
89 SET parents = $parents ,
90 children = $children
91 WHERE id = $id
92 ";
93 CRM_Core_DAO::executeQuery($sql);
94 }
95
96 // this tree stuff is quite useful, so lets store it in the cache
97 CRM_Core_BAO_Cache::setItem($tree, 'contact groups', 'nestable tree hierarchy');
98 }
99
100 static function checkCyclicGraph(&$tree) {
101 // lets keep this simple, we should probably use a graph algoritm here at some stage
102
103 // foreach group that has a parent or a child, ensure that
104 // the ancestors and descendants dont intersect
105 foreach ($tree as $id => $dontCare) {
106 if (self::isCyclic($tree, $id)) {
107 return TRUE;
108 }
109 }
110
111 return FALSE;
112 }
113
114 static function isCyclic(&$tree, $id) {
115 $parents = $children = array();
116 self::getAll($parent, $tree, $id, 'parents');
117 self::getAll($child, $tree, $id, 'children');
118
119 $one = array_intersect($parents, $children);
120 $two = array_intersect($children, $parents);
121 if (!empty($one) ||
122 !empty($two)
123 ) {
124 CRM_Core_Error::debug($id, $tree);
125 CRM_Core_Error::debug($id, $one);
126 CRM_Core_Error::debug($id, $two);
127 return TRUE;
128 }
129 return FALSE;
130 }
131
132 static function getPotentialCandidates($id, &$groups) {
133 $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy');
134
135 if ($tree === NULL) {
136 self::update();
137 $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy');
138 }
139
140 $potential = $groups;
141
142 // remove all descendants
143 self::invalidate($potential, $tree, $id, 'children');
144
145 // remove all ancestors
146 self::invalidate($potential, $tree, $id, 'parents');
147
148 return array_keys($potential);
149 }
150
151 static function invalidate(&$potential, &$tree, $id, $token) {
152 unset($potential[$id]);
153
154 if (!isset($tree[$id]) ||
155 empty($tree[$id][$token])
156 ) {
157 return;
158 }
159
160 foreach ($tree[$id][$token] as $tokenID) {
161 self::invalidate($potential, $tree, $tokenID, $token);
162 }
163 }
164
165 static function getAll(&$all, &$tree, $id, $token) {
166 // if seen before, dont do anything
167 if (isset($all[$id])) {
168 return;
169 }
170
171 $all[$id] = 1;
172 if (!isset($tree[$id]) ||
173 empty($tree[$id][$token])
174 ) {
175 return;
176 }
177
178 foreach ($tree[$id][$token] as $tokenID) {
179 self::getAll($all, $tree, $tokenID, $token);
180 }
181 }
182
183 static function json() {
184 $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy');
185
186 if ($tree === NULL) {
187 self::update();
188 $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy');
189 }
190
191 // get all the groups
192 $groups = CRM_Core_PseudoConstant::group();
193
194 foreach ($groups as $id => $name) {
195 $string = "id:'$id', name:'$name'";
196 if (isset($tree[$id])) {
197 $children = array();
198 if (!empty($tree[$id]['children'])) {
199 foreach ($tree[$id]['children'] as $child) {
200 $children[] = "{_reference:'$child'}";
201 }
202 $children = implode(',', $children);
203 $string .= ", children:[$children]";
204 if (empty($tree[$id]['parents'])) {
205 $string .= ", type:'rootGroup'";
206 }
207 else {
208 $string .= ", type:'middleGroup'";
209 }
210 }
211 else {
212 $string .= ", type:'leafGroup'";
213 }
214 }
215 else {
216 $string .= ", children:[], type:'rootGroup'";
217 }
218 $values[] = "{ $string }";
219 }
220
221 $items = implode(",\n", $values);
222 $json = "{
223 identifier:'id',
224 label:'name',
225 items:[ $items ]
226 }";
227 return $json;
228 }
229 }
230