Completed
Push — master ( 59108a...f6fe87 )
by Ingo
01:59
created

GenerateCSVJob::getGridField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 47
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 47
rs 9.0303
cc 2
eloc 24
nc 2
nop 0
1
<?php
2
3
class GenerateCSVJob extends AbstractQueuedJob {
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...
4
5
    public function __construct() {
6
        $this->ID = uniqid();
7
        $this->Seperator = ',';
8
        $this->IncludeHeader = true;
9
        $this->HeadersOutput = false;
10
        $this->totalSteps = 1;
11
    }
12
13
    public function getJobType() {
14
        return QueuedJob::QUEUED;
15
    }
16
17
    public function getTitle() {
18
        return "Export a CSV of a Gridfield";
19
    }
20
21
    public function getSignature() {
22
        return md5(get_class($this) . '-' . $this->ID);
23
    }
24
25
    function setGridField($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...
26
        $this->GridFieldName = $gridField->getName();
27
        $this->GridFieldURL = $gridField->Link();
28
    }
29
30
    function setSession($session) {
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...
31
        // None of the gridfield actions are needed, and they make the stored session bigger, so pull
32
        // them out.
33
        $actionkeys = array_filter(array_keys($session), function ($i) {
34
            return strpos($i, 'gf_') === 0;
35
        });
36
37
        $session = array_diff_key($session, array_flip($actionkeys));
38
39
        // This causes problems with logins
40
        unset($session['HTTP_USER_AGENT']);
41
42
        $this->Session = $session;
43
    }
44
45
    function setColumns($columns) {
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...
46
        $this->Columns = $columns;
47
    }
48
49
    function setSeparator($seperator) {
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...
50
        $this->Separator = $seperator;
51
    }
52
53
    function setIncludeHeader($includeHeader) {
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...
54
        $this->IncludeHeader = $includeHeader;
55
    }
56
57
    protected function getGridField() {
58
        $session = $this->Session;
59
60
        // Store state in session, and pass ID to client side.
61
        $state = array(
62
            'grid'       => $this->GridFieldName,
63
            'actionName' => 'findgridfield',
64
            'args'       => null
65
        );
66
67
        // Ensure $id doesn't contain only numeric characters
68
        $id = 'gf_' . substr(md5(serialize($state)), 0, 8);
69
70
        // Simulate CSRF token use, hardcode to a random value in our fake session
71
        // so GridField can evaluate it in the Director::test() execution
72
        $token = Injector::inst()->create('RandomGenerator')->randomToken('sha1');
73
74
        // Add new form action into session for GridField to find when Director::test is called below
75
        $session[$id] = $state;
76
        $session['SecurityID'] = $token;
77
78
        // Construct the URL
79
        $actionKey = 'action_gridFieldAlterAction?' . http_build_query(['StateID' => $id]);
80
        $actionValue = 'Find Gridfield';
81
82
        $url = Controller::join_links(
83
            $this->GridFieldURL,
84
            '?' .http_build_query([$actionKey => $actionValue, 'SecurityID' => $token])
85
        );
86
87
        // Restore into the current session the user the job is exporting as
88
        Session::set("loggedInAs", $session['loggedInAs']);
89
90
        // Then make a sub-query that should return a special SS_HTTPResponse with the gridfield object
91
        $res = Director::test($url, null, new Session($session), 'GET');
92
93
        // Great, it did, we can return it
94
        if ($res instanceof GridFieldQueuedExportButton_Response) {
95
            $gridField = $res->getGridField();
96
            $gridField->getConfig()->removeComponentsByType('GridFieldPaginator');
97
            $gridField->getConfig()->removeComponentsByType('GridFieldPageCount');
98
99
            return $gridField;
100
        } else {
101
            user_error('Couldn\'t restore GridField', E_USER_ERROR);
102
        }
103
    }
104
105
    protected function outputHeader($gridField, $columns) {
106
        $fileData = '';
107
        $separator = $this->Separator;
108
109
        $headers = array();
110
111
        // determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
112
        // source name as the header instead
113
        foreach ($columns as $columnSource => $columnHeader) {
114
            $headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
115
        }
116
117
        $fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
118
        $fileData .= "\n";
119
120
        file_put_contents('/tmp/' . $this->getSignature() . '.csv', $fileData, FILE_APPEND);
121
    }
122
123
    protected function outputRows($gridField, $columns, $start, $count) {
124
        $fileData = '';
125
        $separator = $this->Separator;
126
127
        $items = $gridField->getManipulatedList();
128
        $items = $items->limit($count, $start);
129
130
        foreach ($items as $item) {
131
            if (!$item->hasMethod('canView') || $item->canView()) {
132
                $columnData = array();
133
134
                foreach ($columns as $columnSource => $columnHeader) {
135
                    if (!is_string($columnHeader) && is_callable($columnHeader)) {
136
                        if ($item->hasMethod($columnSource)) {
137
                            $relObj = $item->{$columnSource}();
138
                        } else {
139
                            $relObj = $item->relObject($columnSource);
140
                        }
141
142
                        $value = $columnHeader($relObj);
143
                    } else {
144
                        $value = $gridField->getDataFieldValue($item, $columnSource);
145
146
                        if ($value === null) {
147
                            $value = $gridField->getDataFieldValue($item, $columnHeader);
148
                        }
149
                    }
150
151
                    $value = str_replace(array("\r", "\n"), "\n", $value);
152
                    $columnData[] = '"' . str_replace('"', '""', $value) . '"';
153
                }
154
155
                $fileData .= implode($separator, $columnData);
156
                $fileData .= "\n";
157
            }
158
159
            if ($item->hasMethod('destroy')) {
160
                $item->destroy();
161
            }
162
        }
163
164
        file_put_contents('/tmp/' . $this->getSignature() . '.csv', $fileData, FILE_APPEND);
165
    }
166
167
168
    public function setup() {
169
        $gridField = $this->getGridField();
170
        $this->totalSteps = $gridField->getManipulatedList()->count();
171
    }
172
173
    /**
174
     * Generate export fields for CSV.
175
     *
176
     * @param GridField $gridField
0 ignored issues
show
Bug introduced by
There is no parameter named $gridField. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
177
     * @return array
178
     */
179
    public function process() {
180
        $gridField = $this->getGridField();
181
        $columns = $this->Columns ?: singleton($gridField->getModelClass())->summaryFields();
182
183
        if ($this->IncludeHeader && !$this->HeadersOutput) {
184
            $this->outputHeader($gridField, $columns);
185
            $this->HeadersOutput = true;
186
        }
187
188
        $this->outputRows($gridField, $columns, $this->currentStep, 100);
189
190
        $this->currentStep += 100;
191
192
        if ($this->currentStep >= $this->totalSteps) {
193
            $this->isComplete = true;
194
        }
195
    }
196
}
197