Merge pull request #15916 from civicrm/5.20
[civicrm-core.git] / CRM / Utils / Sort.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 * Base class to provide generic sort functionality.
15 *
16 * Note that some ideas have been borrowed from the drupal tablesort.inc code.
17 *
18 * Also note that since the Pager and Sort class are similar, do match the function names
19 * if introducing additional functionality
20 *
21 * @package CRM
22 * @copyright CiviCRM LLC https://civicrm.org/licensing
23 */
24 class CRM_Utils_Sort {
25
26 /**
27 * Constants to determine what direction each variable
28 * is to be sorted
29 *
30 * @var int
31 */
32 const ASCENDING = 1, DESCENDING = 2, DONTCARE = 4,
33
34 /**
35 * The name for the sort GET/POST param
36 *
37 * @var string
38 */
39 SORT_ID = 'crmSID', SORT_DIRECTION = 'crmSortDirection', SORT_ORDER = 'crmSortOrder';
40
41 /**
42 * Name of the sort function. Used to isolate session variables
43 * @var string
44 */
45 protected $_name;
46
47 /**
48 * Array of variables that influence the query
49 *
50 * @var array
51 */
52 public $_vars;
53
54 /**
55 * The newly formulated base url to be used as links
56 * for various table elements
57 *
58 * @var string
59 */
60 protected $_link;
61
62 /**
63 * What's the name of the sort variable in a REQUEST
64 *
65 * @var string
66 */
67 protected $_urlVar;
68
69 /**
70 * What variable are we currently sorting on
71 *
72 * @var string
73 */
74 protected $_currentSortID;
75
76 /**
77 * What direction are we sorting on
78 *
79 * @var string
80 */
81 protected $_currentSortDirection;
82
83 /**
84 * The output generated for the current form
85 *
86 * @var array
87 */
88 public $_response;
89
90 /**
91 * The constructor takes an assoc array
92 * key names of variable (which should be the same as the column name)
93 * value: ascending or descending
94 *
95 * @param mixed $vars
96 * Assoc array as described above.
97 * @param string $defaultSortOrder
98 * Order to sort.
99 *
100 * @return \CRM_Utils_Sort
101 */
102 public function __construct(&$vars, $defaultSortOrder = NULL) {
103 $this->_vars = [];
104 $this->_response = [];
105
106 foreach ($vars as $weight => $value) {
107 $this->_vars[$weight] = [
108 'name' => CRM_Utils_Type::validate($value['sort'], 'MysqlColumnNameOrAlias'),
109 'direction' => CRM_Utils_Array::value('direction', $value),
110 'title' => $value['name'],
111 ];
112 }
113
114 $this->_currentSortID = 1;
115 if (isset($this->_vars[$this->_currentSortID])) {
116 $this->_currentSortDirection = $this->_vars[$this->_currentSortID]['direction'];
117 }
118 $this->_urlVar = self::SORT_ID;
119 $this->_link = CRM_Utils_System::makeURL($this->_urlVar, TRUE);
120
121 $this->initialize($defaultSortOrder);
122 }
123
124 /**
125 * Function returns the string for the order by clause.
126 *
127 * @return string
128 * the order by clause
129 */
130 public function orderBy() {
131 if (empty($this->_vars[$this->_currentSortID])) {
132 return '';
133 }
134
135 if ($this->_vars[$this->_currentSortID]['direction'] == self::ASCENDING ||
136 $this->_vars[$this->_currentSortID]['direction'] == self::DONTCARE
137 ) {
138 $this->_vars[$this->_currentSortID]['name'] = str_replace(' ', '_', $this->_vars[$this->_currentSortID]['name']);
139 return CRM_Utils_Type::escape($this->_vars[$this->_currentSortID]['name'], 'MysqlColumnNameOrAlias') . ' asc';
140 }
141 else {
142 $this->_vars[$this->_currentSortID]['name'] = str_replace(' ', '_', $this->_vars[$this->_currentSortID]['name']);
143 return CRM_Utils_Type::escape($this->_vars[$this->_currentSortID]['name'], 'MysqlColumnNameOrAlias') . ' desc';
144 }
145 }
146
147 /**
148 * Create the sortID string to be used in the GET param.
149 *
150 * @param int $index
151 * The field index.
152 * @param int $dir
153 * The direction of the sort.
154 *
155 * @return string
156 * the string to append to the url
157 */
158 public static function sortIDValue($index, $dir) {
159 return ($dir == self::DESCENDING) ? $index . '_d' : $index . '_u';
160 }
161
162 /**
163 * Init the sort ID values in the object.
164 *
165 * @param string $defaultSortOrder
166 * The sort order to use by default.
167 */
168 public function initSortID($defaultSortOrder) {
169 $url = CRM_Utils_Array::value(self::SORT_ID, $_GET, $defaultSortOrder);
170
171 if (empty($url)) {
172 return;
173 }
174
175 list($current, $direction) = explode('_', $url);
176
177 // if current is weird and does not exist in the vars array, skip
178 if (!array_key_exists($current, $this->_vars)) {
179 return;
180 }
181
182 if ($direction == 'u') {
183 $direction = self::ASCENDING;
184 }
185 elseif ($direction == 'd') {
186 $direction = self::DESCENDING;
187 }
188 else {
189 $direction = self::DONTCARE;
190 }
191
192 $this->_currentSortID = $current;
193 $this->_currentSortDirection = $direction;
194 $this->_vars[$current]['direction'] = $direction;
195 }
196
197 /**
198 * Init the object.
199 *
200 * @param string $defaultSortOrder
201 * The sort order to use by default.
202 */
203 public function initialize($defaultSortOrder) {
204 $this->initSortID($defaultSortOrder);
205
206 $this->_response = [];
207
208 $current = $this->_currentSortID;
209 foreach ($this->_vars as $index => $item) {
210 $name = $item['name'];
211 $this->_response[$name] = [];
212
213 $newDirection = ($item['direction'] == self::ASCENDING) ? self::DESCENDING : self::ASCENDING;
214
215 if ($current == $index) {
216 if ($item['direction'] == self::ASCENDING) {
217 $class = 'sorting_asc';
218 }
219 else {
220 $class = 'sorting_desc';
221 }
222 }
223 else {
224 $class = 'sorting';
225 }
226
227 $this->_response[$name]['link'] = '<a href="' . $this->_link . $this->sortIDValue($index, $newDirection) . '" class="' . $class . '">' . $item['title'] . '</a>';
228 }
229 }
230
231 /**
232 * Getter for currentSortID.
233 *
234 * @return int
235 * returns of the current sort id
236 */
237 public function getCurrentSortID() {
238 return $this->_currentSortID;
239 }
240
241 /**
242 * Getter for currentSortDirection.
243 *
244 * @return int
245 * returns of the current sort direction
246 */
247 public function getCurrentSortDirection() {
248 return $this->_currentSortDirection;
249 }
250
251 /**
252 * Universal callback function for sorting by weight, id, title or name
253 *
254 * @param $a
255 * @param $b
256 *
257 * @return int
258 * (-1 or 1)
259 */
260 public static function cmpFunc($a, $b) {
261 $cmp_order = ['weight', 'id', 'title', 'name'];
262 foreach ($cmp_order as $attribute) {
263 if (isset($a[$attribute]) && isset($b[$attribute])) {
264 if ($a[$attribute] < $b[$attribute]) {
265 return -1;
266 }
267 elseif ($a[$attribute] > $b[$attribute]) {
268 return 1;
269 } // else: $a and $b are equal wrt to this attribute, try next...
270 }
271 }
272 // if we get here, $a and $b are equal for all we know
273 // however, as I understand we don't want equality here:
274 return -1;
275 }
276
277 }