GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — integration ( 5cdb31...81c93f )
by Brendan
04:03
created

contentPublish   F

Complexity

Total Complexity 264

Size/Duplication

Total Lines 1813
Duplicated Lines 7.5 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 13
Bugs 3 Features 0
Metric Value
c 13
b 3
f 0
dl 136
loc 1813
rs 0.6314
wmc 264
lcom 1
cbo 22

27 Methods

Rating   Name   Duplication   Size   Complexity  
A createFilteringInterface() 0 16 3
C __actionIndex() 0 108 8
D __viewNew() 34 118 21
B __wrapFieldWithDiv() 0 37 5
A getPrepopulateString() 0 19 4
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
A createFilterComparisons() 0 18 3
A getFilterQuery() 0 14 4
A build() 13 13 2
A action() 0 4 1
A view() 0 4 1
B __switchboard() 0 23 5
B isFieldHidden() 0 14 5
D prepareAssociationsDrawer() 0 205 36
A parseContext() 0 16 2
C sort() 6 53 12
A createFilterSuggestions() 0 15 2
A createFilterHelp() 0 13 2
F __viewIndex() 21 464 72
D __actionNew() 7 96 20
F __viewEdit() 41 205 30
D __actionEdit() 14 102 10
B getFilterString() 0 29 6

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
    /**
18
     * The Pages page has /action/id/flag/ context.
19
     * eg. /edit/1/saved/
20
     *
21
     * @param array $context
22
     * @param array $parts
23
     * @return array
24
     */
25
    public function parseContext(array &$context, array $parts)
26
    {
27
        // Order is important!
28
        $params = array_fill_keys(array('section_handle', 'page', 'entry_id', 'flag'), null);
29
        $params['section_handle'] = $parts[1];
30
31
        if (isset($parts[2])) {
32
            $extras = preg_split('/\//', $parts[2], -1, PREG_SPLIT_NO_EMPTY);
33
            list($params['page'], $params['entry_id'], $params['flag']) = $extras;
34
            $params['entry_id'] = (int)$params['entry_id'];
35
        } else {
36
            $params['page'] = 'index';
37
        }
38
39
        $context = array_filter($params);
40
    }
41
42
    public function sort(&$sort, &$order, $params)
43
    {
44
        $section = $params['current-section'];
45
46
        // If `?unsort` is appended to the URL, then sorting is reverted
47
        // to 'none', aka. by 'entry-id'.
48
        if ($params['unsort']) {
49
            $section->setSortingField('id', false);
50
            $section->setSortingOrder('desc');
51
52
            redirect(Administration::instance()->getCurrentPageURL());
53
        }
54
55
        // By default, sorting information are retrieved from
56
        // the filesystem and stored inside the `Configuration` object
57
        if (is_null($sort) && is_null($order)) {
58
            $sort = $section->getSortingField();
59
            $order = $section->getSortingOrder();
60
61
            // Set the sorting in the `EntryManager` for subsequent use
62
            EntryManager::setFetchSorting($sort, $order);
63
        } else {
64
            $sort = General::sanitize($sort);
65
66
            // Ensure that this field is infact sortable, otherwise
67
            // fallback to IDs
68
            if (($field = FieldManager::fetch($sort)) instanceof Field && !$field->isSortable()) {
69
                $sort = $section->getDefaultSortingField();
70
            }
71
72
            // If the sort order or direction differs from what is saved,
73
            // update the config file and reload the page
74
            if ($sort !== $section->getSortingField() || $order !== $section->getSortingOrder()) {
75
                $section->setSortingField($sort, false);
76
                $section->setSortingOrder($order);
77
78 View Code Duplication
                if ($params['filters']) {
79
                    $params['filters'] = '?' . trim($params['filters'], '&amp;');
80
                }
81
82
                redirect(Administration::instance()->getCurrentPageURL() . $params['filters']);
83
            }
84
85
            // If the sort order or direction remains the same, reload the page
86
            if ($sort === $section->getSortingField() && $order === $section->getSortingOrder()) {
87 View Code Duplication
                if ($params['filters']) {
88
                    $params['filters'] = '?' . trim($params['filters'], '&amp;');
89
                }
90
91
                redirect(Administration::instance()->getCurrentPageURL() . $params['filters']);
92
            }
93
        }
94
    }
95
96
    /**
97
     * Append filtering interface
98
     */
99
    public function createFilteringInterface()
100
    {
101
        //Check if section has filtering enabled
102
        $context = $this->getContext();
103
        $handle = $context['section_handle'];
104
        $section_id = SectionManager::fetchIDFromHandle($handle);
105
        $section = SectionManager::fetch($section_id);
106
        $filter = $section->get('filter');
107
        $count = EntryManager::fetchCount($section_id);
108
109
        if ($filter !== 'no' && $count > 1) {
110
            $drawer = Widget::Drawer('filtering-' . $section_id, __('Filter Entries'), $this->createFilteringDrawer($section));
0 ignored issues
show
Bug introduced by
It seems like $section defined by \SectionManager::fetch($section_id) on line 105 can also be of type array; however, contentPublish::createFilteringDrawer() 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...
111
            $drawer->addClass('drawer-filtering');
112
            $this->insertDrawer($drawer);
113
        }
114
    }
115
116
    /**
117
     * Create filtering drawer
118
     *
119
     * @param Section $section
120
     * @return XMLElement
121
     */
122
    public function createFilteringDrawer(Section $section)
123
    {
124
        $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...
125
        $this->createFilteringDuplicator($section);
126
127
        return $this->filteringForm;
128
    }
129
130
    public function createFilteringDuplicator($section)
131
    {
132
        $div = new XMLElement('div');
133
        $div->setAttribute('class', 'frame filters-duplicator');
134
        $div->setAttribute('data-interactive', 'data-interactive');
135
136
        $ol = new XMLElement('ol');
137
        $ol->setAttribute('data-add', __('Add filter'));
138
        $ol->setAttribute('data-remove', __('Clear filter'));
139
        $ol->setAttribute('data-empty', __('No filters applied yet.'));
140
141
        $this->createFieldFilters($ol, $section);
142
        $this->createSystemDateFilters($ol);
143
144
        $div->appendChild($ol);
145
        $this->filteringForm->appendChild($div);
146
    }
147
148
    private function createFieldFilters(&$wrapper, $section)
149
    {
150
        $filters = $_GET['filter'];
151
152
        foreach ($section->fetchFilterableFields() as $field) {
153
            if (!$field->canPublishFilter()) {
154
                continue;
155
            }
156
157
            $filter = $filters[$field->get('element_name')];
158
159
            // Filter data
160
            $data = array();
161
            $data['type'] = $field->get('element_name');
162
            $data['name'] = $field->get('label');
163
            $data['filter'] = $filter;
164
            $data['instance'] = 'unique';
165
            $data['search'] = $field->fetchSuggestionTypes();
166
            $data['operators'] = $field->fetchFilterableOperators();
167
            $data['comparisons'] = $this->createFilterComparisons($data);
168
            $data['query'] = $this->getFilterQuery($data);
169
            $data['field-id'] = $field->get('id');
170
171
            // Add existing filter
172
            if (isset($filter)) {
173
                $this->createFilter($wrapper, $data);
174
            }
175
176
            // Add filter template
177
            $data['instance'] = 'unique template';
178
            $data['query'] = '';
179
            $this->createFilter($wrapper, $data);
180
        }
181
    }
182
183
    private function createSystemDateFilters(&$wrapper)
184
    {
185
        $filters = $_GET['filter'];
186
        $dateField = new FieldDate;
187
188
        $fields = array(
189
            array(
190
                'type' => 'system:creation-date',
191
                'label' => __('System Creation Date')
192
            ),
193
            array(
194
                'type' => 'system:modification-date',
195
                'label' => __('System Modification Date')
196
            )
197
        );
198
199
        foreach ($fields as $field) {
200
            $filter = $filters[$field['type']];
201
202
            // Filter data
203
            $data = array();
204
            $data['type'] = $field['type'];
205
            $data['name'] = $field['label'];
206
            $data['filter'] = $filter;
207
            $data['instance'] = 'unique';
208
            $data['search'] = $dateField->fetchSuggestionTypes();
209
            $data['operators'] = $dateField->fetchFilterableOperators();
210
            $data['comparisons'] = $this->createFilterComparisons($data);
211
            $data['query'] = $this->getFilterQuery($data);
212
213
            // Add existing filter
214
            if (isset($filter)) {
215
                $this->createFilter($wrapper, $data);
216
            }
217
218
            // Add filter template
219
            $data['instance'] = 'unique template';
220
            $data['query'] = '';
221
            $this->createFilter($wrapper, $data);
222
        }
223
    }
