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

contentPublish   F

Complexity

Total Complexity 288

Size/Duplication

Total Lines 1951
Duplicated Lines 10.3 %

Coupling/Cohesion

Components 2
Dependencies 23

Importance

Changes 0
Metric Value
dl 201
loc 1951
rs 0.6197
c 0
b 0
f 0
wmc 288
lcom 2
cbo 23

27 Methods

Rating   Name   Duplication   Size   Complexity  
C sort() 0 50 12
A createFilteringInterface() 0 16 3
A createFilteringDrawer() 0 7 1
A createFilteringDuplicator() 0 17 1
B createFieldFilters() 0 34 4
B createSystemDateFilters() 0 41 3
B createFilter() 0 42 1
B createFilterComparisons() 0 33 6
A createFilterSuggestions() 0 15 2
A createFilterHelp() 0 13 2
A getFilterQuery() 0 14 4
A build() 13 13 2
A action() 0 4 1
B __switchboard() 0 23 5
A view() 0 4 1
F __viewIndex() 21 479 71
C __actionIndex() 0 109 8
D __viewNew() 34 119 21
D __actionNew() 7 98 21
F __viewEdit() 41 221 31
D __actionEdit() 14 119 15
B __wrapFieldWithDiv() 0 37 5
B isFieldHidden() 0 14 5
D prepareAssociationsDrawer() 8 281 43
B getPrepopulateString() 21 21 5
B getFilterString() 22 22 5
B validateTimestamp() 20 20 10

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like contentPublish often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use contentPublish, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @package content
5
 */
6
/**
7
 * The Publish page is where the majority of an Authors time will
8
 * be spent in Symphony with adding, editing and removing entries
9
 * from Sections. This Page controls the entries table as well as
10
 * the Entry creation screens.
11
 */
