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 — master ( 4ccb09...2f9dc2 )
by Nicolas
03:16
created

EntryManager::saveFieldData()   C

Complexity

Conditions 11
Paths 85

Size

Total Lines 59
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 29
nc 85
nop 3
dl 0
loc 59
rs 6.3545
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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:

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
     * To order by core fields, use one of
21
     * 'system:creation-date', 'system:modification-date', 'system:id'.
22
     * @var integer|string
23
     */
24
    protected static $_fetchSortField = null;
25
26
    /**
27
     * The direction that entries should be sorted in, available options are
28
     * RAND, ASC or DESC. Defaults to null, which implies ASC
29
     * @var string
30
     */
31
    protected static $_fetchSortDirection = null;
32
33
    /**
34
     * Setter function for the default sorting direction of the Fetch
35
     * function. Available options are RAND, ASC or DESC.
36
     *
37
     * @param string $direction
38
     *  The direction that entries should be sorted in, available options
39
     *  are RAND, ASC or DESC.
40
     */
41
    public static function setFetchSortingDirection($direction)
42
    {
43
        $direction = strtoupper($direction);
44
45
        if ($direction == 'RANDOM') {
46
            $direction = 'RAND';
47
        }
48
49
        self::$_fetchSortDirection = (in_array($direction, array('RAND', 'ASC', 'DESC')) ? $direction : null);
50
    }
51
52
    /**
53
     * Sets the field to applying the sorting direction on when fetching
54
     * entries
55
     *
56
     * @param integer $field_id
57
     *  The ID of the Field that should be sorted on
58
     */
59
    public static function setFetchSortingField($field_id)
60
    {
61
        self::$_fetchSortField = $field_id;
62
    }
63
64
    /**
65
     * Convenience function that will set sorting field and direction
66
     * by calling `setFetchSortingField()` & `setFetchSortingDirection()`
67
     *
68
     * @see toolkit.EntryManager#setFetchSortingField()
69
     * @see toolkit.EntryManager#setFetchSortingDirection()
70
     * @param integer $field_id
71
     *  The ID of the Field that should be sorted on
72
     * @param string $direction
73
     *  The direction that entries should be sorted in, available options
74
     *  are RAND, ASC or DESC. Defaults to ASC
75
     */
76
    public static function setFetchSorting($field_id, $direction = 'ASC')
