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 ( 45cc9f...98bc42 )
by Brendan
05:52
created

contentPublish   F

Complexity

Total Complexity 272

Size/Duplication

Total Lines 1838
Duplicated Lines 7.4 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 14
Bugs 1 Features 1
Metric Value
c 14
b 1
f 1
dl 136
loc 1838
rs 0.6314
wmc 272
lcom 1
cbo 22

27 Methods

Rating   Name   Duplication   Size   Complexity  
A createFilteringDrawer() 0 7 1
C sort() 6 53 12
A parseContext() 0 16 2
A createFilteringInterface() 0 16 3
A createFilteringDuplicator() 0 17 1
B createFieldFilters() 0 34 4
B createSystemDateFilters() 0 41 3
B createFilter() 0 42 1
B createFilterComparisons() 0 33 6
A createFilterSuggestions() 0 15 2
A createFilterHelp() 0 13 2
A getFilterQuery() 0 14 4
A build() 13 13 2
A action() 0 4 1
B __switchboard() 0 23 5
A view() 0 4 1
F __viewIndex() 21 470 72
C __actionIndex() 0 108 8
D __viewNew() 34 118 21
D __actionNew() 7 96 21
F __viewEdit() 41 205 30
C __actionEdit() 14 102 12
B __wrapFieldWithDiv() 0 37 5
B isFieldHidden() 0 14 5
D prepareAssociationsDrawer() 0 205 36
B getPrepopulateString() 0 21 5
C getFilterString() 0 31 7

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'], General::sanitize($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
276
            $filter = trim($operator['filter']);
277
278
            // Check selected state
279
            $selected = false;
280
281
            // Selected state : Comparison mode "between" (x to y)
282
            if ($operator['title'] === 'between' && preg_match('/^(-?(?:\d+(?:\.\d+)?|\.\d+)) to (-?(?:\d+(?:\.\d+)?|\.\d+))$/i', $data['filter'] )) {
283
                $selected = true;
284
            // Selected state : Other comparison modes (except "is")
285
            } else if ((!empty($filter) && strpos($data['filter'], $filter) === 0)) {
286
                $selected = true;
287
            }
288
289
            $comparisons[] = array(
290
                $operator['filter'],
291
                $selected,
292
                __($operator['title']),
293
                null,
294
                null,
295
                array('data-comparison' => $operator['title'])
296
            );
297
        }
298
299
        return $comparisons;
300
    }
301
302
    private function createFilterSuggestions(&$wrapper, $data)
303
    {
304
        $ul = new XMLElement('ul');
305
        $ul->setAttribute('class', 'suggestions');
306
        $ul->setAttribute('data-field-id', $data['field-id']);
307
        $ul->setAttribute('data-associated-ids', '0');
308
        $ul->setAttribute('data-search-types', implode($data['search'], ','));
309
310
        // Add help text for each filter operator
311
        foreach ($data['operators'] as $operator) {
312
            $this->createFilterHelp($ul, $operator);
313
        }
314
315
        $wrapper->appendChild($ul);
316
    }
317
318
    private function createFilterHelp(&$wrapper, $operator)
319
    {
320
        if (empty($operator['help'])) {
321
            return;
322
        }
323
324
        $li = new XMLElement('li', __('Comparison mode') . ': ' . $operator['help'], array(
325
            'class' => 'help',
326
            'data-comparison' => $operator['title']
327
        ));
328
329
        $wrapper->appendChild($li);
330
    }
331
332
    private function getFilterQuery($data)
333
    {
334
        $query = $data['filter'];
335
336
        foreach ($data['operators'] as $operator) {
337
            $filter = trim($operator['filter']);
338
339
            if (!empty($filter) && strpos($data['filter'], $filter) === 0) {
340
                $query = substr($data['filter'], strlen($operator['filter']));
341
            }
342
        }
343
344
        return (string)$query;
345
    }
346
347 View Code Duplication
    public function build(array $context = array())
348
    {
349
        $section_id = SectionManager::fetchIDFromHandle($context['section_handle']);
350
351
        if ($section_id) {
352
            $context['associations'] = array(
353
                'parent' => SectionManager::fetchParentAssociations($section_id),
354
                'child' => SectionManager::fetchChildAssociations($section_id)
355
            );
356
        }
357
358
        return parent::build($context);
359
    }
360
361
    public function action()
362
    {
363
        $this->__switchboard('action');
364
    }
365
366
    public function __switchboard($type = 'view')
367
    {
368
        $function = ($type === 'action' ? '__action' : '__view') . ucfirst($this->_context['page']);
369
370
        if (!method_exists($this, $function)) {
371
            // If there is no action function, just return without doing anything
372
            if ($type === 'action') {
373
                return;
374
            }
375
376
            Administration::instance()->errorPageNotFound();
377
        }
378
379
        // Is this request allowed by server?
380
        if ($this->isRequestValid() === false) {
381
            $this->pageAlert(__('This request exceeds the maximum allowed request size of %s specified by your host.', array(
382
                    ini_get('post_max_size')
383
                )),
384
                Alert::ERROR
385
            );
386
        }
387
        $this->$function();
388
    }
389
390
    public function view()
391
    {
392
        $this->__switchboard();
393
    }
394
395
    public function __viewIndex()
396
    {
397
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
398
            Administration::instance()->throwCustomError(
399
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
400
                __('Unknown Section'),
401
                Page::HTTP_STATUS_NOT_FOUND
402
            );
403
        } elseif (!is_writable(CONFIG)) {
404
            $this->pageAlert(__('The Symphony configuration file, %s, is not writable. The sort order cannot be modified.', array('<code>/manifest/config.php</code>')), Alert::NOTICE);
405
        }
406
407
        $section = SectionManager::fetch($section_id);
408
409
        $this->setPageType('table');
410
        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
411
412
        $filters = array();
413
        $filter_querystring = $prepopulate_querystring = $where = $joins = null;
414
        $current_page = (isset($_REQUEST['pg']) && is_numeric($_REQUEST['pg']) ? max(1, intval($_REQUEST['pg'])) : 1);
