GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#22)
by Bart
19:32 queued 07:46
created

ExportService::findMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Craft;
4
5
/**
6
 * Export service.
7
 *
8
 * Handles common export logics.
9
 *
10
 * @author    Bob Olde Hampsink <[email protected]>
11
 * @copyright Copyright (c) 2015, Bob Olde Hampsink
12
 * @license   http://buildwithcraft.com/license Craft License Agreement
13
 *
14
 * @link      http://github.com/boboldehampsink
15
 */
16
class ExportService extends BaseApplicationComponent
17
{
18
    /**
19
     * Contains the working export service's name.
20
     *
21
     * @var IExportElementType
22
     */
23
    private $_service;
24
25
    /**
26
     * Saves an export map to the database.
27
     *
28
     * @param array $settings
29
     * @param array $map
30
     */
31
    public function saveMap(array $settings, array $map)
32
    {
33
        // Unset non-map settings
34
        unset($settings['limit'], $settings['offset']);
35
        ksort($settings);
36
37
        // Set criteria
38
        $criteria = new \CDbCriteria();
39
        $criteria->condition = 'settings = :settings';
40
        $criteria->params = array(
41
            ':settings' => JsonHelper::encode($settings),
42
        );
43
44
        // Check if we have a map already
45
        $mapRecord = $this->findMap($criteria);
46
47
        if (!count($mapRecord) || $mapRecord->settings != $settings) {
48
49
            // Save settings and map to database
50
            $mapRecord = $this->getNewMap();
51
            $mapRecord->settings = $settings;
52
        }
53
54
        // Save new map to db
55
        $mapRecord->map = $map;
56
        $mapRecord->save(false);
57
    }
58
59
    /**
60
     * @codeCoverageIgnore
61
     * @param \CDbCriteria $criteria
62
     * @return Export_MapRecord|array|null
63
     */
64
    public function findMap(\CDbCriteria $criteria)
65
    {
66
        return Export_MapRecord::model()->find($criteria);
67
    }
68
69
    /**
70
     * @codeCoverageIgnore
71
     * @return Export_MapRecord
72
     */
73
    protected function getNewMap()
74
    {
75
        return new Export_MapRecord();
76
    }
77
78
    /**
79
     * Download the export csv.
80
     *
81
     * @param array $settings
82
     * @return string
83
     * @throws Exception
84
     */
85
    public function download(array $settings)
86
    {
87
        // Get max power
88
        craft()->config->maxPowerCaptain();
89
90
        // Check what service we're gonna need
91
        if (!($this->_service = $this->getService($settings['type']))) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getService($settings['type']) can also be of type boolean. However, the property $_service is declared as type object<Craft\IExportElementType>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
92
            throw new Exception(Craft::t('Unknown Element Type Service called.'));
93
        }
94
95
        // Get delimiter
96
        $delimiter = craft()->plugins->callFirst('registerExportCsvDelimiter');
97
        $delimiter = is_null($delimiter) ? ',' : $delimiter;
98
99
        // Open output buffer
100
        ob_start();
101
102
        // Write to output stream
103
        $export = fopen('php://output', 'w');
104
105
        // Get data
106
        $data = $this->getData($settings);
107
108
        // If there is data, process
109
        if (count($data)) {
110
111
            // Put down columns
112
            fputcsv($export, $this->parseColumns($settings), $delimiter);
113
114
            // Loop trough data
115
            foreach ($data as $element) {
0 ignored issues
show
Bug introduced by
The expression $data of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
116
117
                // Fetch element in case of element id
118
                if (is_numeric($element)) {
119
                    $element = craft()->elements->getElementById($element, $settings['type']);
120
                }
121
122
                // Get fields
123
                $fields = $this->parseFields($settings, $element);
124
125
                // Gather row data
126
                $rows = array();
127
128
                // Loop trough the fields
129
                foreach ($fields as $handle => $data) {
130
131
                    // Parse element data
132
                    $data = $this->parseElementData($handle, $data);
133
134
                    // Parse field data
135
                    $data = $this->parseFieldData($handle, $data);
136
137
                    // Encode and add to rows
138
                    $rows[] = StringHelper::convertToUTF8($data);
139
                }
140
141
                // Add rows to export
142
                fputcsv($export, $rows, $delimiter);
143
            }
144
        }
145
146
        // Close buffer and return data
147
        fclose($export);
148
        $data = ob_get_clean();
149
150
        // Use windows friendly newlines
151
        $data = str_replace("\n", "\r\n", $data);
152
153
        // Return the data to controller
154
        return $data;
155
    }