224
225
    private function createFilter(&$wrapper, $data)
226
    {
227
        $li = new XMLElement('li');
228
        $li->setAttribute('class', $data['instance']);
229
        $li->setAttribute('data-type', $data['type']);
230
231
        // Header
232
        $li->appendChild(new XMLElement('header', $data['name'], array(
233
            'data-name' => $data['name']
234
        )));
235
236
        // Settings
237
        $div = new XMLElement('div', null, array('class' => 'two columns'));
238
239
        // Comparisons
240
        $label = Widget::Label();
241
        $label->setAttribute('class', 'column secondary');
242
243
        $select = Widget::Select($data['type'] . '-comparison', $data['comparisons'], array(
244
            'class' => 'comparison'
245
        ));
246
247
        $label->appendChild($select);
248
        $div->appendChild($label);
249
250
        // Query
251
        $label = Widget::Label();
252
        $label->setAttribute('class', 'column primary');
253
254
        $input = Widget::Input($data['type'], $data['query'], 'text', array(
255
            'placeholder' => __('Type and hit enter to apply filter…'),
256
            'autocomplete' => 'off'
257
        ));
258
        $input->setAttribute('class', 'filter');
259
        $label->appendChild($input);
260
261
        $this->createFilterSuggestions($label, $data);
262
263
        $div->appendChild($label);
264
        $li->appendChild($div);
265
        $wrapper->appendChild($li);
266
    }
267
268
    private function createFilterComparisons($data)
269
    {
270
        // Default comparison
271
        $comparisons = array();
272
273
        // Custom field comparisons
274
        foreach ($data['operators'] as $operator) {
275
            $filter = trim($operator['filter']);
276
277
            $comparisons[] = array(
278
                $filter,
279
                (!empty($filter) && strpos($data['filter'], $filter) === 0),
280
                __($operator['title'])
281
            );
282
        }
283
284
        return $comparisons;
285
    }
286
287
    private function createFilterSuggestions(&$wrapper, $data)
288
    {
289
        $ul = new XMLElement('ul');
290
        $ul->setAttribute('class', 'suggestions');
291
        $ul->setAttribute('data-field-id', $data['field-id']);
292
        $ul->setAttribute('data-associated-ids', '0');
293
        $ul->setAttribute('data-search-types', implode($data['search'], ','));
294
295
        // Add help text for each filter operator
296
        foreach ($data['operators'] as $operator) {
297
            $this->createFilterHelp($ul, $operator);
298
        }
299
300
        $wrapper->appendChild($ul);
301
    }
302
303
    private function createFilterHelp(&$wrapper, $operator)
304
    {
305
        if (empty($operator['help'])) {
306
            return;
307
        }
308
309
        $li = new XMLElement('li', __('Comparison mode') . ': ' . $operator['help'], array(
310
            'class' => 'help',
311
            'data-comparison' => trim($operator['filter'])
312
        ));
313
314
        $wrapper->appendChild($li);
315
    }
316
317
    private function getFilterQuery($data)
318
    {
319
        $query = $data['filter'];
320
321
        foreach ($data['operators'] as $operator) {
322
            $filter = trim($operator['filter']);
323
324
            if (!empty($filter) && strpos($data['filter'], $filter) === 0) {
325
                $query = substr($data['filter'], strlen($filter));
326
            }
327
        }
328
329
        return (string)$query;
330
    }
331
332 View Code Duplication
    public function build(array $context = array())
333
    {
334
        $section_id = SectionManager::fetchIDFromHandle($context['section_handle']);
335
336
        if ($section_id) {
337
            $context['associations'] = array(
338
                'parent' => SectionManager::fetchParentAssociations($section_id),
339
                'child' => SectionManager::fetchChildAssociations($section_id)
340
            );
341
        }
342
343
        return parent::build($context);
344
    }
345
346
    public function action()
347
    {
348
        $this->__switchboard('action');
349
    }
350
351
    public function __switchboard($type = 'view')
352
    {
353
        $function = ($type === 'action' ? '__action' : '__view') . ucfirst($this->_context['page']);
354
355
        if (!method_exists($this, $function)) {
356
            // If there is no action function, just return without doing anything
357
            if ($type === 'action') {
358
                return;
359
            }
360
361
            Administration::instance()->errorPageNotFound();
362
        }
363
364
        // Is this request allowed by server?
365
        if ($this->isRequestValid() === false) {
366
            $this->pageAlert(__('This request exceeds the maximum allowed request size of %s specified by your host.', array(
367
                    ini_get('post_max_size')
368
                )),
369
                Alert::ERROR
370
            );
371
        }
372
        $this->$function();
373
    }
374
375
    public function view()
376
    {
377
        $this->__switchboard();
378
    }
379
380
    public function __viewIndex()
381
    {
382
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
383
            Administration::instance()->throwCustomError(
384
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
385
                __('Unknown Section'),
386
                Page::HTTP_STATUS_NOT_FOUND
387
            );
388
        } elseif (!is_writable(CONFIG)) {
389
            $this->pageAlert(__('The Symphony configuration file, %s, is not writable. The sort order cannot be modified.', array('<code>/manifest/config.php</code>')), Alert::NOTICE);
390
        }
391
392
        $section = SectionManager::fetch($section_id);
393
394
        $this->setPageType('table');
395
        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
396
397
        $filters = array();
398
        $filter_querystring = $prepopulate_querystring = $where = $joins = null;
399
        $current_page = (isset($_REQUEST['pg']) && is_numeric($_REQUEST['pg']) ? max(1, intval($_REQUEST['pg'])) : 1);
