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 — integration (#2604)
by Brendan
04:24
created

EntryManager::fetch()   F

Complexity

Conditions 36
Paths 1251

Size

Total Lines 113
Code Lines 61

Duplication

Lines 11
Ratio 9.73 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 36
c 5
b 0
f 0
dl 11
loc 113
rs 2
eloc 61
nc 1251
nop 10

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
    /**
4
     * @package toolkit
5
     */
6
7
    /**
8
     * The `EntryManager` is responsible for all `Entry` objects in Symphony.
9
     * Entries are stored in the database in a cluster of tables. There is a
10
     * parent entry row stored in `tbl_entries` and then each field's data is
11
     * stored in a separate table, `tbl_entries_data_{field_id}`. Where Field ID
12
     * is generated when the Section is saved. This Manager provides basic
13
     * add, edit, delete and fetching methods for Entries.
14
     */
15
    class EntryManager
16
    {
17
        /**
18
         * The Field ID that will be used to sort when fetching Entries, defaults
19
         * to null, which implies the Entry ID (id column in `tbl_entries`)
20
         *
21
         * @var integer
22
         */
23
        protected static $_fetchSortField = null;
24
25
        /**
26
         * The direction that entries should be sorted in, available options are
27
         * RAND, ASC or DESC. Defaults to null, which implies ASC
28
         *
29
         * @var string
30
         */
31
        protected static $_fetchSortDirection = null;
32
33
        /**
34
         * Convenience function that will set sorting field and direction
35
         * by calling `setFetchSortingField()` & `setFetchSortingDirection()`
36
         *
37
         * @see toolkit.EntryManager#setFetchSortingField()
38
         * @see toolkit.EntryManager#setFetchSortingDirection()
39
         * @param integer $field_id
40
         *  The ID of the Field that should be sorted on
41
         * @param string $direction
42
         *  The direction that entries should be sorted in, available options
43
         *  are RAND, ASC or DESC. Defaults to ASC
44
         */
45
        public static function setFetchSorting($field_id, $direction = 'ASC')
46
        {
47
            self::setFetchSortingField($field_id);
48
            self::setFetchSortingDirection($direction);
49
        }
50
51
        /**
52
         * Sets the field to applying the sorting direction on when fetching
53
         * entries
54
         *
55
         * @param integer $field_id
56
         *  The ID of the Field that should be sorted on
57
         */
58
        public static function setFetchSortingField($field_id)
59
        {
60
            self::$_fetchSortField = $field_id;
61
        }
62
63
        /**
64
         * Setter function for the default sorting direction of the Fetch
65
         * function. Available options are RAND, ASC or DESC.
66
         *
67
         * @param string $direction
68
         *  The direction that entries should be sorted in, available options
69
         *  are RAND, ASC or DESC.
70
         */
71
        public static function setFetchSortingDirection($direction)
72
        {
73
            $direction = strtoupper($direction);
74
75
            if ($direction === 'RANDOM') {
76
                $direction = 'RAND';
77
            }
78
79
            self::$_fetchSortDirection = (in_array($direction, array('RAND', 'ASC', 'DESC')) ? $direction : null);
80
        }
81
82
        /**
83
         * Returns an object representation of the sorting for the
84
         * EntryManager, with the field and direction provided
85
         *
86
         * @return StdClass
87
         */
88
        public static function getFetchSorting()
89
        {
90
            return (object)array(
91
                'field' => self::$_fetchSortField,
92
                'direction' => self::$_fetchSortDirection
93
            );
94
        }
95
96
        /**
97
         * Given an Entry object, iterate over all of the fields in that object
98
         * an insert them into their relevant entry tables.
99
         *
100
         * @param Entry $entry
101
         *  An Entry object to insert into the database
102
         * @throws DatabaseException
103
         * @return boolean
104
         */
105
        public static function add(Entry $entry)
106
        {
107
            $fields = $entry->get();
108
            Symphony::Database()->insert($fields, 'tbl_entries');
109
110
            if (!$entry_id = Symphony::Database()->getInsertID()) {
111
                return false;
112
            }
113
114
            foreach ($entry->getData() as $field_id => $field) {
115
                if (!is_array($field) || empty($field)) {
116
                    continue;
117
                }
118
119
                Symphony::Database()->delete('tbl_entries_data_' . $field_id, " `entry_id` = ?", array($entry_id));
120
121
                $data = array(
122
                    'entry_id' => $entry_id
123
                );
124
125
                $fields = array();
126
127 View Code Duplication
                foreach ($field as $key => $value) {
128
                    if (is_array($value)) {
129
                        foreach ($value as $ii => $v) {
130
                            $fields[$ii][$key] = $v;
131
                        }
132
                    } else {
133
                        $fields[max(0, count($fields) - 1)][$key] = $value;
134
                    }
135
                }
136
137
                $fieldCount = count($fields);
138
                for ($ii = 0; $ii < $fieldCount; $ii++) {
139
                    $fields[$ii] = array_merge($data, $fields[$ii]);
140
                }
141
142
                Symphony::Database()->insert($fields, 'tbl_entries_data_' . $field_id);
143
            }
144
145
            $entry->set('id', $entry_id);
146
147
            return true;
148
        }
149
150
        /**
151
         * Update an existing Entry object given an Entry object
152
         *
153
         * @param Entry $entry
154
         *  An Entry object
155
         * @throws DatabaseException
156
         * @return boolean
157
         */
158
        public static function edit(Entry $entry)
159
        {
160
            // Update modification date.
161
            Symphony::Database()->update(
162
                array(
163
                    'modification_date' => $entry->get('modification_date'),
164
                    'modification_date_gmt' => $entry->get('modification_date_gmt')
165
                ),
166
                'tbl_entries',
167
                ' `id` = ?',
168
                array(
169
                    $entry->get('id')
170
                )
171
            );
172
173
            // Iterate over all data for this entry, deleting existing data first
174
            // then inserting a new row for the data
175
            foreach ($entry->getData() as $field_id => $field) {
176
                if (empty($field_id)) {
177
                    continue;
178
                }
179
180
                try {
181
                    Symphony::Database()->delete('tbl_entries_data_' . $field_id, '`entry_id` = ?',
182
                        array($entry->get('id')));
183
                } catch (Exception $e) {
184
                    // Discard?
185
                }
186
187
                if (!is_array($field) || empty($field)) {
188
                    continue;
189
                }
190
191
                $data = array(
192
                    'entry_id' => $entry->get('id')
193
                );
194
195
                $fields = array();
196
197 View Code Duplication
                foreach ($field as $key => $value) {
198
                    if (is_array($value)) {
199
                        foreach ($value as $ii => $v) {
200
                            $fields[$ii][$key] = $v;
201
                        }
202
                    } else {
203
                        $fields[max(0, count($fields) - 1)][$key] = $value;
204
                    }
205
                }
206
207
                foreach ($fields as $index => $field_data) {
208
                    $fields[$index] = array_merge($data, $field_data);
209
                }
210
211
                Symphony::Database()->insert($fields, 'tbl_entries_data_' . $field_id);
212
            }
213
214
            return true;
215
        }
216
217
        /**
218
         * Given an Entry ID, or an array of Entry ID's, delete all
219
         * data associated with this Entry using a Field's `entryDataCleanup()`
220
         * function, and then remove this Entry from `tbl_entries`. If the `$entries`
221
         * all belong to the same section, passing `$section_id` will improve
222
         * performance
223
         *
224
         * @param array|integer $entries
225
         *  An entry_id, or an array of entry id's to delete
226
         * @param integer $section_id (optional)
227
         *  If possible, the `$section_id` of the the `$entries`. This parameter
228
         *  should be left as null if the `$entries` array contains entry_id's for
229
         *  multiple sections.
230
         * @throws DatabaseException
231
         * @throws Exception
232
         * @return boolean
233
         */
234
        public static function delete($entries, $section_id = null)
235
        {
236
            $needs_data = true;
237
238
            if (!is_array($entries)) {
239
                $entries = array($entries);
240
            }
241
242
            // Get the section's schema
243
            if (!is_null($section_id)) {
244
                $section = SectionManager::fetch($section_id);
245
246
                if ($section instanceof Section) {
247
                    $fields = $section->fetchFields();
248
                    $data = array();
249
250
                    foreach ($fields as $field) {
0 ignored issues
show
Bug introduced by
The expression $fields of type array|object<Field> 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...
251
                        $reflection = new ReflectionClass($field);
252
                        // This field overrides the default implementation, so pass it data.
253
                        $data[$field->get('element_name')] = $reflection->getMethod('entryDataCleanup')->class === 'Field' ? false : true;
254
                    }
255
256
                    $data = array_filter($data);
257
258
                    if (empty($data)) {
259
                        $needs_data = false;
260
                    }
261
                }
262
            }
263
264
            // We'll split $entries into blocks of 2500 (random number)
265
            // and process the deletion in chunks.
266
            $chunks = array_chunk($entries, 2500);
267
268
            foreach ($chunks as $chunk) {
269
                // If we weren't given a `section_id` we'll have to process individually
270
                // If we don't need data for any field, we can process the whole chunk
271
                // without building Entry objects, otherwise we'll need to build
272
                // Entry objects with data
273
                if (is_null($section_id) || !$needs_data) {
274
                    $entries = $chunk;
275
                } elseif ($needs_data) {
276
                    $entries = self::fetch($chunk, $section_id);
277
                }
278
279
                if ($needs_data) {
280
                    foreach ($entries as $id) {
281
                        // Handles the case where `section_id` was not provided
282
                        if (is_null($section_id)) {
283
                            $e = self::fetch($id);
284
285
                            if (!is_array($e)) {
286
                                continue;
287
                            }
288
289
                            $e = current($e);
290
291
                            if (!$e instanceof Entry) {
292
                                continue;
293
                            }
294
295
                            // If we needed data, whole Entry objects will exist
296
                        } elseif ($needs_data) {
297
                            $e = $id;
298
                            $id = $e->get('id');
299
                        }
300
301
                        // Time to loop over it and send it to the fields.
302
                        // Note we can't rely on the `$fields` array as we may
303
                        // also be dealing with the case where `section_id` hasn't
304
                        // been provided
305
                        $entry_data = $e->getData();
0 ignored issues
show
Bug introduced by
The variable $e does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
306
307
                        foreach ($entry_data as $field_id => $data) {
308
                            $field = FieldManager::fetch($field_id);
309
                            $field->entryDataCleanup($id, $data);
310
                        }
311
                    }
312
                } else {
313
                    foreach ($fields as $field) {
0 ignored issues
show
Bug introduced by
The variable $fields does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The expression $fields of type array|object<Field> 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...
314
                        $field->entryDataCleanup($chunk);
315
                    }
316
                }
317
318
                $placeholders = Database::addPlaceholders($chunk);
319
                Symphony::Database()->delete('tbl_entries', " `id` IN ($placeholders) ", $chunk);
320
            }
321
322
            return true;
323
        }
324
325
        /**
326
         * This function will return an array of Entry objects given an ID or an array of ID's.
327
         * Do not provide `$entry_id` as an array if not specifying the `$section_id`. This function
328
         * is commonly passed custom SQL statements through the `$where` and `$join` parameters
329
         * that is generated by the fields of this section
330
         *
331
         * @param integer|array $entry_id
332
         *  An array of Entry ID's or an Entry ID to return
333
         * @param integer $section_id
334
         *  The ID of the Section that these entries are contained in
335
         * @param integer $limit
336
         *  The limit of entries to return
337
         * @param integer $start
338
         *  The starting offset of the entries to return
339
         * @param string $where
340
         *  Any custom WHERE clauses. The tbl_entries alias is `e`
341
         * @param string $joins
342
         *  Any custom JOIN's
343
         * @param boolean $group
344
         *  Whether the entries need to be grouped by Entry ID or not
345
         * @param boolean $buildentries
346
         *  Whether to return an array of entry ID's or Entry objects. Defaults to
347
         *  true, which will return Entry objects
348
         * @param array $element_names
349
         *  Choose whether to get data from a subset of fields or all fields in a section,
350
         *  by providing an array of field names. Defaults to null, which will load data
351
         *  from all fields in a section.
352
         * @param boolean $enable_sort
353
         *  Defaults to true, if false this function will not apply any sorting
354
         * @throws Exception
355
         * @return array
356
         *  If `$buildentries` is true, this function will return an array of Entry objects,
357
         *  otherwise it will return an associative array of Entry data from `tbl_entries`
358
         */
359
        public static function fetch(
360
            $entry_id = null,
361
            $section_id = null,
362
            $limit = null,
363
            $start = null,
364
            $where = null,
365
            $joins = null,
366
            $group = false,
367
            $buildentries = true,
368
            $element_names = null,
369
            $enable_sort = true
370
        ) {
371
            $sort = null;
372
373
            if (!$entry_id && !$section_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $section_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
374
                return false;
375
            }
376
377
            if (!$section_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $section_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
378
                $section_id = self::fetchEntrySectionID($entry_id);
0 ignored issues
show
Bug introduced by
It seems like $entry_id defined by parameter $entry_id on line 360 can also be of type array or null; however, EntryManager::fetchEntrySectionID() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
379
            }
380
381
            $section = SectionManager::fetch($section_id);
382
            if (!is_object($section)) {
383
                return false;
384
            }
385
386
            // SORTING
387
            // A single $entry_id doesn't need to be sorted on, or if it's explicitly disabled
388
            if ((!is_array($entry_id) && !is_null($entry_id) && is_int($entry_id)) || !$enable_sort) {
389
                $sort = null;
390
391
                // Check for RAND first, since this works independently of any specific field
392
            } elseif (self::$_fetchSortDirection === 'RAND') {
393
                $sort = 'ORDER BY RAND() ';
394
395
                // Handle Creation Date or the old Date sorting
396
            } elseif (self::$_fetchSortField === 'system:creation-date' || self::$_fetchSortField === 'date') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'system:creation-date' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'date' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
397
                $sort = sprintf('ORDER BY `e`.`creation_date_gmt` %s', self::$_fetchSortDirection);
398
399
                // Handle Modification Date sorting
400
            } elseif (self::$_fetchSortField === 'system:modification-date') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'system:modification-date' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
401
                $sort = sprintf('ORDER BY `e`.`modification_date_gmt` %s', self::$_fetchSortDirection);
402
403
                // Handle sorting for System ID
404 View Code Duplication
            } elseif (self::$_fetchSortField === 'system:id' || self::$_fetchSortField === 'id') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'system:id' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'id' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
405
                $sort = sprintf('ORDER BY `e`.`id` %s', self::$_fetchSortDirection);
406
407
                // Handle when the sort field is an actual Field
408
            } elseif (self::$_fetchSortField && $field = FieldManager::fetch(self::$_fetchSortField)) {
409
                if ($field->isSortable()) {
410
                    $field->buildSortingSQL($joins, $where, $sort, self::$_fetchSortDirection);
411
                }
412
413
                // Handle if the section has a default sorting field
414
            } elseif ($section->getSortingField() && $field = FieldManager::fetch($section->getSortingField())) {
415
                if ($field->isSortable()) {
416
                    $field->buildSortingSQL($joins, $where, $sort, $section->getSortingOrder());
417
                }
418
419
                if (!$group) {
420
                    $group = $field->requiresSQLGrouping();
421
                }
422
            } else {
423 View Code Duplication
                if (self::$_fetchSortField === 'system:id' || self::$_fetchSortField === 'id') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'system:id' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of self::$_fetchSortField (integer) and 'id' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
424
                    $sort = 'ORDER BY `e`.`id` ' . self::$_fetchSortDirection;
425
                } else {
426
                    $sort = sprintf('ORDER BY `e`.`id` %s', self::$_fetchSortDirection);
427
                }
