Completed
Push — master ( d559a1...2d1e20 )
by Damian
27s
created

GridFieldExportButton::generateExportFileData()   C

Complexity

Conditions 20
Paths 60

Size

Total Lines 77
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 41
nc 60
nop 1
dl 0
loc 77
rs 5.1617
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Forms\GridField;
4
5
use League\Csv\EscapeFormula;
6
use League\Csv\Writer;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\ORM\DataObject;
11
12
/**
13
 * Adds an "Export list" button to the bottom of a {@link GridField}.
14
 */
15
class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionProvider, GridField_URLHandler
16
{
17
    /**
18
     * @var array Map of a property name on the exported objects, with values being the column title in the CSV file.
19
     * Note that titles are only used when {@link $csvHasHeader} is set to TRUE.
20
     */
21
    protected $exportColumns;
22
23
    /**
24
     * @var string
25
     */
26
    protected $csvSeparator = ",";
27
28
    /**
29
     * @var string
30
     */
31
    protected $csvEnclosure = '"';
32
33
    /**
34
     * @var boolean
35
     */
36
    protected $csvHasHeader = true;
37
38
    /**
39
     * Fragment to write the button to
40
     */
41
    protected $targetFragment;
42
43
    /**
44
     * Set to true to disable XLS sanitisation
45
     * [SS-2017-007] Ensure all cells with leading [@=+] have a leading tab
46
     *
47
     * @config
48
     * @var bool
49
     */
50
    private static $xls_export_disabled = false;
0 ignored issues
show
introduced by
The private property $xls_export_disabled is not used, and could be removed.
Loading history...
51
52
    /**
53
     * @param string $targetFragment The HTML fragment to write the button into
54
     * @param array $exportColumns The columns to include in the export
55
     */
56
    public function __construct($targetFragment = "after", $exportColumns = null)
57
    {
58
        $this->targetFragment = $targetFragment;
59
        $this->exportColumns = $exportColumns;
60
    }
61
62
    /**
63
     * Place the export button in a <p> tag below the field
64
     *
65
     * @param GridField $gridField
66
     *
67
     * @return array
68
     */
69
    public function getHTMLFragments($gridField)
70
    {
71
        $button = new GridField_FormAction(
72
            $gridField,
73
            'export',
74
            _t('SilverStripe\\Forms\\GridField\\GridField.CSVEXPORT', 'Export to CSV'),
75
            'export',
76
            null
77
        );
78
        $button->addExtraClass('btn btn-secondary no-ajax font-icon-down-circled action_export');
79
        $button->setForm($gridField->getForm());
80
        return [
81
            $this->targetFragment => $button->Field(),
82
        ];
83
    }
84
85
    /**
86
     * export is an action button
87
     *
88
     * @param GridField $gridField
89
     *
90
     * @return array
91
     */
92
    public function getActions($gridField)
93
    {
94
        return ['export'];
95
    }
96
97
    public function handleAction(GridField $gridField, $actionName, $arguments, $data)
98
    {
99
        if ($actionName == 'export') {
100
            return $this->handleExport($gridField);
101
        }
102
        return null;
103
    }
104
105
    /**
106
     * it is also a URL
107
     *
108
     * @param GridField $gridField
109
     *
110
     * @return array
111
     */
112
    public function getURLHandlers($gridField)
113
    {
114
        return [
115
            'export' => 'handleExport',
116
        ];
117
    }
118
119
    /**
120
     * Handle the export, for both the action button and the URL
121
     *
122
     * @param GridField $gridField
123
     * @param HTTPRequest $request
124
     *
125
     * @return HTTPResponse
126
     */
127
    public function handleExport($gridField, $request = null)
128
    {
129
        $now = date("d-m-Y-H-i");
130
        $fileName = "export-$now.csv";
131
132
        if ($fileData = $this->generateExportFileData($gridField)) {
133
            return HTTPRequest::send_file($fileData, $fileName, 'text/csv');
134
        }
135
        return null;
136
    }
137
138
    /**
139
     * Return the columns to export
140
     *
141
     * @param GridField $gridField
142
     *
143
     * @return array
144
     */
145
    protected function getExportColumnsForGridField(GridField $gridField)
146
    {
147
        if ($this->exportColumns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->exportColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
148
            return $this->exportColumns;
149
        }
150
151
        /** @var GridFieldDataColumns $dataCols */
152
        $dataCols = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
153
        if ($dataCols) {
154
            return $dataCols->getDisplayFields($gridField);
155
        }
156
157
        return DataObject::singleton($gridField->getModelClass())->summaryFields();
158
    }
159
160
    /**
161
     * Generate export fields for CSV.
162
     *
163
     * @param GridField $gridField
164
     *
165
     * @return string
166
     */
167
    public function generateExportFileData($gridField)
168
    {
169
        $csvColumns = $this->getExportColumnsForGridField($gridField);
170
171
        $csvWriter = Writer::createFromFileObject(new \SplTempFileObject());
172
        $csvWriter->setDelimiter($this->getCsvSeparator());
173
        $csvWriter->setEnclosure($this->getCsvEnclosure());
174
        $csvWriter->setNewline("\r\n"); //use windows line endings for compatibility with some csv libraries
175
        $csvWriter->setOutputBOM(Writer::BOM_UTF8);
176
177
        if (!Config::inst()->get(get_class($this), 'xls_export_disabled')) {
178
            $csvWriter->addFormatter(new EscapeFormula());
179
        }
180
181
        if ($this->csvHasHeader) {
182
            $headers = [];
183
184
            // determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
185
            // source name as the header instead
186
            foreach ($csvColumns as $columnSource => $columnHeader) {
187
                if (is_array($columnHeader) && array_key_exists('title', $columnHeader)) {
188
                    $headers[] = $columnHeader['title'];
189
                } else {
190
                    $headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
191
                }
192
            }
193
194
            $csvWriter->insertOne($headers);
195
            unset($headers);
196
        }
197
198
        //Remove GridFieldPaginator as we're going to export the entire list.
199
        $gridField->getConfig()->removeComponentsByType(GridFieldPaginator::class);
200
201
        $items = $gridField->getManipulatedList();
202
203
        // @todo should GridFieldComponents change behaviour based on whether others are available in the config?
204
        foreach ($gridField->getConfig()->getComponents() as $component) {
205
            if ($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) {
206
                $items = $component->getManipulatedData($gridField, $items);
207
            }
208
        }
209
210
        /** @var DataObject $item */
211
        foreach ($items->limit(null) as $item) {
0 ignored issues
show
Bug introduced by
The method limit() does not exist on SilverStripe\ORM\Sortable. Since it exists in all sub-types, consider adding an abstract or default implementation to SilverStripe\ORM\Sortable. ( Ignorable by Annotation )

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

211
        foreach ($items->/** @scrutinizer ignore-call */ limit(null) as $item) {
Loading history...
Bug introduced by
The method limit() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Filterable. Are you sure you never get one of those? ( Ignorable by Annotation )

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

211
        foreach ($items->/** @scrutinizer ignore-call */ limit(null) as $item) {
Loading history...
212
            if (!$item->hasMethod('canView') || $item->canView()) {
213
                $columnData = [];
214
215
                foreach ($csvColumns as $columnSource => $columnHeader) {
216
                    if (!is_string($columnHeader) && is_callable($columnHeader)) {
217
                        if ($item->hasMethod($columnSource)) {
218
                            $relObj = $item->{$columnSource}();
219
                        } else {
220
                            $relObj = $item->relObject($columnSource);
221
                        }
222
223
                        $value = $columnHeader($relObj);
224
                    } else {
225
                        $value = $gridField->getDataFieldValue($item, $columnSource);
226
227
                        if ($value === null) {
228
                            $value = $gridField->getDataFieldValue($item, $columnHeader);
229
                        }
230
                    }
231
232
                    $columnData[] = $value;
233
                }
234
235
                $csvWriter->insertOne($columnData);
236
            }
237
238
            if ($item->hasMethod('destroy')) {
239
                $item->destroy();
240
            }
241
        }
242
243
        return (string)$csvWriter;
244
    }
245
246
    /**
247
     * @return array
248
     */
249
    public function getExportColumns()
250
    {
251
        return $this->exportColumns;
252
    }
253
254
    /**
255
     * @param array $cols
256
     *
257
     * @return $this
258
     */
259
    public function setExportColumns($cols)
260
    {
261
        $this->exportColumns = $cols;
262
        return $this;
263
    }
264
265
    /**
266
     * @return string
267
     */
268
    public function getCsvSeparator()
269
    {
270
        return $this->csvSeparator;
271
    }
272
273
    /**
274
     * @param string $separator
275
     *
276
     * @return $this
277
     */
278
    public function setCsvSeparator($separator)
279
    {
280
        $this->csvSeparator = $separator;
281
        return $this;
282
    }
283
284
    /**
285
     * @return string
286
     */
287
    public function getCsvEnclosure()
288
    {
289
        return $this->csvEnclosure;
290
    }
291
292
    /**
293
     * @param string $enclosure
294
     *
295
     * @return $this
296
     */
297
    public function setCsvEnclosure($enclosure)
298
    {
299
        $this->csvEnclosure = $enclosure;
300
        return $this;
301
    }
302
303
    /**
304
     * @return boolean
305
     */
306
    public function getCsvHasHeader()
307
    {
308
        return $this->csvHasHeader;
309
    }
310
311
    /**
312
     * @param boolean $bool
313
     *
314
     * @return $this
315
     */
316
    public function setCsvHasHeader($bool)
317
    {
318
        $this->csvHasHeader = $bool;
319
        return $this;
320
    }
321
}
322