400
401
        if (isset($_REQUEST['filter'])) {
402
            // legacy implementation, convert single filter to an array
403
            // split string in the form ?filter=handle:value
404
            if (!is_array($_REQUEST['filter'])) {
405
                list($field_handle, $filter_value) = explode(':', $_REQUEST['filter'], 2);
406
                $filters[$field_handle] = rawurldecode($filter_value);
407
            } else {
408
                $filters = $_REQUEST['filter'];
409
            }
410
411
            foreach ($filters as $handle => $value) {
412
                // Handle multiple values through filtering. RE: #2290
413
                if ((is_array($value) && empty($value)) || trim($value) === '') {
414
                    continue;
415
                }
416
417 View Code Duplication
                if (!is_array($value)) {
418
                    $filter_type = Datasource::determineFilterType($value);
419
                    $value = preg_split('/'.($filter_type === Datasource::FILTER_AND ? '\+' : '(?<!\\\\),').'\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
420
                    $value = array_map('trim', $value);
421
                    $value = array_map(array('Datasource', 'removeEscapedCommas'), $value);
422
                }
423
424
                // Handle date meta data #2003
425
                $handle = Symphony::Database()->cleanValue($handle);
426
                if (in_array($handle, array('system:creation-date', 'system:modification-date'))) {
427
                    $date_joins = '';
428
                    $date_where = '';
429
                    $date = new FieldDate();
430
                    $date->buildDSRetrievalSQL($value, $date_joins, $date_where, ($filter_type === Datasource::FILTER_AND ? true : false));
0 ignored issues
show
Bug introduced by
The variable $filter_type 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...
431
432
                    // Replace the date field where with the `creation_date` or `modification_date`.
433
                    $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...
434
                    $where .= $date_where;
435
                } else {
436
                    // Handle normal fields
437
                    $field_id = FieldManager::fetchFieldIDFromElementName(
438
                        $handle,
439
                        $section->get('id')
440
                    );
441
442
                    $field = FieldManager::fetch($field_id);
443
                    if ($field instanceof Field) {
444
                        $field->buildDSRetrievalSQL($value, $joins, $where, ($filter_type === Datasource::FILTER_AND ? true : false));
445
446
                        $value = implode(',', $value);
447
                        $encoded_value = rawurlencode($value);
448
                        $filter_querystring .= sprintf("filter[%s]=%s&amp;", $handle, $encoded_value);
449
450
                        // Some fields require that prepopulation be done via ID. RE: #2331
451
                        if (!is_numeric($value) && method_exists($field, 'fetchIDfromValue')) {
452
                            $encoded_value = $field->fetchIDfromValue($value);
453
                        }
454
                        $prepopulate_querystring .= sprintf("prepopulate[%d]=%s&amp;", $field_id, $encoded_value);
455
                    } else {
456
                        unset($filters[$handle]);
457
                    }
458
                }
459
            }
460
461
            $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
462
            $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
463
        }
464
465
        Sortable::initialize($this, $entries, $sort, $order, array(
466
            'current-section' => $section,
467
            'filters' => ($filter_querystring ? "&amp;" . $filter_querystring : ''),
468
            'unsort' => isset($_REQUEST['unsort'])
469
        ));
470
471
        $this->Form->setAttribute('action', Administration::instance()->getCurrentPageURL(). '?pg=' . $current_page.($filter_querystring ? "&amp;" . $filter_querystring : ''));
472
473
        // Build filtering interface
474
        $this->createFilteringInterface();
475
476
        $subheading_buttons = array(
477
            Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/'.($prepopulate_querystring ? '?' . $prepopulate_querystring : ''), __('Create a new entry'), 'create button', null, array('accesskey' => 'c'))
478
        );
479
480
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
481
        if (Symphony::Author()->isDeveloper()) {
482
            array_unshift($subheading_buttons, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
483
        }
484
485
        $this->appendSubheading($section->get('name'), $subheading_buttons);
486
487
        /**
488
         * Allows adjustments to be made to the SQL where and joins statements
489
         * before they are used to fetch the entries for the page
490
         *
491
         * @delegate AdjustPublishFiltering
492
         * @since Symphony 2.3.3
493
         * @param string $context
494
         * '/publish/'
495
         * @param integer $section_id
496
         * An array of the current columns, passed by reference
497
         * @param string $where
498
         * The current where statement, or null if not set
499
         * @param string $joins
500
         */
501
        Symphony::ExtensionManager()->notifyMembers('AdjustPublishFiltering', '/publish/', array('section-id' => $section_id, 'where' => &$where, 'joins' => &$joins));
502
503
        // Check that the filtered query fails that the filter is dropped and an
504
        // error is logged. #841 ^BA
505
        try {
506
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), $where, $joins, true);
0 ignored issues
show
Documentation introduced by
\Symphony::Configuration...imum_rows', 'symphony') 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...
507
        } catch (DatabaseException $ex) {
508
            $this->pageAlert(__('An error occurred while retrieving filtered entries. Showing all entries instead.'), Alert::ERROR);
509
            $filter_querystring = null;
510
            Symphony::Log()->warning(sprintf(
511
                '%s - %s%s%s',
512
                $section->get('name') . ' Publish Index',
513
                $ex->getMessage(),
514
                ($ex->getFile() ? " in file " .  $ex->getFile() : null),
515
                ($ex->getLine() ? " on line " . $ex->getLine() : null)
516
            ));
517
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'));
0 ignored issues
show
Documentation introduced by
\Symphony::Configuration...imum_rows', 'symphony') 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...
518
        }
519
520
        // Flag filtering
521
        if (isset($_REQUEST['filter'])) {
522
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d of %d entries (filtered)', array($entries['total-entries'], EntryManager::fetchCount($section_id))) . '</span>', array('class' => 'inactive'));
523
        } else {
524
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d entries', array($entries['total-entries'])) . '</span>', array('class' => 'inactive'));
525
        }
526
        $this->Breadcrumbs->appendChild($filter_stats);
527
528
        // Build table
529
        $visible_columns = $section->fetchVisibleColumns();
530
        $columns = array();
531
532
        if (is_array($visible_columns) && !empty($visible_columns)) {
533
            foreach ($visible_columns as $column) {
534
                $columns[] = array(
535
                    'label' => $column->get('label'),
536
                    'sortable' => $column->isSortable(),
537
                    'handle' => $column->get('id'),
538
                    'attrs' => array(
539
                        'id' => 'field-' . $column->get('id'),
540
                        'class' => 'field-' . $column->get('type')
541
                    )
542
                );
543
            }
544
        } else {
545
            $columns[] = array(
546
                'label' => __('ID'),
547
                'sortable' => true,
548
                'handle' => 'id'
549
            );
550
        }
551
552
        $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order, ($filter_querystring) ? "&amp;" . $filter_querystring : '');
553
554
        $child_sections = array();
555
        $associated_sections = $section->fetchChildAssociations(true);
556
557
        if (is_array($associated_sections) && !empty($associated_sections)) {
558
            foreach ($associated_sections as $key => $as) {
559
                $child_sections[$key] = SectionManager::fetch($as['child_section_id']);
560
                $aTableHead[] = array($child_sections[$key]->get('name'), 'col');
561
            }
562
        }
563
564
        /**
565
         * Allows the creation of custom table columns for each entry. Called
566
         * after all the Section Visible columns have been added as well
567
         * as the Section Associations
568
         *
569
         * @delegate AddCustomPublishColumn
570
         * @since Symphony 2.2
571
         * @param string $context
572
         * '/publish/'
573
         * @param array $tableHead
574
         * An array of the current columns, passed by reference
575
         * @param integer $section_id
576
         * The current Section ID
577
         */
578
        Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumn', '/publish/', array('tableHead' => &$aTableHead, 'section_id' => $section->get('id')));
579
580
        // Table Body
581
        $aTableBody = array();
582
583
        if (!is_array($entries['records']) || empty($entries['records'])) {
584
            $aTableBody = array(
585
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
586
            );
587
        } else {
588
            $field_pool = array();
589
590
            if (is_array($visible_columns) && !empty($visible_columns)) {
591
                foreach ($visible_columns as $column) {
592
                    $field_pool[$column->get('id')] = $column;
593
                }
594
            }
595
596
            $link_column = array_reverse($visible_columns);
597
            $link_column = end($link_column);
598
            reset($visible_columns);
599
600
            foreach ($entries['records'] as $entry) {
601
                $tableData = array();
602
603
                // Setup each cell
604
                if (!is_array($visible_columns) || empty($visible_columns)) {
605
                    $tableData[] = Widget::TableData(Widget::Anchor($entry->get('id'), Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'));
606
                } else {
607
                    $link = Widget::Anchor(
608
                        '',
609
                        Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'.($filter_querystring ? '?' . $prepopulate_querystring : ''),
610
                        $entry->get('id'),
611
                        'content'
612
                    );
613
614
                    foreach ($visible_columns as $position => $column) {
615
                        $data = $entry->getData($column->get('id'));
616
                        $field = $field_pool[$column->get('id')];
617
618
                        $value = $field->prepareTableValue($data, ($column === $link_column) ? $link : null, $entry->get('id'));
619
620
                        if (!is_object($value) && (strlen(trim($value)) === 0 || $value === __('None'))) {
621
                            $value = ($position === 0 ? $link->generate() : __('None'));
622
                        }
623
624
                        if ($value === __('None')) {
625
                            $tableData[] = Widget::TableData($value, 'inactive field-' . $column->get('type') . ' field-' . $column->get('id'));
626
                        } else {
627
                            $tableData[] = Widget::TableData($value, 'field-' . $column->get('type') . ' field-' . $column->get('id'));
628
                        }
629
630
                        unset($field);
631
                    }
632
                }
633
634
                if (is_array($child_sections) && !empty($child_sections)) {
635
                    foreach ($child_sections as $key => $as) {
636
                        $field = FieldManager::fetch((int)$associated_sections[$key]['child_section_field_id']);
637
                        $parent_section_field_id = (int)$associated_sections[$key]['parent_section_field_id'];
638
639
                        if (!is_null($parent_section_field_id)) {
640
                            $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...
641
                                $entry->getData($parent_section_field_id),
642
                                $parent_section_field_id,
643
                                $entry->get('id')
644
                            );
645
                        } else {
646
                            $search_value = $entry->get('id');
647
                        }
648
649
                        if (!is_array($search_value)) {
650
                            $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...
651
652
                            $tableData[] = Widget::TableData(
653
                                Widget::Anchor(
654
                                    sprintf('%d &rarr;', max(0, intval($associated_entry_count))),
655
                                    sprintf(
656
                                        '%s/publish/%s/?filter[%s]=%s',
657
                                        SYMPHONY_URL,
658
                                        $as->get('handle'),
659
                                        $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...
660
                                        rawurlencode($search_value)
661
                                    ),
662
                                    $entry->get('id'),
663
                                    'content'
664
                                )
665
                            );
666
                        }
667
                    }
668
                }
669
670
                /**
671
                 * Allows Extensions to inject custom table data for each Entry
672
                 * into the Publish Index
673
                 *
674
                 * @delegate AddCustomPublishColumnData
675
                 * @since Symphony 2.2
676
                 * @param string $context
677
                 * '/publish/'
678
                 * @param array $tableData
679
                 *  An array of `Widget::TableData`, passed by reference
680
                 * @param integer $section_id
681
                 *  The current Section ID
682
                 * @param Entry $entry_id
683
                 *  The entry object, please note that this is by error and this will
684
                 *  be removed in Symphony 2.4. The entry object is available in
685
                 *  the 'entry' key as of Symphony 2.3.1.
686
                 * @param Entry $entry
687
                 *  The entry object for this row
688
                 */
689
                Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumnData', '/publish/', array(
690
                    'tableData' => &$tableData,
691
                    'section_id' => $section->get('id'),
692
                    'entry_id' => $entry,
693
                    'entry' => $entry
694
                ));
695
696
                $tableData[count($tableData) - 1]->appendChild(Widget::Label(__('Select Entry %d', array($entry->get('id'))), null, 'accessible', null, array(
697
                    'for' => 'entry-' . $entry->get('id')
698
                )));
699
                $tableData[count($tableData) - 1]->appendChild(Widget::Input('items['.$entry->get('id').']', null, 'checkbox', array(
700
                    'id' => 'entry-' . $entry->get('id')
701
                )));
702
703
                // Add a row to the body array, assigning each cell to the row
704
                $aTableBody[] = Widget::TableRow($tableData, null, 'id-' . $entry->get('id'));
705
            }
