Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
39de6fd5 | 4 | | CiviCRM version 4.6 | |
6a488035 | 5 | +--------------------------------------------------------------------+ |
e7112fa7 | 6 | | Copyright CiviCRM LLC (c) 2004-2015 | |
6a488035 TO |
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 | +--------------------------------------------------------------------+ | |
d25dd0ee | 26 | */ |
6a488035 TO |
27 | |
28 | /** | |
29 | * | |
30 | * @package CRM | |
e7112fa7 | 31 | * @copyright CiviCRM LLC (c) 2004-2015 |
6a488035 TO |
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)) { | |
6ea503d4 TO |
54 | $tree[$dao->child] = array( |
55 | 'children' => array(), | |
6a488035 TO |
56 | 'parents' => array(), |
57 | ); | |
58 | } | |
59 | ||
60 | if (!array_key_exists($dao->parent, $tree)) { | |
6ea503d4 TO |
61 | $tree[$dao->parent] = array( |
62 | 'children' => array(), | |
6a488035 TO |
63 | 'parents' => array(), |
64 | ); | |
65 | } | |
66 | ||
67 | $tree[$dao->child]['parents'][] = $dao->parent; | |
68 | $tree[$dao->parent]['children'][] = $dao->child; | |
69 | } | |
70 | ||
71 | if (self::checkCyclicGraph($tree)) { | |
5d6aaf6b | 72 | CRM_Core_Error::fatal(ts("We detected a cycle which we can't handle. aborting")); |
6a488035 TO |
73 | } |
74 | ||
75 | // first reset the current cache entries | |
76 | $sql = " | |
77 | UPDATE civicrm_group | |
78 | SET parents = null, | |
79 | children = null | |
80 | "; | |
81 | CRM_Core_DAO::executeQuery($sql); | |
82 | ||
83 | $values = array(); | |
84 | foreach (array_keys($tree) as $id) { | |
353ffa53 | 85 | $parents = implode(',', $tree[$id]['parents']); |
6a488035 | 86 | $children = implode(',', $tree[$id]['children']); |
353ffa53 | 87 | $parents = $parents == NULL ? 'null' : "'$parents'"; |
6a488035 | 88 | $children = $children == NULL ? 'null' : "'$children'"; |
353ffa53 | 89 | $sql = " |
6a488035 TO |
90 | UPDATE civicrm_group |
91 | SET parents = $parents , | |
92 | children = $children | |
93 | WHERE id = $id | |
94 | "; | |
95 | CRM_Core_DAO::executeQuery($sql); | |
96 | } | |
97 | ||
98 | // this tree stuff is quite useful, so lets store it in the cache | |
99 | CRM_Core_BAO_Cache::setItem($tree, 'contact groups', 'nestable tree hierarchy'); | |
100 | } | |
101 | ||
86538308 EM |
102 | /** |
103 | * @param $tree | |
104 | * | |
105 | * @return bool | |
106 | */ | |
00be9182 | 107 | public static function checkCyclicGraph(&$tree) { |
b44e3f84 | 108 | // lets keep this simple, we should probably use a graph algorithm here at some stage |
6a488035 TO |
109 | |
110 | // foreach group that has a parent or a child, ensure that | |
111 | // the ancestors and descendants dont intersect | |
112 | foreach ($tree as $id => $dontCare) { | |
113 | if (self::isCyclic($tree, $id)) { | |
114 | return TRUE; | |
115 | } | |
116 | } | |
117 | ||
118 | return FALSE; | |
119 | } | |
120 | ||
86538308 EM |
121 | /** |
122 | * @param $tree | |
100fef9d | 123 | * @param int $id |
86538308 EM |
124 | * |
125 | * @return bool | |
126 | */ | |
00be9182 | 127 | public static function isCyclic(&$tree, $id) { |
6a488035 TO |
128 | $parents = $children = array(); |
129 | self::getAll($parent, $tree, $id, 'parents'); | |
130 | self::getAll($child, $tree, $id, 'children'); | |
131 | ||
132 | $one = array_intersect($parents, $children); | |
133 | $two = array_intersect($children, $parents); | |
134 | if (!empty($one) || | |
135 | !empty($two) | |
136 | ) { | |
137 | CRM_Core_Error::debug($id, $tree); | |
138 | CRM_Core_Error::debug($id, $one); | |
139 | CRM_Core_Error::debug($id, $two); | |
140 | return TRUE; | |
141 | } | |
142 | return FALSE; | |
143 | } | |
144 | ||
86538308 | 145 | /** |
100fef9d | 146 | * @param int $id |
86538308 EM |
147 | * @param $groups |
148 | * | |
149 | * @return array | |
150 | */ | |
00be9182 | 151 | public static function getPotentialCandidates($id, &$groups) { |
6a488035 TO |
152 | $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); |
153 | ||
154 | if ($tree === NULL) { | |
155 | self::update(); | |
156 | $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); | |
157 | } | |
158 | ||
159 | $potential = $groups; | |
160 | ||
161 | // remove all descendants | |
162 | self::invalidate($potential, $tree, $id, 'children'); | |
163 | ||
164 | // remove all ancestors | |
165 | self::invalidate($potential, $tree, $id, 'parents'); | |
166 | ||
167 | return array_keys($potential); | |
168 | } | |
169 | ||
86538308 EM |
170 | /** |
171 | * @param $potential | |
172 | * @param $tree | |
100fef9d | 173 | * @param int $id |
86538308 EM |
174 | * @param $token |
175 | */ | |
00be9182 | 176 | public static function invalidate(&$potential, &$tree, $id, $token) { |
6a488035 TO |
177 | unset($potential[$id]); |
178 | ||
179 | if (!isset($tree[$id]) || | |
180 | empty($tree[$id][$token]) | |
181 | ) { | |
182 | return; | |
183 | } | |
184 | ||
185 | foreach ($tree[$id][$token] as $tokenID) { | |
186 | self::invalidate($potential, $tree, $tokenID, $token); | |
187 | } | |
188 | } | |
189 | ||
86538308 EM |
190 | /** |
191 | * @param $all | |
192 | * @param $tree | |
100fef9d | 193 | * @param int $id |
86538308 EM |
194 | * @param $token |
195 | */ | |
00be9182 | 196 | public static function getAll(&$all, &$tree, $id, $token) { |
6a488035 TO |
197 | // if seen before, dont do anything |
198 | if (isset($all[$id])) { | |
199 | return; | |
200 | } | |
201 | ||
202 | $all[$id] = 1; | |
203 | if (!isset($tree[$id]) || | |
204 | empty($tree[$id][$token]) | |
205 | ) { | |
206 | return; | |
207 | } | |
208 | ||
209 | foreach ($tree[$id][$token] as $tokenID) { | |
210 | self::getAll($all, $tree, $tokenID, $token); | |
211 | } | |
212 | } | |
213 | ||
86538308 EM |
214 | /** |
215 | * @return string | |
216 | */ | |
00be9182 | 217 | public static function json() { |
6a488035 TO |
218 | $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); |
219 | ||
220 | if ($tree === NULL) { | |
221 | self::update(); | |
222 | $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); | |
223 | } | |
224 | ||
225 | // get all the groups | |
226 | $groups = CRM_Core_PseudoConstant::group(); | |
227 | ||
228 | foreach ($groups as $id => $name) { | |
229 | $string = "id:'$id', name:'$name'"; | |
230 | if (isset($tree[$id])) { | |
231 | $children = array(); | |
232 | if (!empty($tree[$id]['children'])) { | |
233 | foreach ($tree[$id]['children'] as $child) { | |
234 | $children[] = "{_reference:'$child'}"; | |
235 | } | |
236 | $children = implode(',', $children); | |
237 | $string .= ", children:[$children]"; | |
238 | if (empty($tree[$id]['parents'])) { | |
239 | $string .= ", type:'rootGroup'"; | |
240 | } | |
241 | else { | |
242 | $string .= ", type:'middleGroup'"; | |
243 | } | |
244 | } | |
245 | else { | |
246 | $string .= ", type:'leafGroup'"; | |
247 | } | |
248 | } | |
249 | else { | |
250 | $string .= ", children:[], type:'rootGroup'"; | |
251 | } | |
252 | $values[] = "{ $string }"; | |
253 | } | |
254 | ||
255 | $items = implode(",\n", $values); | |
256 | $json = "{ | |
257 | identifier:'id', | |
258 | label:'name', | |
259 | items:[ $items ] | |
260 | }"; | |
261 | return $json; | |
262 | } | |
96025800 | 263 | |
6a488035 | 264 | } |