156
157
    /**
158
     * Get service to use for exporting.
159
     *
160
     * @param string $elementType
161
     *
162
     * @return object|bool
163
     */
164
    public function getService($elementType)
165
    {
166
        // Check if there's a service for this element type elsewhere
167
        $service = craft()->plugins->callFirst('registerExportService', array(
168
            'elementType' => $elementType,
169
        ));
170
171
        // If not, do internal check
172
        if ($service == null) {
173
174
            // Get from right elementType
175
            $service = 'export_'.strtolower($elementType);
176
        }
177
178
        // Check if elementtype can be imported
179
        if (isset(craft()->$service) && craft()->$service instanceof IExportElementType) {
180
181
            // Return this service
182
            return craft()->$service;
183
        }
184
185
        return false;
186
    }
187
188
    /**
189
     * Get data from sources.
190
     *
191
     * @param array $settings
192
     *
193
     * @return array
194
     */
195
    protected function getData(array $settings)
196
    {
197
        // Get other sources
198
        $sources = craft()->plugins->call('registerExportSource', array($settings));
199
200
        // Loop through sources, see if we can get any data
201
        $data = array();
202
        foreach ($sources as $plugin) {
203
            if (is_array($plugin)) {
204
                foreach ($plugin as $source) {
205
                    $data[] = $source;
206
                }
207
            }
208
        }
209
210
        // Cut up data from source
211
        if (array_key_exists('offset', $settings)) {
212
            $data = array_slice($data, $settings['offset'], $settings['limit']);
213
        }
214
215
        // If no data from source, get data by ourselves
216
        if (!count($data)) {
217
218
            // Find data
219
            $criteria = $this->_service->setCriteria($settings);
220
221
            // Gather element ids
222
            $data = $criteria->ids();
223
        }
224
225
        return $data;
226
    }
227
228
    /**
229
     * Parse fields.
230
     *
231
     * @param array $settings
232
     * @param       $element
233
     *
234
     * @return array
235
     */
236
    protected function parseFields(array $settings, $element)
237
    {
238
        $fields = array();
239
240
        // Only get element attributes and content attributes
241
        $attributes = $element;
242
        if ($element instanceof BaseElementModel) {
1 ignored issue
show
Bug introduced by
The class Craft\BaseElementModel does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
243
244
            // Get service
245
            $attributes = $this->_service->getAttributes($settings['map'], $element);
246
        }
247
248
        // Loop through the map
249
        foreach ($settings['map'] as $handle => $data) {
250
251
            // Only get checked fields
252
            if ($data['checked'] == '1' && (array_key_exists($handle, $attributes) || array_key_exists(substr($handle, 0, 5), $attributes))) {
253
254
                // Fill them with data
255
                $fields[$handle] = $attributes[$handle];
256
            }
257
        }
258
259
        return $fields;
260
    }
261
262
    /**
263
     * Parse column names.
264
     *
265
     * @param array $settings [description]
266
     *
267
     * @return string
268
     */
269
    protected function parseColumns(array $settings)
270
    {
271
        $columns = array();
272
273
        // Loop trough map
274
        foreach ($settings['map'] as $handle => $data) {
275
276
            // If checked
277
            if ($data['checked'] == 1) {
278
279
                // Add column
280
                $columns[] = StringHelper::convertToUTF8($data['label']);
281
            }
282
        }
283
284
        return $columns;
285
    }
286
287
    /**
288
     * Parse reserved element values.
289
     *
290
     * @param  $handle
291
     * @param  $data
292
     *
293
     * @return string
294
     */
295
    protected function parseElementData($handle, $data)
296
    {
297
        switch ($handle) {
298
299
            case ExportModel::HandleAuthor:
300
301
                // Get username of author
302
                $data = craft()->users->getUserById($data)->username;
303
304
                break;
305
306
            case ExportModel::HandleEnabled:
307
308
                // Make data human readable
309
                switch ($data) {
310
311
                    case '0':
312
                        $data = Craft::t('No');
313
                        break;
314
315
                    case '1':
316
                        $data = Craft::t('Yes');
317
                        break;
318
319
                }
320
321
                break;
322
323
            case ExportModel::HandlePostDate:
324
            case ExportModel::HandleExpiryDate:
325
326
                // Resolve to string
327
                $data = (string) $data;
328
329
                break;
330
331
        }
332
333
        return $data;
334
    }
