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
Push — integration ( 5cdb31...81c93f )
by Brendan
04:03
created

EntryManager::fetchByPage()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 29
nc 6
nop 9
dl 0
loc 43
rs 6.7272
c 1
b 0
f 0

How to fix   Many Parameters   

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

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
307
                    }
308
                }
309
            } else {
310
                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...
311
                    $field->entryDataCleanup($chunk);
312
                }
313
            }
314
315
            $placeholders = Database::addPlaceholders($chunk);
316
            Symphony::Database()->delete('tbl_entries', " `id` IN ($placeholders) ", $chunk);
317
        }
318
319
        return true;
320
    }
321
322
    /**
323
     * This function will return an array of Entry objects given an ID or an array of ID's.
324
     * Do not provide `$entry_id` as an array if not specifying the `$section_id`. This function
325
     * is commonly passed custom SQL statements through the `$where` and `$join` parameters
326
     * that is generated by the fields of this section
327
     *
328
     * @param integer|array $entry_id
329
     *  An array of Entry ID's or an Entry ID to return
330
     * @param integer $section_id
331
     *  The ID of the Section that these entries are contained in
332
     * @param integer $limit
333
     *  The limit of entries to return
334
     * @param integer $start
335
     *  The starting offset of the entries to return
336
     * @param string $where
337
     *  Any custom WHERE clauses. The tbl_entries alias is `e`
338
     * @param string $joins
339
     *  Any custom JOIN's
340
     * @param boolean $group
341
     *  Whether the entries need to be grouped by Entry ID or not
342
     * @param boolean $buildentries
343
     *  Whether to return an array of entry ID's or Entry objects. Defaults to
344
     *  true, which will return Entry objects
345
     * @param array $element_names
346
     *  Choose whether to get data from a subset of fields or all fields in a section,
347
     *  by providing an array of field names. Defaults to null, which will load data
348
     *  from all fields in a section.
349
     * @param boolean $enable_sort
350
     *  Defaults to true, if false this function will not apply any sorting
351
     * @throws Exception
352
     * @return array
353
     *  If `$buildentries` is true, this function will return an array of Entry objects,
354
     *  otherwise it will return an associative array of Entry data from `tbl_entries`
355
     */
356
    public static function fetch($entry_id = null, $section_id = null, $limit = null, $start = null, $where = null, $joins = null, $group = false, $buildentries = true, $element_names = null, $enable_sort = true)
357
    {
358
        $sort = null;
359
360
        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...
361
            return false;
362
        }
363
364
        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...
365
            $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 356 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...
366
        }
367
368
        $section = SectionManager::fetch($section_id);
369
        if (!is_object($section)) {
370
            return false;
371
        }
372
373
        // SORTING
374
        // A single $entry_id doesn't need to be sorted on, or if it's explicitly disabled
375
        if ((!is_array($entry_id) && !is_null($entry_id) && is_int($entry_id)) || !$enable_sort) {
376
            $sort = null;
377
378
        // Check for RAND first, since this works independently of any specific field
379
        } elseif (self::$_fetchSortDirection === 'RAND') {
380
            $sort = 'ORDER BY RAND() ';
381
382
        // Handle Creation Date or the old Date sorting
383
        } 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...
384
            $sort = sprintf('ORDER BY `e`.`creation_date_gmt` %s', self::$_fetchSortDirection);
385
386
        // Handle Modification Date sorting
387
        } 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...
388
            $sort = sprintf('ORDER BY `e`.`modification_date_gmt` %s', self::$_fetchSortDirection);
389
390
        // Handle sorting for System ID
391 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...
392
            $sort = sprintf('ORDER BY `e`.`id` %s', self::$_fetchSortDirection);
393
394
        // Handle when the sort field is an actual Field
