Passed
Push — 1.11.x ( 4a8b1c...481548 )
by Angel Fernando Quiroz
09:21 queued 11s
created

TableSort::sort_table()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 16
nc 7
nop 4
dl 0
loc 32
rs 8.4444
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * This is a library with some functions to sort tabular data.
6
 */
7
define('SORT_DATE', 3);
8
define('SORT_IMAGE', 4);
9
10
/**
11
 * Class TableSort.
12
 */
13
class TableSort
14
{
15
    /**
16
     * Sorts 2-dimensional table.
17
     *
18
     * @param array $data      the data to be sorted
19
     * @param int   $column    The column on which the data should be sorted (default = 0)
20
     * @param int   $direction The direction to sort (SORT_ASC (default) or SORT_DESC)
21
     * @param int   $type      How should data be sorted (SORT_REGULAR, SORT_NUMERIC,
22
     *                         SORT_STRING,SORT_DATE,SORT_IMAGE)
23
     *
24
     * @return array The sorted dataset
25
     *
26
     * @author [email protected]
27
     */
28
    public static function sort_table(
29
        $data,
30
        $column = 0,
31
        $direction = SORT_ASC,
32
        $type = SORT_REGULAR
33
    ) {
34
        if (!is_array($data) || empty($data)) {
35
            return [];
36
        }
37
        $column = (int) $column;
38
39
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
40
            // Probably an attack
41
            return $data;
42
        }
43
44
        if (SORT_REGULAR == $type) {
45
            $type = SORT_STRING;
46
            if (self::is_image_column($data, $column)) {
47
                $type = SORT_IMAGE;
48
            } elseif (self::is_date_column($data, $column)) {
49
                $type = SORT_DATE;
50
            } elseif (self::is_numeric_column($data, $column)) {
51
                $type = SORT_NUMERIC;
52
            }
53
        }
54
        $function = self::getSortFunction($type, $direction, $column);
55
56
        // Sort the content
57
        usort($data, $function);
58
59
        return $data;
60
    }
61
62
    /**
63
     * @param $type
64
     * @param $direction
65
     * @param $column
66
     *
67
     * @return Closure
68
     */
69
    public static function getSortFunction($type, $direction, $column)
70
    {
71
        $compareOperator = SORT_ASC == $direction ? '>' : '<=';
72
73
        switch ($type) {
74
            case SORT_NUMERIC:
75
                $function = function ($a, $b) use ($column, $compareOperator) {
76
                    $colA = strip_tags($a[$column]);
77
                    $colB = strip_tags($b[$column]);
78
79
                    if ('>' === $compareOperator && $colA > $colB) {
80
                        return 1;
81
                    }
82
83
                    return $colA < $colB ? -1 : 0;
84
                };
85
                break;
86
            case SORT_IMAGE:
87
                $function = function ($a, $b) use ($column, $compareOperator) {
88
                    $result = api_strnatcmp(
89
                        api_strtolower(strip_tags($a[$column], "<img>")),
90
                        api_strtolower(strip_tags($b[$column], "<img>"))
91
                    );
92
                    if ('>' === $compareOperator) {
93
                        $result = api_strnatcmp(
94
                            api_strtolower(strip_tags($a[$column], "<img>")),
95
                            api_strtolower(strip_tags($b[$column], "<img>"))
96
                        );
97
                    }
98
99
                    return $result;
100
                };
101
102
                break;
103
            case SORT_DATE:
104
                $function = function ($a, $b) use ($column, $compareOperator) {
105
                    $dateA = strtotime(strip_tags($a[$column]));
106
                    $dateB = strtotime(strip_tags($b[$column]));
107
108
                    if ('>' === $compareOperator && $dateA > $dateB) {
109
                        return 1;
110
                    }
111
112
                    return $dateA < $dateB ? -1 : 0;
113
                };
114
                break;
115
            case SORT_STRING:
116
            default:
117
                $function = function ($a, $b) use ($column, $compareOperator) {
118
                    $result = api_strnatcmp(
119
                        api_strtolower(strip_tags($a[$column])),
120
                        api_strtolower(strip_tags($b[$column]))
121
                    );
122
                    if ('>' === $compareOperator) {
123
                        $result = api_strnatcmp(
124
                            api_strtolower(strip_tags($a[$column])),
125
                            api_strtolower(strip_tags($b[$column]))
126
                        );
127
                    }
128
129
                    return $result;
130
                };
131
                break;
132
        }
133
134
        return $function;
135
    }