706
        }
707
708
        $table = Widget::Table(
709
            Widget::TableHead($aTableHead),
710
            null,
711
            Widget::TableBody($aTableBody),
712
            'selectable',
713
            null,
714
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading', 'data-interactive' => 'data-interactive')
715
        );
716
717
        $this->Form->appendChild($table);
718
719
        $tableActions = new XMLElement('div');
720
        $tableActions->setAttribute('class', 'actions');
721
722
        $options = array(
723
            array(null, false, __('With Selected...')),
724
            array('delete', false, __('Delete'), 'confirm', null, array(
725
                'data-message' => __('Are you sure you want to delete the selected entries?')
726
            ))
727
        );
728
729
        $toggable_fields = $section->fetchToggleableFields();
730
731
        if (is_array($toggable_fields) && !empty($toggable_fields)) {
732
            $index = 2;
733
734
            foreach ($toggable_fields as $field) {
735
                $toggle_states = $field->getToggleStates();
736
737
                if (is_array($toggle_states)) {
738
                    $options[$index] = array('label' => __('Set %s', array($field->get('label'))), 'options' => array());
739
740
                    foreach ($toggle_states as $value => $state) {
741
                        $options[$index]['options'][] = array('toggle-' . $field->get('id') . '-' . $value, false, $state);
742
                    }
743
                }
744
745
                $index++;
746
            }
747
        }
748
749
        /**
750
         * Allows an extension to modify the existing options for this page's
751
         * With Selected menu. If the `$options` parameter is an empty array,
752
         * the 'With Selected' menu will not be rendered.
753
         *
754
         * @delegate AddCustomActions
755
         * @since Symphony 2.3.2
756
         * @param string $context
757
         * '/publish/'
758
         * @param array $options
759
         *  An array of arrays, where each child array represents an option
760
         *  in the With Selected menu. Options should follow the same format
761
         *  expected by `Widget::__SelectBuildOption`. Passed by reference.
762
         */
763
        Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/publish/', array(
764
            'options' => &$options
765
        ));
766
767
        if (!empty($options)) {
768
            $tableActions->appendChild(Widget::Apply($options));
769
            $this->Form->appendChild($tableActions);
770
        }
771
772
        if ($entries['total-pages'] > 1) {
773
            $ul = new XMLElement('ul');
774
            $ul->setAttribute('class', 'page');
775
776
            // First
777
            $li = new XMLElement('li');
778
779
            if ($current_page > 1) {
780
                $li->appendChild(Widget::Anchor(__('First'), Administration::instance()->getCurrentPageURL(). '?pg=1'.($filter_querystring ? "&amp;" . $filter_querystring : '')));
781
            } else {
782
                $li->setValue(__('First'));
783
            }
784
785
            $ul->appendChild($li);
786
787
            // Previous
788
            $li = new XMLElement('li');
789
790 View Code Duplication
            if ($current_page > 1) {
791
                $li->appendChild(Widget::Anchor(__('&larr; Previous'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page - 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
792
            } else {
793
                $li->setValue(__('&larr; Previous'));
794
            }
795
796
            $ul->appendChild($li);
797
798
            // Summary
799
            $li = new XMLElement('li');
800
801
            $li->setAttribute('title', __('Viewing %1$s - %2$s of %3$s entries', array(
802
                $entries['start'],
803
                ($current_page !== $entries['total-pages']) ? $current_page * Symphony::Configuration()->get('pagination_maximum_rows', 'symphony') : $entries['total-entries'],
804
                $entries['total-entries']
805
            )));
806
807
            $pgform = Widget::Form(Administration::instance()->getCurrentPageURL(), 'get', 'paginationform');
808
809
            $pgmax = max($current_page, $entries['total-pages']);
810
            $pgform->appendChild(Widget::Input('pg', null, 'text', array(
811
                'data-active' => __('Go to page …'),
812
                'data-inactive' => __('Page %1$s of %2$s', array((string)$current_page, $pgmax)),
813
                'data-max' => $pgmax
814
            )));
815
816
            $li->appendChild($pgform);
817
            $ul->appendChild($li);
818
819
            // Next
820
            $li = new XMLElement('li');
821
822 View Code Duplication
            if ($current_page < $entries['total-pages']) {
823
                $li->appendChild(Widget::Anchor(__('Next &rarr;'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page + 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
824
            } else {
825
                $li->setValue(__('Next &rarr;'));
826
            }
827
828
            $ul->appendChild($li);
829
830
            // Last
831
            $li = new XMLElement('li');
832
833 View Code Duplication
            if ($current_page < $entries['total-pages']) {
834
                $li->appendChild(Widget::Anchor(__('Last'), Administration::instance()->getCurrentPageURL(). '?pg=' . $entries['total-pages'].($filter_querystring ? "&amp;" . $filter_querystring : '')));
835
            } else {
836
                $li->setValue(__('Last'));
837
            }
838
839
            $ul->appendChild($li);
840
841
            $this->Contents->appendChild($ul);
842
        }
843
    }
844
845
    public function __actionIndex()
0 ignored issues
show
Coding Style introduced by
__actionIndex uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
__actionIndex uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
846
    {
847
        $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
848
849
        if (is_array($checked) && !empty($checked)) {
850
            /**
851
             * Extensions can listen for any custom actions that were added
852
             * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
853
             * delegates.
854
             *
855
             * @delegate CustomActions
856
             * @since Symphony 2.3.2
857
             * @param string $context
858
             *  '/publish/'
859
             * @param array $checked
860
             *  An array of the selected rows. The value is usually the ID of the
861
             *  the associated object.
862
             */
863
            Symphony::ExtensionManager()->notifyMembers('CustomActions', '/publish/', array(
864
                'checked' => $checked
865
            ));
866
867
            switch ($_POST['with-selected']) {
868
                case 'delete':
869
                    /**
870
                     * Prior to deletion of entries. An array of Entry ID's is provided which
871
                     * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
872
                     * in Symphony 2.3.
873
                     *
874
                     * @delegate EntryPreDelete
875
                     * @param string $context
876
                     * '/publish/'
877
                     * @param array $entry_id
878
                     *  An array of Entry ID's passed by reference
879
                     */
880
                    Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
881
882
                    EntryManager::delete($checked);
883
884
                    /**
885
                     * After the deletion of entries, this delegate provides an array of Entry ID's
886
                     * that were deleted.
887
                     *
888
                     * @since Symphony 2.3
889
                     * @delegate EntryPostDelete
890
                     * @param string $context
891
                     * '/publish/'
892
                     * @param array $entry_id
893
                     *  An array of Entry ID's that were deleted.
894
                     */
895
                    Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
896
897
                    redirect($_SERVER['REQUEST_URI']);
898
                    break;
899
                default:
900
                    list($option, $field_id, $value) = explode('-', $_POST['with-selected'], 3);
901
902
                    if ($option === 'toggle') {
903
                        $field = FieldManager::fetch($field_id);
904
                        $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...
905
906
                        $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...
907
908
                        foreach ($checked as $entry_id) {
909
                            $entry = EntryManager::fetch($entry_id);
910
                            $existing_data = $entry[0]->getData($field_id);
911
                            $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...
912
913
                            /**
914
                             * Just prior to editing of an Entry
915
                             *
916
                             * @delegate EntryPreEdit
917
                             * @param string $context
918
                             * '/publish/edit/'
919
                             * @param Section $section
920
                             * @param Entry $entry
921
                             * @param array $fields
922
                             */
923
                            Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array(
924
                                'section' => $section,
925
                                'entry' => &$entry[0],
926
                                'fields' => $fields
927
                            ));
928
929
                            $entry[0]->commit();
930
931
                            /**
932
                             * Editing an entry. Entry object is provided.
933
                             *
934
                             * @delegate EntryPostEdit
935
                             * @param string $context
936
                             * '/publish/edit/'
937
                             * @param Section $section
938
                             * @param Entry $entry
939
                             * @param array $fields
940
                             */
941
                            Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array(
942
                                'section' => $section,
943
                                'entry' => $entry[0],
944
                                'fields' => $fields
945
                            ));
946
                        }
947
948
                        redirect($_SERVER['REQUEST_URI']);
949
                    }
950
            }
951
        }
952
    }
953
954
    public function __viewNew()
0 ignored issues
show
Coding Style introduced by
__viewNew uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
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($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($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'));
0 ignored issues
show
Bug introduced by
It seems like \Symphony::Configuration..._upload_size', 'admin') targeting Configuration::get() can also be of type array; however, Widget::Input() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

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

An additional type check may prevent trouble.

Loading history...
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
                }
1026
            }
1027
        }
1028
1029
        $primary = new XMLElement('fieldset');
1030
        $primary->setAttribute('class', 'primary column');
1031
1032
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1033
            $message = __('Fields must be added to this section before an entry can be created.');
1034
1035 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1036
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1037
                . __('Add fields')
1038
                . '</a>';
1039
            }
1040
1041
            $this->pageAlert($message, Alert::ERROR);
1042
        } else {
1043 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1044
                foreach ($main_fields as $field) {
1045
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1046
                }
1047
1048
                $this->Form->appendChild($primary);
1049
            }