428
            }
429
430
            if (!empty($field) && !$group) {
431
                $group = $field->requiresSQLGrouping();
432
            }
433
434
            if ($entry_id && !is_array($entry_id)) {
435
                $entry_id = array($entry_id);
436
            }
437
438
            $sql = sprintf("
439
            SELECT %s`e`.`id`, `e`.section_id, `e`.`author_id`,
440
                `e`.`creation_date` AS `creation_date`,
441
                `e`.`modification_date` AS `modification_date`
442
            FROM `tbl_entries` AS `e`
443
            %s
444
            WHERE 1
445
            %s
446
            %s
447
            %s
448
            %s
449
            %s
450
            ",
451
                $group ? 'DISTINCT ' : '',
452
                $joins,
453
                $entry_id ? "AND `e`.`id` IN ('" . implode("', '", $entry_id) . "') " : '',
454
                $section_id ? sprintf("AND `e`.`section_id` = %d", $section_id) : '',
455
                $where,
456
                $sort,
457
                $limit ? sprintf('LIMIT %d, %d', $start, $limit) : ''
458
            );
459
460
            $rows = Symphony::Database()->fetch($sql);
461
462
            // Create UNIX timestamps, as it has always been (Re: #2501)
463
            foreach ($rows as &$entry) {
0 ignored issues
show
Bug introduced by
The expression $rows of type 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...
464
                $entry['creation_date'] = DateTimeObj::get('U', $entry['creation_date']);
465
                $entry['modification_date'] = DateTimeObj::get('U', $entry['modification_date']);
466
            }
