Merge pull request #6548 from eileenmcnaughton/CRM-16512
[civicrm-core.git] / CRM / Contact / BAO / GroupNestingCache.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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-2015
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(
55 'children' => array(),
56 'parents' => array(),
57 );
58 }
59
60 if (!array_key_exists($dao->parent, $tree)) {
61 $tree[$dao->parent] = array(
62 'children' => array(),
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)) {
72 CRM_Core_Error::fatal(ts('We detected a cycle which we cant handle. aborting'));
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) {
85 $parents = implode(',', $tree[$id]['parents']);
86 $children = implode(',', $tree[$id]['children']);
87 $parents = $parents == NULL ? 'null' : "'$parents'";
88 $children = $children == NULL ? 'null' : "'$children'";
89 $sql = "
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
102 /**
103 * @param $tree
104 *
105 * @return bool
106 */
107 public static function checkCyclicGraph(&$tree) {
108 // lets keep this simple, we should probably use a graph algoritm here at some stage
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
121 /**
122 * @param $tree
123 * @param int $id
124 *
125 * @return bool
126 */
127 public static function isCyclic(&$tree, $id) {
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
145 /**
146 * @param int $id
147 * @param $groups
148 *
149 * @return array
150 */
151 public static function getPotentialCandidates($id, &$groups) {
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
170 /**
171 * @param $potential
172 * @param $tree
173 * @param int $id
174 * @param $token
175 */
176 public static function invalidate(&$potential, &$tree, $id, $token) {
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
190 /**
191 * @param $all
192 * @param $tree
193 * @param int $id
194 * @param $token
195 */
196 public static function getAll(&$all, &$tree, $id, $token) {
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
214 /**
215 * @return string
216 */
217 public static function json() {
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 }
263
264 }