1050
1051 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1052
                $sidebar = new XMLElement('fieldset');
1053
                $sidebar->setAttribute('class', 'secondary column');
1054
1055
                foreach ($sidebar_fields as $field) {
1056
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1057
                }
1058
1059
                $this->Form->appendChild($sidebar);
1060
            }
1061
1062
            $div = new XMLElement('div');
1063
            $div->setAttribute('class', 'actions');
1064
            $div->appendChild(Widget::Input('action[save]', __('Create Entry'), 'submit', array('accesskey' => 's')));
1065
1066
            $this->Form->appendChild($div);
1067
1068
            // Create a Drawer for Associated Sections
1069
            $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...
1070
        }
1071
    }
1072
1073
    public function __actionNew()
0 ignored issues
show
Coding Style introduced by
__actionNew uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
__actionNew uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1074
    {
1075
        if (array_key_exists('save', $_POST['action']) || array_key_exists("done", $_POST['action'])) {
1076
            $section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle']);
1077
1078
            if (!$section = SectionManager::fetch($section_id)) {
1079
                Administration::instance()->throwCustomError(
1080
                    __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1081
                    __('Unknown Section'),
1082
                    Page::HTTP_STATUS_NOT_FOUND
1083
                );
1084
            }
1085
1086
            $entry = EntryManager::create();
1087
            $entry->set('author_id', Symphony::Author()->get('id'));
1088
            $entry->set('section_id', $section_id);
1089
            $entry->set('creation_date', DateTimeObj::get('c'));
1090
            $entry->set('modification_date', DateTimeObj::get('c'));
1091
1092
            $fields = $_POST['fields'];
1093
1094
            // Combine FILES and POST arrays, indexed by their custom field handles
1095
            if (isset($_FILES['fields'])) {
1096
                $filedata = General::processFilePostData($_FILES['fields']);
1097
1098
                foreach ($filedata as $handle => $data) {
1099
                    if (!isset($fields[$handle])) {
1100
                        $fields[$handle] = $data;
1101
                    } elseif (isset($data['error']) && $data['error'] === UPLOAD_ERR_NO_FILE) {
1102
                        $fields[$handle] = null;
1103
                    } else {
1104
                        foreach ($data as $ii => $d) {
1105
                            if (isset($d['error']) && $d['error'] === UPLOAD_ERR_NO_FILE) {
1106
                                $fields[$handle][$ii] = null;
1107
                            } elseif (is_array($d) && !empty($d)) {
1108
                                foreach ($d as $key => $val) {
1109
                                    $fields[$handle][$ii][$key] = $val;
1110
                                }
1111
                            }
1112
                        }
1113
                    }
1114
                }
1115
            }
1116
1117
            // Initial checks to see if the Entry is ok
1118
            if (Entry::__ENTRY_FIELD_ERROR__ === $entry->checkPostData($fields, $this->_errors)) {
1119
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1120
1121
                // Secondary checks, this will actually process the data and attempt to save
1122 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ !== $entry->setDataFromPost($fields, $errors)) {
1123
                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...
1124
                    $this->pageAlert($message, Alert::ERROR);
1125
                }
1126
1127
                // Everything is awesome. Dance.
1128
            } else {
1129
                /**
1130
                 * Just prior to creation of an Entry
1131
                 *
1132
                 * @delegate EntryPreCreate
1133
                 * @param string $context
1134
                 * '/publish/new/'
1135
                 * @param Section $section
1136
                 * @param Entry $entry
1137
                 * @param array $fields
1138
                 */
1139
                Symphony::ExtensionManager()->notifyMembers('EntryPreCreate', '/publish/new/', array('section' => $section, 'entry' => &$entry, 'fields' => &$fields));
1140
1141
                // Check to see if the dancing was premature
1142
                if (!$entry->commit()) {
1143
                    $this->pageAlert(null, Alert::ERROR);
1144
                } else {
1145
                    /**
1146
                     * Creation of an Entry. New Entry object is provided.
1147
                     *
1148
                     * @delegate EntryPostCreate
1149
                     * @param string $context
1150
                     * '/publish/new/'
1151
                     * @param Section $section
1152
                     * @param Entry $entry
1153
                     * @param array $fields
1154
                     */
1155
                    Symphony::ExtensionManager()->notifyMembers('EntryPostCreate', '/publish/new/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1156
1157
                    $prepopulate_querystring = $this->getPrepopulateString();
1158
                    redirect(sprintf(
1159
                        '%s/publish/%s/edit/%d/created/%s',
1160
                        SYMPHONY_URL,
1161
                        $this->_context['section_handle'],
1162
                        $entry->get('id'),
1163
                        (!empty($prepopulate_querystring) ? $prepopulate_querystring : null)
1164
                    ));
1165
                }
1166
            }
1167
        }
1168
    }
1169
1170
    public function __viewEdit()
0 ignored issues
show
Coding Style introduced by
__viewEdit uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1171
    {
1172 View Code Duplication
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
1173
            Administration::instance()->throwCustomError(
1174
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1175
                __('Unknown Section'),
1176
                Page::HTTP_STATUS_NOT_FOUND
1177
            );
1178
        }
1179
1180
        $section = SectionManager::fetch($section_id);
1181
        $entry_id = intval($this->_context['entry_id']);
1182
        $base = '/publish/'.$this->_context['section_handle'] . '/';
1183
        $new_link = $base . 'new/';
1184
        $filter_link = $base;
1185
1186
        EntryManager::setFetchSorting('id', 'DESC');