415
416
        if (isset($_REQUEST['filter'])) {
417
            // legacy implementation, convert single filter to an array
418
            // split string in the form ?filter=handle:value
419
            if (!is_array($_REQUEST['filter'])) {
420
                list($field_handle, $filter_value) = explode(':', $_REQUEST['filter'], 2);
421
                $filters[$field_handle] = rawurldecode($filter_value);
422
            } else {
423
                $filters = $_REQUEST['filter'];
424
            }
425
426
            foreach ($filters as $handle => $value) {
427
                // Handle multiple values through filtering. RE: #2290
428
                if ((is_array($value) && empty($value)) || trim($value) === '') {
429
                    continue;
430
                }
431
432 View Code Duplication
                if (!is_array($value)) {
433
                    $filter_type = Datasource::determineFilterType($value);
434
                    $value = preg_split('/'.($filter_type === Datasource::FILTER_AND ? '\+' : '(?<!\\\\),').'\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
435
                    $value = array_map('trim', $value);
436
                    $value = array_map(array('Datasource', 'removeEscapedCommas'), $value);
437
                }
438
439
                // Handle date meta data #2003
440
                $handle = Symphony::Database()->cleanValue($handle);
441
                if (in_array($handle, array('system:creation-date', 'system:modification-date'))) {
442
                    $date_joins = '';
443
                    $date_where = '';
444
                    $date = new FieldDate();
445
                    $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...
446
447
                    // Replace the date field where with the `creation_date` or `modification_date`.
448
                    $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...
449
                    $where .= $date_where;
450
                } else {
451
                    // Handle normal fields
452
                    $field_id = FieldManager::fetchFieldIDFromElementName(
453
                        $handle,
454
                        $section->get('id')
455
                    );
456
457
                    $field = FieldManager::fetch($field_id);
458
                    if ($field instanceof Field) {
459
                        $field->buildDSRetrievalSQL($value, $joins, $where, ($filter_type === Datasource::FILTER_AND ? true : false));
460
461
                        $value = implode(',', $value);
462
                        $encoded_value = rawurlencode($value);
463
                        $filter_querystring .= sprintf("filter[%s]=%s&amp;", $handle, $encoded_value);
464
465
                        // Some fields require that prepopulation be done via ID. RE: #2331
466
                        if (!is_numeric($value) && method_exists($field, 'fetchIDfromValue')) {
467
                            $encoded_value = $field->fetchIDfromValue($value);
468
                        }
469
                        $prepopulate_querystring .= sprintf("prepopulate[%d]=%s&amp;", $field_id, $encoded_value);
470
                    } else {
471
                        unset($filters[$handle]);
472
                    }
473
                }
474
            }
475
476
            $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
477
            $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
478
        }
479
480
        Sortable::initialize($this, $entries, $sort, $order, array(
481
            'current-section' => $section,
482
            'filters' => ($filter_querystring ? "&amp;" . $filter_querystring : ''),
483
            'unsort' => isset($_REQUEST['unsort'])
484
        ));
485
486
        $this->Form->setAttribute('action', Administration::instance()->getCurrentPageURL(). '?pg=' . $current_page.($filter_querystring ? "&amp;" . $filter_querystring : ''));
487
488
        // Build filtering interface
489
        $this->createFilteringInterface();
490
491
        $subheading_buttons = array(
492
            Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/'.($prepopulate_querystring ? '?' . $prepopulate_querystring : ''), __('Create a new entry'), 'create button', null, array('accesskey' => 'c'))
493
        );
494
495
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
496
        if (Symphony::Author()->isDeveloper()) {
497
            array_unshift($subheading_buttons, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
498
        }
499
500
        $this->appendSubheading($section->get('name'), $subheading_buttons);
501
502
        /**
503
         * Allows adjustments to be made to the SQL where and joins statements
504
         * before they are used to fetch the entries for the page
505
         *
506
         * @delegate AdjustPublishFiltering
507
         * @since Symphony 2.3.3
508
         * @param string $context
509
         * '/publish/'
510
         * @param integer $section_id
511
         * An array of the current columns, passed by reference
512
         * @param string $where
513
         * The current where statement, or null if not set
514
         * @param string $joins
515
         */
516
        Symphony::ExtensionManager()->notifyMembers('AdjustPublishFiltering', '/publish/', array('section-id' => $section_id, 'where' => &$where, 'joins' => &$joins));
517
518
        // get visible columns
519
        $visible_columns = $section->fetchVisibleColumns();
520
        // extract the needed schema
521
        $element_names = array_values(array_map(function ($field) {
522
            return $field->get('element_name');
523
        }, $visible_columns));
524
525
        // Check that the filtered query fails that the filter is dropped and an
526
        // error is logged. #841 ^BA
527
        try {
528
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), $where, $joins, true, false, true, $element_names);
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...
529
        } catch (DatabaseException $ex) {
530
            $this->pageAlert(__('An error occurred while retrieving filtered entries. Showing all entries instead.'), Alert::ERROR);
531
            $filter_querystring = null;
532
            Symphony::Log()->warning(sprintf(
533
                '%s - %s%s%s',
534
                $section->get('name') . ' Publish Index',
535
                $ex->getMessage(),
536
                ($ex->getFile() ? " in file " .  $ex->getFile() : null),
537
                ($ex->getLine() ? " on line " . $ex->getLine() : null)
538
            ));
539
            $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...
540
        }
541
542
        // Flag filtering
543
        if (isset($_REQUEST['filter'])) {
544
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d of %d entries (filtered)', array($entries['total-entries'], EntryManager::fetchCount($section_id))) . '</span>', array('class' => 'inactive'));
545
        } else {
546
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d entries', array($entries['total-entries'])) . '</span>', array('class' => 'inactive'));
547
        }
548
        $this->Breadcrumbs->appendChild($filter_stats);
549
550
        // Build table
551
        $columns = array();
552
553
        if (is_array($visible_columns) && !empty($visible_columns)) {
554
            foreach ($visible_columns as $column) {
555
                $columns[] = array(
556
                    'label' => $column->get('label'),
557
                    'sortable' => $column->isSortable(),
558
                    'handle' => $column->get('id'),
559
                    'attrs' => array(
560
                        'id' => 'field-' . $column->get('id'),
561
                        'class' => 'field-' . $column->get('type')
562
                    )
563
                );
564
            }
565
        } else {
566
            $columns[] = array(
567
                'label' => __('ID'),
568
                'sortable' => true,
569
                'handle' => 'id'
570
            );
571
        }
572
573
        $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order, ($filter_querystring) ? "&amp;" . $filter_querystring : '');
574
575
        $child_sections = array();
576
        $associated_sections = $section->fetchChildAssociations(true);
577
578
        if (is_array($associated_sections) && !empty($associated_sections)) {
579
            foreach ($associated_sections as $key => $as) {
580
                $child_sections[$key] = SectionManager::fetch($as['child_section_id']);
581
                $aTableHead[] = array($child_sections[$key]->get('name'), 'col');
582
            }
583
        }
584
585
        /**
586
         * Allows the creation of custom table columns for each entry. Called
587
         * after all the Section Visible columns have been added as well
588
         * as the Section Associations
589
         *
590
         * @delegate AddCustomPublishColumn
591
         * @since Symphony 2.2
592
         * @param string $context
593
         * '/publish/'
594
         * @param array $tableHead
595
         * An array of the current columns, passed by reference
596
         * @param integer $section_id
597
         * The current Section ID
598
         */
599
        Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumn', '/publish/', array('tableHead' => &$aTableHead, 'section_id' => $section->get('id')));
600
601
        // Table Body
602
        $aTableBody = array();