395
        } elseif (self::$_fetchSortField && $field = FieldManager::fetch(self::$_fetchSortField)) {
396
            if ($field->isSortable()) {
0 ignored issues
show
Bug introduced by
The method isSortable cannot be called on $field (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
397
                $field->buildSortingSQL($joins, $where, $sort, self::$_fetchSortDirection);
0 ignored issues
show
Bug introduced by
The method buildSortingSQL cannot be called on $field (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
398
            }
399
400
        // Handle if the section has a default sorting field
401
        } elseif ($section->getSortingField() && $field = FieldManager::fetch($section->getSortingField())) {
0 ignored issues
show
Bug introduced by
It seems like $section->getSortingField() targeting Section::getSortingField() can also be of type string; however, FieldManager::fetch() does only seem to accept integer|array|null, maybe add an additional type check?

This check looks at variables that 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...
402
            if ($field->isSortable()) {
0 ignored issues
show
Bug introduced by
The method isSortable cannot be called on $field (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
403
                $field->buildSortingSQL($joins, $where, $sort, $section->getSortingOrder());
0 ignored issues
show
Bug introduced by
The method buildSortingSQL cannot be called on $field (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
404
            }
405
406
            if (!$group) {
407
                $group = $field->requiresSQLGrouping();
0 ignored issues
show
Bug introduced by
The method requiresSQLGrouping cannot be called on $field (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
408
            }
409 View Code Duplication
        } else 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...
410
            $sort = 'ORDER BY `e`.`id` ' . self::$_fetchSortDirection;
411
412
        } else {
413
            $sort = sprintf('ORDER BY `e`.`id` %s', self::$_fetchSortDirection);
414
        }
415
416
        if ($field && !$group) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $field 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...
417
            $group = $field->requiresSQLGrouping();
0 ignored issues
show
Bug introduced by
The variable $field 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 method requiresSQLGrouping cannot be called on $field (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
418
        }
419
420
        if ($entry_id && !is_array($entry_id)) {
421
            $entry_id = array($entry_id);
422
        }
423
424
        $sql = sprintf("
425
            SELECT %s`e`.`id`, `e`.section_id, `e`.`author_id`,
426
                `e`.`creation_date` AS `creation_date`,
427
                `e`.`modification_date` AS `modification_date`
428
            FROM `tbl_entries` AS `e`
429
            %s
430
            WHERE 1
431
            %s
432
            %s
433
            %s
434
            %s
435
            %s
436
            ",
437
            $group ? 'DISTINCT ' : '',
438
            $joins,
439
            $entry_id ? "AND `e`.`id` IN ('".implode("', '", $entry_id)."') " : '',
440
            $section_id ? sprintf("AND `e`.`section_id` = %d", $section_id) : '',
441
            $where,
442
            $sort,
443
            $limit ? sprintf('LIMIT %d, %d', $start, $limit) : ''
444
        );
445
446
        $rows = Symphony::Database()->fetch($sql);
447
448
        // Create UNIX timestamps, as it has always been (Re: #2501)
449
        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...
450
            $entry['creation_date'] = DateTimeObj::get('U', $entry['creation_date']);
451
            $entry['modification_date'] = DateTimeObj::get('U', $entry['modification_date']);
452
        }
453
        unset($entry);
454
455
        return ($buildentries && (is_array($rows) && !empty($rows)) ? self::__buildEntries($rows, $section_id, $element_names) : $rows);
456
    }
457
458
    /**
459
     * Given an array of Entry data from `tbl_entries` and a section ID, return an
460
     * array of Entry objects. For performance reasons, it's possible to pass an array
461
     * of field handles via `$element_names`, so that only a subset of the section schema
462
     * will be queried. This function currently only supports Entry from one section at a
463
     * time.
464
     *
465
     * @param array $rows
466
     *  An array of Entry data from `tbl_entries` including the Entry ID, Entry section,
467
     *  the ID of the Author who created the Entry, and a Unix timestamp of creation
468
     * @param integer $section_id
469
     *  The section ID of the entries in the `$rows`
470
     * @param array $element_names
471
     *  Choose whether to get data from a subset of fields or all fields in a section,
472
     *  by providing an array of field names. Defaults to null, which will load data
473
     *  from all fields in a section.
474
     * @throws DatabaseException
475
     * @return array
476
     *  An array of Entry objects
477
     */
478
    public static function __buildEntries(array $rows, $section_id, $element_names = null)
479
    {
480
        $entries = array();
481
482
        if (empty($rows)) {
483
            return $entries;
484
        }
485
486
        $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 478 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...
487
488
        if (is_int($schema)) {
489
            $schema = array($schema);
490
        }
491
492
        $raw = array();
493
        $rows_string = '';
494
495
        // Append meta data:
496
        foreach ($rows as $entry) {
497
            $raw[$entry['id']]['meta'] = $entry;
498
            $rows_string .= $entry['id'] . ',';
499
        }
500
501
        $rows_string = trim($rows_string, ',');
502
503
        // Append field data:
504
        if (is_array($schema)) {
505
            foreach ($schema as $field_id) {
506
                try {
507
                    $row = Symphony::Database()->fetch("SELECT * FROM `tbl_entries_data_{$field_id}` WHERE `entry_id` IN ($rows_string) ORDER BY `id` ASC");
508
                } catch (Exception $e) {
509
                    // No data due to error
510
                    continue;
511
                }
512
513
                if (!is_array($row) || empty($row)) {
514
                    continue;
515
                }
516
517
                foreach ($row as $r) {
518
                    $entry_id = $r['entry_id'];
519
520
                    unset($r['id']);
521
                    unset($r['entry_id']);
522
523
                    if (!isset($raw[$entry_id]['fields'][$field_id])) {
524
                        $raw[$entry_id]['fields'][$field_id] = $r;
525
                    } else {
526
                        foreach (array_keys($r) as $key) {
527
                            // If this field already has been set, we need to take the existing
528
                            // value and make it array, adding the current value to it as well
529
                            // There is a special check incase the the field's value has been
530
                            // purposely set to null in the database.
531
                            if (
532
                                (
533
                                    isset($raw[$entry_id]['fields'][$field_id][$key])
534
                                    || is_null($raw[$entry_id]['fields'][$field_id][$key])
535
                                )
536
                                && !is_array($raw[$entry_id]['fields'][$field_id][$key])
537
                            ) {
538
                                $raw[$entry_id]['fields'][$field_id][$key] = array(
539
                                    $raw[$entry_id]['fields'][$field_id][$key],
540
                                    $r[$key]
541
                                );
542
543
                                // This key/value hasn't been set previously, so set it
544
                            } elseif (!isset($raw[$entry_id]['fields'][$field_id][$key])) {
545
                                $raw[$entry_id]['fields'][$field_id] = array($r[$key]);
546
547
                                // This key has been set and it's an array, so just append
548
                                // this value onto the array
549
                            } else {
550
                                $raw[$entry_id]['fields'][$field_id][$key][] = $r[$key];
551
                            }
552
                        }
553
                    }
554
                }
555
            }
556
        }
557
558
        // Loop over the array of entry data and convert it to an array of Entry objects
559
        foreach ($raw as $entry) {
560
            $obj = self::create();
561
562
            $obj->set('id', $entry['meta']['id']);
563
            $obj->set('author_id', $entry['meta']['author_id']);
564
            $obj->set('section_id', $entry['meta']['section_id']);
565
            $obj->set('creation_date', DateTimeObj::get('c', $entry['meta']['creation_date']));
566
567
            if (isset($entry['meta']['modification_date'])) {
568
                $obj->set('modification_date', DateTimeObj::get('c', $entry['meta']['modification_date']));
569
            } else {
570
                $obj->set('modification_date', $obj->get('creation_date'));
571
            }
572
573
            if (isset($entry['fields']) && is_array($entry['fields'])) {
574
                foreach ($entry['fields'] as $field_id => $data) {
575
                    $obj->setData($field_id, $data);
576
                }
577
            }
578
579
            $entries[] = $obj;
580
        }
581
582
        return $entries;
583
    }
584
585
586
    /**
587
     * Given an Entry ID, return the Section ID that it belongs to
588
     *
589
     * @param integer $entry_id
590
     *  The ID of the Entry to return it's section
591
     * @return integer
592
     *  The Section ID for this Entry's section
593
     */
594
    public static function fetchEntrySectionID($entry_id)
595
    {
596
        return Symphony::Database()->fetchVar('section_id', 0, "SELECT `section_id` FROM `tbl_entries` WHERE `id` = ? LIMIT 1",
597
            array($entry_id)
598
        );
599
    }
600
601
    /**
602
     * Return the count of the number of entries in a particular section.
603
     *
604
     * @param integer $section_id
605
     *  The ID of the Section where the Entries are to be counted
606
     * @param string $where
607
     *  Any custom WHERE clauses
608
     * @param string $joins
609
     *  Any custom JOIN's
610
     * @param boolean $group
611
     *  Whether the entries need to be grouped by Entry ID or not
612
     * @return integer
613
     */
614
    public static function fetchCount($section_id = null, $where = null, $joins = null, $group = false)
615
    {
616
        if (is_null($section_id)) {
617
            return false;
618
        }
619
620
        $section = SectionManager::fetch($section_id);
621
622
        if (!is_object($section)) {
623
            return false;
624
        }
625
626
        return Symphony::Database()->fetchVar('count', 0, sprintf("
627
                SELECT COUNT(%s`e`.id) as `count`
628
                FROM `tbl_entries` AS `e`
629
                %s
630
                WHERE `e`.`section_id` = %d
631
                %s
632
            ",
633
            $group ? 'DISTINCT ' : '',
634
            $joins,
635
            $section_id,
636
            $where
637
        ));
638
    }
639
640
    /**
641
     * Returns an array of Entry objects, with some basic pagination given
642
     * the number of Entry's to return and the current starting offset. This
643
     * function in turn calls the fetch function that does alot of the heavy
644
     * lifting. For instance, if there are 60 entries in a section and the pagination
645
     * dictates that per page, 15 entries are to be returned, by passing 2 to
646
     * the $page parameter you could return entries 15-30
647
     *
648
     * @param integer $page
649
     *  The page to return, defaults to 1
650
     * @param integer $section_id
651
     *  The ID of the Section that these entries are contained in
652
     * @param integer $entriesPerPage
653
     *  The number of entries to return per page.
654
     * @param string $where
655
     *  Any custom WHERE clauses
656
     * @param string $joins
657
     *  Any custom JOIN's
658
     * @param boolean $group
659
     *  Whether the entries need to be grouped by Entry ID or not
660
     * @param boolean $records_only
661
     *  If this is set to true, an array of Entry objects will be returned
662
     *  without any basic pagination information. Defaults to false
663
     * @param boolean $buildentries
664
     *  Whether to return an array of entry ID's or Entry objects. Defaults to
665
     *  true, which will return Entry objects
666
     * @param array $element_names
667
     *  Choose whether to get data from a subset of fields or all fields in a section,
668
     *  by providing an array of field names. Defaults to null, which will load data
669
     *  from all fields in a section.
670
     * @throws Exception
671
     * @return array
672
     *  Either an array of Entry objects, or an associative array containing
673
     *  the total entries, the start position, the entries per page and the
674
     *  Entry objects
675
     */
676
    public static function fetchByPage($page = 1, $section_id, $entriesPerPage, $where = null, $joins = null, $group = false, $records_only = false, $buildentries = true, array $element_names = null)
677
    {
678
        if ($entriesPerPage !== null && !is_string($entriesPerPage) && !is_numeric($entriesPerPage)) {
679
            throw new Exception(__('Entry limit specified was not a valid type. String or Integer expected.'));
680
        } elseif ($entriesPerPage === null) {
681
            $records = self::fetch(null, $section_id, null, null, $where, $joins, $group, $buildentries, $element_names);
682
683
            $count = self::fetchCount($section_id, $where, $joins, $group);
684
685
            $entries = array(
686
                'total-entries' => $count,
687
                'total-pages' => 1,
688
                'remaining-pages' => 0,
689
                'remaining-entries' => 0,
690
                'start' => 1,
691
                'limit' => $count,
692
                'records' => $records
693
            );
694
695
            return $entries;
696
        } else {
697
            $start = (max(1, $page) - 1) * $entriesPerPage;
698
699
            $records = ($entriesPerPage === '0' ? null : self::fetch(null, $section_id, $entriesPerPage, $start, $where, $joins, $group, $buildentries, $element_names));
700
701
            if ($records_only) {
702
                return array('records' => $records);
703
            }
704
705
            $entries = array(
706
                'total-entries' => self::fetchCount($section_id, $where, $joins, $group),
707
                'records' => $records,
708
                'start' => max(1, $start),
709
                'limit' => $entriesPerPage
710
            );
711
712
            $entries['remaining-entries'] = max(0, $entries['total-entries'] - ($start + $entriesPerPage));
713
            $entries['total-pages'] = max(1, ceil($entries['total-entries'] * (1 / $entriesPerPage)));
714
            $entries['remaining-pages'] = max(0, $entries['total-pages'] - $page);
715
716
            return $entries;
717
        }
718
    }
719
720
    /**
721
     * Creates a new Entry object using this class as the parent.
722
     *
723
     * @return Entry
724
     */
725
    public static function create()
726
    {
727
        return new Entry;
728
    }
729
}
730