467
            unset($entry);
468
469
            return ($buildentries && (is_array($rows) && !empty($rows)) ? self::__buildEntries($rows, $section_id,
470
                $element_names) : $rows);
471
        }
472
473
        /**
474
         * Given an Entry ID, return the Section ID that it belongs to
475
         *
476
         * @param integer $entry_id
477
         *  The ID of the Entry to return it's section
478
         * @return integer
479
         *  The Section ID for this Entry's section
480
         */
481
        public static function fetchEntrySectionID($entry_id)
482
        {
483
            return Symphony::Database()->fetchVar('section_id', 0,
484
                "SELECT `section_id` FROM `tbl_entries` WHERE `id` = ? LIMIT 1",
485
                array($entry_id)
486
            );
487
        }
488
489
        /**
490
         * Given an array of Entry data from `tbl_entries` and a section ID, return an
491
         * array of Entry objects. For performance reasons, it's possible to pass an array
492
         * of field handles via `$element_names`, so that only a subset of the section schema
493
         * will be queried. This function currently only supports Entry from one section at a
494
         * time.
495
         *
496
         * @param array $rows
497
         *  An array of Entry data from `tbl_entries` including the Entry ID, Entry section,
498
         *  the ID of the Author who created the Entry, and a Unix timestamp of creation
499
         * @param integer $section_id
500
         *  The section ID of the entries in the `$rows`
501
         * @param array $element_names
502
         *  Choose whether to get data from a subset of fields or all fields in a section,
503
         *  by providing an array of field names. Defaults to null, which will load data
504
         *  from all fields in a section.
505
         * @throws DatabaseException
506
         * @return array
507
         *  An array of Entry objects
508
         */