12
13
class contentPublish extends AdministrationPage
14
{
15
    public $_errors = array();
16
17
    public function sort(&$sort, &$order, $params)
18
    {
19
        $section = $params['current-section'];
20
        $filters = '';
21
        // Format the filter query string
22
        if (isset($params['filters']) && !empty($params['filters'])) {
23
            $filters = preg_replace('/^&amp;/i', '', $params['filters'], 1);
24
            $filters = '?' . trim($filters);
25
        }
26
27
        // If `?unsort` is appended to the URL, then sorting is reverted
28
        // to 'none', aka. by 'entry-id'.
29
        if ($params['unsort']) {
30
            $section->setSortingField('id', false);
31
            $section->setSortingOrder('desc');
32
33
            redirect(Administration::instance()->getCurrentPageURL() . $filters);
34
        }
35
36
        // By default, sorting information are retrieved from
37
        // the file system and stored inside the `Configuration` object
38
        if (is_null($sort) && is_null($order)) {
39
            $sort = $section->getSortingField();
40
            $order = $section->getSortingOrder();
41
42
            // Set the sorting in the `EntryManager` for subsequent use
43
            EntryManager::setFetchSorting($sort, $order);
44
        } else {
45
            $sort = General::sanitize($sort);
46
47
            // Ensure that this field is infact sortable, otherwise
48
            // fallback to IDs
49
            if (($field = FieldManager::fetch($sort)) instanceof Field && !$field->isSortable()) {
50
                $sort = $section->getDefaultSortingField();
51
            }
52
53
            // If the sort order or direction differs from what is saved,
54
            // update the config file and reload the page
55
            if ($sort != $section->getSortingField() || $order != $section->getSortingOrder()) {
56
                $section->setSortingField($sort, false);
57
                $section->setSortingOrder($order);
58
                redirect(Administration::instance()->getCurrentPageURL() . $filters);
59
            }
60
61
            // If the sort order and direction remains the same, reload the page
62
            if ($sort == $section->getSortingField() && $order == $section->getSortingOrder()) {
63
                redirect(Administration::instance()->getCurrentPageURL() . $filters);
64
            }
65
        }
66
    }
67
68
    /**
69
     * Append filtering interface
70
     */
71
    public function createFilteringInterface()
72
    {
73
        //Check if section has filtering enabled
74
        $context = $this->getContext();
75
        $handle = $context['section_handle'];
76
        $section_id = SectionManager::fetchIDFromHandle($handle);
77
        $section = SectionManager::fetch($section_id);
78
        $filter = $section->get('filter');
79
        $count = EntryManager::fetchCount($section_id);
80
81
        if ($filter !== 'no' && $count > 1) {
82
            $drawer = Widget::Drawer('filtering-' . $section_id, __('Filter Entries'), $this->createFilteringDrawer($section));
83
            $drawer->addClass('drawer-filtering');
84
            $this->insertDrawer($drawer);
85
        }
86
    }
87
88
    /**
89
     * Create filtering drawer
90
     */
91
    public function createFilteringDrawer($section)
92
    {
93
        $this->filteringForm = Widget::Form(null, 'get', 'filtering');
0 ignored issues
show
Bug introduced by
The property filteringForm does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
94
        $this->createFilteringDuplicator($section);
95
96
        return $this->filteringForm;
97
    }
98
99
    public function createFilteringDuplicator($section)
100
    {
101
        $div = new XMLElement('div');
102
        $div->setAttribute('class', 'frame filters-duplicator');
103
        $div->setAttribute('data-interactive', 'data-interactive');
104
105
        $ol = new XMLElement('ol');
106
        $ol->setAttribute('data-add', __('Add filter'));
107
        $ol->setAttribute('data-remove', __('Clear filter'));
108
        $ol->setAttribute('data-empty', __('No filters applied yet.'));
109
110
        $this->createFieldFilters($ol, $section);
111
        $this->createSystemDateFilters($ol);
112
113
        $div->appendChild($ol);
114
        $this->filteringForm->appendChild($div);
115
    }
116
117
    private function createFieldFilters(&$wrapper, $section)
118
    {
119
        $filters = $_GET['filter'];
120
121
        foreach ($section->fetchFilterableFields() as $field) {
122
            if (!$field->canPublishFilter()) {
123
                continue;
124
            }
125
126
            $filter = $filters[$field->get('element_name')];
127
128
            // Filter data
129
            $data = array();
130
            $data['type'] = $field->get('element_name');
131
            $data['name'] = $field->get('label');
132
            $data['filter'] = $filter;
133
            $data['instance'] = 'unique';
134
            $data['search'] = $field->fetchSuggestionTypes();
135
            $data['operators'] = $field->fetchFilterableOperators();
136
            $data['comparisons'] = $this->createFilterComparisons($data);
137
            $data['query'] = $this->getFilterQuery($data);
138
            $data['field-id'] = $field->get('id');
139
140
            // Add existing filter
141
            if (isset($filter)) {
142
                $this->createFilter($wrapper, $data);
143
            }
144
145
            // Add filter template
146
            $data['instance'] = 'unique template';
147
            $data['query'] = '';
148
            $this->createFilter($wrapper, $data);
149
        }
150
    }
151
152
    private function createSystemDateFilters(&$wrapper)
153
    {
154
        $filters = $_GET['filter'];
155
        $dateField = new FieldDate;
156
157
        $fields = array(
158
            array(
159
                'type' => 'system:creation-date',
160
                'label' => __('System Creation Date')
161
            ),
162
            array(
163
                'type' => 'system:modification-date',
164
                'label' => __('System Modification Date')
165
            )
166
        );
167
168
        foreach ($fields as $field) {
169
            $filter = $filters[$field['type']];
170
171
            // Filter data
172
            $data = array();
173
            $data['type'] = $field['type'];
174
            $data['name'] = $field['label'];
175
            $data['filter'] = $filter;
176
            $data['instance'] = 'unique';
177
            $data['search'] = $dateField->fetchSuggestionTypes();
178
            $data['operators'] = $dateField->fetchFilterableOperators();
179
            $data['comparisons'] = $this->createFilterComparisons($data);
180
            $data['query'] = $this->getFilterQuery($data);
181
182
            // Add existing filter
183
            if (isset($filter)) {
184
                $this->createFilter($wrapper, $data);
185
            }
186
187
            // Add filter template
188
            $data['instance'] = 'unique template';
189
            $data['query'] = '';
190
            $this->createFilter($wrapper, $data);
191
        }
192
    }
193
194
    private function createFilter(&$wrapper, $data)
195
    {
196
        $li = new XMLElement('li');
197
        $li->setAttribute('class', $data['instance']);
198
        $li->setAttribute('data-type', $data['type']);
199
200
        // Header
201
        $li->appendChild(new XMLElement('header', $data['name'], array(
202
            'data-name' => $data['name']
203
        )));
204
205
        // Settings
206
        $div = new XMLElement('div', null, array('class' => 'two columns'));
207
208
        // Comparisons
209
        $label = Widget::Label();
210
        $label->setAttribute('class', 'column secondary');
211
212
        $select = Widget::Select($data['type'] . '-comparison', $data['comparisons'], array(
213
            'class' => 'comparison'
214
        ));
215
216
        $label->appendChild($select);
217
        $div->appendChild($label);
218
219
        // Query
220
        $label = Widget::Label();
221
        $label->setAttribute('class', 'column primary');
222
223
        $input = Widget::Input($data['type'], General::sanitize($data['query']), 'text', array(
224
            'placeholder' => __('Type and hit enter to apply filter…'),
225
            'autocomplete' => 'off'
226
        ));
227
        $input->setAttribute('class', 'filter');
228
        $label->appendChild($input);
229
230
        $this->createFilterSuggestions($label, $data);
231
232
        $div->appendChild($label);
233
        $li->appendChild($div);
234
        $wrapper->appendChild($li);
235
    }
236
237
    private function createFilterComparisons($data)
238
    {
239
        // Default comparison
240
        $comparisons = array();
241
242
        // Custom field comparisons
243
        foreach ($data['operators'] as $operator) {
244
245
            $filter = trim($operator['filter']);
246
247
            // Check selected state
248
            $selected = false;
249
250
            // Selected state : Comparison mode "between" (x to y)
251
            if ($operator['title'] === 'between' && preg_match('/^(-?(?:\d+(?:\.\d+)?|\.\d+)) to (-?(?:\d+(?:\.\d+)?|\.\d+))$/i', $data['filter'] )) {
252
                $selected = true;
253
            // Selected state : Other comparison modes (except "is")
254
            } elseif ((!empty($filter) && strpos($data['filter'], $filter) === 0)) {
255
                $selected = true;
256
            }
257
258
            $comparisons[] = array(
259
                $operator['filter'],
260
                $selected,
261
                __($operator['title']),
262
                null,
263
                null,
264
                array('data-comparison' => $operator['title'])
265
            );
266
        }
267
268
        return $comparisons;
269
    }
270
271
    private function createFilterSuggestions(&$wrapper, $data)
272
    {
273
        $ul = new XMLElement('ul');
274
        $ul->setAttribute('class', 'suggestions');
275
        $ul->setAttribute('data-field-id', $data['field-id']);
276
        $ul->setAttribute('data-associated-ids', '0');
277
        $ul->setAttribute('data-search-types', implode($data['search'], ','));
278
279
        // Add help text for each filter operator
280
        foreach ($data['operators'] as $operator) {
281
            $this->createFilterHelp($ul, $operator);
282
        }
283
284
        $wrapper->appendChild($ul);
285
    }
286
287
    private function createFilterHelp(&$wrapper, $operator)
288
    {
289
        if (empty($operator['help'])) {
290
            return;
291
        }
292
293
        $li = new XMLElement('li', __('Comparison mode') . ': ' . $operator['help'], array(
294
            'class' => 'help',
295
            'data-comparison' => $operator['title']
296
        ));
297
298
        $wrapper->appendChild($li);
299
    }
300
301
    private function getFilterQuery($data)
302
    {
303
        $query = $data['filter'];
304
305
        foreach ($data['operators'] as $operator) {
306
            $filter = trim($operator['filter']);
307
308
            if (!empty($filter) && strpos($data['filter'], $filter) === 0) {
309
                $query = substr($data['filter'], strlen($operator['filter']));
310
            }
311
        }
312
313
        return (string)$query;
314
    }
315
316 View Code Duplication
    public function build(array $context = array())
317
    {
318
        $section_id = SectionManager::fetchIDFromHandle($context['section_handle']);
319
320
        if ($section_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $section_id of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
321
            $context['associations'] = array(
322
                'parent' => SectionManager::fetchParentAssociations($section_id),
323
                'child' => SectionManager::fetchChildAssociations($section_id)
324
            );
325
        }
326
327
        return parent::build($context);
328
    }
329
330
    public function action()
331
    {
332
        $this->__switchboard('action');
333
    }
334
335
    public function __switchboard($type = 'view')
336
    {
337
        $function = ($type == 'action' ? '__action' : '__view') . ucfirst($this->_context['page']);
338
339
        if (!method_exists($this, $function)) {
340
            // If there is no action function, just return without doing anything
341
            if ($type == 'action') {
342
                return;
343
            }
344
345
            Administration::instance()->errorPageNotFound();
346
        }
347
348
        // Is this request allowed by server?
349
        if ($this->isRequestValid() === false) {
350
            $this->pageAlert(__('This request exceeds the maximum allowed request size of %s specified by your host.', array(
351
                    ini_get('post_max_size')
352
                )),
353
                Alert::ERROR
354
            );
355
        }
356
        $this->$function();
357
    }
358
359
    public function view()
360
    {
361
        $this->__switchboard();
362
    }
363
364
    public function __viewIndex()
365
    {
366
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
367
            Administration::instance()->throwCustomError(
368
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
369
                __('Unknown Section'),
370
                Page::HTTP_STATUS_NOT_FOUND
371
            );
372
        } elseif (!is_writable(CONFIG)) {
373
            $this->pageAlert(__('The Symphony configuration file, %s, is not writable. The sort order cannot be modified.', array('<code>/manifest/config.php</code>')), Alert::NOTICE);
374
        }
375
376
        $section = SectionManager::fetch($section_id);
377
378
        $this->setPageType('table');
379
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
380
381
        $filters = array();
382
        $filter_querystring = $prepopulate_querystring = $where = $joins = null;
383
        $current_page = (isset($_REQUEST['pg']) && is_numeric($_REQUEST['pg']) ? max(1, intval($_REQUEST['pg'])) : 1);
384
385
        if (isset($_REQUEST['filter'])) {
386
            // legacy implementation, convert single filter to an array
387
            // split string in the form ?filter=handle:value
388
            // @deprecated
389
            // This should be removed in Symphony 4.0.0
390
            if (!is_array($_REQUEST['filter'])) {
391
                list($field_handle, $filter_value) = explode(':', $_REQUEST['filter'], 2);
392
                $filters[$field_handle] = rawurldecode($filter_value);
393
            } else {
394
                $filters = $_REQUEST['filter'];
395
            }
396
397
            foreach ($filters as $handle => $value) {
398
                // Handle multiple values through filtering. RE: #2290
399
                if ((is_array($value) && empty($value)) || trim($value) == '') {
400
                    continue;
401
                }
402
403 View Code Duplication
                if (!is_array($value)) {
404
                    $filter_type = Datasource::determineFilterType($value);
405
                    $value = Datasource::splitFilter($filter_type, $value);
406
                } else {
407
                    $filter_type = Datasource::FILTER_OR;
408
                }
409
410
                // Handle date meta data #2003
411
                $handle = Symphony::Database()->cleanValue($handle);
412
                if (in_array($handle, array('system:creation-date', 'system:modification-date'))) {
413
                    $date_joins = '';
414
                    $date_where = '';
415
                    $date = new FieldDate();
416
                    $date->buildDSRetrievalSQL($value, $date_joins, $date_where, ($filter_type == Datasource::FILTER_AND ? true : false));
417
418
                    // Replace the date field where with the `creation_date` or `modification_date`.
419
                    $date_where = preg_replace('/`t\d+`.date/', ($field_id !== 'system:modification-date') ? '`e`.creation_date_gmt' : '`e`.modification_date_gmt', $date_where);
0 ignored issues
show
Bug introduced by
The variable $field_id 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...
420
                    $where .= $date_where;
421
                } else {
422
                    // Handle normal fields
423
                    $field_id = FieldManager::fetchFieldIDFromElementName(
424
                        $handle,
425
                        $section->get('id')
426
                    );
427
428
                    $field = FieldManager::fetch($field_id);
429
                    if ($field instanceof Field) {
430
                        $field->buildDSRetrievalSQL($value, $joins, $where, ($filter_type == Datasource::FILTER_AND ? true : false));
431
432
                        $value = implode(',', $value);
433
                        $encoded_value = rawurlencode($value);
434
                        $filter_querystring .= sprintf("filter[%s]=%s&amp;", $handle, $encoded_value);
435
436
                        // Some fields require that prepopulation be done via ID. RE: #2331
437
                        if (!is_numeric($value) && method_exists($field, 'fetchIDfromValue')) {
438
                            $encoded_value = $field->fetchIDfromValue($value);
439
                        }
440
                        $prepopulate_querystring .= sprintf("prepopulate[%d]=%s&amp;", $field_id, $encoded_value);
441
                    } else {
442
                        unset($filters[$handle]);
443
                    }
444
                    unset($field);
445
                }
446
            }
447
448
            $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
449
            $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
450
        }
451
452
        Sortable::initialize($this, $entries, $sort, $order, array(
453
            'current-section' => $section,
454
            'filters' => ($filter_querystring ? "&amp;" . $filter_querystring : ''),
455
            'unsort' => isset($_REQUEST['unsort'])
456
        ));
457
458
        $this->Form->setAttribute('action', Administration::instance()->getCurrentPageURL(). '?pg=' . $current_page.($filter_querystring ? "&amp;" . $filter_querystring : ''));
459
460
        // Build filtering interface
461
        $this->createFilteringInterface();
462
463
        $subheading_buttons = array(
464
            Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/'.($prepopulate_querystring ? '?' . $prepopulate_querystring : ''), __('Create a new entry'), 'create button', null, array('accesskey' => 'c'))
465
        );
466
467
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
468
        if (Symphony::Author()->isDeveloper()) {
469
            array_unshift($subheading_buttons, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
470
        }
471
472
        $this->appendSubheading(General::sanitize($section->get('name')), $subheading_buttons);
473
474
        /**
475
         * Allows adjustments to be made to the SQL where and joins statements
476
         * before they are used to fetch the entries for the page
477
         *
478
         * @delegate AdjustPublishFiltering
479
         * @since Symphony 2.3.3
480
         * @param string $context
481
         * '/publish/'
482
         * @param integer $section_id
483
         * An array of the current columns, passed by reference
484
         * @param string $where
485
         * The current where statement, or null if not set
486
         * @param string $joins
487
         */
488
        Symphony::ExtensionManager()->notifyMembers('AdjustPublishFiltering', '/publish/', array('section-id' => $section_id, 'where' => &$where, 'joins' => &$joins));
489
490
        // get visible columns
491
        $visible_columns = $section->fetchVisibleColumns();
492
        // extract the needed schema
493
        $element_names = array_values(array_map(function ($field) {
494
            return $field->get('element_name');
495
        }, $visible_columns));
496
497
        // Check that the filtered query fails that the filter is dropped and an
498
        // error is logged. #841 ^BA
499
        try {
500
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), $where, $joins, true, false, true, $element_names);
501
        } catch (DatabaseException $ex) {
502
            $this->pageAlert(__('An error occurred while retrieving filtered entries. Showing all entries instead.'), Alert::ERROR);
503
            $filter_querystring = null;
504
            Symphony::Log()->pushToLog(sprintf(
505
                    '%s - %s%s%s',
506
                    $section->get('name') . ' Publish Index',
507
                    $ex->getMessage(),
508
                    ($ex->getFile() ? " in file " .  $ex->getFile() : null),
509
                    ($ex->getLine() ? " on line " . $ex->getLine() : null)
510
                ),
511
                E_NOTICE,
512
                true
513
            );
514
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), null, null, true, false, true, $element_names);
515
        }