1187
1188 View Code Duplication
        if (!$existingEntry = EntryManager::fetch($entry_id)) {
1189
            Administration::instance()->throwCustomError(
1190
                __('Unknown Entry'),
1191
                __('The Entry, %s, could not be found.', array($entry_id)),
1192
                Page::HTTP_STATUS_NOT_FOUND
1193
            );
1194
        }
1195
        $existingEntry = $existingEntry[0];
1196
1197
        // If there is post data floating around, due to errors, create an entry object
1198
        if (isset($_POST['fields'])) {
1199
            $fields = $_POST['fields'];
1200
1201
            $entry = EntryManager::create();
1202
            $entry->set('id', $entry_id);
1203
            $entry->set('author_id', $existingEntry->get('author_id'));
1204
            $entry->set('section_id', $existingEntry->get('section_id'));
1205
            $entry->set('creation_date', $existingEntry->get('creation_date'));
1206
            $entry->set('modification_date', $existingEntry->get('modification_date'));
1207
            $entry->setDataFromPost($fields, $errors, true);
1208
1209
            // Editing an entry, so need to create some various objects
1210
        } else {
1211
            $entry = $existingEntry;
1212
            $fields = array();
1213
1214
            if (!$section) {
1215
                $section = SectionManager::fetch($entry->get('section_id'));
1216
            }
1217
        }
1218
1219
        /**
1220
         * Just prior to rendering of an Entry edit form.
1221
         *
1222
         * @delegate EntryPreRender
1223
         * @param string $context
1224
         * '/publish/edit/'
1225
         * @param Section $section
1226
         * @param Entry $entry
1227
         * @param array $fields
1228
         */
1229
        Symphony::ExtensionManager()->notifyMembers('EntryPreRender', '/publish/edit/', array(
1230
            'section' => $section,
1231
            'entry' => &$entry,
1232
            'fields' => $fields
1233
        ));
1234
1235
        // Iterate over the `prepopulate` parameters to build a URL
1236
        // to remember this state for Create New, View all Entries and
1237
        // Breadcrumb links. If `prepopulate` doesn't exist, this will
1238
        // just use the standard pages (ie. no filtering)
1239
        if (isset($_REQUEST['prepopulate'])) {
1240
            $new_link .= $this->getPrepopulateString();
1241
            $filter_link .= $this->getFilterString();
1242
        }
1243
1244
        if (isset($this->_context['flag'])) {
1245
            // These flags are only relevant if there are no errors
1246
            if (empty($this->_errors)) {
1247
                $time = Widget::Time();
1248
1249
                switch ($this->_context['flag']) {
1250
                    case 'saved':
1251
                        $message = __('Entry updated at %s.', array($time->generate()));
1252
                        break;
1253
                    case 'created':
1254
                        $message = __('Entry created at %s.', array($time->generate()));
1255
                }
1256
1257
                $this->pageAlert(
1258
                    $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...
1259
                    . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
1260
                    . __('Create another?')
1261
                    . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
1262
                    . __('View all Entries')
1263
                    . '</a>',
1264
                    Alert::SUCCESS
1265
                );
1266
            }
1267
        }
1268
1269
        // Determine the page title
1270
        $field_id = Symphony::Database()->fetchVar('id', 0, "
1271
            SELECT `id`
1272
            FROM `tbl_fields`
1273
            WHERE `parent_section` = ?
1274
            ORDER BY `sortorder` LIMIT 1",
1275
            array($section->get('id'))
1276
        );
1277
        if (!is_null($field_id)) {
1278
            $field = FieldManager::fetch($field_id);
1279
        }
1280
1281
        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...
1282
            $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...
1283
        } else {
1284
            $title = '';
1285
        }
1286
1287
        if (trim($title) === '') {
1288
            $title = __('Untitled');
1289
        }
1290
1291
        // Check if there is a field to prepopulate
1292
        if (isset($_REQUEST['prepopulate'])) {
1293
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1294
                $this->Form->prependChild(Widget::Input(
1295
                    "prepopulate[{$field_id}]",
1296
                    rawurlencode($value),
1297
                    'hidden'
1298
                ));
1299
            }
1300
        }
1301
1302
        $this->setPageType('form');
1303
        $this->Form->setAttribute('enctype', 'multipart/form-data');
1304
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, $section->get('name'), __('Symphony'))));
1305
1306
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
1307
        $main_fields = $section->fetchFields(null, 'main');
1308
1309 View Code Duplication
        if (!empty($sidebar_fields) && !empty($main_fields)) {
1310
            $this->Form->setAttribute('class', 'two columns');
1311
        } else {
1312
            $this->Form->setAttribute('class', 'columns');
1313
        }
1314
1315
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
1316
        if (Symphony::Author()->isDeveloper()) {
1317
            $this->appendSubheading($title, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
1318
        } else {
1319
            $this->appendSubheading($title);
1320
        }
1321
1322
        $this->insertBreadcrumbs(array(
1323
            Widget::Anchor($section->get('name'), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
1324
        ));
1325
1326
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
0 ignored issues
show
Bug introduced by
It seems like \Symphony::Configuration..._upload_size', 'admin') targeting Configuration::get() can also be of type array; however, Widget::Input() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

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

An additional type check may prevent trouble.

Loading history...
1327
1328
        $primary = new XMLElement('fieldset');
1329
        $primary->setAttribute('class', 'primary column');
1330
1331
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1332
            $message = __('Fields must be added to this section before an entry can be created.');
1333
1334 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1335
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1336
                . __('Add fields')
1337
                . '</a>';
1338
            }
1339
1340
            $this->pageAlert($message, Alert::ERROR);
1341
        } else {
1342 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1343
                foreach ($main_fields as $field) {
1344
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1345
                }
1346
1347
                $this->Form->appendChild($primary);
1348
            }
1349
1350 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1351
                $sidebar = new XMLElement('fieldset');
1352
                $sidebar->setAttribute('class', 'secondary column');
1353
1354
                foreach ($sidebar_fields as $field) {
1355
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1356
                }
1357
1358
                $this->Form->appendChild($sidebar);
1359
            }
1360
1361
            $div = new XMLElement('div');
1362
            $div->setAttribute('class', 'actions');
1363
            $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));
1364
1365
            $button = new XMLElement('button', __('Delete'));
1366
            $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?')));
1367
            $div->appendChild($button);
1368
1369
            $this->Form->appendChild($div);
1370
1371
            // Create a Drawer for Associated Sections
1372
            $this->prepareAssociationsDrawer($section);
0 ignored issues
show
Bug introduced by
It seems like $section 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...
1373
        }
1374
    }
1375
1376
    public function __actionEdit()