603
604
        if (!is_array($entries['records']) || empty($entries['records'])) {
605
            $aTableBody = array(
606
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
607
            );
608
        } else {
609
            $field_pool = array();
610
611
            if (is_array($visible_columns) && !empty($visible_columns)) {
612
                foreach ($visible_columns as $column) {
613
                    $field_pool[$column->get('id')] = $column;
614
                }
615
            }
616
617
            $link_column = array_reverse($visible_columns);
618
            $link_column = end($link_column);
619
            reset($visible_columns);
620
621
            foreach ($entries['records'] as $entry) {
622
                $tableData = array();
623
624
                // Setup each cell
625
                if (!is_array($visible_columns) || empty($visible_columns)) {
626
                    $tableData[] = Widget::TableData(Widget::Anchor($entry->get('id'), Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'));
627
                } else {
628
                    $link = Widget::Anchor(
629
                        '',
630
                        Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'.($filter_querystring ? '?' . $prepopulate_querystring : ''),
631
                        $entry->get('id'),
632
                        'content'
633
                    );
634
635
                    foreach ($visible_columns as $position => $column) {
636
                        $data = $entry->getData($column->get('id'));
637
                        $field = $field_pool[$column->get('id')];
638
639
                        $value = $field->prepareTableValue($data, ($column === $link_column) ? $link : null, $entry->get('id'));
640
641
                        if (!is_object($value) && (strlen(trim($value)) === 0 || $value === __('None'))) {
642
                            $value = ($position === 0 ? $link->generate() : __('None'));
643
                        }
644
645
                        if ($value === __('None')) {
646
                            $tableData[] = Widget::TableData($value, 'inactive field-' . $column->get('type') . ' field-' . $column->get('id'));
647
                        } else {
648
                            $tableData[] = Widget::TableData($value, 'field-' . $column->get('type') . ' field-' . $column->get('id'));
649
                        }
650
651
                        unset($field);
652
                    }
653
                }
654
655
                if (is_array($child_sections) && !empty($child_sections)) {
656
                    foreach ($child_sections as $key => $as) {
657
                        $field = FieldManager::fetch((int)$associated_sections[$key]['child_section_field_id']);
658
                        $parent_section_field_id = (int)$associated_sections[$key]['parent_section_field_id'];
659
660
                        if (!is_null($parent_section_field_id)) {
661
                            $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...
662
                                $entry->getData($parent_section_field_id),
663
                                $parent_section_field_id,
664
                                $entry->get('id')
665
                            );
666
                        } else {
667
                            $search_value = $entry->get('id');
668
                        }
669
670
                        if (!is_array($search_value)) {
671
                            $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...
672
673
                            $tableData[] = Widget::TableData(
674
                                Widget::Anchor(
675
                                    sprintf('%d &rarr;', max(0, intval($associated_entry_count))),
676
                                    sprintf(
677
                                        '%s/publish/%s/?filter[%s]=%s',
678
                                        SYMPHONY_URL,
679
                                        $as->get('handle'),
680
                                        $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...
681
                                        rawurlencode($search_value)
682
                                    ),
683
                                    $entry->get('id'),
684
                                    'content'
685
                                )
686
                            );
687
                        }
688
                    }
689
                }
690
691
                /**
692
                 * Allows Extensions to inject custom table data for each Entry
693
                 * into the Publish Index
694
                 *
695
                 * @delegate AddCustomPublishColumnData
696
                 * @since Symphony 2.2
697
                 * @param string $context
698
                 * '/publish/'
699
                 * @param array $tableData
700
                 *  An array of `Widget::TableData`, passed by reference
701
                 * @param integer $section_id
702
                 *  The current Section ID
703
                 * @param Entry $entry_id
704
                 *  The entry object, please note that this is by error and this will
705
                 *  be removed in Symphony 2.4. The entry object is available in
706
                 *  the 'entry' key as of Symphony 2.3.1.
707
                 * @param Entry $entry
708
                 *  The entry object for this row
709
                 */
710
                Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumnData', '/publish/', array(
711
                    'tableData' => &$tableData,
712
                    'section_id' => $section->get('id'),
713
                    'entry_id' => $entry,
714
                    'entry' => $entry
715
                ));
716
717
                $tableData[count($tableData) - 1]->appendChild(Widget::Label(__('Select Entry %d', array($entry->get('id'))), null, 'accessible', null, array(
718
                    'for' => 'entry-' . $entry->get('id')
719
                )));
720
                $tableData[count($tableData) - 1]->appendChild(Widget::Input('items['.$entry->get('id').']', null, 'checkbox', array(
721
                    'id' => 'entry-' . $entry->get('id')
722
                )));
723
724
                // Add a row to the body array, assigning each cell to the row
725
                $aTableBody[] = Widget::TableRow($tableData, null, 'id-' . $entry->get('id'));
726
            }
727
        }
728
729
        $table = Widget::Table(
730
            Widget::TableHead($aTableHead),
731
            null,
732
            Widget::TableBody($aTableBody),
733
            'selectable',
734
            null,
735
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading', 'data-interactive' => 'data-interactive')
736
        );
737
738
        $this->Form->appendChild($table);
739
740
        $tableActions = new XMLElement('div');
741
        $tableActions->setAttribute('class', 'actions');
742
743
        $options = array(
744
            array(null, false, __('With Selected...')),
745
            array('delete', false, __('Delete'), 'confirm', null, array(
746
                'data-message' => __('Are you sure you want to delete the selected entries?')
747
            ))
748
        );
749
750
        $toggable_fields = $section->fetchToggleableFields();
751
752
        if (is_array($toggable_fields) && !empty($toggable_fields)) {
753
            $index = 2;
754
755
            foreach ($toggable_fields as $field) {
756
                $toggle_states = $field->getToggleStates();
757
758
                if (is_array($toggle_states)) {
759
                    $options[$index] = array('label' => __('Set %s', array($field->get('label'))), 'options' => array());
760
761
                    foreach ($toggle_states as $value => $state) {
762
                        $options[$index]['options'][] = array('toggle-' . $field->get('id') . '-' . $value, false, $state);
763
                    }
764
                }
765
766
                $index++;
767
            }
768
        }
769
770
        /**
771
         * Allows an extension to modify the existing options for this page's
772
         * With Selected menu. If the `$options` parameter is an empty array,
773
         * the 'With Selected' menu will not be rendered.
774
         *
775
         * @delegate AddCustomActions
776
         * @since Symphony 2.3.2
777
         * @param string $context
778
         * '/publish/'
779
         * @param array $options
780
         *  An array of arrays, where each child array represents an option
781
         *  in the With Selected menu. Options should follow the same format
782
         *  expected by `Widget::__SelectBuildOption`. Passed by reference.
783
         */
784
        Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/publish/', array(
785
            'options' => &$options
786
        ));
787
788
        if (!empty($options)) {
789
            $tableActions->appendChild(Widget::Apply($options));
790
            $this->Form->appendChild($tableActions);
791
        }