77
    {
78
        self::setFetchSortingField($field_id);
79
        self::setFetchSortingDirection($direction);
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
     * Executes the SQL queries need to save a field's data for the specified
98
     * entry id.
99
     *
100
     * It first locks the table for writes, it then deletes existing data and then
101
     * it inserts a new row for the data. Errors are discarded and the lock is
102
     * released, if it was acquired.
103
     *
104
     * @param int $entry_id
105
     *  The entry id to save the data for
106
     * @param int $field_id
107
     *  The field id to save the data for
108
     * @param array $field
109
     *  The field data to save
110
     */
111
    protected static function saveFieldData($entry_id, $field_id, $field)
112
    {
113
        // Check that we have a field id
114
        if (empty($field_id)) {
115
            return;
116
        }
117
118
        // Check if we have field data
119
        if (!is_array($field) || empty($field)) {
120
            return;
121
        }
122
123
        $did_lock = false;
124
        try {
125
126
            // Check if table exists
127
            $table_name = 'tbl_entries_data_' . General::intval($field_id);
128
            if (!Symphony::Database()->tableExists($table_name)) {
129
                return;
130
            }
131
132
            // Lock the table for write
133
            $did_lock = Symphony::Database()->query("LOCK TABLES `$table_name` WRITE");
134
135
            // Delete old data
136
            Symphony::Database()->delete($table_name, sprintf("
137
                `entry_id` = %d", $entry_id
138
            ));
139
140
            // Insert new data
141
            $data = array(
142
                'entry_id' => $entry_id
143
            );
144
145
            $fields = array();
146
147
            foreach ($field as $key => $value) {
148
                if (is_array($value)) {
149
                    foreach ($value as $ii => $v) {
150
                        $fields[$ii][$key] = $v;
151
                    }
152
                } else {
153
                    $fields[max(0, count($fields) - 1)][$key] = $value;
154
                }
155
            }
156
157
            foreach ($fields as $index => $field_data) {
158
                $fields[$index] = array_merge($data, $field_data);
159
            }
160
161
            Symphony::Database()->insert($fields, $table_name);
162
        } catch (Exception $ex) {
163
            Symphony::Log()->pushExceptionToLog($ex, true);
0 ignored issues
show
Compatibility introduced by
$ex of type object<Exception> is not a sub-type of object<Throwable>. It seems like you assume a child class of the class Exception to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
164
        }
165
166
        if ($did_lock) {
167
            Symphony::Database()->query('UNLOCK TABLES');
168
        }
169
    }
170
171
    /**
172
     * Given an Entry object, iterate over all of the fields in that object
173
     * an insert them into their relevant entry tables.
174
     *
175
     * @see EntryManager::saveFieldData()
176
     * @param Entry $entry
177
     *  An Entry object to insert into the database
178
     * @throws DatabaseException
179
     * @return boolean
180
     */
181
    public static function add(Entry $entry)
182
    {
183
        $fields = $entry->get();
184
        Symphony::Database()->insert($fields, 'tbl_entries');
185
186
        if (!$entry_id = Symphony::Database()->getInsertID()) {
187
            return false;
188
        }
189
190
        // Iterate over all data for this entry
191
        foreach ($entry->getData() as $field_id => $field) {
192
            // Write data
193
            static::saveFieldData($entry_id, $field_id, $field);
194
        }
195
196
        $entry->set('id', $entry_id);
197
198
        return true;
199
    }
200
201
    /**
202
     * Update an existing Entry object given an Entry object
203
     *
204
     * @see EntryManager::saveFieldData()
205
     * @param Entry $entry
206
     *  An Entry object
207
     * @throws DatabaseException
208
     * @return boolean
209
     */
210
    public static function edit(Entry $entry)
211
    {
212
        // Update modification date and modification author.
213
        Symphony::Database()->update(
214
            array(
215
                'modification_author_id' => $entry->get('modification_author_id'),
216
                'modification_date' => $entry->get('modification_date'),
217
                'modification_date_gmt' => $entry->get('modification_date_gmt')
218
            ),
219
            'tbl_entries',
220
            sprintf(' `id` = %d', (int)$entry->get('id'))
221
        );
222
223
        // Iterate over all data for this entry
224
        foreach ($entry->getData() as $field_id => $field) {
225
            // Write data
226
            static::saveFieldData($entry->get('id'), $field_id, $field);
227
        }
228
229
        return true;
230
    }
231
232
    /**
233
     * Given an Entry ID, or an array of Entry ID's, delete all
234
     * data associated with this Entry using a Field's `entryDataCleanup()`
235
     * function, and then remove this Entry from `tbl_entries`. If the `$entries`
236
     * all belong to the same section, passing `$section_id` will improve
237
     * performance
238
     *
239
     * @param array|integer $entries
240
     *  An entry_id, or an array of entry id's to delete
241
     * @param integer $section_id (optional)
242
     *  If possible, the `$section_id` of the the `$entries`. This parameter
243
     *  should be left as null if the `$entries` array contains entry_id's for
244
     *  multiple sections.
245
     * @throws DatabaseException
246
     * @throws Exception
247
     * @return boolean
248
     */
249
    public static function delete($entries, $section_id = null)
250
    {
251
        $needs_data = true;
252
253
        if (!is_array($entries)) {
254
            $entries = array($entries);
255
        }
256
257
        // Get the section's schema
258
        if (!is_null($section_id)) {
259
            $section = SectionManager::fetch($section_id);
260
261
            if ($section instanceof Section) {
262
                $fields = $section->fetchFields();
263
                $data = array();
264
265
                foreach ($fields as $field) {
266
                    $reflection = new ReflectionClass($field);
267
                    // This field overrides the default implementation, so pass it data.
268
                    $data[$field->get('element_name')] = $reflection->getMethod('entryDataCleanup')->class == 'Field' ? false : true;
269
                }
270
271
                $data = array_filter($data);
272
273
                if (empty($data)) {
274
                    $needs_data = false;
275
                }
276
            }
277
        }
278
279
        // We'll split $entries into blocks of 2500 (random number)
280
        // and process the deletion in chunks.
281
        $chunks = array_chunk($entries, 2500);
282
283
        foreach ($chunks as $chunk) {
284
            $entry_list = implode("', '", $chunk);
285
286
            // If we weren't given a `section_id` we'll have to process individually
287
            // If we don't need data for any field, we can process the whole chunk
288
            // without building Entry objects, otherwise we'll need to build
289
            // Entry objects with data
290
            if (is_null($section_id) || !$needs_data) {
291
                $entries = $chunk;
292
            } elseif ($needs_data) {
293
                $entries = self::fetch($chunk, $section_id);
294
            }
295
296
            if ($needs_data) {
297
                foreach ($entries as $id) {
298
                    // Handles the case where `section_id` was not provided
299
                    if (is_null($section_id)) {
300
                        $e = self::fetch($id);
301
302
                        if (!is_array($e)) {
303
                            continue;
304
                        }
305
306
                        $e = current($e);
307
308
                        if (!$e instanceof Entry) {
309
                            continue;
310
                        }
311
312
                        // If we needed data, whole Entry objects will exist
313
                    } elseif ($needs_data) {
314
                        $e = $id;
315
                        $id = $e->get('id');
316
                    }
317
318
                    // Time to loop over it and send it to the fields.
319
                    // Note we can't rely on the `$fields` array as we may
320
                    // also be dealing with the case where `section_id` hasn't
321
                    // been provided
322
                    $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...
323
324
                    foreach ($entry_data as $field_id => $data) {
325
                        $field = FieldManager::fetch($field_id);
326
                        $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...
327
                    }
328
                }
329
            } else {
330
                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...
331
                    $field->entryDataCleanup($chunk);
332
                }
333
            }
334
335
            Symphony::Database()->delete('tbl_entries', " `id` IN ('$entry_list') ");
336
        }
337
338
        return true;
339
    }