0 ignored issues
show
Coding Style introduced by
__actionEdit uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1377
    {
1378
        $entry_id = intval($this->_context['entry_id']);
1379
1380
        if (@array_key_exists('save', $_POST['action']) || @array_key_exists("done", $_POST['action'])) {
1381 View Code Duplication
            if (!$ret = EntryManager::fetch($entry_id)) {
1382
                Administration::instance()->throwCustomError(
1383
                    __('The Entry, %s, could not be found.', array($entry_id)),
1384
                    __('Unknown Entry'),
1385
                    Page::HTTP_STATUS_NOT_FOUND
1386
                );
1387
            }
1388
1389
            $entry = $ret[0];
1390
1391
            $section = SectionManager::fetch($entry->get('section_id'));
1392
1393
            $post = General::getPostData();
1394
            $fields = $post['fields'];
1395
1396
            // Initial checks to see if the Entry is ok
1397
            if (Entry::__ENTRY_FIELD_ERROR__ === $entry->checkPostData($fields, $this->_errors)) {
1398
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1399
1400
                // Secondary checks, this will actually process the data and attempt to save
1401 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ !== $entry->setDataFromPost($fields, $errors)) {
0 ignored issues
show
Bug introduced by
The variable $errors does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1402
                foreach ($errors as $field_id => $message) {
1403
                    $this->pageAlert($message, Alert::ERROR);
1404
                }
1405
1406
                // Everything is awesome. Dance.
1407
            } else {
1408
                /**
1409
                 * Just prior to editing of an Entry.
1410
                 *
1411
                 * @delegate EntryPreEdit
1412
                 * @param string $context
1413
                 * '/publish/edit/'
1414
                 * @param Section $section
1415
                 * @param Entry $entry
1416
                 * @param array $fields
1417
                 */
1418
                Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array('section' => $section, 'entry' => &$entry, 'fields' => $fields));
1419
1420
                // Check to see if the dancing was premature
1421
                if (!$entry->commit()) {
1422
                    $this->pageAlert(null, Alert::ERROR);
1423
                } else {
1424
                    /**
1425
                     * Just after the editing of an Entry
1426
                     *
1427
                     * @delegate EntryPostEdit
1428
                     * @param string $context
1429
                     * '/publish/edit/'
1430
                     * @param Section $section
1431
                     * @param Entry $entry
1432
                     * @param array $fields
1433
                     */
1434
                    Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1435
1436
                    redirect(sprintf(
1437
                        '%s/publish/%s/edit/%d/saved/%s',
1438
                        SYMPHONY_URL,
1439
                        $this->_context['section_handle'],
1440
                        $entry->get('id'),
1441
                        $this->getPrepopulateString()
1442
                    ));
1443
                }
1444
            }
1445
        } elseif (@array_key_exists('delete', $_POST['action']) && is_numeric($entry_id)) {
1446
            /**
1447
             * Prior to deletion of entries. An array of Entry ID's is provided which
1448
             * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
1449
             * in Symphony 2.3.
1450
             *
1451
             * @delegate EntryPreDelete
1452
             * @param string $context
1453
             * '/publish/'
1454
             * @param array $entry_id
1455
             *    An array of Entry ID's passed by reference
1456
             */
1457
            $checked = array($entry_id);
1458
            Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
1459
1460
            EntryManager::delete($checked);
1461
1462
            /**
1463
             * After the deletion of entries, this delegate provides an array of Entry ID's
1464
             * that were deleted.
1465
             *
1466
             * @since Symphony 2.3
1467
             * @delegate EntryPostDelete
1468
             * @param string $context
1469
             * '/publish/'
1470
             * @param array $entry_id
1471
             *  An array of Entry ID's that were deleted.
1472
             */
1473
            Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
1474
1475
            redirect(SYMPHONY_URL . '/publish/'.$this->_context['section_handle'].'/');
1476
        }
1477
    }
1478
1479
    /**
1480
     * Given a Field and Entry object, this function will wrap
1481
     * the Field's displayPublishPanel result with a div that
1482
     * contains some contextual information such as the Field ID,
1483
     * the Field handle and whether it is required or not.
1484
     *
1485
     * @param Field $field
1486
     * @param Entry $entry
1487
     * @return XMLElement
1488
     */
1489
    private function __wrapFieldWithDiv(Field $field, Entry $entry)
1490
    {
1491
        $is_hidden = $this->isFieldHidden($field);
1492
        $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' : '')));
1493
1494
        $field->setAssociationContext($div);
1495
1496
        $field->displayPublishPanel(
1497
            $div, $entry->getData($field->get('id')),
1498
            (isset($this->_errors[$field->get('id')]) ? $this->_errors[$field->get('id')] : null),
1499
            null, null, (is_numeric($entry->get('id')) ? $entry->get('id') : null)
1500
        );
1501
1502
        /**
1503
         * Allows developers modify the field before it is rendered in the publish
1504
         * form. Passes the `Field` object, `Entry` object, the `XMLElement` div and
1505
         * any errors for the entire `Entry`. Only the `$div` element
1506
         * will be altered before appending to the page, the rest are read only.
1507
         *
1508
         * @since Symphony 2.5.0
1509
         * @delegate ModifyFieldPublishWidget
1510
         * @param string $context
1511
         * '/backend/'
1512
         * @param Field $field
1513
         * @param Entry $entry
1514
         * @param array $errors
1515
         * @param Widget $widget
1516
         */
1517
        Symphony::ExtensionManager()->notifyMembers('ModifyFieldPublishWidget', '/backend/', array(
1518
            'field' => $field,
1519
            'entry' => $entry,
1520
            'errors' => $this->_errors,
1521
            'widget' => &$div
1522
        ));
1523
1524
        return $div;
1525
    }
1526
1527
    /**
1528
     * Check whether the given `$field` will be hidden because it's been
1529
     * prepopulated.
1530
     *
1531
     * @param  Field  $field
1532
     * @return boolean
1533
     */
1534
    public function isFieldHidden(Field $field)
1535
    {
1536
        if ($field->get('hide_when_prepopulated') === 'yes') {
1537
            if (isset($_REQUEST['prepopulate'])) {
1538
                foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1539
                    if ($field_id === $field->get('id')) {
1540
                        return true;
1541
                    }
1542
                }
1543
            }
1544
        }
1545
1546
        return false;
1547
    }
1548
1549
    /**
1550
     * Prepare a Drawer to visualize section associations
1551
     *
1552
     * @param  Section $section The current Section object
1553
     * @throws InvalidArgumentException
1554
     * @throws Exception
1555
     */
1556
    private function prepareAssociationsDrawer($section)
1557
    {
1558
        $entry_id = (!is_null($this->_context['entry_id'])) ? $this->_context['entry_id'] : null;
1559
        $show_entries = Symphony::Configuration()->get('association_maximum_rows', 'symphony');
1560
1561
        if (is_null($entry_id) && !isset($_GET['prepopulate']) || is_null($show_entries) || $show_entries === 0) {
1562
            return;
1563
        }
1564
1565
        $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...
1566
        $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...
1567
        $content = null;
1568
        $drawer_position = 'vertical-right';
1569
1570
        /**
1571
         * Prepare Associations Drawer from an Extension
1572
         *
1573
         * @since Symphony 2.3.3
1574
         * @delegate PrepareAssociationsDrawer
1575
         * @param string $context
1576
         * '/publish/'
1577
         * @param integer $entry_id
1578
         *  The entry ID or null
1579
         * @param array $parent_associations
1580
         *  Array of Sections
1581
         * @param array $child_associations
1582
         *  Array of Sections
1583
         * @param string $drawer_position
1584
         *  The position of the Drawer, defaults to `vertical-right`. Available
1585
         *  values of `vertical-left, `vertical-right` and `horizontal`
1586
         */
1587
        Symphony::ExtensionManager()->notifyMembers('PrepareAssociationsDrawer', '/publish/', array(
1588
            'entry_id' => $entry_id,
1589
            'parent_associations' => &$parent_associations,
1590
            'child_associations' => &$child_associations,
1591
            'content' => &$content,
1592
            'drawer-position' => &$drawer_position
1593
        ));
1594
1595
        // If there are no associations, return now.
1596
        if (
1597
            (is_null($parent_associations) || empty($parent_associations))
1598
            &&
1599
            (is_null($child_associations) || empty($child_associations))
1600
        ) {
1601
            return;
1602
        }
1603
1604
        if (!($content instanceof XMLElement)) {
1605
            $content = new XMLElement('div', null, array('class' => 'content'));
1606
            $content->setSelfClosingTag(false);
1607
1608
            // Process Parent Associations
1609
            if (!is_null($parent_associations) && !empty($parent_associations)) {
1610
                foreach ($parent_associations as $as) {
0 ignored issues
show
Bug introduced by
The expression $parent_associations of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
1611
                    if ($field = FieldManager::fetch($as['parent_section_field_id'])) {
1612
                        if (isset($_GET['prepopulate'])) {
1613
                            $prepopulate_field = key($_GET['prepopulate']);
1614
                        }
1615
1616
                        // get associated entries if entry exists,
1617
                        if ($entry_id) {
1618
                            $entry_ids = $field->findParentRelatedEntries($as['child_section_field_id'], $entry_id);
0 ignored issues
show
Bug introduced by
The method findParentRelatedEntries 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...
1619
1620
                            // get prepopulated entry otherwise
1621
                        } elseif (isset($_GET['prepopulate'])) {
1622
                            $entry_ids = array(intval(current($_GET['prepopulate'])));
1623
                        } else {
1624
                            $entry_ids = array();
1625
                        }
1626
1627
                        // Use $schema for perf reasons
1628
                        $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...
1629
                        $where = (!empty($entry_ids)) ? sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids)) : null;
1630
                        $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...
1631
                            ? EntryManager::fetchByPage(1, $as['parent_section_id'], $show_entries, $where, null, false, false, true, $schema)
0 ignored issues
show
Documentation introduced by
$show_entries 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...
1632
                            : array();
1633
                        $has_entries = !empty($entries) && $entries['total-entries'] !== 0;
1634
1635
                        if ($has_entries) {
1636
                            $element = new XMLElement('section', null, array('class' => 'association parent'));
1637
                            $header = new XMLElement('header');
1638
                            $header->appendChild(new XMLElement('p', __('Linked to %s in', array('<a class="association-section" href="' . SYMPHONY_URL . '/publish/' . $as['handle'] . '/">' . $as['name'] . '</a>'))));
1639
                            $element->appendChild($header);
1640
1641
                            $ul = new XMLElement('ul', null, array(
1642
                                'class' => 'association-links',
1643
                                'data-section-id' => $as['child_section_id'],
1644
                                'data-association-ids' => implode(', ', $entry_ids)
1645
                            ));
1646
1647
                            foreach ($entries['records'] as $e) {
1648
                                // let the field create the mark up
1649
                                $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...
1650
                                // add it to the unordered list
1651
                                $ul->appendChild($li);
1652
                            }
1653
1654
                            $element->appendChild($ul);
1655
                            $content->appendChild($element);
1656
                        }
1657
                    }
1658
                }
1659
            }