509
        public static function __buildEntries(array $rows, $section_id, $element_names = null)
510
        {
511
            $entries = array();
512
513
            if (empty($rows)) {
514
                return $entries;
515
            }
516
517
            $schema = FieldManager::fetchFieldIDFromElementName($element_names, $section_id);
0 ignored issues
show
Bug introduced by
It seems like $element_names defined by parameter $element_names on line 509 can also be of type null; however, FieldManager::fetchFieldIDFromElementName() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
518
519
            if (is_int($schema)) {
520
                $schema = array($schema);
521
            }
522
523
            $raw = array();
524
            $rows_string = '';
525
526
            // Append meta data:
527
            foreach ($rows as $entry) {
528
                $raw[$entry['id']]['meta'] = $entry;
529
                $rows_string .= $entry['id'] . ',';
530
            }
531
532
            $rows_string = trim($rows_string, ',');
533
534
            // Append field data:
535
            if (is_array($schema)) {
536
                foreach ($schema as $field_id) {
537
                    try {
538
                        $row = Symphony::Database()->fetch("SELECT * FROM `tbl_entries_data_{$field_id}` WHERE `entry_id` IN ($rows_string) ORDER BY `id` ASC");
539
                    } catch (Exception $e) {
540
                        // No data due to error
541
                        continue;
542
                    }
543
544
                    if (!is_array($row) || empty($row)) {
545
                        continue;
546
                    }
547
548
                    foreach ($row as $r) {
549
                        $entry_id = $r['entry_id'];
550
551
                        unset($r['id']);
552
                        unset($r['entry_id']);
553
554
                        if (!isset($raw[$entry_id]['fields'][$field_id])) {
555
                            $raw[$entry_id]['fields'][$field_id] = $r;
556
                        } else {
557
                            foreach (array_keys($r) as $key) {
558
                                // If this field already has been set, we need to take the existing
559
                                // value and make it array, adding the current value to it as well
560
                                // There is a special check incase the the field's value has been
561
                                // purposely set to null in the database.
562
                                if (
563
                                    (
564
                                        isset($raw[$entry_id]['fields'][$field_id][$key])
565
                                        || is_null($raw[$entry_id]['fields'][$field_id][$key])
566
                                    )
567
                                    && !is_array($raw[$entry_id]['fields'][$field_id][$key])
568
                                ) {
569
                                    $raw[$entry_id]['fields'][$field_id][$key] = array(
570
                                        $raw[$entry_id]['fields'][$field_id][$key],
571
                                        $r[$key]
572
                                    );
573
574
                                    // This key/value hasn't been set previously, so set it
575
                                } elseif (!isset($raw[$entry_id]['fields'][$field_id][$key])) {
576
                                    $raw[$entry_id]['fields'][$field_id] = array($r[$key]);
577
578
                                    // This key has been set and it's an array, so just append
579
                                    // this value onto the array
580
                                } else {
581
                                    $raw[$entry_id]['fields'][$field_id][$key][] = $r[$key];
582
                                }
583
                            }
584
                        }
585
                    }
586
                }
587
            }
588
589
            // Loop over the array of entry data and convert it to an array of Entry objects
590
            foreach ($raw as $entry) {
591
                $obj = self::create();
592
593
                $obj->set('id', $entry['meta']['id']);
594
                $obj->set('author_id', $entry['meta']['author_id']);
595
                $obj->set('section_id', $entry['meta']['section_id']);
596
                $obj->set('creation_date', DateTimeObj::get('c', $entry['meta']['creation_date']));
597
598
                if (isset($entry['meta']['modification_date'])) {
599
                    $obj->set('modification_date', DateTimeObj::get('c', $entry['meta']['modification_date']));
600
                } else {
601
                    $obj->set('modification_date', $obj->get('creation_date'));
602
                }
603
604
                if (isset($entry['fields']) && is_array($entry['fields'])) {
605
                    foreach ($entry['fields'] as $field_id => $data) {
606
                        $obj->setData($field_id, $data);
607
                    }
608
                }
609
610
                $entries[] = $obj;
611
            }
612
613
            return $entries;
614
        }