136
137
    /**
138
     * Sorts 2-dimensional table. It is possible changing the columns that will be
139
     * shown and the way that the columns are to be sorted.
140
     *
141
     * @param array $data         the data to be sorted
142
     * @param int   $column       The column on which the data should be sorted (default = 0)
143
     * @param int   $direction    The direction to sort (SORT_ASC (default) or SORT_DESC)
144
     * @param array $column_show  The columns that we will show in the table
145
     *                            i.e: $column_show = array('1','0','1') we will show the 1st and the 3th column.
146
     * @param array $column_order Changes how the columns will be sorted
147
     *                            ie. $column_order = array('0','3','2','3') The column [1] will be sorted like the column [3]
148
     * @param int   $type         How should data be sorted (SORT_REGULAR, SORT_NUMERIC, SORT_STRING, SORT_DATE, SORT_IMAGE)
149
     *
150
     * @return array The sorted dataset
151
     *
152
     * @author [email protected]
153
     */
154
    public static function sort_table_config(
155
        $data,
156
        $column = 0,
157
        $direction = SORT_ASC,
158
        $column_show = null,
159
        $column_order = null,
160
        $type = SORT_REGULAR,
161
        $doc_filter = false
162
    ) {
163
        if (!is_array($data) || empty($data)) {
164
            return [];
165
        }
166
167
        $column = (int) $column;
168
169
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
170
            // Probably an attack
171
            return $data;
172
        }
173
174
        // Change columns sort
175
        // Here we say that the real way of how the columns are going to be order is manage by the $column_order array
176
        if (is_array($column_order)) {
177
            $column = isset($column_order[$column]) ? $column_order[$column] : $column;
178
        }
179
180
        if (SORT_REGULAR == $type) {
181
            if (self::is_image_column($data, $column)) {
182
                $type = SORT_IMAGE;
183
            } elseif (self::is_date_column($data, $column)) {
184
                $type = SORT_DATE;
185
            } elseif (self::is_numeric_column($data, $column)) {
186
                $type = SORT_NUMERIC;
187
            } else {
188
                $type = SORT_STRING;
189
            }
190
        }
191
192
        //This fixes only works in the document tool when ordering by name
193
        if ($doc_filter && in_array($type, [SORT_STRING])) {
194
            $folder_to_sort = [];
195
            $new_data = [];
196
            if (!empty($data)) {
197
                foreach ($data as $document) {
198
                    if ('folder' === $document['type']) {
199
                        $docs_to_sort[$document['id']] = api_strtolower($document['name']);
200
                    } else {
201
                        $folder_to_sort[$document['id']] = api_strtolower($document['name']);
202
                    }
203
                    $new_data[$document['id']] = $document;
204
                }
205
206
                if (SORT_ASC == $direction) {
207
                    if (!empty($docs_to_sort)) {
208
                        api_natsort($docs_to_sort);
209
                    }
210
                    if (!empty($folder_to_sort)) {
211
                        api_natsort($folder_to_sort);
212
                    }
213
                } else {
214
                    if (!empty($docs_to_sort)) {
215
                        api_natrsort($docs_to_sort);
216
                    }
217
                    if (!empty($folder_to_sort)) {
218
                        api_natrsort($folder_to_sort);
219
                    }
220
                }
221
222
                $new_data_order = [];
223
                if (!empty($docs_to_sort)) {
224
                    foreach ($docs_to_sort as $id => $document) {
225
                        if (isset($new_data[$id])) {
226
                            $new_data_order[] = $new_data[$id];
227
                        }
228
                    }
229
                }
230
231
                if (!empty($folder_to_sort)) {
232
                    foreach ($folder_to_sort as $id => $document) {
233
                        if (isset($new_data[$id])) {
234
                            $new_data_order[] = $new_data[$id];
235
                        }
236
                    }
237
                }
238
                $data = $new_data_order;
239
            }
240
        } else {
241
            $function = self::getSortFunction($type, $direction, $column);
242
243
            // Sort the content
244
            usort($data, $function);
245
        }