1660
1661
            // Process Child Associations
1662
            if (!is_null($child_associations) && !empty($child_associations)) {
1663
                foreach ($child_associations as $as) {
0 ignored issues
show
Bug introduced by
The expression $child_associations of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
1664
                    // Get the related section
1665
                    $child_section = SectionManager::fetch($as['child_section_id']);
1666
1667
                    if (!($child_section instanceof Section)) {
1668
                        continue;
1669
                    }
1670
1671
                    // Get the visible field instance (using the sorting field, this is more flexible than visibleColumns())
1672
                    // Get the link field instance
1673
                    $visible_field   = current($child_section->fetchVisibleColumns());
1674
                    $relation_field  = FieldManager::fetch($as['child_section_field_id']);
1675
1676
                    // Get entries, using $schema for performance reasons.
1677
                    $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...
1678
                    $schema = $visible_field ? array($visible_field->get('element_name')) : array();
1679
                    $where = sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids));
1680
1681
                    $entries = (!empty($entry_ids)) ? EntryManager::fetchByPage(1, $as['child_section_id'], $show_entries, $where, null, false, false, true, $schema) : array();
0 ignored issues
show
Documentation introduced by
$show_entries 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...
1682
                    $has_entries = !empty($entries) && $entries['total-entries'] !== 0;
1683
1684
                    // Build the HTML of the relationship
1685
                    $element = new XMLElement('section', null, array('class' => 'association child'));
1686
                    $header = new XMLElement('header');
1687
                    $filter = '?filter[' . $relation_field->get('element_name') . ']=' . $entry_id;
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...
1688
                    $prepopulate = '?prepopulate[' . $as['child_section_field_id'] . ']=' . $entry_id;
1689
1690
                    // Create link with filter or prepopulate
1691
                    $link = SYMPHONY_URL . '/publish/' . $as['handle'] . '/' . $filter;
1692
                    $a = new XMLElement('a', $as['name'], array(
1693
                        'class' => 'association-section',
1694
                        'href' => $link
1695
                    ));
1696
1697
                    // Create new entries
1698
                    $create = new XMLElement('a', __('Create New'), array(
1699
                        'class' => 'button association-new',
1700
                        'href' => SYMPHONY_URL . '/publish/' . $as['handle'] . '/new/' . $prepopulate
1701
                    ));
1702
1703
                    // Display existing entries
1704
                    if ($has_entries) {
1705
                        $header->appendChild(new XMLElement('p', __('Links in %s', array($a->generate()))));
1706
1707
                        $ul = new XMLElement('ul', null, array(
1708
                            'class' => 'association-links',
1709
                            'data-section-id' => $as['child_section_id'],
1710
                            'data-association-ids' => implode(', ', $entry_ids)
1711
                        ));
1712
1713
                        foreach ($entries['records'] as $key => $e) {
1714
                            // let the first visible field create the mark up
1715
                            if ($visible_field) {
1716
                                $li = $visible_field->prepareAssociationsDrawerXMLElement($e, $as, $prepopulate);
1717
                            }
1718
                            // or use the system:id if no visible field exists.
1719
                            else {
1720
                                $li = Field::createAssociationsDrawerXMLElement($e->get('id'), $e, $as, $prepopulate);
1721
                            }
1722
1723
                            // add it to the unordered list
1724
                            $ul->appendChild($li);
1725
                        }
1726
1727
                        $element->appendChild($ul);
1728
1729
                        // If we are only showing 'some' of the entries, then show this on the UI
1730
                        if ($entries['total-entries'] > $show_entries) {
1731
                            $pagination = new XMLElement('li', null, array(
1732
                                'class' => 'association-more',
1733
                                'data-current-page' => '1',
1734
                                'data-total-pages' => ceil($entries['total-entries'] / $show_entries),
1735
                                'data-total-entries' => $entries['total-entries']
1736
                            ));
1737
                            $counts = new XMLElement('a', __('Show more entries'), array(
1738
                                'href' => $link
1739
                            ));
1740
1741
                            $pagination->appendChild($counts);
1742
                            $ul->appendChild($pagination);
1743
                        }
1744
1745
                        // No entries
1746
                    } else {
1747
                        $element->setAttribute('class', 'association child empty');
1748
                        $header->appendChild(new XMLElement('p', __('No links in %s', array($a->generate()))));
1749
                    }
1750
1751
                    $header->appendChild($create);
1752
                    $element->prependChild($header);
1753
                    $content->appendChild($element);
1754
                }
1755
            }
1756
        }
1757
1758
        $drawer = Widget::Drawer('section-associations', __('Show Associations'), $content);
1759
        $this->insertDrawer($drawer, $drawer_position, 'prepend');
1760
    }
1761
1762
    /**
1763
     * If this entry is being prepopulated, this function will return the prepopulated
1764
     * fields and values as a query string.
1765
     *
1766
     * @since Symphony 2.5.2
1767
     * @return string
1768
     */
1769
    public function getPrepopulateString()
1770
    {
1771
        $prepopulate_querystring = '';
1772
1773
        if (isset($_REQUEST['prepopulate'])) {
1774
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1775
                $prepopulate_querystring .= sprintf("prepopulate[%s]=%s&", $field_id, rawurldecode($value));
1776
            }
1777
            $prepopulate_querystring = trim($prepopulate_querystring, '&');
1778
        }
1779
1780
        // This is to prevent the value being interpreted as an additional GET
1781
        // parameter. eg. prepopulate[cat]=Minx&June, would come through as:
1782
        // $_GET['cat'] = Minx
1783
        // $_GET['June'] = ''
1784
        $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
1785
1786
        return $prepopulate_querystring ? '?' . $prepopulate_querystring : null;
1787
    }
1788
1789
    /**
1790
     * If the entry is being prepopulated, we may want to filter other views by this entry's
1791
     * value. This function will create that filter query string.
1792
     *
1793
     * @since Symphony 2.5.2
1794
     * @return string
1795
     */
1796
    public function getFilterString()
1797
    {
1798
        $filter_querystring = '';
1799
1800
        if (isset($_REQUEST['prepopulate'])) {
1801
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1802
                $handle = FieldManager::fetchHandleFromID($field_id);
1803
1804
                //This is in case it is an Association so the filter reads the text value instead of the ID
1805
                $field = FieldManager::fetch($field_id);
1806
                if ($field instanceof Field) {
1807
                    if (method_exists($field, 'fetchValueFromID')) {
1808
                        $value = $field->fetchValueFromID($value);
1809
                    }
1810
                }
1811
1812
                $filter_querystring .= sprintf("filter[%s]=%s&", $handle, rawurldecode($value));
1813
            }
1814
            $filter_querystring = trim($filter_querystring, '&');
1815
        }
1816
1817
        // This is to prevent the value being interpreted as an additional GET
1818
        // parameter. eg. filter[cat]=Minx&June, would come through as:
1819
        // $_GET['cat'] = Minx
1820
        // $_GET['June'] = ''
1821
        $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
1822
1823
        return $filter_querystring ? '?' . $filter_querystring : null;
1824
    }
1825
}
1826