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