516
517
        // Flag filtering
518
        if (isset($_REQUEST['filter'])) {
519
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d of %d entries (filtered)', array($entries['total-entries'], EntryManager::fetchCount($section_id))) . '</span>', array('class' => 'inactive'));
520
        } else {
521
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d entries', array($entries['total-entries'])) . '</span>', array('class' => 'inactive'));
522
        }
523
        $this->Breadcrumbs->appendChild($filter_stats);
524
525
        // Build table
526
        $columns = array();
527
528
        if (is_array($visible_columns) && !empty($visible_columns)) {
529
            foreach ($visible_columns as $column) {
530
                $columns[] = array(
531
                    'label' => $column->get('label'),
532
                    'sortable' => $column->isSortable(),
533
                    'handle' => $column->get('id'),
534
                    'attrs' => array(
535
                        'id' => 'field-' . $column->get('id'),
536
                        'class' => 'field-' . $column->get('type')
537
                    )
538
                );
539
            }
540
        } else {
541
            $columns[] = array(
542
                'label' => __('ID'),
543
                'sortable' => true,
544
                'handle' => 'id'
545
            );
546
        }
547
548
        $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order, ($filter_querystring) ? "&amp;" . $filter_querystring : '');
549
550
        $child_sections = array();
551
        $associated_sections = $section->fetchChildAssociations(true);
552
553
        if (is_array($associated_sections) && !empty($associated_sections)) {
554
            foreach ($associated_sections as $key => $as) {
555
                $child_sections[$key] = SectionManager::fetch($as['child_section_id']);
556
                $aTableHead[] = array($child_sections[$key]->get('name'), 'col');
557
            }
558
        }
559
560
        /**
561
         * Allows the creation of custom table columns for each entry. Called
562
         * after all the Section Visible columns have been added as well
563
         * as the Section Associations
564
         *
565
         * @delegate AddCustomPublishColumn
566
         * @since Symphony 2.2
567
         * @param string $context
568
         * '/publish/'
569
         * @param array $tableHead
570
         * An array of the current columns, passed by reference
571
         * @param integer $section_id
572
         * The current Section ID
573
         */
574
        Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumn', '/publish/', array('tableHead' => &$aTableHead, 'section_id' => $section->get('id')));
575
576
        // Table Body
577
        $aTableBody = array();
578
579
        if (!is_array($entries['records']) || empty($entries['records'])) {
580
            $aTableBody = array(
581
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
582
            );
583
        } else {
584
            $field_pool = array();
585
586
            if (is_array($visible_columns) && !empty($visible_columns)) {
587
                foreach ($visible_columns as $column) {
588
                    $field_pool[$column->get('id')] = $column;
589
                }
590
            }
591
592
            $link_column = array_reverse($visible_columns);
593
            $link_column = end($link_column);
594
            reset($visible_columns);
595
596
            foreach ($entries['records'] as $entry) {
597
                $tableData = array();
598
599
                // Setup each cell
600
                if (!is_array($visible_columns) || empty($visible_columns)) {
601
                    $tableData[] = Widget::TableData(Widget::Anchor($entry->get('id'), Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'));
602
                } else {
603
                    $link = Widget::Anchor(
604
                        '',
605
                        Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'.($filter_querystring ? '?' . $prepopulate_querystring : ''),
606
                        $entry->get('id'),
607
                        'content'
608
                    );
609
610
                    foreach ($visible_columns as $position => $column) {
611
                        $data = $entry->getData($column->get('id'));
612
                        $field = $field_pool[$column->get('id')];
613
614
                        $value = $field->prepareTableValue($data, ($column == $link_column) ? $link : null, $entry->get('id'));
615
616
                        if (!is_object($value) && (strlen(trim($value)) == 0 || $value == __('None'))) {
617
                            $value = ($position == 0 ? $link->generate() : __('None'));
618
                        }
619
620
                        if ($value == __('None')) {
621
                            $tableData[] = Widget::TableData($value, 'inactive field-' . $column->get('type') . ' field-' . $column->get('id'));
622
                        } else {
623
                            $tableData[] = Widget::TableData($value, 'field-' . $column->get('type') . ' field-' . $column->get('id'));
624
                        }
625
626
                        unset($field);
627
                    }
628
                }
629
630
                if (is_array($child_sections) && !empty($child_sections)) {
631
                    foreach ($child_sections as $key => $as) {
632
                        $field = FieldManager::fetch((int)$associated_sections[$key]['child_section_field_id']);
633
                        $parent_section_field_id = (int)$associated_sections[$key]['parent_section_field_id'];
634
635
                        if (!is_null($parent_section_field_id)) {
636
                            $search_value = $field->fetchAssociatedEntrySearchValue(
0 ignored issues
show
Bug introduced by
The method fetchAssociatedEntrySearchValue 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...
637
                                $entry->getData($parent_section_field_id),
638
                                $parent_section_field_id,
639
                                $entry->get('id')
640
                            );
641
                        } else {
642
                            $search_value = $entry->get('id');
643
                        }
644
645
                        if (!is_array($search_value)) {
646
                            $associated_entry_count = $field->fetchAssociatedEntryCount($search_value);
0 ignored issues
show
Bug introduced by
The method fetchAssociatedEntryCount 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...
647
648
                            $tableData[] = Widget::TableData(
649
                                Widget::Anchor(
650
                                    sprintf('%d &rarr;', max(0, intval($associated_entry_count))),
651
                                    sprintf(
652
                                        '%s/publish/%s/?filter[%s]=%s',
653
                                        SYMPHONY_URL,
654
                                        $as->get('handle'),
655
                                        $field->get('element_name'),
0 ignored issues
show
Bug introduced by
The method get 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...
656
                                        rawurlencode($search_value)
657
                                    ),
658
                                    $entry->get('id'),
659
                                    'content'
660
                                )
661
                            );
662
                        }
663
664
                        unset($field);
665
                    }
666
                }
667
668
                /**
669
                 * Allows Extensions to inject custom table data for each Entry
670
                 * into the Publish Index
671
                 *
672
                 * @delegate AddCustomPublishColumnData
673
                 * @since Symphony 2.2
674
                 * @param string $context
675
                 * '/publish/'
676
                 * @param array $tableData
677
                 *  An array of `Widget::TableData`, passed by reference
678
                 * @param integer $section_id
679
                 *  The current Section ID
680
                 * @param Entry $entry_id
681
                 *  The entry object, please note that this is by error and this will
682
                 *  be removed in Symphony 2.4. The entry object is available in
683
                 *  the 'entry' key as of Symphony 2.3.1.
684
                 * @param Entry $entry
685
                 *  The entry object for this row
686
                 */
687
                Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumnData', '/publish/', array(
688
                    'tableData' => &$tableData,
689
                    'section_id' => $section->get('id'),
690
                    'entry_id' => $entry,
691
                    'entry' => $entry
692
                ));
693
694
                $lastCol = $tableData[count($tableData) - 1];
695
                $lastCol->appendChild(Widget::Label(__('Select Entry %d', array($entry->get('id'))), null, 'accessible', null, array(
696
                    'for' => 'entry-' . $entry->get('id')
697
                )));
698
                $lastCol->appendChild(Widget::Input('items['.$entry->get('id').']', $entry->get('modification_date'), 'checkbox', array(
699
                    'id' => 'entry-' . $entry->get('id')
700
                )));
701
702
                // Add a row to the body array, assigning each cell to the row
703
                $aTableBody[] = Widget::TableRow($tableData, null, 'id-' . $entry->get('id'));
704
            }
705
        }
706
707
        $table = Widget::Table(
708
            Widget::TableHead($aTableHead),
709
            null,
710
            Widget::TableBody($aTableBody),
711
            'selectable',
712
            null,
713
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading', 'data-interactive' => 'data-interactive')
714
        );
715
716
        $this->Form->appendChild($table);
717
718
        $tableActions = new XMLElement('div');
719
        $tableActions->setAttribute('class', 'actions');
720
721
        $options = array(
722
            array(null, false, __('With Selected...')),
723
            array('delete', false, __('Delete'), 'confirm', null, array(
724
                'data-message' => __('Are you sure you want to delete the selected entries?')
725
            ))
726
        );
727
728
        $toggable_fields = $section->fetchToggleableFields();
729
730
        if (is_array($toggable_fields) && !empty($toggable_fields)) {
731
            $index = 2;
732
733
            foreach ($toggable_fields as $field) {
734
                $toggle_states = $field->getToggleStates();
735
736
                if (is_array($toggle_states)) {
737
                    $options[$index] = array('label' => __('Set %s', array($field->get('label'))), 'options' => array());
738
739
                    foreach ($toggle_states as $value => $state) {
740
                        $options[$index]['options'][] = array('toggle-' . $field->get('id') . '-' . $value, false, $state);
741
                    }
742
                }
743
744
                $index++;
745
            }
746
        }
