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; |
||||
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) { |
||||
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
Loading history...
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
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 |