792
793
        if ($entries['total-pages'] > 1) {
794
            $ul = new XMLElement('ul');
795
            $ul->setAttribute('class', 'page');
796
797
            // First
798
            $li = new XMLElement('li');
799
800
            if ($current_page > 1) {
801
                $li->appendChild(Widget::Anchor(__('First'), Administration::instance()->getCurrentPageURL(). '?pg=1'.($filter_querystring ? "&amp;" . $filter_querystring : '')));
802
            } else {
803
                $li->setValue(__('First'));
804
            }
805
806
            $ul->appendChild($li);
807
808
            // Previous
809
            $li = new XMLElement('li');
810
811 View Code Duplication
            if ($current_page > 1) {
812
                $li->appendChild(Widget::Anchor(__('&larr; Previous'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page - 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
813
            } else {
814
                $li->setValue(__('&larr; Previous'));
815
            }
816
817
            $ul->appendChild($li);
818
819
            // Summary
820
            $li = new XMLElement('li');
821
822
            $li->setAttribute('title', __('Viewing %1$s - %2$s of %3$s entries', array(
823
                $entries['start'],
824
                ($current_page !== $entries['total-pages']) ? $current_page * Symphony::Configuration()->get('pagination_maximum_rows', 'symphony') : $entries['total-entries'],
825
                $entries['total-entries']
826
            )));
827
828
            $pgform = Widget::Form(Administration::instance()->getCurrentPageURL(), 'get', 'paginationform');
829
830
            $pgmax = max($current_page, $entries['total-pages']);
831
            $pgform->appendChild(Widget::Input('pg', null, 'text', array(
832
                'data-active' => __('Go to page …'),
833
                'data-inactive' => __('Page %1$s of %2$s', array((string)$current_page, $pgmax)),
834
                'data-max' => $pgmax
835
            )));
836
837
            $li->appendChild($pgform);
838
            $ul->appendChild($li);
839
840
            // Next
841
            $li = new XMLElement('li');
842
843 View Code Duplication
            if ($current_page < $entries['total-pages']) {
844
                $li->appendChild(Widget::Anchor(__('Next &rarr;'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page + 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
845
            } else {
846
                $li->setValue(__('Next &rarr;'));
847
            }
848
849
            $ul->appendChild($li);
850
851
            // Last
852
            $li = new XMLElement('li');
853
854 View Code Duplication
            if ($current_page < $entries['total-pages']) {
855
                $li->appendChild(Widget::Anchor(__('Last'), Administration::instance()->getCurrentPageURL(). '?pg=' . $entries['total-pages'].($filter_querystring ? "&amp;" . $filter_querystring : '')));
856
            } else {
857
                $li->setValue(__('Last'));
858
            }
859
860
            $ul->appendChild($li);
861
862
            $this->Contents->appendChild($ul);
863
        }
864
    }
865
866
    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...
867
    {
868
        $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
869
870
        if (is_array($checked) && !empty($checked)) {
871
            /**
872
             * Extensions can listen for any custom actions that were added
873
             * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
874
             * delegates.
875
             *
876
             * @delegate CustomActions
877
             * @since Symphony 2.3.2
878
             * @param string $context
879
             *  '/publish/'
880
             * @param array $checked
881
             *  An array of the selected rows. The value is usually the ID of the
882
             *  the associated object.
883
             */
884
            Symphony::ExtensionManager()->notifyMembers('CustomActions', '/publish/', array(
885
                'checked' => $checked
886
            ));
887
888
            switch ($_POST['with-selected']) {
889
                case 'delete':
890
                    /**
891
                     * Prior to deletion of entries. An array of Entry ID's is provided which
892
                     * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
893
                     * in Symphony 2.3.
894
                     *
895
                     * @delegate EntryPreDelete
896
                     * @param string $context
897
                     * '/publish/'
898
                     * @param array $entry_id
899
                     *  An array of Entry ID's passed by reference
900
                     */
901
                    Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
902
903
                    EntryManager::delete($checked);
904
905
                    /**
906
                     * After the deletion of entries, this delegate provides an array of Entry ID's
907
                     * that were deleted.
908
                     *
909
                     * @since Symphony 2.3
910
                     * @delegate EntryPostDelete
911
                     * @param string $context
912
                     * '/publish/'
913
                     * @param array $entry_id
914
                     *  An array of Entry ID's that were deleted.
915
                     */
916
                    Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
917
918
                    redirect(server_safe('REQUEST_URI'));
919
                    break;
920
                default:
921
                    list($option, $field_id, $value) = explode('-', $_POST['with-selected'], 3);
922
923
                    if ($option === 'toggle') {
924
                        $field = FieldManager::fetch($field_id);
925
                        $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...
926
927
                        $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...
928
929
                        foreach ($checked as $entry_id) {
930
                            $entry = EntryManager::fetch($entry_id);
931
                            $existing_data = $entry[0]->getData($field_id);
932
                            $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...
933
934
                            /**
935
                             * Just prior to editing of an Entry
936
                             *
937
                             * @delegate EntryPreEdit
938
                             * @param string $context
939
                             * '/publish/edit/'
940
                             * @param Section $section
941
                             * @param Entry $entry
942
                             * @param array $fields
943
                             */
944
                            Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array(
945
                                'section' => $section,
946
                                'entry' => &$entry[0],
947
                                'fields' => $fields
948
                            ));
949
950
                            $entry[0]->commit();
951
952
                            /**
953
                             * Editing an entry. Entry object is provided.
954
                             *
955
                             * @delegate EntryPostEdit
956
                             * @param string $context
957
                             * '/publish/edit/'
958
                             * @param Section $section
959
                             * @param Entry $entry
960
                             * @param array $fields
961
                             */
962
                            Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array(
963
                                'section' => $section,
964
                                'entry' => $entry[0],
965
                                'fields' => $fields
966
                            ));
967
                        }
968
969
                        redirect(server_safe('REQUEST_URI'));
970
                    }
971
            }
972
        }
973
    }
974
975
    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...
976
    {
977 View Code Duplication
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
978
            Administration::instance()->throwCustomError(
979
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
980
                __('Unknown Section'),
981
                Page::HTTP_STATUS_NOT_FOUND
982
            );
983
        }
984
985
        $section = SectionManager::fetch($section_id);
986
987
        $this->setPageType('form');
988
        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
989
990
        // Ensure errored entries still maintain any prepopulated values [#2211]
991
        $this->Form->setAttribute('action', $this->Form->getAttribute('action') . $this->getPrepopulateString());
992
        $this->Form->setAttribute('enctype', 'multipart/form-data');
993
994
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
995
        $main_fields = $section->fetchFields(null, 'main');
996
997 View Code Duplication
        if (!empty($sidebar_fields) && !empty($main_fields)) {
998
            $this->Form->setAttribute('class', 'two columns');
999
        } else {
1000
            $this->Form->setAttribute('class', 'columns');
1001
        }
1002
1003
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
1004
        if (Symphony::Author()->isDeveloper()) {
1005
            $this->appendSubheading(__('Untitled'),
1006
                Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button')
1007
            );
1008
        } else {
1009
            $this->appendSubheading(__('Untitled'));
1010
        }
1011
1012
        // Build filtered breadcrumb [#1378}
1013
        $this->insertBreadcrumbs(array(
1014
            Widget::Anchor($section->get('name'), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
1015
        ));
1016
1017
        $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...
1018
1019
        // If there is post data floating around, due to errors, create an entry object
1020
        if (isset($_POST['fields'])) {
1021
            $entry = EntryManager::create();
1022
            $entry->set('section_id', $section_id);
1023
            $entry->setDataFromPost($_POST['fields'], $error, true);
1024
1025
            // Brand new entry, so need to create some various objects
1026
        } else {
1027
            $entry = EntryManager::create();
1028
            $entry->set('section_id', $section_id);
1029
        }
1030
1031
        // Check if there is a field to prepopulate
1032
        if (isset($_REQUEST['prepopulate'])) {
1033
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1034
                $this->Form->prependChild(Widget::Input(
1035
                    "prepopulate[{$field_id}]",
1036
                    rawurlencode($value),
1037
                    'hidden'
1038
                ));
1039
1040
                // The actual pre-populating should only happen if there is not existing fields post data
1041
                if (!isset($_POST['fields']) && $field = FieldManager::fetch($field_id)) {
1042
                    $entry->setData(
1043
                        $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...
1044
                        $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...
1045
                    );
1046
                }
1047
            }
1048
        }
1049
1050
        $primary = new XMLElement('fieldset');
1051
        $primary->setAttribute('class', 'primary column');
1052
1053
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1054
            $message = __('Fields must be added to this section before an entry can be created.');
1055
1056 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1057
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1058
                . __('Add fields')
1059
                . '</a>';
1060
            }
1061
1062
            $this->pageAlert($message, Alert::ERROR);
1063
        } else {
1064 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1065
                foreach ($main_fields as $field) {
1066
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1067
                }
1068
1069
                $this->Form->appendChild($primary);
1070
            }
1071
1072 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1073
                $sidebar = new XMLElement('fieldset');
1074
                $sidebar->setAttribute('class', 'secondary column');
1075
1076
                foreach ($sidebar_fields as $field) {
1077
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1078
                }
1079
1080
                $this->Form->appendChild($sidebar);
1081
            }