747
748
        /**
749
         * Allows an extension to modify the existing options for this page's
750
         * With Selected menu. If the `$options` parameter is an empty array,
751
         * the 'With Selected' menu will not be rendered.
752
         *
753
         * @delegate AddCustomActions
754
         * @since Symphony 2.3.2
755
         * @param string $context
756
         * '/publish/'
757
         * @param array $options
758
         *  An array of arrays, where each child array represents an option
759
         *  in the With Selected menu. Options should follow the same format
760
         *  expected by `Widget::__SelectBuildOption`. Passed by reference.
761
         */
762
        Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/publish/', array(
763
            'options' => &$options
764
        ));
765
766
        if (!empty($options)) {
767
            $tableActions->appendChild(Widget::Apply($options));
768
            $this->Form->appendChild($tableActions);
769
        }
770
771
        if ($entries['total-pages'] > 1) {
772
            $ul = new XMLElement('ul');
773
            $ul->setAttribute('class', 'page');
774
775
            // First
776
            $li = new XMLElement('li');
777
778
            if ($current_page > 1) {
779
                $li->appendChild(Widget::Anchor(__('First'), Administration::instance()->getCurrentPageURL(). '?pg=1'.($filter_querystring ? "&amp;" . $filter_querystring : '')));
780
            } else {
781
                $li->setValue(__('First'));
782
            }
783
784
            $ul->appendChild($li);
785
786
            // Previous
787
            $li = new XMLElement('li');
788
789 View Code Duplication
            if ($current_page > 1) {
790
                $li->appendChild(Widget::Anchor(__('&larr; Previous'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page - 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
791
            } else {
792
                $li->setValue(__('&larr; Previous'));
793
            }
794
795
            $ul->appendChild($li);
796
797
            // Summary
798
            $li = new XMLElement('li');
799
800
            $li->setAttribute('title', __('Viewing %1$s - %2$s of %3$s entries', array(
801
                $entries['start'],
802
                ($current_page != $entries['total-pages']) ? $current_page * Symphony::Configuration()->get('pagination_maximum_rows', 'symphony') : $entries['total-entries'],
803
                $entries['total-entries']
804
            )));
805
806
            $pgform = Widget::Form(Administration::instance()->getCurrentPageURL(), 'get', 'paginationform');
807
808
            $pgmax = max($current_page, $entries['total-pages']);
809
            $pgform->appendChild(Widget::Input('pg', null, 'text', array(
810
                'data-active' => __('Go to page …'),
811
                'data-inactive' => __('Page %1$s of %2$s', array((string)$current_page, $pgmax)),
812
                'data-max' => $pgmax
813
            )));
814
815
            $li->appendChild($pgform);
816
            $ul->appendChild($li);
817
818
            // Next
819
            $li = new XMLElement('li');
820
821 View Code Duplication
            if ($current_page < $entries['total-pages']) {
822
                $li->appendChild(Widget::Anchor(__('Next &rarr;'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page + 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
823
            } else {
824
                $li->setValue(__('Next &rarr;'));
825
            }
826
827
            $ul->appendChild($li);
828
829
            // Last
830
            $li = new XMLElement('li');
831
832 View Code Duplication
            if ($current_page < $entries['total-pages']) {
833
                $li->appendChild(Widget::Anchor(__('Last'), Administration::instance()->getCurrentPageURL(). '?pg=' . $entries['total-pages'].($filter_querystring ? "&amp;" . $filter_querystring : '')));
834
            } else {
835
                $li->setValue(__('Last'));
836
            }
837
838
            $ul->appendChild($li);
839
840
            $this->Contents->appendChild($ul);
841
        }
842
    }
843
844
    public function __actionIndex()
845
    {
846
        $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
847
848
        if (is_array($checked) && !empty($checked)) {
849
            /**
850
             * Extensions can listen for any custom actions that were added
851
             * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
852
             * delegates.
853
             *
854
             * @delegate CustomActions
855
             * @since Symphony 2.3.2
856
             * @param string $context
857
             *  '/publish/'
858
             * @param array $checked
859
             *  An array of the selected rows. The value is usually the ID of the
860
             *  the associated object.
861
             */
862
            Symphony::ExtensionManager()->notifyMembers('CustomActions', '/publish/', array(
863
                'checked' => $checked
864
            ));
865
866
            switch ($_POST['with-selected']) {
867
                case 'delete':
868
                    /**
869
                     * Prior to deletion of entries. An array of Entry ID's is provided which
870
                     * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
871
                     * in Symphony 2.3.
872
                     *
873
                     * @delegate EntryPreDelete
874
                     * @param string $context
875
                     * '/publish/'
876
                     * @param array $entry_id
877
                     *  An array of Entry ID's passed by reference
878
                     */
879
                    Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
880
881
                    EntryManager::delete($checked);
882
883
                    /**
884
                     * After the deletion of entries, this delegate provides an array of Entry ID's
885
                     * that were deleted.
886
                     *
887
                     * @since Symphony 2.3
888
                     * @delegate EntryPostDelete
889
                     * @param string $context
890
                     * '/publish/'
891
                     * @param array $entry_id
892
                     *  An array of Entry ID's that were deleted.
893
                     */
894
                    Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
895
896
                    redirect(server_safe('REQUEST_URI'));
897
                    break;
898
                default:
899
                    list($option, $field_id, $value) = explode('-', $_POST['with-selected'], 3);
900
901
                    if ($option == 'toggle') {
902
                        $field = FieldManager::fetch($field_id);
903
                        $fields = array($field->get('element_name') => $value);
0 ignored issues
show
Bug introduced by
The method get 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...
904
905
                        $section = SectionManager::fetch($field->get('parent_section'));
0 ignored issues
show
Bug introduced by
The method get 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...
906
907
                        foreach ($checked as $entry_id) {
908
                            $entry = EntryManager::fetch($entry_id);
909
                            $existing_data = $entry[0]->getData($field_id);
910
                            $entry[0]->setData($field_id, $field->toggleFieldData(is_array($existing_data) ? $existing_data : array(), $value, $entry_id));
0 ignored issues
show
Bug introduced by
The method toggleFieldData 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...
911
912
                            /**
913
                             * Just prior to editing of an Entry
914
                             *
915
                             * @delegate EntryPreEdit
916
                             * @param string $context
917
                             * '/publish/edit/'
918
                             * @param Section $section
919
                             * @param Entry $entry
920
                             * @param array $fields
921
                             */
922
                            Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array(
923
                                'section' => $section,
924
                                'entry' => &$entry[0],
925
                                'fields' => $fields
926
                            ));
927
928
                            $entry[0]->commit();
929
930
                            /**
931
                             * Editing an entry. Entry object is provided.
932
                             *
933
                             * @delegate EntryPostEdit
934
                             * @param string $context
935
                             * '/publish/edit/'
936
                             * @param Section $section
937
                             * @param Entry $entry
938
                             * @param array $fields
939
                             */
940
                            Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array(
941
                                'section' => $section,
942
                                'entry' => $entry[0],
943
                                'fields' => $fields
944
                            ));
945
                        }
946
947
                        unset($field);
948
                        redirect(server_safe('REQUEST_URI'));
949
                    }
950
            }
951
        }
952
    }
953
954
    public function __viewNew()
955
    {
956 View Code Duplication
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
957
            Administration::instance()->throwCustomError(
958
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
959
                __('Unknown Section'),
960
                Page::HTTP_STATUS_NOT_FOUND
961
            );
962
        }
963
964
        $section = SectionManager::fetch($section_id);
965
966
        $this->setPageType('form');
967
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
968
969
        // Ensure errored entries still maintain any prepopulated values [#2211]
970
        $this->Form->setAttribute('action', $this->Form->getAttribute('action') . $this->getPrepopulateString());
971
        $this->Form->setAttribute('enctype', 'multipart/form-data');
972
973
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
974
        $main_fields = $section->fetchFields(null, 'main');
975
976 View Code Duplication
        if (!empty($sidebar_fields) && !empty($main_fields)) {
977
            $this->Form->setAttribute('class', 'two columns');
978
        } else {
979
            $this->Form->setAttribute('class', 'columns');
980
        }
981
982
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
983
        if (Symphony::Author()->isDeveloper()) {
984
            $this->appendSubheading(__('Untitled'),
985
                Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button')
986
            );
987
        } else {
988
            $this->appendSubheading(__('Untitled'));
989
        }
990
991
        // Build filtered breadcrumb [#1378}
992
        $this->insertBreadcrumbs(array(
993
            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
994
        ));
995
996
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
997
998
        // If there is post data floating around, due to errors, create an entry object
999
        if (isset($_POST['fields'])) {
1000
            $entry = EntryManager::create();
1001
            $entry->set('section_id', $section_id);
1002
            $entry->setDataFromPost($_POST['fields'], $error, true);
1003
1004
            // Brand new entry, so need to create some various objects
1005
        } else {
1006
            $entry = EntryManager::create();
1007
            $entry->set('section_id', $section_id);
1008
        }
1009
1010
        // Check if there is a field to prepopulate
1011
        if (isset($_REQUEST['prepopulate'])) {
1012
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1013
                $this->Form->prependChild(Widget::Input(
1014
                    "prepopulate[{$field_id}]",
1015
                    rawurlencode($value),
1016
                    'hidden'
1017
                ));
1018
1019
                // The actual pre-populating should only happen if there is not existing fields post data
1020
                if (!isset($_POST['fields']) && $field = FieldManager::fetch($field_id)) {
1021
                    $entry->setData(
1022
                        $field->get('id'),
0 ignored issues
show
Bug introduced by
The method get 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...
1023
                        $field->processRawFieldData($value, $error, $message, true)
0 ignored issues
show
Bug introduced by
The variable $error 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 variable $message seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The method processRawFieldData 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...
1024
                    );
1025
                    unset($field);
1026
                }
1027
            }
1028
        }
1029
1030
        $primary = new XMLElement('fieldset');
1031
        $primary->setAttribute('class', 'primary column');
1032
1033
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1034
            $message = __('Fields must be added to this section before an entry can be created.');
1035
1036 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1037
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1038
                . __('Add fields')
1039
                . '</a>';
1040
            }
1041
1042
            $this->pageAlert($message, Alert::ERROR);
1043
        } else {
1044 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1045
                foreach ($main_fields as $field) {
1046
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1047
                }
1048
1049
                $this->Form->appendChild($primary);
1050
            }