246
247
        if (is_array($column_show) && !empty($column_show)) {
248
            // We show only the columns data that were set up on the $column_show array
249
            $new_order_data = [];
250
            $count_data = count($data);
251
            $count_column_show = count($column_show);
252
            for ($j = 0; $j < $count_data; $j++) {
253
                $k = 0;
254
                for ($i = 0; $i < $count_column_show; $i++) {
255
                    if ($column_show[$i]) {
256
                        $new_order_data[$j][$k] = $data[$j][$i];
257
                    }
258
                    $k++;
259
                }
260
            }
261
            // Replace the multi-arrays
262
            $data = $new_order_data;
263
        }
264
265
        return $data;
266
    }
267
268
    /**
269
     * Checks whether a column of a 2D-array contains only numeric values.
270
     *
271
     * @param array $data   The data-array
272
     * @param int   $column The index of the column to check
273
     *
274
     * @return bool TRUE if column contains only dates, FALSE otherwise
275
     *
276
     * @todo Take locale into account (eg decimal point or comma ?)
277
     *
278
     * @author [email protected]
279
     */
280
    private static function is_numeric_column(&$data, $column)
281
    {
282
        $is_numeric = true;
283
        foreach ($data as $index => &$row) {
284
            $is_numeric &= is_numeric(strip_tags($row[$column]));
285
            if (!$is_numeric) {
286
                break;
287
            }
288
        }
289
290
        return $is_numeric;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $is_numeric also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
291
    }
292
293
    /**
294
     * Checks whether a column of a 2D-array contains only dates (GNU date syntax).
295
     *
296
     * @param array $data   The data-array
297
     * @param int   $column The index of the column to check
298
     *
299
     * @return bool TRUE if column contains only dates, FALSE otherwise
300
     *
301
     * @author [email protected]
302
     */
303
    private static function is_date_column(&$data, $column)
304
    {
305
        $is_date = true;
306
        foreach ($data as $index => &$row) {
307
            if (strlen(strip_tags($row[$column])) != 0) {
308
                $check_date = strtotime(strip_tags($row[$column]));
309
                // strtotime Returns a timestamp on success, FALSE otherwise.
310
                // Previous to PHP 5.1.0, this function would return -1 on failure.
311
                $is_date &= ($check_date != -1 && $check_date);
312
            } else {
313
                $is_date &= false;
314
            }
315
            if (!$is_date) {
316
                break;
317
            }
318
        }
319
320
        return $is_date;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $is_date also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
321
    }
322
323
    /**
324
     * Checks whether a column of a 2D-array contains only images (<img src="path/file.ext" alt=".."/>).
325
     *
326
     * @param array $data   The data-array
327
     * @param int   $column The index of the column to check
328
     *
329
     * @return bool TRUE if column contains only images, FALSE otherwise
330
     *
331
     * @author [email protected]
332
     */
333
    private static function is_image_column(&$data, $column)
334
    {
335
        $is_image = true;
336
        foreach ($data as $index => &$row) {
337
            if (isset($row[$column])) {
338
                // at least one img-tag
339
                $is_image &= strlen(trim(strip_tags($row[$column], '<img>'))) > 0;
340
                // and no text outside attribute-values
341
                $is_image &= 0 == strlen(trim(strip_tags($row[$column])));
342
            }
343
            if (!$is_image) {
344
                break;
345
            }
346
        }
347
348
        return $is_image;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $is_image also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
349
    }
350
}
351