1082
1083
            $div = new XMLElement('div');
1084
            $div->setAttribute('class', 'actions');
1085
            $div->appendChild(Widget::Input('action[save]', __('Create Entry'), 'submit', array('accesskey' => 's')));
1086
1087
            $this->Form->appendChild($div);
1088
1089
            // Create a Drawer for Associated Sections
1090
            $this->prepareAssociationsDrawer($section);
0 ignored issues
show
Bug introduced by
It seems like $section defined by \SectionManager::fetch($section_id) on line 985 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...
1091
        }
1092
    }
1093
1094
    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...
1095
    {
1096
        if (is_array($_POST['action']) && (array_key_exists('save', $_POST['action']) || array_key_exists('done', $_POST['action']))) {
1097
            $section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle']);
1098
1099
            if (!$section = SectionManager::fetch($section_id)) {
1100
                Administration::instance()->throwCustomError(
1101
                    __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1102
                    __('Unknown Section'),
1103
                    Page::HTTP_STATUS_NOT_FOUND
1104
                );
1105
            }
1106
1107
            $entry = EntryManager::create();
1108
            $entry->set('author_id', Symphony::Author()->get('id'));
1109
            $entry->set('section_id', $section_id);
1110
            $entry->set('creation_date', DateTimeObj::get('c'));
1111
            $entry->set('modification_date', DateTimeObj::get('c'));
1112
1113
            $fields = $_POST['fields'];
1114
1115
            // Combine FILES and POST arrays, indexed by their custom field handles
1116
            if (isset($_FILES['fields'])) {
1117
                $filedata = General::processFilePostData($_FILES['fields']);
1118
1119
                foreach ($filedata as $handle => $data) {
1120
                    if (!isset($fields[$handle])) {
1121
                        $fields[$handle] = $data;
1122
                    } elseif (isset($data['error']) && $data['error'] === UPLOAD_ERR_NO_FILE) {
1123
                        $fields[$handle] = null;
1124
                    } else {
1125
                        foreach ($data as $ii => $d) {
1126
                            if (isset($d['error']) && $d['error'] === UPLOAD_ERR_NO_FILE) {
1127
                                $fields[$handle][$ii] = null;
1128
                            } elseif (is_array($d) && !empty($d)) {
1129
                                foreach ($d as $key => $val) {
1130
                                    $fields[$handle][$ii][$key] = $val;
1131
                                }
1132
                            }
1133
                        }
1134
                    }
1135
                }
1136
            }
1137
1138
            // Initial checks to see if the Entry is ok
1139
            if (Entry::__ENTRY_FIELD_ERROR__ === $entry->checkPostData($fields, $this->_errors)) {
1140
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1141
1142
                // Secondary checks, this will actually process the data and attempt to save
1143 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ !== $entry->setDataFromPost($fields, $errors)) {
1144
                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...
1145
                    $this->pageAlert($message, Alert::ERROR);
1146
                }
1147
1148
                // Everything is awesome. Dance.
1149
            } else {
1150
                /**
1151
                 * Just prior to creation of an Entry
1152
                 *
1153
                 * @delegate EntryPreCreate
1154
                 * @param string $context
1155
                 * '/publish/new/'
1156
                 * @param Section $section
1157
                 * @param Entry $entry
1158
                 * @param array $fields
1159
                 */
1160
                Symphony::ExtensionManager()->notifyMembers('EntryPreCreate', '/publish/new/', array('section' => $section, 'entry' => &$entry, 'fields' => &$fields));
1161
1162
                // Check to see if the dancing was premature
1163
                if (!$entry->commit()) {
1164
                    $this->pageAlert(null, Alert::ERROR);
1165
                } else {
1166
                    /**
1167
                     * Creation of an Entry. New Entry object is provided.
1168
                     *
1169
                     * @delegate EntryPostCreate
1170
                     * @param string $context
1171
                     * '/publish/new/'
1172
                     * @param Section $section
1173
                     * @param Entry $entry
1174
                     * @param array $fields
1175
                     */
1176
                    Symphony::ExtensionManager()->notifyMembers('EntryPostCreate', '/publish/new/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1177
1178
                    $prepopulate_querystring = $this->getPrepopulateString();
1179
                    redirect(sprintf(
1180
                        '%s/publish/%s/edit/%d/created/%s',
1181
                        SYMPHONY_URL,
1182
                        $this->_context['section_handle'],
1183
                        $entry->get('id'),
1184
                        (!empty($prepopulate_querystring) ? $prepopulate_querystring : null)
1185
                    ));
1186
                }
1187
            }
1188
        }
1189
    }
1190
1191
    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...
1192
    {
1193 View Code Duplication
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
1194
            Administration::instance()->throwCustomError(
1195
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1196
                __('Unknown Section'),
1197
                Page::HTTP_STATUS_NOT_FOUND
1198
            );
1199
        }
1200
1201
        $section = SectionManager::fetch($section_id);
1202
        $entry_id = intval($this->_context['entry_id']);
1203
        $base = '/publish/'.$this->_context['section_handle'] . '/';
1204
        $new_link = $base . 'new/';
1205
        $filter_link = $base;
1206
1207
        EntryManager::setFetchSorting('id', 'DESC');
1208
1209 View Code Duplication
        if (!$existingEntry = EntryManager::fetch($entry_id)) {
1210
            Administration::instance()->throwCustomError(
1211
                __('Unknown Entry'),
1212
                __('The Entry, %s, could not be found.', array($entry_id)),
1213
                Page::HTTP_STATUS_NOT_FOUND
1214
            );
1215
        }
1216
        $existingEntry = $existingEntry[0];
1217
1218
        // If there is post data floating around, due to errors, create an entry object
1219
        if (isset($_POST['fields'])) {
1220
            $fields = $_POST['fields'];
1221
1222
            $entry = EntryManager::create();
1223
            $entry->set('id', $entry_id);
1224
            $entry->set('author_id', $existingEntry->get('author_id'));
1225
            $entry->set('section_id', $existingEntry->get('section_id'));
1226
            $entry->set('creation_date', $existingEntry->get('creation_date'));
1227
            $entry->set('modification_date', $existingEntry->get('modification_date'));
1228
            $entry->setDataFromPost($fields, $errors, true);
1229
1230
            // Editing an entry, so need to create some various objects
1231
        } else {
1232
            $entry = $existingEntry;
1233
            $fields = array();
1234
1235
            if (!$section) {
1236
                $section = SectionManager::fetch($entry->get('section_id'));
1237
            }
1238
        }
1239
1240
        /**
1241
         * Just prior to rendering of an Entry edit form.
1242
         *
1243
         * @delegate EntryPreRender
1244
         * @param string $context
1245
         * '/publish/edit/'
1246
         * @param Section $section
1247
         * @param Entry $entry
1248
         * @param array $fields
1249
         */
