Import from SVN (r45945, r596)
[civicrm-core.git] / CRM / Utils / Weight.php
CommitLineData
6a488035
TO
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*/
27class CRM_Utils_Weight {
28 /**
29 * @var array, list of GET fields which must be validated
30 *
31 * To reduce the size of this patch, we only sign the exploitable fields
32 * which make up "$baseURL" in addOrder() (eg 'filter' or 'dao').
33 * Less-exploitable fields (eg 'dir') are left unsigned.
34 */
35 static $SIGNABLE_FIELDS = array('reset', 'dao', 'idName', 'url', 'filter'); // 'id','src','dst','dir'
36
37 /**
38 * Function to correct duplicate weight entries by putting them (duplicate weights) in sequence.
39 *
40 * @param string $daoName full name of the DAO
41 * @param array $fieldValues field => value to be used in the WHERE
42 * @param string $weightField field which contains the weight value,
43 * defaults to 'weight'
44 *
45 * @return bool
46 */
47 static function correctDuplicateWeights($daoName, $fieldValues = NULL, $weightField = 'weight') {
48 $selectField = "MIN(id) AS dupeId, count(id) as dupeCount, $weightField as dupeWeight";
49 $groupBy = "$weightField having dupeCount>1";
50
51 $minDupeID = CRM_Utils_Weight::query('SELECT', $daoName, $fieldValues, $selectField, NULL, NULL, $groupBy);
52
53 // return early if query returned empty
54 // CRM-8043
55 if (!$minDupeID->fetch()) {
56 return TRUE;
57 }
58
59 if ($minDupeID->dupeId) {
60 $additionalWhere = "id !=" . $minDupeID->dupeId . " AND $weightField >= " . $minDupeID->dupeWeight;
61 $update = "$weightField = $weightField + 1";
62 $status = CRM_Utils_Weight::query('UPDATE', $daoName, $fieldValues, $update, $additionalWhere);
63 }
64
65 if ($minDupeID->dupeId && $status) {
66 //recursive call to correct all duplicate weight entries.
67 return CRM_Utils_Weight::correctDuplicateWeights($daoName, $fieldValues, $weightField);
68 }
69 elseif (!$minDupeID->dupeId) {
70 // case when no duplicate records are found.
71 return TRUE;
72 }
73 elseif (!$status) {
74 // case when duplicate records are found but update status is false.
75 return FALSE;
76 }
77 }
78
79 /**
80 * Remove a row from the specified weight, and shift all rows below it up
81 *
82 * @param string $daoName full name of the DAO
83 * $param integer $weight the weight to be removed
84 * @param array $fieldValues field => value to be used in the WHERE
85 * @param string $weightField field which contains the weight value,
86 * defaults to 'weight'
87 *
88 * @return bool
89 */
90 static function delWeight($daoName, $fieldID, $fieldValues = NULL, $weightField = 'weight') {
91 require_once (str_replace('_', DIRECTORY_SEPARATOR, $daoName) . ".php");
92 eval('$object = new ' . $daoName . '( );');
93 $object->id = $fieldID;
94 if (!$object->find(TRUE)) {
95 return FALSE;
96 }
97
98 $weight = (int)$object->weight;
99 if ($weight < 1) {
100 return FALSE;
101 }
102
103 // fill the gap
104 $additionalWhere = "$weightField > $weight";
105 $update = "$weightField = $weightField - 1";
106 $status = CRM_Utils_Weight::query('UPDATE', $daoName, $fieldValues, $update, $additionalWhere);
107
108 return $status;
109 }
110
111 /**
112 * Updates the weight fields of other rows according to the new and old weight paased in.
113 * And returns the new weight be used. If old-weight not present, Creates a gap for a new row to be inserted
114 * at the specified new weight
115 *
116 * @param string $daoName full name of the DAO
117 * @param integer $oldWeight
118 * @param integer $newWeight
119 * @param array $fieldValues field => value to be used in the WHERE
120 * @param string $weightField field which contains the weight value,
121 * defaults to 'weight'
122 *
123 * @return int
124 */
125 static function updateOtherWeights($daoName, $oldWeight, $newWeight, $fieldValues = NULL, $weightField = 'weight') {
126 $oldWeight = (int ) $oldWeight;
127 $newWeight = (int ) $newWeight;
128
129 // max weight is the highest current weight
130 $maxWeight = CRM_Utils_Weight::getMax($daoName, $fieldValues, $weightField);
131 if (!$maxWeight) {
132 $maxWeight = 1;
133 }
134
135 if ($newWeight > $maxWeight) {
136 //calculate new weight, CRM-4133
137 $calNewWeight = CRM_Utils_Weight::getNewWeight($daoName, $fieldValues, $weightField);
138
139 //no need to update weight for other fields.
140 if ($calNewWeight > $maxWeight) {
141 return $calNewWeight;
142 }
143 $newWeight = $maxWeight;
144
145 if (!$oldWeight) {
146 return $newWeight + 1;
147 }
148 }
149 elseif ($newWeight < 1) {
150 $newWeight = 1;
151 }
152
153 // if they're the same, nothing to do
154 if ($oldWeight == $newWeight) {
155 return $newWeight;
156 }
157
158 // if oldWeight not present, indicates new weight is to be added. So create a gap for a new row to be inserted.
159 if (!$oldWeight) {
160 $additionalWhere = "$weightField >= $newWeight";
161 $update = "$weightField = ($weightField + 1)";
162 CRM_Utils_Weight::query('UPDATE', $daoName, $fieldValues, $update, $additionalWhere);
163 return $newWeight;
164 }
165 else {
166 if ($newWeight > $oldWeight) {
167 $additionalWhere = "$weightField > $oldWeight AND $weightField <= $newWeight";
168 $update = "$weightField = ($weightField - 1)";
169 }
170 elseif ($newWeight < $oldWeight) {
171 $additionalWhere = "$weightField >= $newWeight AND $weightField < $oldWeight";
172 $update = "$weightField = ($weightField + 1)";
173 }
174 CRM_Utils_Weight::query('UPDATE', $daoName, $fieldValues, $update, $additionalWhere);
175 return $newWeight;
176 }
177 }
178
179 /**
180 * returns the new calculated weight.
181 *
182 * @param string $daoName full name of the DAO
183 * @param array $fieldValues field => value to be used in the WHERE
184 * @param string $weightField field which used to get the wt, default to 'weight'.
185 *
186 * @return integer
187 */
188 static function getNewWeight($daoName, $fieldValues = NULL, $weightField = 'weight') {
189 $selectField = "id AS fieldID, $weightField AS weight";
190 $field = CRM_Utils_Weight::query('SELECT', $daoName, $fieldValues, $selectField);
191 $sameWeightCount = 0;
192 $weights = array();
193 while ($field->fetch()) {
194 if (in_array($field->weight, $weights)) {
195 $sameWeightCount++;
196 }
197 $weights[$field->fieldID] = $field->weight;
198 }
199
200 $newWeight = 1;
201 if ($sameWeightCount) {
202 $newWeight = max($weights) + 1;
203
204 //check for max wt should not greater than cal max wt.
205 $calMaxWt = min($weights) + count($weights) - 1;
206 if ($newWeight > $calMaxWt) {
207 $newWeight = $calMaxWt;
208 }
209 }
210 elseif (!empty($weights)) {
211 $newWeight = max($weights);
212 }
213
214 return $newWeight;
215 }
216
217 /**
218 * returns the highest weight.
219 *
220 * @param string $daoName full name of the DAO
221 * @param array $fieldValues field => value to be used in the WHERE
222 * @param string $weightField field which contains the weight value,
223 * defaults to 'weight'
224 *
225 * @return integer
226 */
227 static function getMax($daoName, $fieldValues = NULL, $weightField = 'weight') {
228 $selectField = "MAX(ROUND($weightField)) AS max_weight";
229 $weightDAO = CRM_Utils_Weight::query('SELECT', $daoName, $fieldValues, $selectField);
230 $weightDAO->fetch();
231 if ($weightDAO->max_weight) {
232 return $weightDAO->max_weight;
233 }
234 return 0;
235 }
236
237 /**
238 * returns the default weight ( highest weight + 1 ) to be used.
239 *
240 * @param string $daoName full name of the DAO
241 * @param array $fieldValues field => value to be used in the WHERE
242 * @param string $weightField field which contains the weight value,
243 * defaults to 'weight'
244 *
245 * @return integer
246 */
247 static function getDefaultWeight($daoName, $fieldValues = NULL, $weightField = 'weight') {
248 $maxWeight = CRM_Utils_Weight::getMax($daoName, $fieldValues, $weightField);
249 return $maxWeight + 1;
250 }
251
252 /**
253 * Execute a weight-related query
254 *
255 * @param string $queryType SELECT, UPDATE, DELETE
256 * @param string $daoName full name of the DAO
257 * @param array $fieldValues field => value to be used in the WHERE
258 * @param string $queryData data to be used, dependent on the query type
259 * @param string $orderBy optional ORDER BY field
260 *
261 * @return Object CRM_Core_DAO objet that holds the results of the query
262 */
263 static function &query($queryType,
264 $daoName,
265 $fieldValues = NULL,
266 $queryData,
267 $additionalWhere = NULL,
268 $orderBy = NULL,
269 $groupBy = NULL
270 ) {
271
272 require_once (str_replace('_', DIRECTORY_SEPARATOR, $daoName) . ".php");
273
274 $dao = new $daoName;
275 $table = $dao->getTablename();
276 $fields = &$dao->fields();
277 $fieldlist = array_keys($fields);
278
279 $whereConditions = array();
280 if ($additionalWhere) {
281 $whereConditions[] = $additionalWhere;
282 }
283 $params = array();
284 $fieldNum = 0;
285 if (is_array($fieldValues)) {
286 foreach ($fieldValues as $fieldName => $value) {
287 if (!in_array($fieldName, $fieldlist)) {
288 // invalid field specified. abort.
289 return FALSE;
290 }
291 $fieldNum++;
292 $whereConditions[] = "$fieldName = %$fieldNum";
293 $fieldType = $fields[$fieldName]['type'];
294 $params[$fieldNum] = array($value, CRM_Utils_Type::typeToString($fieldType));
295 }
296 }
297 $where = implode(' AND ', $whereConditions);
298
299 switch ($queryType) {
300 case 'SELECT':
301 $query = "SELECT $queryData FROM $table";
302 if ($where) {
303 $query .= " WHERE $where";
304 }
305 if ($groupBy) {
306 $query .= " GROUP BY $groupBy";
307 }
308 if ($orderBy) {
309 $query .= " ORDER BY $orderBy";
310 }
311 break;
312
313 case 'UPDATE':
314 $query = "UPDATE $table SET $queryData";
315 if ($where) {
316 $query .= " WHERE $where";
317 }
318 break;
319
320 case 'DELETE':
321 $query = "DELETE FROM $table WHERE $where AND $queryData";
322 break;
323
324 default:
325 return FALSE;
326 }
327
328 $resultDAO = CRM_Core_DAO::executeQuery($query, $params);
329 return $resultDAO;
330 }
331
332 static function addOrder(&$rows, $daoName, $idName, $returnURL, $filter = NULL) {
333 if (empty($rows)) {
334 return;
335 }
336
337 $ids = array_keys($rows);
338 $numIDs = count($ids);
339 array_unshift($ids, 0);
340 $ids[] = 0;
341 $firstID = $ids[1];
342 $lastID = $ids[$numIDs];
343 if ($firstID == $lastID) {
344 $rows[$firstID]['order'] = NULL;
345 return;
346 }
347 $config = CRM_Core_Config::singleton();
348 $imageURL = $config->userFrameworkResourceURL . 'i/arrow';
349
350 $queryParams = array(
351 'reset' => 1,
352 'dao' => $daoName,
353 'idName' => $idName,
354 'url' => $returnURL,
355 'filter' => $filter,
356 );
357
358 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$SIGNABLE_FIELDS);
359 $queryParams['_sgn'] = $signer->sign($queryParams);
360 $baseURL = CRM_Utils_System::url('civicrm/admin/weight', $queryParams);
361
362 for ($i = 1; $i <= $numIDs; $i++) {
363 $id = $ids[$i];
364 $prevID = $ids[$i - 1];
365 $nextID = $ids[$i + 1];
366
367 $links = array();
368 $url = "{$baseURL}&src=$id";
369
370 if ($prevID != 0) {
371 $alt = ts('Move to top');
372 $links[] = "<a href=\"{$url}&dst={$firstID}&dir=first\"><img src=\"{$imageURL}/first.gif\" title=\"$alt\" alt=\"$alt\" class=\"order-icon\"></a>";
373
374 $alt = ts('Move up one row');
375 $links[] = "<a href=\"{$url}&dst={$prevID}&dir=swap\"><img src=\"{$imageURL}/up.gif\" title=\"$alt\" alt=\"$alt\" class=\"order-icon\"></a>";
376 }
377 else {
378 $links[] = "<img src=\"{$imageURL}/spacer.gif\" class=\"order-icon\">";
379 $links[] = "<img src=\"{$imageURL}/spacer.gif\" class=\"order-icon\">";
380 }
381
382 if ($nextID != 0) {
383 $alt = ts('Move down one row');
384 $links[] = "<a href=\"{$url}&dst={$nextID}&dir=swap\"><img src=\"{$imageURL}/down.gif\" title=\"$alt\" alt=\"$alt\" class=\"order-icon\"></a>";
385
386 $alt = ts('Move to bottom');
387 $links[] = "<a href=\"{$url}&dst={$lastID}&dir=last\"><img src=\"{$imageURL}/last.gif\" title=\"$alt\" alt=\"$alt\" class=\"order-icon\"></a>";
388 }
389 else {
390 $links[] = "<img src=\"{$imageURL}/spacer.gif\" class=\"order-icon\">";
391 $links[] = "<img src=\"{$imageURL}/spacer.gif\" class=\"order-icon\">";
392 }
393 $rows[$id]['weight'] = implode('&nbsp;', $links);
394 }
395 }
396
397 static function fixOrder() {
398 $signature = CRM_Utils_Request::retrieve( '_sgn', 'String', CRM_Core_DAO::$_nullObject);
399 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$SIGNABLE_FIELDS);
400
401 // Validate $_GET values b/c subsequent code reads $_GET (via CRM_Utils_Request::retrieve)
402 if (! $signer->validate($signature, $_GET)) {
403 CRM_Core_Error::fatal('Request signature is invalid');
404 }
405
406 // Note: Ensure this list matches self::$SIGNABLE_FIELDS
407 $daoName = CRM_Utils_Request::retrieve('dao', 'String', CRM_Core_DAO::$_nullObject);
408 $id = CRM_Utils_Request::retrieve('id', 'Integer', CRM_Core_DAO::$_nullObject);
409 $idName = CRM_Utils_Request::retrieve('idName', 'String', CRM_Core_DAO::$_nullObject);
410 $url = CRM_Utils_Request::retrieve('url', 'String', CRM_Core_DAO::$_nullObject);
411 $filter = CRM_Utils_Request::retrieve('filter', 'String', CRM_Core_DAO::$_nullObject);
412 $src = CRM_Utils_Request::retrieve('src', 'Integer', CRM_Core_DAO::$_nullObject);
413 $dst = CRM_Utils_Request::retrieve('dst', 'Integer', CRM_Core_DAO::$_nullObject);
414 $dir = CRM_Utils_Request::retrieve('dir', 'String', CRM_Core_DAO::$_nullObject);
415
416 require_once (str_replace('_', DIRECTORY_SEPARATOR, $daoName) . ".php");
417 eval('$object = new ' . $daoName . '( );');
418 $srcWeight = CRM_Core_DAO::getFieldValue($daoName, $src, 'weight', $idName);
419 $dstWeight = CRM_Core_DAO::getFieldValue($daoName, $dst, 'weight', $idName);
420 if ($srcWeight == $dstWeight) {
421 CRM_Utils_System::redirect($url);
422 }
423
424 $tableName = $object->tableName();
425
426 $query = "UPDATE $tableName SET weight = %1 WHERE $idName = %2";
427 $params = array(1 => array($dstWeight, 'Integer'),
428 2 => array($src, 'Integer'),
429 );
430 CRM_Core_DAO::executeQuery($query, $params);
431
432 if ($dir == 'swap') {
433 $params = array(1 => array($srcWeight, 'Integer'),
434 2 => array($dst, 'Integer'),
435 );
436 CRM_Core_DAO::executeQuery($query, $params);
437 }
438 elseif ($dir == 'first') {
439 // increment the rest by one
440 $query = "UPDATE $tableName SET weight = weight + 1 WHERE $idName != %1 AND weight < %2";
441 if ($filter) {
442 $query .= " AND $filter";
443 }
444 $params = array(1 => array($src, 'Integer'),
445 2 => array($srcWeight, 'Integer'),
446 );
447 CRM_Core_DAO::executeQuery($query, $params);
448 }
449 elseif ($dir == 'last') {
450 // increment the rest by one
451 $query = "UPDATE $tableName SET weight = weight - 1 WHERE $idName != %1 AND weight > %2";
452 if ($filter) {
453 $query .= " AND $filter";
454 }
455 $params = array(1 => array($src, 'Integer'),
456 2 => array($srcWeight, 'Integer'),
457 );
458 CRM_Core_DAO::executeQuery($query, $params);
459 }
460
461 CRM_Utils_System::redirect($url);
462 }
463}
464