1051
1052 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1053
                $sidebar = new XMLElement('fieldset');
1054
                $sidebar->setAttribute('class', 'secondary column');
1055
1056
                foreach ($sidebar_fields as $field) {
1057
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1058
                }
1059
1060
                $this->Form->appendChild($sidebar);
1061
            }
1062
1063
            $div = new XMLElement('div');
1064
            $div->setAttribute('class', 'actions');
1065
            $div->appendChild(Widget::Input('action[save]', __('Create Entry'), 'submit', array('accesskey' => 's')));
1066
1067
            $this->Form->appendChild($div);
1068
1069
            // Create a Drawer for Associated Sections
1070
            $this->prepareAssociationsDrawer($section);
0 ignored issues
show
Bug introduced by
It seems like $section defined by \SectionManager::fetch($section_id) on line 964 can also be of type array; however, contentPublish::prepareAssociationsDrawer() does only seem to accept object<Section>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1071
        }
1072
    }
1073
1074
    public function __actionNew()
1075
    {
1076
        if (is_array($_POST['action']) && (array_key_exists('save', $_POST['action']) || array_key_exists('done', $_POST['action']))) {
1077
            $section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle']);
1078
1079
            if (!$section = SectionManager::fetch($section_id)) {
1080
                Administration::instance()->throwCustomError(
1081
                    __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1082
                    __('Unknown Section'),
1083
                    Page::HTTP_STATUS_NOT_FOUND
1084
                );
1085
            }
1086
1087
            $entry = EntryManager::create();
1088
            $entry->set('author_id', Symphony::Author()->get('id'));
1089
            $entry->set('section_id', $section_id);
1090
            $entry->set('creation_date', DateTimeObj::get('c'));
1091
            $entry->set('modification_date', DateTimeObj::get('c'));
1092
1093
            $fields = $_POST['fields'];
1094
1095
            // Combine FILES and POST arrays, indexed by their custom field handles
1096
            if (isset($_FILES['fields'])) {
1097
                $filedata = General::processFilePostData($_FILES['fields']);
1098
1099
                foreach ($filedata as $handle => $data) {
1100
                    if (!isset($fields[$handle])) {
1101
                        $fields[$handle] = $data;
1102
                    } elseif (isset($data['error']) && $data['error'] == UPLOAD_ERR_NO_FILE) {
1103
                        $fields[$handle] = null;
1104
                    } else {
1105
                        foreach ($data as $ii => $d) {
1106
                            if (isset($d['error']) && $d['error'] == UPLOAD_ERR_NO_FILE) {
1107
                                $fields[$handle][$ii] = null;
1108
                            } elseif (is_array($d) && !empty($d)) {
1109
                                foreach ($d as $key => $val) {
1110
                                    $fields[$handle][$ii][$key] = $val;
1111
                                }
1112
                            }
1113
                        }
1114
                    }
1115
                }
1116
            }
1117
1118
            // Initial checks to see if the Entry is ok
1119
            if (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
1120
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1121
1122
                // Secondary checks, this will actually process the data and attempt to save
1123 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
1124
                foreach ($errors as $field_id => $message) {
0 ignored issues
show
Bug introduced by
The expression $errors of type array|null 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...
1125
                    $this->pageAlert($message, Alert::ERROR);
1126
                }
1127
1128
                // Everything is awesome. Dance.
1129
            } else {
1130
                /**
1131
                 * Just prior to creation of an Entry
1132
                 *
1133
                 * @delegate EntryPreCreate
1134
                 * @param string $context
1135
                 * '/publish/new/'
1136
                 * @param Section $section
1137
                 * @param Entry $entry
1138
                 * @param array $fields
1139
                 */
1140
                Symphony::ExtensionManager()->notifyMembers('EntryPreCreate', '/publish/new/', array('section' => $section, 'entry' => &$entry, 'fields' => &$fields));
1141
1142
                $entry->set('modification_author_id', Symphony::Author()->get('id'));
1143
1144
                // Check to see if the dancing was premature
1145
                if (!$entry->commit()) {
1146
                    $this->pageAlert(null, Alert::ERROR);
1147
                } else {
1148
                    /**
1149
                     * Creation of an Entry. New Entry object is provided.
1150
                     *
1151
                     * @delegate EntryPostCreate
1152
                     * @param string $context
1153
                     * '/publish/new/'
1154
                     * @param Section $section
1155
                     * @param Entry $entry
1156
                     * @param array $fields
1157
                     */
1158
                    Symphony::ExtensionManager()->notifyMembers('EntryPostCreate', '/publish/new/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1159
1160
                    $prepopulate_querystring = $this->getPrepopulateString();
1161
                    redirect(sprintf(
1162
                        '%s/publish/%s/edit/%d/created/%s',
1163
                        SYMPHONY_URL,
1164
                        $this->_context['section_handle'],
1165
                        $entry->get('id'),
1166
                        (!empty($prepopulate_querystring) ? $prepopulate_querystring : null)
1167
                    ));
1168
                }
1169
            }
1170
        }
1171
    }
1172
1173
    public function __viewEdit()
1174
    {
1175 View Code Duplication
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
1176
            Administration::instance()->throwCustomError(
1177
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1178
                __('Unknown Section'),
1179
                Page::HTTP_STATUS_NOT_FOUND
1180
            );
1181
        }
1182
1183
        $section = SectionManager::fetch($section_id);
1184
        $entry_id = intval($this->_context['entry_id']);
1185
        $base = '/publish/'.$this->_context['section_handle'] . '/';
1186
        $new_link = $base . 'new/';
1187
        $filter_link = $base;
1188
        $canonical_link = $base . 'edit/' . $entry_id . '/';
1189
1190
        EntryManager::setFetchSorting('id', 'DESC');
1191
1192 View Code Duplication
        if (!$existingEntry = EntryManager::fetch($entry_id)) {
1193
            Administration::instance()->throwCustomError(
1194
                __('Unknown Entry'),
1195
                __('The Entry, %s, could not be found.', array($entry_id)),
1196
                Page::HTTP_STATUS_NOT_FOUND
1197
            );
1198
        }
1199
        $existingEntry = $existingEntry[0];
1200
1201
        // If there is post data floating around, due to errors, create an entry object
1202
        if (isset($_POST['fields'])) {
1203
            $fields = $_POST['fields'];
1204
1205
            $entry = EntryManager::create();
1206
            $entry->set('id', $entry_id);
1207
            $entry->set('author_id', $existingEntry->get('author_id'));
1208
            $entry->set('modification_author_id', $existingEntry->get('modification_author_id'));
1209
            $entry->set('section_id', $existingEntry->get('section_id'));
1210
            $entry->set('creation_date', $existingEntry->get('creation_date'));
1211
            $entry->set('modification_date', $existingEntry->get('modification_date'));
1212
            $entry->setDataFromPost($fields, $errors, true);
1213
1214
            $timestamp = isset($_POST['action']['timestamp'])
1215
                ? $_POST['action']['timestamp']
1216
                : $entry->get('modification_date');
1217
1218
            // Editing an entry, so need to create some various objects
1219
        } else {
1220
            $entry = $existingEntry;
1221
            $fields = array();
1222
1223
            if (!$section) {
1224
                $section = SectionManager::fetch($entry->get('section_id'));
1225
            }
1226
1227
            $timestamp = $entry->get('modification_date');
1228
        }
1229
1230
        /**
1231
         * Just prior to rendering of an Entry edit form.
1232
         *
1233
         * @delegate EntryPreRender
1234
         * @param string $context
1235
         * '/publish/edit/'
1236
         * @param Section $section
1237
         * @param Entry $entry
1238
         * @param array $fields
1239
         */
1240
        Symphony::ExtensionManager()->notifyMembers('EntryPreRender', '/publish/edit/', array(
1241
            'section' => $section,
1242
            'entry' => &$entry,
1243
            'fields' => $fields
1244
        ));
1245
1246
        // Iterate over the `prepopulate` parameters to build a URL
1247
        // to remember this state for Create New, View all Entries and
1248
        // Breadcrumb links. If `prepopulate` doesn't exist, this will
1249
        // just use the standard pages (ie. no filtering)
1250
        if (isset($_REQUEST['prepopulate'])) {
1251
            $new_link .= $this->getPrepopulateString();
1252
            $filter_link .= $this->getFilterString();
1253
            $canonical_link .= $this->getPrepopulateString();
1254
        }
1255
1256
        if (isset($this->_context['flag'])) {
1257
            // These flags are only relevant if there are no errors
1258
            if (empty($this->_errors)) {
1259
                $time = Widget::Time();
1260
1261
                switch ($this->_context['flag']) {
1262
                    case 'saved':
1263
                        $message = __('Entry updated at %s.', array($time->generate()));
1264
                        break;
1265
                    case 'created':
1266
                        $message = __('Entry created at %s.', array($time->generate()));
1267
                }
1268
1269
                $this->pageAlert(
1270
                    $message
0 ignored issues
show
Bug introduced by
The variable $message 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...
1271
                    . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
1272
                    . __('Create another?')
1273
                    . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
1274
                    . __('View all Entries')
1275
                    . '</a>',
1276
                    Alert::SUCCESS
1277
                );
1278
            }
1279
        }
1280
1281
        // Determine the page title
1282
        $field_id = Symphony::Database()->fetchVar('id', 0, sprintf("
1283
            SELECT `id`
1284
            FROM `tbl_fields`
1285
            WHERE `parent_section` = %d
1286
            ORDER BY `sortorder` LIMIT 1",
1287
            $section->get('id')
1288
        ));
1289
        if (!is_null($field_id)) {
1290
            $field = FieldManager::fetch($field_id);
1291
        }
1292
1293
        if ($field) {
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...
1294
            $title = $field->prepareReadableValue($existingEntry->getData($field->get('id')), $entry_id, true);
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 get 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...
Bug introduced by
The method prepareReadableValue 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...
1295
        } else {
1296
            $title = '';
1297
        }
1298
1299
        if (trim($title) == '') {
1300
            $title = __('Untitled');
1301
        }
1302
1303
        // Check if there is a field to prepopulate
1304
        if (isset($_REQUEST['prepopulate'])) {
1305
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1306
                $this->Form->prependChild(Widget::Input(
1307
                    "prepopulate[{$field_id}]",
1308
                    rawurlencode($value),
1309
                    'hidden'
1310
                ));
1311
            }
1312
        }