1250
        Symphony::ExtensionManager()->notifyMembers('EntryPreRender', '/publish/edit/', array(
1251
            'section' => $section,
1252
            'entry' => &$entry,
1253
            'fields' => $fields
1254
        ));
1255
1256
        // Iterate over the `prepopulate` parameters to build a URL
1257
        // to remember this state for Create New, View all Entries and
1258
        // Breadcrumb links. If `prepopulate` doesn't exist, this will
1259
        // just use the standard pages (ie. no filtering)
1260
        if (isset($_REQUEST['prepopulate'])) {
1261
            $new_link .= $this->getPrepopulateString();
1262
            $filter_link .= $this->getFilterString();
1263
        }
1264
1265
        if (isset($this->_context['flag'])) {
1266
            // These flags are only relevant if there are no errors
1267
            if (empty($this->_errors)) {
1268
                $time = Widget::Time();
1269
1270
                switch ($this->_context['flag']) {
1271
                    case 'saved':
1272
                        $message = __('Entry updated at %s.', array($time->generate()));
1273
                        break;
1274
                    case 'created':
1275
                        $message = __('Entry created at %s.', array($time->generate()));
1276
                }
1277
1278
                $this->pageAlert(
1279
                    $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...
1280
                    . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
1281
                    . __('Create another?')
1282
                    . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
1283
                    . __('View all Entries')
1284
                    . '</a>',
1285
                    Alert::SUCCESS
1286
                );
1287
            }
1288
        }
1289
1290
        // Determine the page title
1291
        $field_id = Symphony::Database()->fetchVar('id', 0, "
1292
            SELECT `id`
1293
            FROM `tbl_fields`
1294
            WHERE `parent_section` = ?
1295
            ORDER BY `sortorder` LIMIT 1",
1296
            array($section->get('id'))
1297
        );
1298
        if (!is_null($field_id)) {
1299
            $field = FieldManager::fetch($field_id);
1300
        }
1301
1302
        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...
1303
            $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...
1304
        } else {
1305
            $title = '';
1306
        }
1307
1308
        if (trim($title) === '') {
1309
            $title = __('Untitled');
1310
        }
1311
1312
        // Check if there is a field to prepopulate
1313
        if (isset($_REQUEST['prepopulate'])) {
1314
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1315
                $this->Form->prependChild(Widget::Input(
1316
                    "prepopulate[{$field_id}]",
1317
                    rawurlencode($value),
1318
                    'hidden'
1319
                ));
1320
            }
1321
        }
1322
1323
        $this->setPageType('form');
1324
        $this->Form->setAttribute('enctype', 'multipart/form-data');
1325
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, $section->get('name'), __('Symphony'))));
1326
1327
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
1328
        $main_fields = $section->fetchFields(null, 'main');
1329
1330 View Code Duplication
        if (!empty($sidebar_fields) && !empty($main_fields)) {
1331
            $this->Form->setAttribute('class', 'two columns');
1332
        } else {
1333
            $this->Form->setAttribute('class', 'columns');
1334
        }
1335
1336
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
1337
        if (Symphony::Author()->isDeveloper()) {
1338
            $this->appendSubheading($title, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
1339
        } else {
1340
            $this->appendSubheading($title);
1341
        }
1342
1343
        $this->insertBreadcrumbs(array(
1344
            Widget::Anchor($section->get('name'), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
1345
        ));
1346
1347
        $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...
1348
1349
        $primary = new XMLElement('fieldset');
1350
        $primary->setAttribute('class', 'primary column');
1351
1352
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1353
            $message = __('Fields must be added to this section before an entry can be created.');
1354
1355 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1356
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1357
                . __('Add fields')
1358
                . '</a>';
1359
            }
1360
1361
            $this->pageAlert($message, Alert::ERROR);
1362
        } else {
1363 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1364
                foreach ($main_fields as $field) {
1365
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1366
                }
1367
1368
                $this->Form->appendChild($primary);
1369
            }
1370
1371 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1372
                $sidebar = new XMLElement('fieldset');
1373
                $sidebar->setAttribute('class', 'secondary column');
1374
1375
                foreach ($sidebar_fields as $field) {
1376
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1377
                }
1378
1379
                $this->Form->appendChild($sidebar);
1380
            }
1381
1382
            $div = new XMLElement('div');
1383
            $div->setAttribute('class', 'actions');
1384
            $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));
1385
1386
            $button = new XMLElement('button', __('Delete'));
1387
            $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?')));
1388
            $div->appendChild($button);
1389
1390
            $this->Form->appendChild($div);
1391
1392
            // Create a Drawer for Associated Sections
1393
            $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...
1394
        }
1395
    }
1396
1397
    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...
1398
    {
1399
        $entry_id = intval($this->_context['entry_id']);
1400
1401
        if (is_array($_POST['action']) && (array_key_exists('save', $_POST['action']) || array_key_exists("done", $_POST['action']))) {
1402 View Code Duplication
            if (!$ret = EntryManager::fetch($entry_id)) {
1403
                Administration::instance()->throwCustomError(
1404
                    __('The Entry, %s, could not be found.', array($entry_id)),
1405
                    __('Unknown Entry'),
1406
                    Page::HTTP_STATUS_NOT_FOUND
1407
                );
1408
            }
1409
1410
            $entry = $ret[0];
1411
1412
            $section = SectionManager::fetch($entry->get('section_id'));
1413
1414
            $post = General::getPostData();
1415
            $fields = $post['fields'];
1416
1417
            // Initial checks to see if the Entry is ok
1418
            if (Entry::__ENTRY_FIELD_ERROR__ === $entry->checkPostData($fields, $this->_errors)) {
1419
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1420
1421
                // Secondary checks, this will actually process the data and attempt to save
1422 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...
1423
                foreach ($errors as $field_id => $message) {
1424
                    $this->pageAlert($message, Alert::ERROR);
1425
                }
1426
1427
                // Everything is awesome. Dance.
1428
            } else {
1429
                /**
1430
                 * Just prior to editing of an Entry.
1431
                 *
1432
                 * @delegate EntryPreEdit
1433
                 * @param string $context
1434
                 * '/publish/edit/'
1435
                 * @param Section $section
1436
                 * @param Entry $entry
1437
                 * @param array $fields
1438
                 */
1439
                Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array('section' => $section, 'entry' => &$entry, 'fields' => $fields));
1440
1441
                // Check to see if the dancing was premature
1442
                if (!$entry->commit()) {
1443
                    $this->pageAlert(null, Alert::ERROR);
1444
                } else {
1445
                    /**
1446
                     * Just after the editing of an Entry
1447
                     *
1448
                     * @delegate EntryPostEdit
1449
                     * @param string $context
1450
                     * '/publish/edit/'
1451
                     * @param Section $section
1452
                     * @param Entry $entry
1453
                     * @param array $fields
1454
                     */
1455
                    Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1456
1457
                    redirect(sprintf(
1458
                        '%s/publish/%s/edit/%d/saved/%s',
1459
                        SYMPHONY_URL,
1460
                        $this->_context['section_handle'],
1461
                        $entry->get('id'),
1462
                        $this->getPrepopulateString()
1463
                    ));
1464
                }
1465
            }
1466
        } elseif (is_array($_POST['action']) && array_key_exists('delete', $_POST['action']) && is_numeric($entry_id)) {
1467
            /**
1468
             * Prior to deletion of entries. An array of Entry ID's is provided which
1469
             * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
1470
             * in Symphony 2.3.
1471
             *
1472
             * @delegate EntryPreDelete
1473
             * @param string $context
1474
             * '/publish/'
1475
             * @param array $entry_id
1476
             *    An array of Entry ID's passed by reference
1477
             */
1478
            $checked = array($entry_id);
1479
            Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
1480
1481
            EntryManager::delete($checked);
1482
1483
            /**
1484
             * After the deletion of entries, this delegate provides an array of Entry ID's
1485
             * that were deleted.
1486
             *
1487
             * @since Symphony 2.3
1488
             * @delegate EntryPostDelete
1489
             * @param string $context
1490
             * '/publish/'
1491
             * @param array $entry_id
1492
             *  An array of Entry ID's that were deleted.
1493
             */
1494
            Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
1495
1496
            redirect(SYMPHONY_URL . '/publish/'.$this->_context['section_handle'].'/');
1497
        }