335
336
    /**
337
     * Parse field values.
338
     *
339
     * @param string $handle
340
     * @param mixed  $data
341
     *
342
     * @return string
343
     */
344
    public function parseFieldData($handle, $data)
345
    {
346
347
        // Do we have any data at all
348
        if (!is_null($data)) {
349
350
            // Get field info
351
            $field = craft()->fields->getFieldByHandle($handle);
352
353
            // If it's a field ofcourse
354
            if (!is_null($field)) {
355
356
                // For some fieldtypes the're special rules
357
                switch ($field->type) {
358
359
                    case ExportModel::FieldTypeEntries:
360
                    case ExportModel::FieldTypeCategories:
361
                    case ExportModel::FieldTypeAssets:
362
                    case ExportModel::FieldTypeUsers:
363
364
                        // Show names
365
                        $data = $data instanceof ElementCriteriaModel ? implode(', ', $data->find()) : $data;
1 ignored issue
show
Bug introduced by
The class Craft\ElementCriteriaModel does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
366
367
                        break;
368
369
                    case ExportModel::FieldTypeLightswitch:
370
371
                        // Make data human readable
372
                        switch ($data) {
373
374
                            case '0':
375
                                $data = Craft::t('No');
376
                                break;
377
378
                            case '1':
379
                                $data = Craft::t('Yes');
380
                                break;
381
382
                        }
383
384
                        break;
385
386
                    case ExportModel::FieldTypeTable:
387
388
                        // Parse table checkboxes
389
                        $table = array();
390
                        foreach ($data as $row) {
0 ignored issues
show
Bug introduced by
The expression $data of type object|integer|double|string|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
391
392
                            // Keep track of column #
393
                            $i = 1;
394
395
                            // Loop through columns
396
                            foreach ($row as $column => $value) {
397
398
                                // Get column
399
                                $column = isset($field->settings['columns'][$column]) ? $field->settings['columns'][$column] : (isset($field->settings['columns']['col'.$i]) ? $field->settings['columns']['col'.$i] : array('type' => 'dummy'));
400
401
                                // Keep track of column #
402
                                $i++;
403
404
                                // Parse
405
                                $table[] = $column['type'] == 'checkbox' ? ($value == 1 ? Craft::t('Yes') : Craft::t('No')) : $value;
406
                            }
407
                        }
408
409
                        // Return parsed data as array
410
                        $data = $table;
411
412
                        break;
413
414
                    case ExportModel::FieldTypeRichText:
415
                    case ExportModel::FieldTypeDate:
416
                    case ExportModel::FieldTypeRadioButtons:
417
                    case ExportModel::FieldTypeDropdown:
418
419
                        // Resolve to string
420
                        $data = (string) $data;
421
422
                        break;
423
424
                    case ExportModel::FieldTypeCheckboxes:
425
                    case ExportModel::FieldTypeMultiSelect:
426
427
                        // Parse multi select values
428
                        $multi = array();
429
                        foreach ($data as $row) {
0 ignored issues
show
Bug introduced by
The expression $data of type object|integer|double|string|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
430
                            $multi[] = $row->value;
431
                        }
432
433
                        // Return parsed data as array
434
                        $data = $multi;
435
436
                        break;
437
438
                }
439
            }
440
441
            // Get other operations
442
            craft()->plugins->call('registerExportOperation', array(&$data, $handle));
443
        } else {
444
445
            // Don't return null, return empty
446
            $data = '';
447
        }
448
449
        // If it's an object or an array, make it a string
450
        if (is_array($data)) {
451
            $data = StringHelper::arrayToString(ArrayHelper::filterEmptyStringsFromArray(ArrayHelper::flattenArray($data)), ', ');
452
        }
453
454
        // If it's an object, make it a string
455
        if (is_object($data)) {
456
            $data = StringHelper::arrayToString(ArrayHelper::filterEmptyStringsFromArray(ArrayHelper::flattenArray(get_object_vars($data))), ', ');
457
        }
458
459
        return $data;
460
    }
461
}
462