1313
1314
        $this->setPageType('form');
1315
        $this->Form->setAttribute('enctype', 'multipart/form-data');
1316
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, General::sanitize($section->get('name')), __('Symphony'))));
1317
        $this->addElementToHead(new XMLElement('link', null, array(
1318
            'rel' => 'canonical',
1319
            'href' => SYMPHONY_URL . $canonical_link,
1320
        )));
1321
1322
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
1323
        $main_fields = $section->fetchFields(null, 'main');
1324
1325 View Code Duplication
        if (!empty($sidebar_fields) && !empty($main_fields)) {
1326
            $this->Form->setAttribute('class', 'two columns');
1327
        } else {
1328
            $this->Form->setAttribute('class', 'columns');
1329
        }
1330
1331
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
1332
        if (Symphony::Author()->isDeveloper()) {
1333
            $this->appendSubheading($title, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
1334
        } else {
1335
            $this->appendSubheading($title);
1336
        }
1337
1338
        $this->insertBreadcrumbs(array(
1339
            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
1340
        ));
1341
1342
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
1343
1344
        $primary = new XMLElement('fieldset');
1345
        $primary->setAttribute('class', 'primary column');
1346
1347
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1348
            $message = __('Fields must be added to this section before an entry can be created.');
1349
1350 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1351
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1352
                . __('Add fields')
1353
                . '</a>';
1354
            }
1355
1356
            $this->pageAlert($message, Alert::ERROR);
1357
        } else {
1358 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1359
                foreach ($main_fields as $field) {
1360
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1361
                }
1362
1363
                $this->Form->appendChild($primary);
1364
            }
1365
1366 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1367
                $sidebar = new XMLElement('fieldset');
1368
                $sidebar->setAttribute('class', 'secondary column');
1369
1370
                foreach ($sidebar_fields as $field) {
1371
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1372
                }
1373
1374
                $this->Form->appendChild($sidebar);
1375
            }
1376
1377
            $div = new XMLElement('div');
1378
            $div->setAttribute('class', 'actions');
1379
            $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));
1380
1381
            $button = new XMLElement('button', __('Delete'));
1382
            $button->setAttributeArray(array('name' => 'action[delete]', 'class' => 'button confirm delete', 'title' => __('Delete this entry'), 'type' => 'submit', 'accesskey' => 'd', 'data-message' => __('Are you sure you want to delete this entry?')));
1383
            $div->appendChild($button);
1384
1385
            $div->appendChild(Widget::Input('action[timestamp]', $timestamp, 'hidden'));
1386
            $div->appendChild(Widget::Input('action[ignore-timestamp]', 'yes', 'checkbox', array('class' => 'irrelevant')));
1387
1388
            $this->Form->appendChild($div);
1389
1390
            // Create a Drawer for Associated Sections
1391
            $this->prepareAssociationsDrawer($section);
1392
        }
1393
    }
1394
1395
    public function __actionEdit()
1396
    {
1397
        $entry_id = intval($this->_context['entry_id']);
1398
1399
        if (is_array($_POST['action']) && (array_key_exists('save', $_POST['action']) || array_key_exists('done', $_POST['action']))) {
1400 View Code Duplication
            if (!$ret = EntryManager::fetch($entry_id)) {
1401
                Administration::instance()->throwCustomError(
1402
                    __('The Entry, %s, could not be found.', array($entry_id)),
1403
                    __('Unknown Entry'),
1404
                    Page::HTTP_STATUS_NOT_FOUND
1405
                );
1406
            }
1407
1408
            $entry = $ret[0];
1409
1410
            $section = SectionManager::fetch($entry->get('section_id'));
1411
1412
            $post = General::getPostData();
1413
            $fields = $post['fields'];
1414
1415
            $canProceed = $this->validateTimestamp($entry_id, true);
1416
1417
            // Timestamp validation
1418
            if (!$canProceed) {
1419
                $this->addTimestampValidationPageAlert($this->_errors['timestamp'], $entry, 'save');
1420
1421
                // Initial checks to see if the Entry is ok
1422
            } elseif (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
1423
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1424
1425
                // Secondary checks, this will actually process the data and attempt to save
1426 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
1427
                foreach ($errors as $field_id => $message) {
1428
                    $this->pageAlert($message, Alert::ERROR);
1429
                }
1430
1431
                // Everything is awesome. Dance.
1432
            } else {
1433
                /**
1434
                 * Just prior to editing of an Entry.
1435
                 *
1436
                 * @delegate EntryPreEdit
1437
                 * @param string $context
1438
                 * '/publish/edit/'
1439
                 * @param Section $section
1440
                 * @param Entry $entry
1441
                 * @param array $fields
1442
                 */
1443
                Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array('section' => $section, 'entry' => &$entry, 'fields' => $fields));
1444
1445
                $entry->set('modification_author_id', Symphony::Author()->get('id'));
1446
1447
                // Check to see if the dancing was premature
1448
                if (!$entry->commit()) {
1449
                    $this->pageAlert(null, Alert::ERROR);
1450
                } else {
1451
                    /**
1452
                     * Just after the editing of an Entry
1453
                     *
1454
                     * @delegate EntryPostEdit
1455
                     * @param string $context
1456
                     * '/publish/edit/'
1457
                     * @param Section $section
1458
                     * @param Entry $entry
1459
                     * @param array $fields
1460
                     */
1461
                    Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1462
1463
                    redirect(sprintf(
1464
                        '%s/publish/%s/edit/%d/saved/%s',
1465
                        SYMPHONY_URL,
1466
                        $this->_context['section_handle'],
1467
                        $entry->get('id'),
1468
                        $this->getPrepopulateString()
1469
                    ));
1470
                }
1471
            }
1472
        } elseif (is_array($_POST['action']) && array_key_exists('delete', $_POST['action']) && is_numeric($entry_id)) {
1473
            /**
1474
             * Prior to deletion of entries. An array of Entry ID's is provided which
1475
             * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
1476
             * in Symphony 2.3.
1477
             *
1478
             * @delegate EntryPreDelete
1479
             * @param string $context
1480
             * '/publish/'
1481
             * @param array $entry_id
1482
             *    An array of Entry ID's passed by reference
1483
             */
1484
            $checked = array($entry_id);
1485
            Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
1486
1487
            $canProceed = $this->validateTimestamp($entry_id);
1488
1489
            if ($canProceed) {
1490
                EntryManager::delete($checked);
1491
1492
                /**
1493
                 * After the deletion of entries, this delegate provides an array of Entry ID's
1494
                 * that were deleted.
1495
                 *
1496
                 * @since Symphony 2.3
1497
                 * @delegate EntryPostDelete
1498
                 * @param string $context
1499
                 * '/publish/'
1500
                 * @param array $entry_id
1501
                 *  An array of Entry ID's that were deleted.
1502
                 */
1503
                Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
1504
1505
                redirect(SYMPHONY_URL . '/publish/'.$this->_context['section_handle'].'/');
1506
            } else {
1507
                if (is_array($ret = EntryManager::fetch($entry_id))) {
1508
                    $entry = $ret[0];
1509
                    $this->addTimestampValidationPageAlert($this->_errors['timestamp'], $entry, 'delete');
1510
                }
1511
            }
1512
        }
1513
    }
1514
1515
    /**
1516
     * Given a Field and Entry object, this function will wrap
1517
     * the Field's displayPublishPanel result with a div that
1518
     * contains some contextual information such as the Field ID,
1519
     * the Field handle and whether it is required or not.
1520
     *
1521
     * @param Field $field
1522
     * @param Entry $entry
1523
     * @return XMLElement
1524
     */
1525
    private function __wrapFieldWithDiv(Field $field, Entry $entry)