340
341
    /**
342
     * This function will return an array of Entry objects given an ID or an array of ID's.
343
     * Do not provide `$entry_id` as an array if not specifying the `$section_id`. This function
344
     * is commonly passed custom SQL statements through the `$where` and `$join` parameters
345
     * that is generated by the fields of this section.
346
     *
347
     * @since Symphony 2.7.0 it will also call a new method on fields,
348
     * `buildSortingSelectSQL()`, to make sure fields can add ordering columns in
349
     * the SELECT clause. This is required on MySQL 5.7+ strict mode.
350
     *
351
     * @param integer|array $entry_id
352
     *  An array of Entry ID's or an Entry ID to return
353
     * @param integer $section_id
354
     *  The ID of the Section that these entries are contained in
355
     * @param integer $limit
356
     *  The limit of entries to return
357
     * @param integer $start
358
     *  The starting offset of the entries to return
359
     * @param string $where
360
     *  Any custom WHERE clauses. The tbl_entries alias is `e`
361
     * @param string $joins
362
     *  Any custom JOIN's
363
     * @param boolean $group
364
     *  Whether the entries need to be grouped by Entry ID or not
365
     * @param boolean $buildentries
366
     *  Whether to return an array of entry ID's or Entry objects. Defaults to
367
     *  true, which will return Entry objects
368
     * @param array $element_names
369
     *  Choose whether to get data from a subset of fields or all fields in a section,
370
     *  by providing an array of field names. Defaults to null, which will load data
371
     *  from all fields in a section.
372
     * @param boolean $enable_sort
373
     *  Defaults to true, if false this function will not apply any sorting
374
     * @throws Exception
375
     * @return array
376
     *  If `$buildentries` is true, this function will return an array of Entry objects,
377
     *  otherwise it will return an associative array of Entry data from `tbl_entries`
378
     */
379
    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)
380
    {
381
        $sort = null;
382
        $sortSelectClause = null;
383
384
        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...
385
            return false;
386
        }
387
388
        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...
389
            $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 379 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...
390
        }
391
392
        $section = SectionManager::fetch($section_id);
393
        if (!is_object($section)) {
394
            return false;
395
        }
396
397
        // SORTING
398
        // A single $entry_id doesn't need to be sorted on, or if it's explicitly disabled
399
        if ((!is_array($entry_id) && !is_null($entry_id) && is_int($entry_id)) || !$enable_sort) {
400
            $sort = null;
401
402
        // Check for RAND first, since this works independently of any specific field
403
        } elseif (self::$_fetchSortDirection == 'RAND') {
404
            $sort = 'ORDER BY RAND() ';
405
406
        // Handle Creation Date or the old Date sorting
407
        } elseif (self::$_fetchSortField === 'system:creation-date' || self::$_fetchSortField === 'date') {
408
            $sort = sprintf('ORDER BY `e`.`creation_date_gmt` %s', self::$_fetchSortDirection);
409
410
        // Handle Modification Date sorting
411
        } elseif (self::$_fetchSortField === 'system:modification-date') {
412
            $sort = sprintf('ORDER BY `e`.`modification_date_gmt` %s', self::$_fetchSortDirection);
413
414
        // Handle sorting for System ID
415
        } elseif (self::$_fetchSortField == 'system:id' || self::$_fetchSortField == 'id') {
416
            $sort = sprintf('ORDER BY `e`.`id` %s', self::$_fetchSortDirection);
417
418
        // Handle when the sort field is an actual Field
419
        } elseif (self::$_fetchSortField && $field = FieldManager::fetch(self::$_fetchSortField)) {
420
            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...
421
                $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...
422
                $sortSelectClause = $field->buildSortingSelectSQL($sort, self::$_fetchSortDirection);
0 ignored issues
show
Bug introduced by
The method buildSortingSelectSQL 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...
423
            }