1498
    }
1499
1500
    /**
1501
     * Given a Field and Entry object, this function will wrap
1502
     * the Field's displayPublishPanel result with a div that
1503
     * contains some contextual information such as the Field ID,
1504
     * the Field handle and whether it is required or not.
1505
     *
1506
     * @param Field $field
1507
     * @param Entry $entry
1508
     * @return XMLElement
1509
     */
1510
    private function __wrapFieldWithDiv(Field $field, Entry $entry)
1511
    {
1512
        $is_hidden = $this->isFieldHidden($field);
1513
        $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' : '')));
1514
1515
        $field->setAssociationContext($div);
1516
1517
        $field->displayPublishPanel(
1518
            $div, $entry->getData($field->get('id')),
1519
            (isset($this->_errors[$field->get('id')]) ? $this->_errors[$field->get('id')] : null),
1520
            null, null, (is_numeric($entry->get('id')) ? $entry->get('id') : null)
1521
        );
1522
1523
        /**
1524
         * Allows developers modify the field before it is rendered in the publish
1525
         * form. Passes the `Field` object, `Entry` object, the `XMLElement` div and
1526
         * any errors for the entire `Entry`. Only the `$div` element
1527
         * will be altered before appending to the page, the rest are read only.
1528
         *
1529
         * @since Symphony 2.5.0
1530
         * @delegate ModifyFieldPublishWidget
1531
         * @param string $context
1532
         * '/backend/'
1533
         * @param Field $field
1534
         * @param Entry $entry
1535
         * @param array $errors
1536
         * @param Widget $widget
1537
         */
1538
        Symphony::ExtensionManager()->notifyMembers('ModifyFieldPublishWidget', '/backend/', array(
1539
            'field' => $field,
1540
            'entry' => $entry,
1541
            'errors' => $this->_errors,
1542
            'widget' => &$div
1543
        ));
1544
1545
        return $div;
1546
    }
1547
1548
    /**
1549
     * Check whether the given `$field` will be hidden because it's been
1550
     * prepopulated.
1551
     *
1552
     * @param  Field  $field
1553
     * @return boolean
1554
     */
1555
    public function isFieldHidden(Field $field)
1556
    {
1557
        if ($field->get('hide_when_prepopulated') === 'yes') {
1558
            if (isset($_REQUEST['prepopulate'])) {
1559
                foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1560
                    if ($field_id === $field->get('id')) {
1561
                        return true;
1562
                    }
1563
                }
1564
            }
1565
        }
1566
1567
        return false;
1568
    }
1569
1570
    /**
1571
     * Prepare a Drawer to visualize section associations
1572
     *
1573
     * @param  Section $section The current Section object
1574
     * @throws InvalidArgumentException
1575
     * @throws Exception
1576
     */
1577
    private function prepareAssociationsDrawer($section)
1578
    {
1579
        $entry_id = (!is_null($this->_context['entry_id'])) ? $this->_context['entry_id'] : null;
1580
        $show_entries = Symphony::Configuration()->get('association_maximum_rows', 'symphony');
1581
1582
        if (is_null($entry_id) && !isset($_GET['prepopulate']) || is_null($show_entries) || $show_entries === 0) {
1583
            return;
1584
        }
1585
1586
        $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...
1587
        $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...
1588
        $content = null;
1589
        $drawer_position = 'vertical-right';
1590
1591
        /**
1592
         * Prepare Associations Drawer from an Extension
1593
         *
1594
         * @since Symphony 2.3.3
1595
         * @delegate PrepareAssociationsDrawer
1596
         * @param string $context
1597
         * '/publish/'
1598
         * @param integer $entry_id
1599
         *  The entry ID or null
1600
         * @param array $parent_associations
1601
         *  Array of Sections
1602
         * @param array $child_associations
1603
         *  Array of Sections
1604
         * @param string $drawer_position
1605
         *  The position of the Drawer, defaults to `vertical-right`. Available
1606
         *  values of `vertical-left, `vertical-right` and `horizontal`
1607
         */
1608
        Symphony::ExtensionManager()->notifyMembers('PrepareAssociationsDrawer', '/publish/', array(
1609
            'entry_id' => $entry_id,
1610
            'parent_associations' => &$parent_associations,
1611
            'child_associations' => &$child_associations,
1612
            'content' => &$content,
1613
            'drawer-position' => &$drawer_position
1614
        ));
1615
1616
        // If there are no associations, return now.
1617
        if (
1618
            (is_null($parent_associations) || empty($parent_associations))
1619
            &&
1620
            (is_null($child_associations) || empty($child_associations))
1621
        ) {
1622
            return;
1623
        }
1624
1625
        if (!($content instanceof XMLElement)) {
1626
            $content = new XMLElement('div', null, array('class' => 'content'));
1627
            $content->setSelfClosingTag(false);
1628
1629
            // Process Parent Associations
1630
            if (!is_null($parent_associations) && !empty($parent_associations)) {
1631
                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...
1632
                    if ($field = FieldManager::fetch($as['parent_section_field_id'])) {
1633
                        if (isset($_GET['prepopulate'])) {
1634
                            $prepopulate_field = key($_GET['prepopulate']);
1635
                        }
1636
1637
                        // get associated entries if entry exists,
1638
                        if ($entry_id) {
1639
                            $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...
1640
1641
                            // get prepopulated entry otherwise
1642
                        } elseif (isset($_GET['prepopulate'])) {
1643
                            $entry_ids = array(intval(current($_GET['prepopulate'])));
1644
                        } else {
1645
                            $entry_ids = array();
1646
                        }
1647
1648
                        // Use $schema for perf reasons
1649
                        $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...
1650
                        $where = (!empty($entry_ids)) ? sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids)) : null;
1651
                        $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...
1652
                            ? 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...
1653
                            : array();
1654
                        $has_entries = !empty($entries) && $entries['total-entries'] !== 0;
1655
1656
                        if ($has_entries) {
1657
                            $element = new XMLElement('section', null, array('class' => 'association parent'));
1658
                            $header = new XMLElement('header');
1659
                            $header->appendChild(new XMLElement('p', __('Linked to %s in', array('<a class="association-section" href="' . SYMPHONY_URL . '/publish/' . $as['handle'] . '/">' . $as['name'] . '</a>'))));
1660
                            $element->appendChild($header);
1661
1662
                            $ul = new XMLElement('ul', null, array(
1663
                                'class' => 'association-links',
1664
                                'data-section-id' => $as['child_section_id'],
1665
                                'data-association-ids' => implode(', ', $entry_ids)
1666
                            ));
1667
1668
                            foreach ($entries['records'] as $e) {
1669
                                // let the field create the mark up
1670
                                $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...
1671
                                // add it to the unordered list
1672
                                $ul->appendChild($li);
1673
                            }
1674
1675
                            $element->appendChild($ul);
1676
                            $content->appendChild($element);
1677
                        }
1678
                    }
1679
                }