1526
    {
1527
        $is_hidden = $this->isFieldHidden($field);
1528
        $div = new XMLElement('div', null, array('id' => 'field-' . $field->get('id'), 'class' => 'field field-'.$field->handle().($field->get('required') == 'yes' ? ' required' : '').($is_hidden === true ? ' irrelevant' : '')));
1529
1530
        $field->setAssociationContext($div);
1531
1532
        $field->displayPublishPanel(
1533
            $div, $entry->getData($field->get('id')),
1534
            (isset($this->_errors[$field->get('id')]) ? $this->_errors[$field->get('id')] : null),
1535
            null, null, (is_numeric($entry->get('id')) ? $entry->get('id') : null)
1536
        );
1537
1538
        /**
1539
         * Allows developers modify the field before it is rendered in the publish
1540
         * form. Passes the `Field` object, `Entry` object, the `XMLElement` div and
1541
         * any errors for the entire `Entry`. Only the `$div` element
1542
         * will be altered before appending to the page, the rest are read only.
1543
         *
1544
         * @since Symphony 2.5.0
1545
         * @delegate ModifyFieldPublishWidget
1546
         * @param string $context
1547
         * '/backend/'
1548
         * @param Field $field
1549
         * @param Entry $entry
1550
         * @param array $errors
1551
         * @param Widget $widget
1552
         */
1553
        Symphony::ExtensionManager()->notifyMembers('ModifyFieldPublishWidget', '/backend/', array(
1554
            'field' => $field,
1555
            'entry' => $entry,
1556
            'errors' => $this->_errors,
1557
            'widget' => &$div
1558
        ));
1559
1560
        return $div;
1561
    }
1562
1563
    /**
1564
     * Check whether the given `$field` will be hidden because it's been
1565
     * prepopulated.
1566
     *
1567
     * @param  Field  $field
1568
     * @return boolean
1569
     */
1570
    public function isFieldHidden(Field $field)
1571
    {
1572
        if ($field->get('hide_when_prepopulated') == 'yes') {
1573
            if (isset($_REQUEST['prepopulate'])) {
1574
                foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1575
                    if ($field_id == $field->get('id')) {
1576
                        return true;
1577
                    }
1578
                }
1579
            }
1580
        }
1581
1582
        return false;
1583
    }
1584
1585
    /**
1586
     * Prepare a Drawer to visualize section associations
1587
     *
1588
     * @param  Section $section The current Section object
1589
     * @throws InvalidArgumentException
1590
     * @throws Exception
1591
     */
1592
    private function prepareAssociationsDrawer($section)
1593
    {
1594
        $entry_id = (!is_null($this->_context['entry_id'])) ? $this->_context['entry_id'] : null;
1595
        $show_entries = Symphony::Configuration()->get('association_maximum_rows', 'symphony');
1596
1597
        if (is_null($entry_id) && !isset($_GET['prepopulate']) || is_null($show_entries) || $show_entries == 0) {
1598
            return;
1599
        }
1600
1601
        $parent_associations = SectionManager::fetchParentAssociations($section->get('id'), true);
0 ignored issues
show
Documentation introduced by
$section->get('id') is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1602
        $child_associations = SectionManager::fetchChildAssociations($section->get('id'), true);
0 ignored issues
show
Documentation introduced by
$section->get('id') is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1603
        $content = null;
1604
        $drawer_position = 'vertical-right';
1605
1606
        /**
1607
         * Prepare Associations Drawer from an Extension
1608
         *
1609
         * @since Symphony 2.3.3
1610
         * @delegate PrepareAssociationsDrawer
1611
         * @param string $context
1612
         * '/publish/'
1613
         * @param integer $entry_id
1614
         *  The entry ID or null
1615
         * @param array $parent_associations
1616
         *  Array of Sections
1617
         * @param array $child_associations
1618
         *  Array of Sections
1619
         * @param string $drawer_position
1620
         *  The position of the Drawer, defaults to `vertical-right`. Available
1621
         *  values of `vertical-left, `vertical-right` and `horizontal`
1622
         */
1623
        Symphony::ExtensionManager()->notifyMembers('PrepareAssociationsDrawer', '/publish/', array(
1624
            'entry_id' => $entry_id,
1625
            'parent_associations' => &$parent_associations,
1626
            'child_associations' => &$child_associations,
1627
            'content' => &$content,
1628
            'drawer-position' => &$drawer_position
1629
        ));
1630
1631
        // If there are no associations, return now.
1632
        if (
1633
            (is_null($parent_associations) || empty($parent_associations))
1634
            &&
1635
            (is_null($child_associations) || empty($child_associations))
1636
        ) {
1637
            return;
1638
        }
1639
1640
        if (!($content instanceof XMLElement)) {
1641
            $content = new XMLElement('div', null, array('class' => 'content'));
1642
            $content->setSelfClosingTag(false);
1643
1644
            // backup global sorting
1645
            $sorting = EntryManager::getFetchSorting();
1646
1647
            // Process Parent Associations
1648
            if (!is_null($parent_associations) && !empty($parent_associations)) {
1649
                $title = new XMLElement('h2', __('Linked to') . ':', array('class' => 'association-title'));
1650
                $content->appendChild($title);
1651
1652
                foreach ($parent_associations as $as) {
1653
                    if (empty($as['parent_section_field_id'])) {
1654
                        continue;
1655
                    }
1656
                    if ($field = FieldManager::fetch($as['parent_section_field_id'])) {
1657
                        // Get the related section
1658
                        $parent_section = SectionManager::fetch($as['parent_section_id']);
1659
1660
                        if (!($parent_section instanceof Section)) {
1661
                            continue;
1662
                        }
1663
1664
                        // set global sorting for associated section
1665
                        EntryManager::setFetchSorting(
1666
                            $parent_section->getSortingField(),
1667
                            $parent_section->getSortingOrder()
1668
                        );
1669
1670
                        if (isset($_GET['prepopulate'])) {
1671
                            $prepopulate_field = key($_GET['prepopulate']);
1672
                        }
1673
1674
                        // get associated entries if entry exists,
1675
                        if ($entry_id) {
1676
                            $relation_field = FieldManager::fetch($as['child_section_field_id']);
1677
                            $entry_ids = $relation_field->findParentRelatedEntries($as['parent_section_field_id'], $entry_id);
0 ignored issues
show
Bug introduced by
The method findParentRelatedEntries cannot be called on $relation_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...
1678
1679
                            // get prepopulated entry otherwise
1680
                        } elseif (isset($_GET['prepopulate']) && is_array($_GET['prepopulate']) && isset($_GET['prepopulate'][$as['child_section_field_id']])) {
1681
                            $entry_ids = array(intval($_GET['prepopulate'][$as['child_section_field_id']]));
1682
                        } else {
1683
                            $entry_ids = array();
1684
                        }
1685
1686
                        // Use $schema for perf reasons
1687
                        $schema = array($field->get('element_name'));
0 ignored issues
show
Bug introduced by
The method get 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...
1688
                        $where = (!empty($entry_ids)) ? sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids)) : null;
1689
                        $entries = (!empty($entry_ids) || isset($_GET['prepopulate']) && $field->get('id') === $prepopulate_field)
0 ignored issues
show
Bug introduced by
The variable $prepopulate_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 get 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...
1690
                            ? EntryManager::fetchByPage(1, $as['parent_section_id'], $show_entries, $where, null, false, false, true, $schema)
1691
                            : array();
1692
                        $has_entries = !empty($entries) && $entries['total-entries'] != 0;
1693
1694
                        // Create link
1695
                        $link = SYMPHONY_URL . '/publish/' . $as['handle'] . '/';
1696
                        $aname = General::sanitize($as['name']);
1697
                        if ($has_entries) {
1698
                            $aname .= ' <span>(' . $entries['total-entries'] . ')</span>';
1699
                        }
1700
                        $a = new XMLElement('a', $aname, array(
1701
                            'class' => 'association-section',
1702
                            'href' => $link,
1703
                            'title' => strip_tags($aname),
1704
                        ));
1705
1706
                        $element = new XMLElement('section', null, array('class' => 'association parent'));
1707
                        $header = new XMLElement('header');
1708
                        $element->appendChild($header);
1709
1710
                        if ($has_entries) {
1711
                            $element = new XMLElement('section', null, array('class' => 'association parent'));
1712
                            $header = new XMLElement('header');
1713
                            $header->appendChild(new XMLElement('p', $a->generate()));
1714
                            $element->appendChild($header);
1715
1716
                            $ul = new XMLElement('ul', null, array(
1717
                                'class' => 'association-links',
1718
                                'data-section-id' => $as['child_section_id'],
1719
                                'data-association-ids' => implode(', ', $entry_ids)
1720
                            ));
1721
1722
                            foreach ($entries['records'] as $e) {
1723
                                // let the field create the mark up
1724
                                $li = $field->prepareAssociationsDrawerXMLElement($e, $as);
0 ignored issues
show
Bug introduced by
The method prepareAssociationsDrawerXMLElement 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...
1725
                                // add it to the unordered list
1726
                                $ul->appendChild($li);
1727
                            }
1728
1729
                            $element->appendChild($ul);
1730
                        // No entries
1731 View Code Duplication
                        } else {
1732
                            $element->setAttribute('class', 'association parent empty');
1733
                            $header->appendChild(new XMLElement('p', __('No links to %s', array($a->generate()))));
1734
                        }
1735
                        $content->appendChild($element);
1736
                        unset($field);
1737
                    }
1738
                }
1739
            }
1740
1741
            // Process Child Associations