615
616
        /**
617
         * Creates a new Entry object using this class as the parent.
618
         *
619
         * @return Entry
620
         */
621
        public static function create()
622
        {
623
            return new Entry;
624
        }
625
626
        /**
627
         * Returns an array of Entry objects, with some basic pagination given
628
         * the number of Entry's to return and the current starting offset. This
629
         * function in turn calls the fetch function that does alot of the heavy
630
         * lifting. For instance, if there are 60 entries in a section and the pagination
631
         * dictates that per page, 15 entries are to be returned, by passing 2 to
632
         * the $page parameter you could return entries 15-30
633
         *
634
         * @param integer $page
635
         *  The page to return, defaults to 1
636
         * @param integer $section_id
637
         *  The ID of the Section that these entries are contained in
638
         * @param integer $entriesPerPage
639
         *  The number of entries to return per page.
640
         * @param string $where
641
         *  Any custom WHERE clauses
642
         * @param string $joins
643
         *  Any custom JOIN's
644
         * @param boolean $group
645
         *  Whether the entries need to be grouped by Entry ID or not
646
         * @param boolean $records_only
647
         *  If this is set to true, an array of Entry objects will be returned
648
         *  without any basic pagination information. Defaults to false
649
         * @param boolean $buildentries
650
         *  Whether to return an array of entry ID's or Entry objects. Defaults to
651
         *  true, which will return Entry objects
652
         * @param array $element_names
653
         *  Choose whether to get data from a subset of fields or all fields in a section,
654
         *  by providing an array of field names. Defaults to null, which will load data
655
         *  from all fields in a section.
656
         * @throws Exception
657
         * @return array
658
         *  Either an array of Entry objects, or an associative array containing
659
         *  the total entries, the start position, the entries per page and the
660
         *  Entry objects
661
         */
