Completed
Push — master ( 906332...92d568 )
by Hamish
02:01
created

GridFieldQueuedExportButton::downloadExport()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 1
eloc 5
nc 1
nop 2
1
<?php
2
3
/**
4
 * A button you can add to a GridField to export that GridField as a CSV. Should work with any sized GridField,
5
 * as the export is done using a queuedjob in the background.
6
 */
7
class GridFieldQueuedExportButton implements GridField_HTMLProvider, GridField_ActionProvider, GridField_URLHandler {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
9
    /**
10
     * @var array Map of a property name on the exported objects, with values being the column title in the CSV file.
11
     * Note that titles are only used when {@link $csvHasHeader} is set to TRUE.
12
     */
13
    protected $exportColumns;
14
15
    /**
16
     * @var string
17
     */
18
    protected $csvSeparator = ",";
19
20
    /**
21
     * @var boolean
22
     */
23
    protected $csvHasHeader = true;
24
25
    /**
26
     * Fragment to write the button to
27
     */
28
    protected $targetFragment;
29
30
    /**
31
     * @param string $targetFragment The HTML fragment to write the button into
32
     * @param array $exportColumns The columns to include in the export
33
     */
34
    public function __construct($targetFragment = "after", $exportColumns = null) {
35
        $this->targetFragment = $targetFragment;
36
        $this->exportColumns = $exportColumns;
0 ignored issues
show
Documentation Bug introduced by
It seems like $exportColumns can be null. However, the property $exportColumns is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
37
    }
38
39
    /**
40
     * Place the export button in a <p> tag below the field
41
     */
42
    public function getHTMLFragments($gridField) {
43
        $button = new GridField_FormAction(
44
            $gridField,
45
            'export',
46
            _t('TableListField.CSVEXPORT', 'Export to CSV'),
47
            'export',
48
            null
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
49
        );
50
        $button->setAttribute('data-icon', 'download-csv');
51
        $button->addExtraClass('action_batch_export');
52
        $button->setForm($gridField->getForm());
53
        return array(
54
            $this->targetFragment => '<p class="grid-csv-button">' . $button->Field() . '</p>',
55
        );
56
    }
57
58
    /**
59
     * This class is an action button
60
     */
61
    public function getActions($gridField) {
62
        return array('export', 'findgridfield');
63
    }
64
65
    public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
66
        if ($actionName == 'export') {
67
            return $this->startExport($gridField);
68
        } else if ($actionName == 'findgridfield') {
69
            return new GridFieldQueuedExportButton_Response($gridField);
70
        }
71
    }
72
73
    function startExport($gridField) {
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...
74
        $job = new GenerateCSVJob();
75
76
        // Set the parameters that allow re-discovering this gridfield during background execution
77
        $job->setGridField($gridField);
78
        $job->setSession(Controller::curr()->getSession()->get_all());
79
80
        // Set the parameters that control CSV exporting
81
        $job->setSeparator($this->csvSeparator);
82
        $job->setIncludeHeader($this->csvHasHeader);
83
        if ($this->exportColumns) $job->setColumns($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...
84
85
        // Queue the job
86
        singleton('QueuedJobService')->queueJob($job);
87
88
        // Redirect to the status update page
89
        return Controller::curr()->redirect($gridField->Link() . '/export/' . $job->getSignature());
90
    }
91
92
    /**
93
     * This class is also a URL handler
94
     */
95
    public function getURLHandlers($gridField) {
96
        return array(
97
            'export/$ID'          => 'checkExport',
98
            'export_download/$ID' => 'downloadExport'
99
        );
100
    }
101
102
    /**
103
     * Handle the export, for both the action button and the URL
104
     */
105
    public function checkExport($gridField, $request = null) {
106
        $id = $request->param('ID');
107
        $job = QueuedJobDescriptor::get()->filter('Signature', $id)->first();
108
109
        $controller = $gridField->getForm()->getController();
110
111
        $breadcrumbs = $controller->Breadcrumbs(false);
112
        $breadcrumbs->push(new ArrayData(array(
113
            'Title' => 'Export CSV',
114
            'Link'  => false
115
        )));
116
117
        $parents = $controller->Breadcrumbs(false)->items;
118
        $backlink = array_pop($parents)->Link;
119
120
        $data = new ArrayData(array(
121
            'Backlink'    => $backlink,
122
            'Breadcrumbs' => $breadcrumbs,
123
            'GridName'    => $gridField->getname()
124
        ));
125
126
        if ($job->JobStatus == QueuedJob::STATUS_COMPLETE) {
127
            $data->Link = $gridField->Link() . '/export_download/' . $job->Signature;
128
        } else if ($job->JobStatus == QueuedJob::STATUS_BROKEN) {
129
            $data->ErrorMessage = "Sorry, but there was an error exporting the CSV";
130
        } else if ($job->JobStatus == QueuedJob::STATUS_CANCELLED) {
131
            $data->ErrorMessage = "This export job was cancelled";
132
        } else {
133
            $data->Count = $job->StepsProcessed;
134
            $data->Total = $job->TotalSteps;
135
        }
136
137
        $return = $data->renderWith('GridFieldQueuedExportButton');
138
139
        if ($request->isAjax()) {
140
            return $return;
141
        } else {
142
            return $controller->customise(array('Content' => $return));
143
        }
144
    }
145
146
    public function downloadExport($gridField, $request = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $gridField is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
147
        $id = $request->param('ID');
148
149
        $now = Date("d-m-Y-H-i");
150
        $fileName = "export-$now.csv";
151
152
        return SS_HTTPRequest::send_file(file_get_contents('/tmp/' . $id . '.csv'), $fileName, 'text/csv');
153
    }
154
155
    /**
156
     * @return array
157
     */
158
    public function getExportColumns() {
159
        return $this->exportColumns;
160
    }
161
162
    /**
163
     * @param array
164
     */
165
    public function setExportColumns($cols) {
166
        $this->exportColumns = $cols;
167
        return $this;
168
    }
169
170
    /**
171
     * @return string
172
     */
173
    public function getCsvSeparator() {
174
        return $this->csvSeparator;
175
    }
176
177
    /**
178
     * @param string
179
     */
180
    public function setCsvSeparator($separator) {
181
        $this->csvSeparator = $separator;
182
        return $this;
183
    }
184
185
    /**
186
     * @return boolean
187
     */
188
    public function getCsvHasHeader() {
189
        return $this->csvHasHeader;
190
    }
191
192
    /**
193
     * @param boolean
194
     */
195
    public function setCsvHasHeader($bool) {
196
        $this->csvHasHeader = $bool;
197
        return $this;
198
    }
199
}
200
201
/**
202
 * A special type of SS_HTTPResponse that GridFieldQueuedExportButton returns in response to the "findgridfield"
203
 * action, which includes a reference to the gridfield
204
 */
205
class GridFieldQueuedExportButton_Response extends SS_HTTPResponse {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
206
    private $gridField;
207
208
    public function __construct(GridField $gridField) {
209
        $this->gridField = $gridField;
210
        parent::__construct('', 500);
211
    }
212
213
    public function getGridField() {
214
        return $this->gridField;
215
    }
216
}
217