424
425
        // Handle if the section has a default sorting field
426
        } elseif ($section->getSortingField() && $field = FieldManager::fetch($section->getSortingField())) {
427
            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...
428
                $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...
429
                $sortSelectClause = $field->buildSortingSelectSQL($sort, $section->getSortingOrder());
0 ignored issues
show
Bug introduced by
The method buildSortingSelectSQL 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...
430
            }
431
432
        // No sort specified, so just sort on system id
433
        } else {
434
            $sort = sprintf('ORDER BY `e`.`id` %s', self::$_fetchSortDirection);
435
        }
436
437
        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...
438
            $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...
439
        }
440
441
        if ($entry_id && !is_array($entry_id)) {
442
            $entry_id = array($entry_id);
443
        }
444
445
        $sql = sprintf("
446
            SELECT %s`e`.`id`, `e`.section_id,
447
                `e`.`author_id`, `e`.`modification_author_id`,
448
                `e`.`creation_date` AS `creation_date`,
449
                `e`.`modification_date` AS `modification_date`
450
                %s
451
            FROM `tbl_entries` AS `e`
452
            %s
453
            WHERE 1
454
            %s
455
            %s
456
            %s
457
            %s
458
            %s
459
            ",
460
            $group ? 'DISTINCT ' : '',
461
            $sortSelectClause ? ', ' . $sortSelectClause : '',
462
            $joins,
463
            $entry_id ? "AND `e`.`id` IN ('".implode("', '", $entry_id)."') " : '',
464
            $section_id ? sprintf("AND `e`.`section_id` = %d", $section_id) : '',
465
            $where,
466
            $sort,
467
            $limit ? sprintf('LIMIT %d, %d', $start, $limit) : ''
468
        );
469
470
        $rows = Symphony::Database()->fetch($sql);
471
472
        // Create UNIX timestamps, as it has always been (Re: #2501)
473
        foreach ($rows as &$entry) {
474
            $entry['creation_date'] = DateTimeObj::get('U', $entry['creation_date']);
475
            $entry['modification_date'] = DateTimeObj::get('U', $entry['modification_date']);
476
        }
477
        unset($entry);
478
479
        return ($buildentries && (is_array($rows) && !empty($rows)) ? self::__buildEntries($rows, $section_id, $element_names) : $rows);
480
    }
481
482
    /**
483
     * Given an array of Entry data from `tbl_entries` and a section ID, return an
484
     * array of Entry objects. For performance reasons, it's possible to pass an array
485
     * of field handles via `$element_names`, so that only a subset of the section schema
486
     * will be queried. This function currently only supports Entry from one section at a
487
     * time.
488
     *
489
     * @param array $rows
490
     *  An array of Entry data from `tbl_entries` including the Entry ID, Entry section,
491
     *  the ID of the Author who created the Entry, and a Unix timestamp of creation
492
     * @param integer $section_id
493
     *  The section ID of the entries in the `$rows`
494
     * @param array $element_names
495
     *  Choose whether to get data from a subset of fields or all fields in a section,
496
     *  by providing an array of field names. Defaults to null, which will load data
497
     *  from all fields in a section.
498
     * @throws DatabaseException
499
     * @return array
500
     *  An array of Entry objects
501
     */
502
    public static function __buildEntries(array $rows, $section_id, $element_names = null)