662
        public static function fetchByPage(
663
            $page = 1,
664
            $section_id,
665
            $entriesPerPage,
666
            $where = null,
667
            $joins = null,
668
            $group = false,
669
            $records_only = false,
670
            $buildentries = true,
671
            array $element_names = null
672
        ) {
673
            if ($entriesPerPage !== null && !is_string($entriesPerPage) && !is_numeric($entriesPerPage)) {
674
                throw new Exception(__('Entry limit specified was not a valid type. String or Integer expected.'));
675
            } elseif ($entriesPerPage === null) {
676
                $records = self::fetch(null, $section_id, null, null, $where, $joins, $group, $buildentries,
677
                    $element_names);
678
679
                $count = self::fetchCount($section_id, $where, $joins, $group);
680
681
                $entries = array(
682
                    'total-entries' => $count,
683
                    'total-pages' => 1,
684
                    'remaining-pages' => 0,
685
                    'remaining-entries' => 0,
686
                    'start' => 1,
687
                    'limit' => $count,
688
                    'records' => $records
689
                );
690
691
                return $entries;
692
            } else {
693
                $start = (max(1, $page) - 1) * $entriesPerPage;
694
695
                $records = ($entriesPerPage === '0' ? null : self::fetch(null, $section_id, $entriesPerPage, $start,
696
                    $where, $joins, $group, $buildentries, $element_names));
697
698
                if ($records_only) {
699
                    return array('records' => $records);
700
                }
701
702
                $entries = array(
703
                    'total-entries' => self::fetchCount($section_id, $where, $joins, $group),
704
                    'records' => $records,
705
                    'start' => max(1, $start),
706
                    'limit' => $entriesPerPage
707
                );
708
709
                $entries['remaining-entries'] = max(0, $entries['total-entries'] - ($start + $entriesPerPage));
710
                $entries['total-pages'] = max(1, ceil($entries['total-entries'] * (1 / $entriesPerPage)));
711
                $entries['remaining-pages'] = max(0, $entries['total-pages'] - $page);
712
713
                return $entries;
714
            }
715
        }
716
717
        /**
718
         * Return the count of the number of entries in a particular section.
719
         *
720
         * @param integer $section_id
721
         *  The ID of the Section where the Entries are to be counted
722
         * @param string $where
723
         *  Any custom WHERE clauses
724
         * @param string $joins
725
         *  Any custom JOIN's
726
         * @param boolean $group
727
         *  Whether the entries need to be grouped by Entry ID or not
728
         * @return integer
729
         */
730
        public static function fetchCount($section_id = null, $where = null, $joins = null, $group = false)
731
        {
732
            if (is_null($section_id)) {
733
                return false;
734
            }
735
736
            $section = SectionManager::fetch($section_id);
737
738
            if (!is_object($section)) {
739
                return false;
740
            }
741
742
            return Symphony::Database()->fetchVar('count', 0, sprintf("
743
                SELECT COUNT(%s`e`.id) AS `count`
744
                FROM `tbl_entries` AS `e`
745
                %s
746
                WHERE `e`.`section_id` = %d
747
                %s
748
            ",
749
                $group ? 'DISTINCT ' : '',
750
                $joins,
751
                $section_id,
752
                $where
753
            ));
754
        }
755
    }
756