Passed
Push — master ( 6e1f19...dca8c4 )
by Thomas
12:39
created

ExcelGridFieldExportButton::getIgnoreFilters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace LeKoala\ExcelImportExport;
4
5
use InvalidArgumentException;
6
use SilverStripe\Assets\FileNameFilter;
7
use SilverStripe\Forms\GridField\GridField;
8
use SilverStripe\Forms\GridField\GridFieldPaginator;
9
use SilverStripe\Forms\GridField\GridField_FormAction;
10
use SilverStripe\Forms\GridField\GridField_URLHandler;
11
use SilverStripe\Forms\GridField\GridFieldFilterHeader;
12
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
13
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
14
use SilverStripe\Forms\GridField\GridField_ActionProvider;
15
use LeKoala\SpreadCompat\SpreadCompat;
0 ignored issues
show
Bug introduced by
The type LeKoala\SpreadCompat\SpreadCompat was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use SilverStripe\ORM\DataList;
17
18
/**
19
 * Adds an "Export list" button to the bottom of a {@link GridField}.
20
 */
21
class ExcelGridFieldExportButton implements
22
    GridField_HTMLProvider,
23
    GridField_ActionProvider,
24
    GridField_URLHandler
25
{
26
    /**
27
     * Map of a property name on the exported objects, with values being the column title in the file.
28
     * Note that titles are only used when {@link $hasHeader} is set to TRUE.
29
     */
30
    protected ?array $exportColumns;
31
32
    /**
33
     * Fragment to write the button to
34
     */
35
    protected string $targetFragment;
36
37
    protected bool $hasHeader = true;
38
39
    protected string $exportType = 'xlsx';
40
41
    protected ?string $exportName = null;
42
43
    protected ?string $buttonTitle = null;
44
45
    protected bool $checkCanView = true;
46
47
    protected bool $isLimited = true;
48
49
    protected array $listFilters = [];
50
51
    /**
52
     *
53
     * @var callable
54
     */
55
    protected $afterExportCallback;
56
57
    protected bool $ignoreFilters = false;
58
59
    /**
60
     * @param string $targetFragment The HTML fragment to write the button into
61
     * @param array $exportColumns The columns to include in the export
62
     */
63
    public function __construct($targetFragment = "after", $exportColumns = null)
64
    {
65
        $this->targetFragment = $targetFragment;
66
        $this->exportColumns = $exportColumns;
67
    }
68
69
    /**
70
     * @param GridField $gridField
71
     * @return string
72
     */
73
    public function getActionName($gridField)
74
    {
75
        $name = strtolower($gridField->getName());
76
        return 'excelexport_' . $name;
77
    }
78
79
    /**
80
     * Place the export button in a <p> tag below the field
81
     */
82
    public function getHTMLFragments($gridField)
83
    {
84
        $title = $this->buttonTitle ? $this->buttonTitle : _t(
85
            'ExcelImportExport.XLSEXPORT',
86
            'Export to Excel'
87
        );
88
89
        $name = $this->getActionName($gridField);
90
91
        $button = new GridField_FormAction(
92
            $gridField,
93
            $name,
94
            $title,
95
            $name,
96
            null
97
        );
98
        $button->addExtraClass('btn btn-secondary no-ajax font-icon-down-circled action_export');
99
        $button->setForm($gridField->getForm());
100
101
        return array(
102
            $this->targetFragment => $button->Field()
103
        );
104
    }
105
106
    /**
107
     * export is an action button
108
     */
109
    public function getActions($gridField)
110
    {
111
        return array($this->getActionName($gridField));
112
    }
113
114
    public function handleAction(
115
        GridField $gridField,
116
        $actionName,
117
        $arguments,
118
        $data
119
    ) {
120
        if (in_array($actionName, $this->getActions($gridField))) {
121
            return $this->handleExport($gridField);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->handleExport($gridField) targeting LeKoala\ExcelImportExpor...tButton::handleExport() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
122
        }
123
    }
124
125
    /**
126
     * it is also a URL
127
     */
128
    public function getURLHandlers($gridField)
129
    {
130
        return array($this->getActionName($gridField) => 'handleExport');
131
    }
132
133
    /**
134
     * Handle the export, for both the action button and the URL
135
     */
136
    public function handleExport($gridField, $request = null)
137
    {
138
        $now = Date("Ymd_Hi");
139
140
        $this->updateExportName($gridField);
141
142
        $data = $this->generateExportFileData($gridField);
143
144
        $ext = $this->exportType;
145
        $name = $this->exportName;
146
        $fileName = "$name-$now.$ext";
147
148
        if ($this->afterExportCallback) {
149
            $func = $this->afterExportCallback;
150
            $func();
151
        }
152
153
        $opts = [
154
            'extension' => $ext,
155
        ];
156
157
        if ($ext != 'csv') {
158
            $end = ExcelImportExport::getLetter(count($this->getRealExportColumns($gridField)));
159
            $opts['creator'] = "SilverStripe";
160
            $opts['autofilter'] = "A1:{$end}1";
161
        }
162
163
        SpreadCompat::output($data, $fileName, ...$opts);
164
        exit();
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...
165
    }
166
167
168
    /**
169
     * @param GridField|\LeKoala\Tabulator\TabulatorGrid $gridField
0 ignored issues
show
Bug introduced by
The type LeKoala\Tabulator\TabulatorGrid was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
170
     */
171
    protected function updateExportName($gridField)
172
    {
173
        $filter = new FileNameFilter;
174
        if ($this->exportName) {
175
            $this->exportName = $filter->filter($this->exportName);
176
        } else {
177
            $class = $gridField->getModelClass();
178
            $singl = singleton($class);
179
            $plural = $class ? $singl->i18n_plural_name() : '';
180
181
            $this->exportName = $filter->filter('export-' . $plural);
182
        }
183
    }
184
185
    /**
186
     * @param GridField|\LeKoala\Tabulator\TabulatorGrid $gridField
187
     * @return DataList|ArrayList
0 ignored issues
show
Bug introduced by
The type LeKoala\ExcelImportExport\ArrayList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
188
     */
189
    protected function retrieveList($gridField)
190
    {
191
        // Remove GridFieldPaginator as we're going to export the entire list.
192
        $gridField->getConfig()->removeComponentsByType(GridFieldPaginator::class);
193
194
        /** @var DataList|ArrayList $items */
195
        $items = $gridField->getManipulatedList();
196
197
        // Keep filters
198
        if (!$this->ignoreFilters) {
199
            foreach ($gridField->getConfig()->getComponents() as $component) {
200
                if ($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) {
201
                    $items = $component->getManipulatedData($gridField, $items);
202
                }
203
            }
204
        }
205
206
        $list = $items;
207
        $limit = ExcelImportExport::$limit_exports;
208
        if ($list instanceof DataList) {
209
            if ($this->isLimited && $limit > 0) {
210
                $list = $list->limit($limit);
211
            }
212
            if (!empty($this->listFilters)) {
213
                $list = $list->filter($this->listFilters);
214
            }
215
        }
216
217
        return $list;
218
    }
219
220
    /**
221
     * @param GridField|\LeKoala\Tabulator\TabulatorGrid $gridField
222
     */
223
    protected function getRealExportColumns($gridField)
224
    {
225
        $class = $gridField->getModelClass();
226
        return ($this->exportColumns) ? $this->exportColumns : ExcelImportExport::exportFieldsForClass($class);
227
    }
228
229
    /**
230
     * Generate export fields for Excel.
231
     *
232
     * @param GridField|\LeKoala\Tabulator\TabulatorGrid $gridField
233
     */
234
    public function generateExportFileData($gridField): iterable
235
    {
236
        $columns = $this->getRealExportColumns($gridField);
237
238
        if ($this->hasHeader) {
239
            $headers = [];
240
241
            // determine the headers. If a field is callable (e.g. anonymous function) then use the
242
            // source name as the header instead
243
            foreach ($columns as $columnSource => $columnHeader) {
244
                $headers[] = (!is_string($columnHeader) && is_callable($columnHeader))
245
                    ? $columnSource : $columnHeader;
246
            }
247
248
            yield $headers;
249
        }
250
251
        $list = $this->retrieveList($gridField);
252
253
        if (!$list) {
254
            return;
255
        }
256
257
        $exportFormat = ExcelImportExport::config()->export_format;
258
259
        foreach ($list as $item) {
260
            // This can be really slow for large exports depending on how canView is implemented
261
            if ($this->checkCanView) {
262
                $canView = true;
263
                if ($item->hasMethod('canView') && !$item->canView()) {
264
                    $canView = false;
265
                }
266
                if (!$canView) {
267
                    continue;
268
                }
269
            }
270
271
            $dataRow = [];
272
273
            // Loop and transforms records as needed
274
            foreach ($columns as $columnSource => $columnHeader) {
275
                if (!is_string($columnHeader) && is_callable($columnHeader)) {
276
                    if ($item->hasMethod($columnSource)) {
277
                        $relObj = $item->{$columnSource}();
278
                    } else {
279
                        $relObj = $item->relObject($columnSource);
280
                    }
281
282
                    $value = $columnHeader($relObj);
283
                } else {
284
                    if (is_string($columnSource)) {
285
                        // It can be a method
286
                        if (strpos($columnSource, '(') !== false) {
287
                            $matches = [];
288
                            preg_match('/([a-zA-Z]*)\((.*)\)/', $columnSource, $matches);
289
                            $func = $matches[1];
290
                            $params = explode(",", $matches[2]);
291
                            // Support only one param for now
292
                            $value = $item->$func($params[0]);
293
                        } else {
294
                            if (array_key_exists($columnSource, $exportFormat)) {
295
                                $format = $exportFormat[$columnSource];
296
                                $value = $item->dbObject($columnSource)->$format();
297
                            } else {
298
                                $value = $gridField->getDataFieldValue($item, $columnSource);
299
                            }
300
                        }
301
                    } else {
302
                        // We can also use a simple dot notation
303
                        $parts = explode(".", $columnHeader);
304
                        if (count($parts) == 1) {
305
                            $value = $item->$columnHeader;
306
                        } else {
307
                            $value = $item->relObject($parts[0]);
308
                            if ($value) {
309
                                $relObjField = $parts[1];
310
                                $value = $value->$relObjField;
311
                            }
312
                        }
313
                    }
314
                }
315
316
                $dataRow[] = $value;
317
            }
318
319
            if ($item->hasMethod('destroy')) {
320
                $item->destroy();
321
            }
322
323
            yield $dataRow;
324
        }
325
    }
326
327
    /**
328
     * @return array
329
     */
330
    public function getExportColumns()
331
    {
332
        return $this->exportColumns;
333
    }
334
335
    /**
336
     * @param array
337
     */
338
    public function setExportColumns($cols)
339
    {
340
        $this->exportColumns = $cols;
341
        return $this;
342
    }
343
344
    /**
345
     * @return boolean
346
     */
347
    public function getHasHeader()
348
    {
349
        return $this->hasHeader;
350
    }
351
352
    /**
353
     * @param boolean
354
     */
355
    public function setHasHeader($bool)
356
    {
357
        $this->hasHeader = $bool;
358
        return $this;
359
    }
360
361
    /**
362
     * @return string
363
     */
364
    public function getExportType()
365
    {
366
        return $this->exportType;
367
    }
368
369
    /**
370
     * @param string xlsx (default), xls or csv
0 ignored issues
show
Bug introduced by
The type LeKoala\ExcelImportExport\xlsx was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
371
     */
372
    public function setExportType($exportType)
373
    {
374
        if (!in_array($exportType, ['xls', 'xlsx', 'csv'])) {
375
            throw new InvalidArgumentException("Export type must be one of : xls, xlsx, csv");
376
        }
377
        $this->exportType = $exportType;
378
        return $this;
379
    }
380
381
    /**
382
     * @return string
383
     */
384
    public function getExportName()
385
    {
386
        return $this->exportName;
387
    }
388
389
    /**
390
     * @param string $exportName
391
     * @return ExcelGridFieldExportButton
392
     */
393
    public function setExportName($exportName)
394
    {
395
        $this->exportName = $exportName;
396
        return $this;
397
    }
398
399
    /**
400
     * @return string
401
     */
402
    public function getButtonTitle()
403
    {
404
        return $this->buttonTitle;
405
    }
406
407
    /**
408
     * @param string $buttonTitle
409
     * @return ExcelGridFieldExportButton
410
     */
411
    public function setButtonTitle($buttonTitle)
412
    {
413
        $this->buttonTitle = $buttonTitle;
414
        return $this;
415
    }
416
417
    /**
418
     *
419
     * @return bool
420
     */
421
    public function getCheckCanView()
422
    {
423
        return $this->checkCanView;
424
    }
425
426
    /**
427
     *
428
     * @param bool $checkCanView
429
     * @return ExcelGridFieldExportButton
430
     */
431
    public function setCheckCanView($checkCanView)
432
    {
433
        $this->checkCanView = $checkCanView;
434
        return $this;
435
    }
436
437
    /**
438
     *
439
     * @return array
440
     */
441
    public function getListFilters()
442
    {
443
        return $this->listFilters;
444
    }
445
446
    /**
447
     *
448
     * @param array $listFilters
449
     * @return ExcelGridFieldExportButton
450
     */
451
    public function setListFilters($listFilters)
452
    {
453
        $this->listFilters = $listFilters;
454
        return $this;
455
    }
456
457
    /**
458
     *
459
     * @return callable
460
     */
461
    public function getAfterExportCallback()
462
    {
463
        return $this->afterExportCallback;
464
    }
465
466
    /**
467
     *
468
     * @param callable $afterExportCallback
469
     * @return ExcelGridFieldExportButton
470
     */
471
    public function setAfterExportCallback(callable $afterExportCallback)
472
    {
473
        $this->afterExportCallback = $afterExportCallback;
474
        return $this;
475
    }
476
477
    /**
478
     * Get the value of isLimited
479
     */
480
    public function getIsLimited(): bool
481
    {
482
        return $this->isLimited;
483
    }
484
485
    /**
486
     * Set the value of isLimited
487
     *
488
     * @param bool $isLimited
489
     */
490
    public function setIsLimited(bool $isLimited)
491
    {
492
        $this->isLimited = $isLimited;
493
        return $this;
494
    }
495
496
    /**
497
     * Get the value of ignoreFilters
498
     */
499
    public function getIgnoreFilters(): bool
500
    {
501
        return $this->ignoreFilters;
502
    }
503
504
    /**
505
     * Set the value of ignoreFilters
506
     *
507
     * @param bool $ignoreFilters
508
     */
509
    public function setIgnoreFilters(bool $ignoreFilters): self
510
    {
511
        $this->ignoreFilters = $ignoreFilters;
512
        return $this;
513
    }
514
}
515