503
    {
504
        $entries = array();
505
506
        if (empty($rows)) {
507
            return $entries;
508
        }
509
510
        $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 502 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...
511
512
        if (is_int($schema)) {
513
            $schema = array($schema);
514
        }
515
516
        $raw = array();
517
        $rows_string = '';
518
519
        // Append meta data:
520
        foreach ($rows as $entry) {
521
            $raw[$entry['id']]['meta'] = $entry;
522
            $rows_string .= $entry['id'] . ',';
523
        }
524
525
        $rows_string = trim($rows_string, ',');
526
527
        // Append field data:
528
        if (is_array($schema)) {
529
            foreach ($schema as $field_id) {
530
                try {
531
                    $row = Symphony::Database()->fetch("SELECT * FROM `tbl_entries_data_{$field_id}` WHERE `entry_id` IN ($rows_string) ORDER BY `id` ASC");
532
                } catch (Exception $e) {
533
                    // No data due to error
534
                    continue;
535
                }
536
537
                if (!is_array($row) || empty($row)) {
538
                    continue;
539
                }
540
541
                foreach ($row as $r) {
542
                    $entry_id = $r['entry_id'];
543
544
                    unset($r['id']);
545
                    unset($r['entry_id']);
546
547
                    if (!isset($raw[$entry_id]['fields'][$field_id])) {
548
                        $raw[$entry_id]['fields'][$field_id] = $r;
549
                    } else {
550
                        foreach (array_keys($r) as $key) {
551
                            // If this field already has been set, we need to take the existing
552
                            // value and make it array, adding the current value to it as well
553
                            // There is a special check incase the the field's value has been
554
                            // purposely set to null in the database.
555
                            if (
556
                                (
557
                                    isset($raw[$entry_id]['fields'][$field_id][$key])
558
                                    || is_null($raw[$entry_id]['fields'][$field_id][$key])
559
                                )
560
                                && !is_array($raw[$entry_id]['fields'][$field_id][$key])
561
                            ) {
562
                                $raw[$entry_id]['fields'][$field_id][$key] = array(
563
                                    $raw[$entry_id]['fields'][$field_id][$key],
564
                                    $r[$key]
565
                                );
566
567
                                // This key/value hasn't been set previously, so set it
568
                            } elseif (!isset($raw[$entry_id]['fields'][$field_id][$key])) {
569
                                $raw[$entry_id]['fields'][$field_id] = array($r[$key]);
570
571
                                // This key has been set and it's an array, so just append
572
                                // this value onto the array
573
                            } else {
574
                                $raw[$entry_id]['fields'][$field_id][$key][] = $r[$key];
575
                            }
576
                        }
577
                    }
578
                }
579
            }
580
        }
581
582
        // Loop over the array of entry data and convert it to an array of Entry objects
583
        foreach ($raw as $entry) {
584
            $obj = self::create();
585
586
            $obj->set('id', $entry['meta']['id']);
587
            $obj->set('author_id', $entry['meta']['author_id']);
588
            $obj->set('modification_author_id', $entry['meta']['modification_author_id']);
589
            $obj->set('section_id', $entry['meta']['section_id']);
590
            $obj->set('creation_date', DateTimeObj::get('c', $entry['meta']['creation_date']));
591
592
            if (isset($entry['meta']['modification_date'])) {
593
                $obj->set('modification_date', DateTimeObj::get('c', $entry['meta']['modification_date']));
594
            } else {
595
                $obj->set('modification_date', $obj->get('creation_date'));
596
            }
597
598
            $obj->creationDate = $obj->get('creation_date');
599
600
            if (isset($entry['fields']) && is_array($entry['fields'])) {
601
                foreach ($entry['fields'] as $field_id => $data) {
602
                    $obj->setData($field_id, $data);
603
                }
604
            }
605
606
            $entries[] = $obj;
607
        }
608
609
        return $entries;
610
    }
611
612
613
    /**
614
     * Given an Entry ID, return the Section ID that it belongs to
615
     *
616
     * @param integer $entry_id
617
     *  The ID of the Entry to return it's section
618
     * @return integer
619
     *  The Section ID for this Entry's section
620
     */
621
    public static function fetchEntrySectionID($entry_id)
622
    {
623
        return Symphony::Database()->fetchVar('section_id', 0, sprintf("
624
            SELECT `section_id` FROM `tbl_entries` WHERE `id` = %d LIMIT 1",
625
            $entry_id
626
        ));
627
    }
628
629
    /**
630
     * Return the count of the number of entries in a particular section.
631
     *
632
     * @param integer $section_id
633
     *  The ID of the Section where the Entries are to be counted
634
     * @param string $where
635
     *  Any custom WHERE clauses
636
     * @param string $joins
637
     *  Any custom JOIN's
638
     * @param boolean $group
639
     *  Whether the entries need to be grouped by Entry ID or not
640
     * @return integer
641
     */
