Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
13 | * | |
14 | * @package CRM | |
ca5cec67 | 15 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
16 | */ |
17 | class CRM_Contact_BAO_GroupNestingCache { | |
67d19299 | 18 | |
19 | /** | |
20 | * Update cache. | |
21 | * | |
a02672cc | 22 | * @throws \CRM_Core_Exception |
67d19299 | 23 | */ |
69078420 | 24 | public static function update() { |
6a488035 TO |
25 | // lets build the tree in memory first |
26 | ||
a02672cc | 27 | $sql = ' |
6a488035 TO |
28 | SELECT n.child_group_id as child , |
29 | n.parent_group_id as parent | |
30 | FROM civicrm_group_nesting n, | |
31 | civicrm_group gc, | |
32 | civicrm_group gp | |
33 | WHERE n.child_group_id = gc.id | |
34 | AND n.parent_group_id = gp.id | |
a02672cc | 35 | '; |
6a488035 TO |
36 | |
37 | $dao = CRM_Core_DAO::executeQuery($sql); | |
38 | ||
be2fb01f | 39 | $tree = []; |
6a488035 TO |
40 | while ($dao->fetch()) { |
41 | if (!array_key_exists($dao->child, $tree)) { | |
be2fb01f CW |
42 | $tree[$dao->child] = [ |
43 | 'children' => [], | |
44 | 'parents' => [], | |
45 | ]; | |
6a488035 TO |
46 | } |
47 | ||
48 | if (!array_key_exists($dao->parent, $tree)) { | |
be2fb01f CW |
49 | $tree[$dao->parent] = [ |
50 | 'children' => [], | |
51 | 'parents' => [], | |
52 | ]; | |
6a488035 TO |
53 | } |
54 | ||
55 | $tree[$dao->child]['parents'][] = $dao->parent; | |
56 | $tree[$dao->parent]['children'][] = $dao->child; | |
57 | } | |
58 | ||
59 | if (self::checkCyclicGraph($tree)) { | |
a02672cc | 60 | throw new CRM_Core_Exception(ts('We detected a cycle which we can\'t handle. aborting')); |
6a488035 TO |
61 | } |
62 | ||
63 | // first reset the current cache entries | |
a02672cc | 64 | $sql = ' |
6a488035 TO |
65 | UPDATE civicrm_group |
66 | SET parents = null, | |
67 | children = null | |
a02672cc | 68 | '; |
6a488035 TO |
69 | CRM_Core_DAO::executeQuery($sql); |
70 | ||
be2fb01f | 71 | $values = []; |
6a488035 | 72 | foreach (array_keys($tree) as $id) { |
353ffa53 | 73 | $parents = implode(',', $tree[$id]['parents']); |
6a488035 | 74 | $children = implode(',', $tree[$id]['children']); |
353ffa53 | 75 | $parents = $parents == NULL ? 'null' : "'$parents'"; |
6a488035 | 76 | $children = $children == NULL ? 'null' : "'$children'"; |
353ffa53 | 77 | $sql = " |
6a488035 TO |
78 | UPDATE civicrm_group |
79 | SET parents = $parents , | |
80 | children = $children | |
81 | WHERE id = $id | |
82 | "; | |
83 | CRM_Core_DAO::executeQuery($sql); | |
84 | } | |
85 | ||
86 | // this tree stuff is quite useful, so lets store it in the cache | |
0136044e | 87 | Civi::cache('groups')->set('nestable tree hierarchy', $tree); |
6a488035 TO |
88 | } |
89 | ||
86538308 EM |
90 | /** |
91 | * @param $tree | |
92 | * | |
93 | * @return bool | |
94 | */ | |
00be9182 | 95 | public static function checkCyclicGraph(&$tree) { |
b44e3f84 | 96 | // lets keep this simple, we should probably use a graph algorithm here at some stage |
6a488035 TO |
97 | |
98 | // foreach group that has a parent or a child, ensure that | |
99 | // the ancestors and descendants dont intersect | |
100 | foreach ($tree as $id => $dontCare) { | |
101 | if (self::isCyclic($tree, $id)) { | |
102 | return TRUE; | |
103 | } | |
104 | } | |
105 | ||
106 | return FALSE; | |
107 | } | |
108 | ||
86538308 EM |
109 | /** |
110 | * @param $tree | |
100fef9d | 111 | * @param int $id |
86538308 EM |
112 | * |
113 | * @return bool | |
114 | */ | |
00be9182 | 115 | public static function isCyclic(&$tree, $id) { |
be2fb01f | 116 | $parents = $children = []; |
6a488035 TO |
117 | self::getAll($parent, $tree, $id, 'parents'); |
118 | self::getAll($child, $tree, $id, 'children'); | |
119 | ||
120 | $one = array_intersect($parents, $children); | |
121 | $two = array_intersect($children, $parents); | |
122 | if (!empty($one) || | |
123 | !empty($two) | |
124 | ) { | |
125 | CRM_Core_Error::debug($id, $tree); | |
126 | CRM_Core_Error::debug($id, $one); | |
127 | CRM_Core_Error::debug($id, $two); | |
128 | return TRUE; | |
129 | } | |
130 | return FALSE; | |
131 | } | |
132 | ||
86538308 | 133 | /** |
100fef9d | 134 | * @param int $id |
a02672cc | 135 | * @param array $groups |
86538308 EM |
136 | * |
137 | * @return array | |
a02672cc | 138 | * @throws \CRM_Core_Exception |
86538308 | 139 | */ |
00be9182 | 140 | public static function getPotentialCandidates($id, &$groups) { |
0136044e | 141 | $tree = Civi::cache('groups')->get('nestable tree hierarchy'); |
6a488035 TO |
142 | |
143 | if ($tree === NULL) { | |
144 | self::update(); | |
0136044e | 145 | $tree = Civi::cache('groups')->get('nestable tree hierarchy'); |
6a488035 TO |
146 | } |
147 | ||
148 | $potential = $groups; | |
149 | ||
150 | // remove all descendants | |
151 | self::invalidate($potential, $tree, $id, 'children'); | |
152 | ||
153 | // remove all ancestors | |
154 | self::invalidate($potential, $tree, $id, 'parents'); | |
155 | ||
156 | return array_keys($potential); | |
157 | } | |
158 | ||
86538308 EM |
159 | /** |
160 | * @param $potential | |
161 | * @param $tree | |
100fef9d | 162 | * @param int $id |
86538308 EM |
163 | * @param $token |
164 | */ | |
00be9182 | 165 | public static function invalidate(&$potential, &$tree, $id, $token) { |
6a488035 TO |
166 | unset($potential[$id]); |
167 | ||
168 | if (!isset($tree[$id]) || | |
169 | empty($tree[$id][$token]) | |
170 | ) { | |
171 | return; | |
172 | } | |
173 | ||
174 | foreach ($tree[$id][$token] as $tokenID) { | |
175 | self::invalidate($potential, $tree, $tokenID, $token); | |
176 | } | |
177 | } | |
178 | ||
86538308 EM |
179 | /** |
180 | * @param $all | |
181 | * @param $tree | |
100fef9d | 182 | * @param int $id |
86538308 EM |
183 | * @param $token |
184 | */ | |
00be9182 | 185 | public static function getAll(&$all, &$tree, $id, $token) { |
6a488035 TO |
186 | // if seen before, dont do anything |
187 | if (isset($all[$id])) { | |
188 | return; | |
189 | } | |
190 | ||
191 | $all[$id] = 1; | |
192 | if (!isset($tree[$id]) || | |
193 | empty($tree[$id][$token]) | |
194 | ) { | |
195 | return; | |
196 | } | |
197 | ||
198 | foreach ($tree[$id][$token] as $tokenID) { | |
199 | self::getAll($all, $tree, $tokenID, $token); | |
200 | } | |
201 | } | |
202 | ||
86538308 EM |
203 | /** |
204 | * @return string | |
a02672cc | 205 | * |
206 | * @throws \CRM_Core_Exception | |
86538308 | 207 | */ |
00be9182 | 208 | public static function json() { |
0136044e | 209 | $tree = Civi::cache('groups')->get('nestable tree hierarchy'); |
6a488035 TO |
210 | |
211 | if ($tree === NULL) { | |
212 | self::update(); | |
0136044e | 213 | $tree = Civi::cache('groups')->get('nestable tree hierarchy'); |
6a488035 TO |
214 | } |
215 | ||
216 | // get all the groups | |
217 | $groups = CRM_Core_PseudoConstant::group(); | |
218 | ||
219 | foreach ($groups as $id => $name) { | |
220 | $string = "id:'$id', name:'$name'"; | |
221 | if (isset($tree[$id])) { | |
be2fb01f | 222 | $children = []; |
6a488035 TO |
223 | if (!empty($tree[$id]['children'])) { |
224 | foreach ($tree[$id]['children'] as $child) { | |
225 | $children[] = "{_reference:'$child'}"; | |
226 | } | |
227 | $children = implode(',', $children); | |
228 | $string .= ", children:[$children]"; | |
229 | if (empty($tree[$id]['parents'])) { | |
230 | $string .= ", type:'rootGroup'"; | |
231 | } | |
232 | else { | |
233 | $string .= ", type:'middleGroup'"; | |
234 | } | |
235 | } | |
236 | else { | |
237 | $string .= ", type:'leafGroup'"; | |
238 | } | |
239 | } | |
240 | else { | |
241 | $string .= ", children:[], type:'rootGroup'"; | |
242 | } | |
243 | $values[] = "{ $string }"; | |
244 | } | |
245 | ||
246 | $items = implode(",\n", $values); | |
247 | $json = "{ | |
248 | identifier:'id', | |
249 | label:'name', | |
250 | items:[ $items ] | |
251 | }"; | |
252 | return $json; | |
253 | } | |
96025800 | 254 | |
6a488035 | 255 | } |