Passed
Push — master ( ea6784...a13310 )
by Plamen
01:26
created

table_getter::exportFile()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 20
rs 9.4555
cc 5
nc 7
nop 2
1
<?php
2
3
class table_getter extends table_setter
4
{
5
    /** @param string @example: env('APP_NAME', 'APP_NAME') | APP_NAME */
6
    public static $app = 'App';
7
    /** @param array Attributes for the table Html tag */
8
    public static $attributes = [];
9
    /** @param array Query result, allows altering before table::load() */
10
    public static $data;
11
    /** @param bool Export is allowed (shows buttons in the tfoot) */
12
    public static $exportActive = true;
13
    /** @param bool Export data altered (use false for pure result) */
14
    public static $exportDataAsDisplayed = true;
15
    /** @param bool Filter (shown before the table) is allowed */
16
    public static $filterActive = true;
17
    /** @param null|str Extension (from $_SERVER['REQUEST_URI']) */
18
    public static $pageExt;
19
20
    protected static function ths()
21
    {
22
        if (self::$cols) {
23
            $ths = [];
24
            $length = sizeof(self::$cols);
25
            for ($i = 0; $i<$length; $i++) {
26
                list($lbl, $col, $arg) = array_pad(self::$cols[$i], 3, null);
27
28
                if (is_null($col)) {
29
                    $arg['sort'] = false;
30
                }
31
32
                $sortable = !isset($arg['sort'])||$arg['sort'] !==false;
33
34
                $sort = $sortable ?
35
                        (isset($arg['sort']) ? $arg['sort'] : $col) :
36
                        null;
37
38
                if (($del = isset($arg['type'])&&$arg['type'] =='delete')) {
39
                    $sortable = false;
40
                    if (!isset($arg['width'])&&
41
                            false ===strpos($arg['width'], 'width:')
42
                    ) {
43
                        $arg['width'] = '1%';
44
                    }
45
                }
46
47
                if (isset($arg['width'])) { // Width attribute -> style
48
                    $width = 'width:' . $arg['width'] . ';';
49
                    $arg['style'] = isset($arg['style']) ?
50
                            $width . $arg['style'] :
51
                            $width;
52
                }
53
54
                $attr = array_diff_key((array) $arg, ['sort', 'type', 'width']);
55
56
                $th = '<th' . self::attributes($attr) . '>';
57
                if ($sortable) {
58
                    $th .= '<a onclick="table.Sort(' . $i . ',this);">';
59
                }
60
                if (!$del) {
61
                    if ($sort ==self::$t['order']['col']) {
62
                        $span = self::$t['order']['dir'] ==='desc' ?
63
                                self::config('UTF8_DESC_SYMBOL') :
64
                                self::config('UTF8_ASC_SYMBOL');
65
                    } else {
66
                        $span = "";
67
                    }
68
                    $th .= '<span>' . $span . '</span>' . $lbl;
69
                } else {
70
                    $th .= '<input id="' . self::$t['items'] . 'CheckDeleteAll"' .
71
                            ' onclick=\"checkAllDeleteCheckboxes(this,' .
72
                            ' \'' . self::$t['items'] . '\')" type="checkbox"/>';
73
                }
74
                if ($sortable) {
75
                    $th .= '</a>';
76
                }
77
                $th .= '</th>';
78
                $ths[] = $th;
79
            }
80
            return implode('', $ths);
81
        }
82
    }
83
84
    /** Adds conditions, orderBy and Limits to query.
85
     * @param string $q
86
     * @param string $cond
87
     * @param arr $order
88
     * @param mixed $limit -> 5 or [$start, $count]
89
     * @param bool $h Having flag - to use HAVING instead of WHERE
90
     * @return (string) */
91
    protected static function q($q, $cond = null, array $order = [], $limit = 0, $h = false)
92
    {
93
        //Condition: '' | '(HAVING|WHERE|AND) ' . $cond
94
        $c = !empty($cond)&&!strpos($q, ($cl = !$h ? 'WHERE' : 'HAVING')) ?
95
                (' ' . (!strpos($q, $cl) ? $cl : ' AND ') . ' ' . $cond ) :
96
                '';
97
        //Order: '' | 'ORDER BY ' . array_keys($order)[0] . ' ' . $order[0]
98
        $o = !empty($order) ?
99
                ' ORDER BY ' . implode(', ',
100
                        array_map(function(&$v, $k) {
101
                            return $k . ' ' . strtoupper($v);
102
                        }, $order, array_keys($order))
103
                ) :
104
                '';
105
        //Limit: '' | ' LIMIT ' . '(20, 40|20)'
106
        $l = !empty($limit) ?
107
                (' LIMIT ' . (is_array($limit) ? implode(', ', $limit) : $limit)) :
108
                '';
109
110
        return $q . $c . $o . $l;
111
    }
112
113
    protected static function exportData()
114
    {
115
        $data = self::$exportDataAsDisplayed ?
116
                self::$data :
117
                self::select(self::$t['q']);
118
        $fnReplace = [];
119
        $fnReplace[':app'] = self::$app;
120
        $fnReplace[':items'] = self::$t['items'];
121
        $format = str_replace(':', '.', '%d.%m.%Y  %H:%i:%s');
122
        $timeQuery = 'SELECT DATE_FORMAT(Now(), "' . $format . '") AS `now`;';
123
        $fnReplace[':datetime'] = self::select($timeQuery);
124
        $outFilename = strtr(self::config('EXPORT_FILE_NAME'), $fnReplace);
125
        $outColumns = $outHeader = [];
126
        foreach (self::$cols as $c) {
127
            if (isset($c[2]['sort'])&&$c[2]['sort'] ===false) {
128
                continue;
129
            }
130
            $outColumns[] = $c[1];
131
            $outHeader[] = $c[0];
132
        }
133
        $eData = [$outHeader];
134
        if (count($data)>0) {
135
            foreach ($data as $row) {
136
                $rowData = [];
137
                foreach ($row as $dbName => $value) {
138
                    if (in_array($dbName, $outColumns)) {
139
                        $rowData[] = is_array($value) ? $value[0] : $value;
140
                    }
141
                }
142
                $eData[] = $rowData;
143
            }
144
        }
145
        self::exportFile($eData, $outFilename);
146
    }
147
148
    private static function exportFile($eData, $outFilename)
149
    {
150
        switch (self::$export) {
151
            case 'Excel':
152
            case 'CSV':
153
                $escape = function($v) {
154
                    return str_replace("\t", "&#9;", $v);
155
                };
156
                header('Content-Type:application/csv');
157
                header('Content-Disposition:attachment; filename=' .
158
                        $outFilename . '.csv');
159
                
160
                $output = fopen('php://output', 'w');
161
                foreach ($eData as $v) {
162
                    self::$export ==='CSV' ?
163
                    fputcsv($output, $v) :
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $handle of fputcsv() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
                    fputcsv(/** @scrutinizer ignore-type */ $output, $v) :
Loading history...
164
                    fputs($output, implode("\t", array_map($escape, $v)) . "\r\n");
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $handle of fputs() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

164
                    fputs(/** @scrutinizer ignore-type */ $output, implode("\t", array_map($escape, $v)) . "\r\n");
Loading history...
165
                }
166
                fclose($output);
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
                fclose(/** @scrutinizer ignore-type */ $output);
Loading history...
167
                break;
168
        }
169
    }
170
171
    protected static function jsonGetBodyData()
172
    {
173
        $json = $outColumns = [];
174
        foreach (self::$cols as $c) {
175
            $outColumns[] = isset($c[1]) ? $c[1] : null;
176
        }
177
        if ((self::$t['rows'] = count(self::$data))>0) {
178
            foreach (self::$data as $row) {
179
                $rowData = [];
180
                foreach ($outColumns as $dbName) {
181
                    $rowData[] = array_key_exists($dbName, $row) ?
182
                            $row[$dbName] : '';
183
                }
184
                $json[] = $rowData;
185
            }
186
        } else {
187
            $attr = ['colspan' => count(self::$cols), 'class' => 'no-results'];
188
            $json[] = [[self::config('EMPTY_TABLE_MSG'), $attr]];
189
        }
190
        return $json;
191
    }
192
193
    protected static function jsonGetFooterData()
194
    {
195
        $jsonArrFooter = [];
196
        if (count(self::$data)>0) {
197
            $pageNo = self::$t['page'];
198
            if ($pageNo ===1&&count(self::$data)<self::$t['paging']) {
199
                //Skips total count query
200
                self::$t['rows'] = count(self::$data);
201
            } else {
202
                $q = 'SELECT COUNT(*) FROM (' . self::$t['qAll'] . ') AS dt';
203
                self::$t['rows'] = self::select($q);
204
            }
205
            self::$t['pages'] = ceil(self::$t['rows']/self::$t['paging']);
206
207
            $v = [];
208
209
            $v['from'] = ($pageNo-1)*self::$t['paging']+1;
210
            $v['upto'] = ($pageNo*self::$t['paging']<self::$t['rows']) ?
211
                    $pageNo*self::$t['paging'] :
212
                    self::$t['rows'];
213
214
            $v["total"] = self::$t['rows'];
215
            $v["items"] = self::$t['items'];
216
217
            if (self::$exportActive ===true) {
218
                $url = strtok(self::$t['slug'], "?&") . ".json?table=" .
219
                        self::$t['items'] . "&export=";
220
                $v["export"] = ["url" => $url, "types" => self::config('SAVES')];
221
            }
222
223
            self::paging($v);
224
225
            $html = self::view('views/table.footer.html', $v);
226
227
            $jsonArrFooter[] = [[$html, ["colspan" => count(self::$cols)]]];
228
        }
229
        return $jsonArrFooter;
230
    }
231
232
    protected static function load()
233
    {
234
        $items = self::$t['items'];
235
        $v = ['items' => $items];
236
        $div_attr = ['id' => $items . '-list', 'class' => 'table'];
237
        if (array_key_exists('div', self::$attributes)) {
238
            $div_attr += self::$attributes['div'];
239
        }
240
        if (isset(self::$attributes['div']['class'])) {
241
            $div_attr['class'] .= ' ' . self::$attributes['div']['class'];
242
        }
243
        $v['div_attributes'] = self::attributes($div_attr);
244
245
        if (self::$filterActive) {
246
            self::filterValues($v['f_value'], $v['f_options']);
247
        }
248
249
        $tbl_attr = ['id' => $items . '-table', 'data-table' => 'js'];
250
        if (array_key_exists('table', self::$attributes)) {
251
            $tbl_attr += self::$attributes['table'];
252
        }
253
        $v['table_attributes'] = self::attributes($tbl_attr);
254
255
        $v['ths'] = self::ths();
256
257
        $v["body"] = self::rows('body');
258
259
        $v["footer"] = self::rows('footer');
260
261
        return self::view('views/table.html', $v);
262
    }
263
264
    private static function rows($for)
265
    {
266
        $trs = '';
267
        if ($for ==='body') {
268
            foreach (self::jsonGetBodyData() as $r) {
269
                if (isset($r[0][1], $r[0][1]['class'])&&
270
                        $r[0][1]['class'] ==='no-results'
271
                ) {
272
                    $trs .= '<tr><td' . self::attributes($r[0][1]) . '>'
273
                            . $r[0][0] . '</td></tr>';
274
                } else {
275
                    $trs .= '<tr><td>' . implode('</td><td>', $r)
276
                            . '</td></tr>';
277
                }
278
            }
279
        } else if ($for ==='footer') {
280
            if (self::$t['rows']>0) {
281
                $ftr = self::jsonGetFooterData()[0][0];
282
                $trs .= '<tr><td' . self::attributes($ftr[1]) . '>'
283
                        . $ftr[0] . '</td></tr>';
284
            }
285
        }
286
        return $trs;
287
    }
288
289
    protected static function reset($tableId)
290
    {
291
        if (!in_array($tableId, self::$t["tables"])) {
292
            self::$t["tables"][] = $tableId;
293
            self::$cols = [];
294
            self::$t['rows'] = null;
295
        } else {
296
            die('Existing table-id used in a table::create(): ' . $tableId);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
297
        }
298
    }
299
300
    static function select(string $expression, array $bindings = [])
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
301
    {
302
        if (is_object(static::$select)&&(static::$select instanceof Closure)) {
303
            $select = static::$select;
304
            $res = $select($expression, $bindings);
305
            //if result is single cell value ($res[0]->value), return the value
306
            return (count($res) ===1&&count((array) $res[0]) ===1) ?
307
                    reset($res[0]) :
308
                    $res;
309
        } else {
310
            die('ERROR: table::$select is not a valid closure. '
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
311
                    . __METHOD__ . '() #' . __LINE__);
312
        }
313
    }
314
}
315