642
    public static function fetchCount($section_id = null, $where = null, $joins = null, $group = false)
643
    {
644
        if (is_null($section_id)) {
645
            return false;
646
        }
647
648
        $section = SectionManager::fetch($section_id);
649
650
        if (!is_object($section)) {
651
            return false;
652
        }
653
654
        return Symphony::Database()->fetchVar('count', 0, sprintf("
655
                SELECT COUNT(%s`e`.id) as `count`
656
                FROM `tbl_entries` AS `e`
657
                %s
658
                WHERE `e`.`section_id` = %d
659
                %s
660
            ",
661
            $group ? 'DISTINCT ' : '',
662
            $joins,
663
            $section_id,
664
            $where
665
        ));
666
    }
667
668
    /**
669
     * Returns an array of Entry objects, with some basic pagination given
670
     * the number of Entry's to return and the current starting offset. This
671
     * function in turn calls the fetch function that does alot of the heavy
672
     * lifting. For instance, if there are 60 entries in a section and the pagination
673
     * dictates that per page, 15 entries are to be returned, by passing 2 to
674
     * the $page parameter you could return entries 15-30
675
     *
676
     * @param integer $page
677
     *  The page to return, defaults to 1
678
     * @param integer $section_id
679
     *  The ID of the Section that these entries are contained in
680
     * @param integer $entriesPerPage
681
     *  The number of entries to return per page.
682
     * @param string $where
683
     *  Any custom WHERE clauses
684
     * @param string $joins
685
     *  Any custom JOIN's
686
     * @param boolean $group
687
     *  Whether the entries need to be grouped by Entry ID or not
688
     * @param boolean $records_only
689
     *  If this is set to true, an array of Entry objects will be returned
690
     *  without any basic pagination information. Defaults to false
691
     * @param boolean $buildentries
692
     *  Whether to return an array of entry ID's or Entry objects. Defaults to
693
     *  true, which will return Entry objects
694
     * @param array $element_names
695
     *  Choose whether to get data from a subset of fields or all fields in a section,
696
     *  by providing an array of field names. Defaults to null, which will load data
697
     *  from all fields in a section.
698
     * @throws Exception
699
     * @return array
700
     *  Either an array of Entry objects, or an associative array containing
701
     *  the total entries, the start position, the entries per page and the
702
     *  Entry objects
703
     */
704
    public static function fetchByPage($page = 1, $section_id, $entriesPerPage, $where = null, $joins = null, $group = false, $records_only = false, $buildentries = true, array $element_names = null)
705
    {
706
        if ($entriesPerPage != null && !is_string($entriesPerPage) && !is_numeric($entriesPerPage)) {
707
            throw new Exception(__('Entry limit specified was not a valid type. String or Integer expected.'));
708
        } elseif ($entriesPerPage == null) {
709
            $records = self::fetch(null, $section_id, null, null, $where, $joins, $group, $buildentries, $element_names);
710
711
            $count = self::fetchCount($section_id, $where, $joins, $group);
712
713
            $entries = array(
714
                'total-entries' => $count,
715
                'total-pages' => 1,
716
                'remaining-pages' => 0,
717
                'remaining-entries' => 0,
718
                'start' => 1,
719
                'limit' => $count,
720
                'records' => $records
721
            );
722
723
            return $entries;
724
        } else {
725
            $start = (max(1, $page) - 1) * $entriesPerPage;
726
727
            $records = ($entriesPerPage == '0' ? null : self::fetch(null, $section_id, $entriesPerPage, $start, $where, $joins, $group, $buildentries, $element_names));
728
729
            if ($records_only) {
730
                return array('records' => $records);
731
            }
732
733
            $entries = array(
734
                'total-entries' => self::fetchCount($section_id, $where, $joins, $group),
735
                'records' => $records,
736
                'start' => max(1, $start),
737
                'limit' => $entriesPerPage
738
            );
739
740
            $entries['remaining-entries'] = max(0, $entries['total-entries'] - ($start + $entriesPerPage));
741
            $entries['total-pages'] = max(1, ceil($entries['total-entries'] * (1 / $entriesPerPage)));
742
            $entries['remaining-pages'] = max(0, $entries['total-pages'] - $page);
743
744
            return $entries;
745
        }
746
    }
747
748
    /**
749
     * Creates a new Entry object using this class as the parent.
750
     *
751
     * @return Entry
752
     */
753
    public static function create()
754
    {
755
        return new Entry;
756
    }
757
}
758