1680
            }
1681
1682
            // Process Child Associations
1683
            if (!is_null($child_associations) && !empty($child_associations)) {
1684
                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...
1685
                    // Get the related section
1686
                    $child_section = SectionManager::fetch($as['child_section_id']);
1687
1688
                    if (!($child_section instanceof Section)) {
1689
                        continue;
1690
                    }
1691
1692
                    // Get the visible field instance (using the sorting field, this is more flexible than visibleColumns())
1693
                    // Get the link field instance
1694
                    $visible_field   = current($child_section->fetchVisibleColumns());
1695
                    $relation_field  = FieldManager::fetch($as['child_section_field_id']);
1696
1697
                    // Get entries, using $schema for performance reasons.
1698
                    $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...
1699
                    $schema = $visible_field ? array($visible_field->get('element_name')) : array();
1700
                    $where = sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids));
1701
1702
                    $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...
1703
                    $has_entries = !empty($entries) && $entries['total-entries'] !== 0;
1704
1705
                    // Build the HTML of the relationship
1706
                    $element = new XMLElement('section', null, array('class' => 'association child'));
1707
                    $header = new XMLElement('header');
1708
                    $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...
1709
                    $prepopulate = '?prepopulate[' . $as['child_section_field_id'] . ']=' . $entry_id;
1710
1711
                    // Create link with filter or prepopulate
1712
                    $link = SYMPHONY_URL . '/publish/' . $as['handle'] . '/' . $filter;
1713
                    $a = new XMLElement('a', $as['name'], array(
1714
                        'class' => 'association-section',
1715
                        'href' => $link
1716
                    ));
1717
1718
                    // Create new entries
1719
                    $create = new XMLElement('a', __('Create New'), array(
1720
                        'class' => 'button association-new',
1721
                        'href' => SYMPHONY_URL . '/publish/' . $as['handle'] . '/new/' . $prepopulate
1722
                    ));
1723
1724
                    // Display existing entries
1725
                    if ($has_entries) {
1726
                        $header->appendChild(new XMLElement('p', __('Links in %s', array($a->generate()))));
1727
1728
                        $ul = new XMLElement('ul', null, array(
1729
                            'class' => 'association-links',
1730
                            'data-section-id' => $as['child_section_id'],
1731
                            'data-association-ids' => implode(', ', $entry_ids)
1732
                        ));
1733
1734
                        foreach ($entries['records'] as $key => $e) {
1735
                            // let the first visible field create the mark up
1736
                            if ($visible_field) {
1737
                                $li = $visible_field->prepareAssociationsDrawerXMLElement($e, $as, $prepopulate);
1738
                            }
1739
                            // or use the system:id if no visible field exists.
1740
                            else {
1741
                                $li = Field::createAssociationsDrawerXMLElement($e->get('id'), $e, $as, $prepopulate);
1742
                            }
1743
1744
                            // add it to the unordered list
1745
                            $ul->appendChild($li);
1746
                        }
1747
1748
                        $element->appendChild($ul);
1749
1750
                        // If we are only showing 'some' of the entries, then show this on the UI
1751
                        if ($entries['total-entries'] > $show_entries) {
1752
                            $pagination = new XMLElement('li', null, array(
1753
                                'class' => 'association-more',
1754
                                'data-current-page' => '1',
1755
                                'data-total-pages' => ceil($entries['total-entries'] / $show_entries),
1756
                                'data-total-entries' => $entries['total-entries']
1757
                            ));
1758
                            $counts = new XMLElement('a', __('Show more entries'), array(
1759
                                'href' => $link
1760
                            ));
1761
1762
                            $pagination->appendChild($counts);
1763
                            $ul->appendChild($pagination);
1764
                        }
1765
1766
                        // No entries
1767
                    } else {
1768
                        $element->setAttribute('class', 'association child empty');
1769
                        $header->appendChild(new XMLElement('p', __('No links in %s', array($a->generate()))));
1770
                    }
1771
1772
                    $header->appendChild($create);
1773
                    $element->prependChild($header);
1774
                    $content->appendChild($element);
1775
                }
1776
            }
1777
        }
1778
1779
        $drawer = Widget::Drawer('section-associations', __('Show Associations'), $content);
1780
        $this->insertDrawer($drawer, $drawer_position, 'prepend');
1781
    }
1782
1783
    /**
1784
     * If this entry is being prepopulated, this function will return the prepopulated
1785
     * fields and values as a query string.
1786
     *
1787
     * @since Symphony 2.5.2
1788
     * @return string
1789
     */
1790
    public function getPrepopulateString()
1791
    {
1792
        $prepopulate_querystring = '';
1793
1794
        if (isset($_REQUEST['prepopulate']) && is_array($_REQUEST['prepopulate'])) {
1795
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1796
                // Properly decode and re-encode value for output
1797
                $value = rawurlencode(rawurldecode($value));
1798
                $prepopulate_querystring .= sprintf("prepopulate[%s]=%s&", $field_id, $value);
1799
            }
1800
            $prepopulate_querystring = trim($prepopulate_querystring, '&');
1801
        }
1802
1803
        // This is to prevent the value being interpreted as an additional GET
1804
        // parameter. eg. prepopulate[cat]=Minx&June, would come through as:
1805
        // $_GET['cat'] = Minx
1806
        // $_GET['June'] = ''
1807
        $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
1808
1809
        return $prepopulate_querystring ? '?' . $prepopulate_querystring : null;
1810
    }
1811
1812
    /**
1813
     * If the entry is being prepopulated, we may want to filter other views by this entry's
1814
     * value. This function will create that filter query string.
1815
     *
1816
     * @since Symphony 2.5.2
1817
     * @return string
1818
     */
1819
    public function getFilterString()
1820
    {
1821
        $filter_querystring = '';
1822
1823
        if (isset($_REQUEST['prepopulate']) && is_array($_REQUEST['prepopulate'])) {
1824
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1825
                $handle = FieldManager::fetchHandleFromID($field_id);
1826
                // Properly decode and re-encode value for output
1827
                $value = rawurlencode(rawurldecode($value));
1828
1829
                //This is in case it is an Association so the filter reads the text value instead of the ID
1830
                $field = FieldManager::fetch($field_id);
1831
                if ($field instanceof Field) {
1832
                    if (method_exists($field, 'fetchValueFromID')) {
1833
                        $value = $field->fetchValueFromID($value);
1834
                    }
1835
                }
1836
1837
                $filter_querystring .= sprintf("filter[%s]=%s&", $handle, $value);
1838
            }
1839
            $filter_querystring = trim($filter_querystring, '&');
1840
        }
1841
1842
        // This is to prevent the value being interpreted as an additional GET
1843
        // parameter. eg. filter[cat]=Minx&June, would come through as:
1844
        // $_GET['cat'] = Minx
1845
        // $_GET['June'] = ''
1846
        $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
1847
1848
        return $filter_querystring ? '?' . $filter_querystring : null;
1849
    }
1850
}
1851