1742
            if (!is_null($child_associations) && !empty($child_associations)) {
1743
                $title = new XMLElement('h2', __('Links in') . ':', array('class' => 'association-title'));
1744
                $content->appendChild($title);
1745
1746
                foreach ($child_associations as $as) {
1747
                    // Get the related section
1748
                    $child_section = SectionManager::fetch($as['child_section_id']);
1749
1750
                    if (!($child_section instanceof Section)) {
1751
                        continue;
1752
                    }
1753
1754
                    // set global sorting for associated section
1755
                    EntryManager::setFetchSorting(
1756
                        $child_section->getSortingField(),
1757
                        $child_section->getSortingOrder()
1758
                    );
1759
1760
                    // Get the visible field instance (using the sorting field, this is more flexible than visibleColumns())
1761
                    // Get the link field instance
1762
                    $visible_field   = current($child_section->fetchVisibleColumns());
1763
                    $relation_field  = FieldManager::fetch($as['child_section_field_id']);
1764
1765
                    $entry_ids = $relation_field->findRelatedEntries($entry_id, $as['parent_section_field_id']);
0 ignored issues
show
Bug introduced by
The method findRelatedEntries cannot be called on $relation_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...
1766
1767
                    $schema = $visible_field ? array($visible_field->get('element_name')) : array();
1768
                    $where = sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids));
1769
1770
                    $entries = (!empty($entry_ids)) ? EntryManager::fetchByPage(1, $as['child_section_id'], $show_entries, $where, null, false, false, true, $schema) : array();
1771
                    $has_entries = !empty($entries) && $entries['total-entries'] != 0;
1772
1773
                    // Build the HTML of the relationship
1774
                    $element = new XMLElement('section', null, array('class' => 'association child'));
1775
                    $header = new XMLElement('header');
1776
1777
                    // Get the search value for filters and prepopulate
1778
                    $entry = current(EntryManager::fetch($entry_id));
1779
                    $search_value = $relation_field->fetchAssociatedEntrySearchValue(
0 ignored issues
show
Bug introduced by
The method fetchAssociatedEntrySearchValue cannot be called on $relation_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...
1780
                        $entry->getData($as['parent_section_field_id']),
1781
                        $as['parent_section_field_id'],
1782
                        $entry_id
1783
                    );
1784
                    if (is_array($search_value)) {
1785
                        $search_value = $entry_id;
1786
                    }
1787
1788
                    $filter = '?filter[' . $relation_field->get('element_name') . ']=' . $search_value;
0 ignored issues
show
Bug introduced by
The method get cannot be called on $relation_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...
1789
                    $prepopulate = '?prepopulate[' . $as['child_section_field_id'] . ']=' . $search_value;
1790
1791
                    // Create link with filter or prepopulate
1792
                    $link = SYMPHONY_URL . '/publish/' . $as['handle'] . '/' . $filter;
1793
                    $aname = General::sanitize($as['name']);
1794
                    if ($has_entries) {
1795
                        $aname .= ' <span>(' . $entries['total-entries'] . ')</span>';
1796
                    }
1797
                    $a = new XMLElement('a', $aname, array(
1798
                        'class' => 'association-section',
1799
                        'href' => $link,
1800
                        'title' => strip_tags($aname),
1801
                    ));
1802
1803
                    // Create new entries
1804
                    $create = new XMLElement('a', __('New'), array(
1805
                        'class' => 'button association-new',
1806
                        'href' => SYMPHONY_URL . '/publish/' . $as['handle'] . '/new/' . $prepopulate
1807
                    ));
1808
1809
                    // Display existing entries
1810
                    if ($has_entries) {
1811
                        $header->appendChild(new XMLElement('p', $a->generate()));
1812
1813
                        $ul = new XMLElement('ul', null, array(
1814
                            'class' => 'association-links',
1815
                            'data-section-id' => $as['child_section_id'],
1816
                            'data-association-ids' => implode(', ', $entry_ids)
1817
                        ));
1818
1819
                        foreach ($entries['records'] as $key => $e) {
1820
                            // let the first visible field create the mark up
1821
                            if ($visible_field) {
1822
                                $li = $visible_field->prepareAssociationsDrawerXMLElement($e, $as);
1823
                            }
1824
                            // or use the system:id if no visible field exists.
1825
                            else {
1826
                                $li = Field::createAssociationsDrawerXMLElement($e->get('id'), $e, $as, $prepopulate);
1827
                            }
1828
1829
                            // add it to the unordered list
1830
                            $ul->appendChild($li);
1831
                        }
1832
1833
                        $element->appendChild($ul);
1834
1835
                        // If we are only showing 'some' of the entries, then show this on the UI
1836
                        if ($entries['total-entries'] > $show_entries) {
1837
                            $pagination = new XMLElement('li', null, array(
1838
                                'class' => 'association-more',
1839
                                'data-current-page' => '1',
1840
                                'data-total-pages' => ceil($entries['total-entries'] / $show_entries),
1841
                                'data-total-entries' => $entries['total-entries']
1842
                            ));
1843
                            $counts = new XMLElement('a', __('Show more entries'), array(
1844
                                'href' => $link
1845
                            ));
1846
1847
                            $pagination->appendChild($counts);
1848
                            $ul->appendChild($pagination);
1849
                        }
1850
1851
                        // No entries
1852 View Code Duplication
                    } else {
1853
                        $element->setAttribute('class', 'association child empty');
1854
                        $header->appendChild(new XMLElement('p', __('No links in %s', array($a->generate()))));
1855
                    }
1856
1857
                    $header->appendChild($create);
1858
                    $element->prependChild($header);
1859
                    $content->appendChild($element);
1860
                }
1861
            }
1862
1863
            // reset global sorting
1864
            EntryManager::setFetchSorting(
1865
                $sorting->field,
1866
                $sorting->direction
1867
            );
1868
        }
1869
1870
        $drawer = Widget::Drawer('section-associations', __('Show Associations'), $content);
1871
        $this->insertDrawer($drawer, $drawer_position, 'prepend');
1872
    }
1873
1874
    /**
1875
     * If this entry is being prepopulated, this function will return the prepopulated
1876
     * fields and values as a query string.
1877
     *
1878
     * @since Symphony 2.5.2
1879
     * @return string
1880
     */
1881 View Code Duplication
    public function getPrepopulateString()
1882
    {
1883
        $prepopulate_querystring = '';
1884
1885
        if (isset($_REQUEST['prepopulate']) && is_array($_REQUEST['prepopulate'])) {
1886
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1887
                // Properly decode and re-encode value for output
1888
                $value = rawurlencode(rawurldecode($value));
1889
                $prepopulate_querystring .= sprintf("prepopulate[%s]=%s&", $field_id, $value);
1890
            }
1891
            $prepopulate_querystring = trim($prepopulate_querystring, '&');
1892
        }
1893
1894
        // This is to prevent the value being interpreted as an additional GET
1895
        // parameter. eg. prepopulate[cat]=Minx&June, would come through as:
1896
        // $_GET['cat'] = Minx
1897
        // $_GET['June'] = ''
1898
        $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
1899
1900
        return $prepopulate_querystring ? '?' . $prepopulate_querystring : null;
1901
    }
1902
1903
    /**
1904
     * If the entry is being prepopulated, we may want to filter other views by this entry's
1905
     * value. This function will create that filter query string.
1906
     *
1907
     * @since Symphony 2.5.2
1908
     * @return string
1909
     */
1910 View Code Duplication
    public function getFilterString()
1911
    {
1912
        $filter_querystring = '';
1913
1914
        if (isset($_REQUEST['prepopulate']) && is_array($_REQUEST['prepopulate'])) {
1915
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1916
                $handle = FieldManager::fetchHandleFromID($field_id);
1917
                // Properly decode and re-encode value for output
1918
                $value = rawurlencode(rawurldecode($value));
1919
                $filter_querystring .= sprintf('filter[%s]=%s&', $handle, $value);
1920
            }
1921
            $filter_querystring = trim($filter_querystring, '&');
1922
        }
1923
1924
        // This is to prevent the value being interpreted as an additional GET
1925
        // parameter. eg. filter[cat]=Minx&June, would come through as:
1926
        // $_GET['cat'] = Minx
1927
        // $_GET['June'] = ''
1928
        $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
1929
1930
        return $filter_querystring ? '?' . $filter_querystring : null;
1931
    }
1932
1933
    /**
1934
     * Given $_POST values, this function will validate the current timestamp
1935
     * and set the proper error messages.
1936
     *
1937
     * @since Symphony 2.7.0
1938
     * @param int $entry_id
1939
     *  The entry id to validate
1940
     * @return boolean
1941
     *  true if the timestamp is valid
1942
     */
1943 View Code Duplication
    protected function validateTimestamp($entry_id, $checkMissing = false)
1944
    {
1945
        if (!isset($_POST['action']['ignore-timestamp'])) {
1946
            if ($checkMissing && !isset($_POST['action']['timestamp'])) {
1947
                if (isset($this->_errors) && is_array($this->_errors)) {
1948
                    $this->_errors['timestamp'] = __('The entry could not be saved due to conflicting changes');
1949
                }
1950
                return false;
1951
            } elseif (isset($_POST['action']['timestamp'])) {
1952
                $tv = new TimestampValidator('entries');
1953
                if (!$tv->check($entry_id, $_POST['action']['timestamp'])) {
1954
                    if (isset($this->_errors) && is_array($this->_errors)) {
1955
                        $this->_errors['timestamp'] = __('The entry could not be saved due to conflicting changes');
1956
                    }
1957
                    return false;
1958
                }
1959
            }
1960
        }
1961
        return true;
1962
    }
1963
}
1964