Merge pull request #14667 from hoegrammer/master
[civicrm-core.git] / CRM / Contact / BAO / GroupNestingCache.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Contact_BAO_GroupNestingCache {
18
19 /**
20 * Update cache.
21 *
22 * @throws \CRM_Core_Exception
23 */
24 public static function update() {
25 // lets build the tree in memory first
26
27 $sql = '
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
35 ';
36
37 $dao = CRM_Core_DAO::executeQuery($sql);
38
39 $tree = [];
40 while ($dao->fetch()) {
41 if (!array_key_exists($dao->child, $tree)) {
42 $tree[$dao->child] = [
43 'children' => [],
44 'parents' => [],
45 ];
46 }
47
48 if (!array_key_exists($dao->parent, $tree)) {
49 $tree[$dao->parent] = [
50 'children' => [],
51 'parents' => [],
52 ];
53 }
54
55 $tree[$dao->child]['parents'][] = $dao->parent;
56 $tree[$dao->parent]['children'][] = $dao->child;
57 }
58
59 if (self::checkCyclicGraph($tree)) {
60 throw new CRM_Core_Exception(ts('We detected a cycle which we can\'t handle. aborting'));
61 }
62
63 // first reset the current cache entries
64 $sql = '
65 UPDATE civicrm_group
66 SET parents = null,
67 children = null
68 ';
69 CRM_Core_DAO::executeQuery($sql);
70
71 $values = [];
72 foreach (array_keys($tree) as $id) {
73 $parents = implode(',', $tree[$id]['parents']);
74 $children = implode(',', $tree[$id]['children']);
75 $parents = $parents == NULL ? 'null' : "'$parents'";
76 $children = $children == NULL ? 'null' : "'$children'";
77 $sql = "
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
87 Civi::cache('groups')->set('nestable tree hierarchy', $tree);
88 }
89
90 /**
91 * @param $tree
92 *
93 * @return bool
94 */
95 public static function checkCyclicGraph(&$tree) {
96 // lets keep this simple, we should probably use a graph algorithm here at some stage
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
109 /**
110 * @param $tree
111 * @param int $id
112 *
113 * @return bool
114 */
115 public static function isCyclic(&$tree, $id) {
116 $parents = $children = [];
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
133 /**
134 * @param int $id
135 * @param array $groups
136 *
137 * @return array
138 * @throws \CRM_Core_Exception
139 */
140 public static function getPotentialCandidates($id, &$groups) {
141 $tree = Civi::cache('groups')->get('nestable tree hierarchy');
142
143 if ($tree === NULL) {
144 self::update();
145 $tree = Civi::cache('groups')->get('nestable tree hierarchy');
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
159 /**
160 * @param $potential
161 * @param $tree
162 * @param int $id
163 * @param $token
164 */
165 public static function invalidate(&$potential, &$tree, $id, $token) {
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
179 /**
180 * @param $all
181 * @param $tree
182 * @param int $id
183 * @param $token
184 */
185 public static function getAll(&$all, &$tree, $id, $token) {
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
203 /**
204 * @return string
205 *
206 * @throws \CRM_Core_Exception
207 */
208 public static function json() {
209 $tree = Civi::cache('groups')->get('nestable tree hierarchy');
210
211 if ($tree === NULL) {
212 self::update();
213 $tree = Civi::cache('groups')->get('nestable tree hierarchy');
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])) {
222 $children = [];
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 }
254
255 }