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.
Passed
Pull Request — master (#2843)
by Brendan
04:11
created

contentPublish   F

Complexity

Total Complexity 290

Size/Duplication

Total Lines 1951
Duplicated Lines 0 %

Importance

Changes 37
Bugs 0 Features 0
Metric Value
c 37
b 0
f 0
dl 0
loc 1951
rs 0.6314
wmc 290

27 Methods

Rating   Name   Duplication   Size   Complexity  
B getPrepopulateString() 0 20 5
D __actionNew() 0 93 21
A createFilteringInterface() 0 14 3
A createFilterHelp() 0 12 2
A createFilterSuggestions() 0 14 2
B validateTimestamp() 0 19 10
F __viewIndex() 0 477 71
C sort() 0 47 12
A createFilteringDuplicator() 0 16 1
D __actionEdit() 0 117 15
B createSystemDateFilters() 0 39 3
A view() 0 3 1
D prepareAssociationsDrawer() 0 278 44
A getFilterQuery() 0 13 4
B createFilter() 0 41 1
A createFilteringDrawer() 0 6 1
B __switchboard() 0 22 5
B getFilterString() 0 21 5
A build() 0 12 2
A action() 0 3 1
B isFieldHidden() 0 13 5
B __wrapFieldWithDiv() 0 36 5
C __actionIndex() 0 105 8
F __viewEdit() 0 220 31
B createFilterComparisons() 0 32 6
D __viewNew() 0 118 22
B createFieldFilters() 0 32 4

How to fix   Complexity   

Complex Class

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.

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

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

1
<?php
2
3
/**
4
 * @package content
5
 */
6
/**
7
 * The Publish page is where the majority of an Authors time will
8
 * be spent in Symphony with adding, editing and removing entries
9
 * from Sections. This Page controls the entries table as well as
10
 * the Entry creation screens.
11
 */
12
13
class contentPublish extends AdministrationPage
14
{
15
    public $_errors = array();
16
17
    public function sort(&$sort, &$order, $params)
18
    {
19
        $section = $params['current-section'];
20
        $filters = '';
21
        // Format the filter query string
22
        if (isset($params['filters']) && !empty($params['filters'])) {
23
            $filters = preg_replace('/^&amp;/i', '', $params['filters'], 1);
24
            $filters = '?' . trim($filters);
25
        }
26
27
        // If `?unsort` is appended to the URL, then sorting is reverted
28
        // to 'none', aka. by 'entry-id'.
29
        if ($params['unsort']) {
30
            $section->setSortingField('id', false);
31
            $section->setSortingOrder('desc');
32
33
            redirect(Administration::instance()->getCurrentPageURL() . $filters);
34
        }
35
36
        // By default, sorting information are retrieved from
37
        // the file system and stored inside the `Configuration` object
38
        if (is_null($sort) && is_null($order)) {
39
            $sort = $section->getSortingField();
40
            $order = $section->getSortingOrder();
41
42
            // Set the sorting in the `EntryManager` for subsequent use
43
            EntryManager::setFetchSorting($sort, $order);
44
        } else {
45
            $sort = General::sanitize($sort);
46
47
            // Ensure that this field is infact sortable, otherwise
48
            // fallback to IDs
49
            if (($field = FieldManager::fetch($sort)) instanceof Field && !$field->isSortable()) {
0 ignored issues
show
introduced by
$field = FieldManager::fetch($sort) is never a sub-type of Field.
Loading history...
Bug introduced by
$sort of type string is incompatible with the type integer|array expected by parameter $id of FieldManager::fetch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

49
            if (($field = FieldManager::fetch(/** @scrutinizer ignore-type */ $sort)) instanceof Field && !$field->isSortable()) {
Loading history...
50
                $sort = $section->getDefaultSortingField();
51
            }
52
53
            // If the sort order or direction differs from what is saved,
54
            // update the config file and reload the page
55
            if ($sort != $section->getSortingField() || $order != $section->getSortingOrder()) {
56
                $section->setSortingField($sort, false);
57
                $section->setSortingOrder($order);
58
                redirect(Administration::instance()->getCurrentPageURL() . $filters);
59
            }
60
61
            // If the sort order and direction remains the same, reload the page
62
            if ($sort == $section->getSortingField() && $order == $section->getSortingOrder()) {
63
                redirect(Administration::instance()->getCurrentPageURL() . $filters);
64
            }
65
        }
66
    }
67
68
    /**
69
     * Append filtering interface
70
     */
71
    public function createFilteringInterface()
72
    {
73
        //Check if section has filtering enabled
74
        $context = $this->getContext();
75
        $handle = $context['section_handle'];
76
        $section_id = SectionManager::fetchIDFromHandle($handle);
77
        $section = SectionManager::fetch($section_id);
78
        $filter = $section->get('filter');
79
        $count = EntryManager::fetchCount($section_id);
80
81
        if ($filter !== 'no' && $count > 1) {
82
            $drawer = Widget::Drawer('filtering-' . $section_id, __('Filter Entries'), $this->createFilteringDrawer($section));
83
            $drawer->addClass('drawer-filtering');
84
            $this->insertDrawer($drawer);
85
        }
86
    }
87
88
    /**
89
     * Create filtering drawer
90
     */
91
    public function createFilteringDrawer($section)
92
    {
93
        $this->filteringForm = Widget::Form(null, 'get', 'filtering');
0 ignored issues
show
Bug Best Practice introduced by
The property filteringForm does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
94
        $this->createFilteringDuplicator($section);
95
96
        return $this->filteringForm;
97
    }
98
99
    public function createFilteringDuplicator($section)
100
    {
101
        $div = new XMLElement('div');
102
        $div->setAttribute('class', 'frame filters-duplicator');
103
        $div->setAttribute('data-interactive', 'data-interactive');
104
105
        $ol = new XMLElement('ol');
106
        $ol->setAttribute('data-add', __('Add filter'));
107
        $ol->setAttribute('data-remove', __('Clear filter'));
108
        $ol->setAttribute('data-empty', __('No filters applied yet.'));
109
110
        $this->createFieldFilters($ol, $section);
111
        $this->createSystemDateFilters($ol);
112
113
        $div->appendChild($ol);
114
        $this->filteringForm->appendChild($div);
115
    }
116
117
    private function createFieldFilters(&$wrapper, $section)
118
    {
119
        $filters = $_GET['filter'];
120
121
        foreach ($section->fetchFilterableFields() as $field) {
122
            if (!$field->canPublishFilter()) {
123
                continue;
124
            }
125
126
            $filter = $filters[$field->get('element_name')];
127
128
            // Filter data
129
            $data = array();
130
            $data['type'] = $field->get('element_name');
131
            $data['name'] = $field->get('label');
132
            $data['filter'] = $filter;
133
            $data['instance'] = 'unique';
134
            $data['search'] = $field->fetchSuggestionTypes();
135
            $data['operators'] = $field->fetchFilterableOperators();
136
            $data['comparisons'] = $this->createFilterComparisons($data);
137
            $data['query'] = $this->getFilterQuery($data);
138
            $data['field-id'] = $field->get('id');
139
140
            // Add existing filter
141
            if (isset($filter)) {
142
                $this->createFilter($wrapper, $data);
143
            }
144
145
            // Add filter template
146
            $data['instance'] = 'unique template';
147
            $data['query'] = '';
148
            $this->createFilter($wrapper, $data);
149
        }
150
    }
151
152
    private function createSystemDateFilters(&$wrapper)
153
    {
154
        $filters = $_GET['filter'];
155
        $dateField = new FieldDate;
156
157
        $fields = array(
158
            array(
159
                'type' => 'system:creation-date',
160
                'label' => __('System Creation Date')
161
            ),
162
            array(
163
                'type' => 'system:modification-date',
164
                'label' => __('System Modification Date')
165
            )
166
        );
167
168
        foreach ($fields as $field) {
169
            $filter = $filters[$field['type']];
170
171
            // Filter data
172
            $data = array();
173
            $data['type'] = $field['type'];
174
            $data['name'] = $field['label'];
175
            $data['filter'] = $filter;
176
            $data['instance'] = 'unique';
177
            $data['search'] = $dateField->fetchSuggestionTypes();
178
            $data['operators'] = $dateField->fetchFilterableOperators();
179
            $data['comparisons'] = $this->createFilterComparisons($data);
180
            $data['query'] = $this->getFilterQuery($data);
181
182
            // Add existing filter
183
            if (isset($filter)) {
184
                $this->createFilter($wrapper, $data);
185
            }
186
187
            // Add filter template
188
            $data['instance'] = 'unique template';
189
            $data['query'] = '';
190
            $this->createFilter($wrapper, $data);
191
        }
192
    }
193
194
    private function createFilter(&$wrapper, $data)
195
    {
196
        $li = new XMLElement('li');
197
        $li->setAttribute('class', $data['instance']);
198
        $li->setAttribute('data-type', $data['type']);
199
200
        // Header
201
        $li->appendChild(new XMLElement('header', $data['name'], array(
202
            'data-name' => $data['name']
203
        )));
204
205
        // Settings
206
        $div = new XMLElement('div', null, array('class' => 'two columns'));
207
208
        // Comparisons
209
        $label = Widget::Label();
210
        $label->setAttribute('class', 'column secondary');
211
212
        $select = Widget::Select($data['type'] . '-comparison', $data['comparisons'], array(
213
            'class' => 'comparison'
214
        ));
215
216
        $label->appendChild($select);
217
        $div->appendChild($label);
218
219
        // Query
220
        $label = Widget::Label();
221
        $label->setAttribute('class', 'column primary');
222
223
        $input = Widget::Input($data['type'], General::sanitize($data['query']), 'text', array(
224
            'placeholder' => __('Type and hit enter to apply filter…'),
225
            'autocomplete' => 'off'
226
        ));
227
        $input->setAttribute('class', 'filter');
228
        $label->appendChild($input);
229
230
        $this->createFilterSuggestions($label, $data);
231
232
        $div->appendChild($label);
233
        $li->appendChild($div);
234
        $wrapper->appendChild($li);
235
    }
236
237
    private function createFilterComparisons($data)
238
    {
239
        // Default comparison
240
        $comparisons = array();
241
242
        // Custom field comparisons
243
        foreach ($data['operators'] as $operator) {
244
245
            $filter = trim($operator['filter']);
246
247
            // Check selected state
248
            $selected = false;
249
250
            // Selected state : Comparison mode "between" (x to y)
251
            if ($operator['title'] === 'between' && preg_match('/^(-?(?:\d+(?:\.\d+)?|\.\d+)) to (-?(?:\d+(?:\.\d+)?|\.\d+))$/i', $data['filter'] )) {
252
                $selected = true;
253
            // Selected state : Other comparison modes (except "is")
254
            } elseif ((!empty($filter) && strpos($data['filter'], $filter) === 0)) {
255
                $selected = true;
256
            }
257
258
            $comparisons[] = array(
259
                $operator['filter'],
260
                $selected,
261
                __($operator['title']),
262
                null,
263
                null,
264
                array('data-comparison' => $operator['title'])
265
            );
266
        }
267
268
        return $comparisons;
269
    }
270
271
    private function createFilterSuggestions(&$wrapper, $data)
272
    {
273
        $ul = new XMLElement('ul');
274
        $ul->setAttribute('class', 'suggestions');
275
        $ul->setAttribute('data-field-id', $data['field-id']);
276
        $ul->setAttribute('data-associated-ids', '0');
277
        $ul->setAttribute('data-search-types', implode($data['search'], ','));
0 ignored issues
show
Bug introduced by
',' of type string is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

277
        $ul->setAttribute('data-search-types', implode($data['search'], /** @scrutinizer ignore-type */ ','));
Loading history...
278
279
        // Add help text for each filter operator
280
        foreach ($data['operators'] as $operator) {
281
            $this->createFilterHelp($ul, $operator);
282
        }
283
284
        $wrapper->appendChild($ul);
285
    }
286
287
    private function createFilterHelp(&$wrapper, $operator)
288
    {
289
        if (empty($operator['help'])) {
290
            return;
291
        }
292
293
        $li = new XMLElement('li', __('Comparison mode') . ': ' . $operator['help'], array(
294
            'class' => 'help',
295
            'data-comparison' => $operator['title']
296
        ));
297
298
        $wrapper->appendChild($li);
299
    }
300
301
    private function getFilterQuery($data)
302
    {
303
        $query = $data['filter'];
304
305
        foreach ($data['operators'] as $operator) {
306
            $filter = trim($operator['filter']);
307
308
            if (!empty($filter) && strpos($data['filter'], $filter) === 0) {
309
                $query = substr($data['filter'], strlen($operator['filter']));
310
            }
311
        }
312
313
        return (string)$query;
314
    }
315
316
    public function build(array $context = array())
317
    {
318
        $section_id = SectionManager::fetchIDFromHandle($context['section_handle']);
319
320
        if ($section_id) {
321
            $context['associations'] = array(
322
                'parent' => SectionManager::fetchParentAssociations($section_id),
323
                'child' => SectionManager::fetchChildAssociations($section_id)
324
            );
325
        }
326
327
        return parent::build($context);
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::build($context) targeting AdministrationPage::build() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
328
    }
329
330
    public function action()
331
    {
332
        $this->__switchboard('action');
333
    }
334
335
    public function __switchboard($type = 'view')
336
    {
337
        $function = ($type == 'action' ? '__action' : '__view') . ucfirst($this->_context['page']);
338
339
        if (!method_exists($this, $function)) {
340
            // If there is no action function, just return without doing anything
341
            if ($type == 'action') {
342
                return;
343
            }
344
345
            Administration::instance()->errorPageNotFound();
346
        }
347
348
        // Is this request allowed by server?
349
        if ($this->isRequestValid() === false) {
350
            $this->pageAlert(__('This request exceeds the maximum allowed request size of %s specified by your host.', array(
351
                    ini_get('post_max_size')
352
                )),
353
                Alert::ERROR
354
            );
355
        }
356
        $this->$function();
357
    }
358
359
    public function view()
360
    {
361
        $this->__switchboard();
362
    }
363
364
    public function __viewIndex()
365
    {
366
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
367
            Administration::instance()->throwCustomError(
368
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
369
                __('Unknown Section'),
370
                Page::HTTP_STATUS_NOT_FOUND
371
            );
372
        } elseif (!is_writable(CONFIG)) {
0 ignored issues
show
Bug introduced by
The constant CONFIG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
373
            $this->pageAlert(__('The Symphony configuration file, %s, is not writable. The sort order cannot be modified.', array('<code>/manifest/config.php</code>')), Alert::NOTICE);
374
        }
375
376
        $section = SectionManager::fetch($section_id);
377
378
        $this->setPageType('table');
379
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
0 ignored issues
show
Bug introduced by
It seems like $section->get('name') can also be of type array; however, parameter $source of General::sanitize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

379
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize(/** @scrutinizer ignore-type */ $section->get('name')), __('Symphony'))));
Loading history...
380
381
        $filters = array();
382
        $filter_querystring = $prepopulate_querystring = $where = $joins = null;
383
        $current_page = (isset($_REQUEST['pg']) && is_numeric($_REQUEST['pg']) ? max(1, intval($_REQUEST['pg'])) : 1);
384
385
        if (isset($_REQUEST['filter'])) {
386
            // legacy implementation, convert single filter to an array
387
            // split string in the form ?filter=handle:value
388
            // @deprecated
389
            // This should be removed in Symphony 4.0.0
390
            if (!is_array($_REQUEST['filter'])) {
391
                list($field_handle, $filter_value) = explode(':', $_REQUEST['filter'], 2);
392
                $filters[$field_handle] = rawurldecode($filter_value);
393
            } else {
394
                $filters = $_REQUEST['filter'];
395
            }
396
397
            foreach ($filters as $handle => $value) {
398
                // Handle multiple values through filtering. RE: #2290
399
                if ((is_array($value) && empty($value)) || trim($value) == '') {
400
                    continue;
401
                }
402
403
                if (!is_array($value)) {
404
                    $filter_type = Datasource::determineFilterType($value);
405
                    $value = Datasource::splitFilter($filter_type, $value);
406
                } else {
407
                    $filter_type = Datasource::FILTER_OR;
408
                }
409
410
                // Handle date meta data #2003
411
                $handle = Symphony::Database()->cleanValue($handle);
412
                if (in_array($handle, array('system:creation-date', 'system:modification-date'))) {
413
                    $date_joins = '';
414
                    $date_where = '';
415
                    $date = new FieldDate();
416
                    $date->buildDSRetrievalSQL($value, $date_joins, $date_where, ($filter_type == Datasource::FILTER_AND ? true : false));
417
418
                    // Replace the date field where with the `creation_date` or `modification_date`.
419
                    $date_where = preg_replace('/`t\d+`.date/', ($field_id !== 'system:modification-date') ? '`e`.creation_date_gmt' : '`e`.modification_date_gmt', $date_where);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $field_id does not seem to be defined for all execution paths leading up to this point.
Loading history...
420
                    $where .= $date_where;
421
                } else {
422
                    // Handle normal fields
423
                    $field_id = FieldManager::fetchFieldIDFromElementName(
424
                        $handle,
425
                        $section->get('id')
0 ignored issues
show
Bug introduced by
$section->get('id') of type string|array is incompatible with the type integer expected by parameter $section_id of FieldManager::fetchFieldIDFromElementName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

425
                        /** @scrutinizer ignore-type */ $section->get('id')
Loading history...
426
                    );
427
428
                    $field = FieldManager::fetch($field_id);
0 ignored issues
show
Bug introduced by
It seems like $field_id can also be of type false; however, parameter $id of FieldManager::fetch() does only seem to accept integer|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

428
                    $field = FieldManager::fetch(/** @scrutinizer ignore-type */ $field_id);
Loading history...
429
                    if ($field instanceof Field) {
430
                        $field->buildDSRetrievalSQL($value, $joins, $where, ($filter_type == Datasource::FILTER_AND ? true : false));
431
432
                        $value = implode(',', $value);
433
                        $encoded_value = rawurlencode($value);
434
                        $filter_querystring .= sprintf("filter[%s]=%s&amp;", $handle, $encoded_value);
435
436
                        // Some fields require that prepopulation be done via ID. RE: #2331
437
                        if (!is_numeric($value) && method_exists($field, 'fetchIDfromValue')) {
438
                            $encoded_value = $field->fetchIDfromValue($value);
439
                        }
440
                        $prepopulate_querystring .= sprintf("prepopulate[%d]=%s&amp;", $field_id, $encoded_value);
441
                    } else {
442
                        unset($filters[$handle]);
443
                    }
444
                    unset($field);
445
                }
446
            }
447
448
            $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
449
            $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
450
        }
451
452
        Sortable::initialize($this, $entries, $sort, $order, array(
453
            'current-section' => $section,
454
            'filters' => ($filter_querystring ? "&amp;" . $filter_querystring : ''),
455
            'unsort' => isset($_REQUEST['unsort'])
456
        ));
457
458
        $this->Form->setAttribute('action', Administration::instance()->getCurrentPageURL(). '?pg=' . $current_page.($filter_querystring ? "&amp;" . $filter_querystring : ''));
459
460
        // Build filtering interface
461
        $this->createFilteringInterface();
462
463
        $subheading_buttons = array(
464
            Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/'.($prepopulate_querystring ? '?' . $prepopulate_querystring : ''), __('Create a new entry'), 'create button', null, array('accesskey' => 'c'))
465
        );
466
467
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
468
        if (Symphony::Author()->isDeveloper()) {
469
            array_unshift($subheading_buttons, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
470
        }
471
472
        $this->appendSubheading(General::sanitize($section->get('name')), $subheading_buttons);
473
474
        /**
475
         * Allows adjustments to be made to the SQL where and joins statements
476
         * before they are used to fetch the entries for the page
477
         *
478
         * @delegate AdjustPublishFiltering
479
         * @since Symphony 2.3.3
480
         * @param string $context
481
         * '/publish/'
482
         * @param integer $section_id
483
         * An array of the current columns, passed by reference
484
         * @param string $where
485
         * The current where statement, or null if not set
486
         * @param string $joins
487
         */
488
        Symphony::ExtensionManager()->notifyMembers('AdjustPublishFiltering', '/publish/', array('section-id' => $section_id, 'where' => &$where, 'joins' => &$joins));
489
490
        // get visible columns
491
        $visible_columns = $section->fetchVisibleColumns();
492
        // extract the needed schema
493
        $element_names = array_values(array_map(function ($field) {
494
            return $field->get('element_name');
495
        }, $visible_columns));
496
497
        // Check that the filtered query fails that the filter is dropped and an
498
        // error is logged. #841 ^BA
499
        try {
500
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), $where, $joins, true, false, true, $element_names);
501
        } catch (DatabaseException $ex) {
502
            $this->pageAlert(__('An error occurred while retrieving filtered entries. Showing all entries instead.'), Alert::ERROR);
503
            $filter_querystring = null;
504
            Symphony::Log()->pushToLog(sprintf(
505
                    '%s - %s%s%s',
506
                    $section->get('name') . ' Publish Index',
0 ignored issues
show
Bug introduced by
Are you sure $section->get('name') of type string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

506
                    /** @scrutinizer ignore-type */ $section->get('name') . ' Publish Index',
Loading history...
507
                    $ex->getMessage(),
508
                    ($ex->getFile() ? " in file " .  $ex->getFile() : null),
509
                    ($ex->getLine() ? " on line " . $ex->getLine() : null)
510
                ),
511
                E_NOTICE,
512
                true
513
            );
514
            $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), null, null, true, false, true, $element_names);
515
        }
516
517
        // Flag filtering
518
        if (isset($_REQUEST['filter'])) {
519
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d of %d entries (filtered)', array($entries['total-entries'], EntryManager::fetchCount($section_id))) . '</span>', array('class' => 'inactive'));
520
        } else {
521
            $filter_stats = new XMLElement('p', '<span>– ' . __('%d entries', array($entries['total-entries'])) . '</span>', array('class' => 'inactive'));
522
        }
523
        $this->Breadcrumbs->appendChild($filter_stats);
524
525
        // Build table
526
        $columns = array();
527
528
        if (is_array($visible_columns) && !empty($visible_columns)) {
529
            foreach ($visible_columns as $column) {
530
                $columns[] = array(
531
                    'label' => $column->get('label'),
532
                    'sortable' => $column->isSortable(),
533
                    'handle' => $column->get('id'),
534
                    'attrs' => array(
535
                        'id' => 'field-' . $column->get('id'),
536
                        'class' => 'field-' . $column->get('type')
537
                    )
538
                );
539
            }
540
        } else {
541
            $columns[] = array(
542
                'label' => __('ID'),
543
                'sortable' => true,
544
                'handle' => 'id'
545
            );
546
        }
547
548
        $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order, ($filter_querystring) ? "&amp;" . $filter_querystring : '');
549
550
        $child_sections = array();
551
        $associated_sections = $section->fetchChildAssociations(true);
552
553
        if (is_array($associated_sections) && !empty($associated_sections)) {
554
            foreach ($associated_sections as $key => $as) {
555
                $child_sections[$key] = SectionManager::fetch($as['child_section_id']);
556
                $aTableHead[] = array($child_sections[$key]->get('name'), 'col');
557
            }
558
        }
559
560
        /**
561
         * Allows the creation of custom table columns for each entry. Called
562
         * after all the Section Visible columns have been added as well
563
         * as the Section Associations
564
         *
565
         * @delegate AddCustomPublishColumn
566
         * @since Symphony 2.2
567
         * @param string $context
568
         * '/publish/'
569
         * @param array $tableHead
570
         * An array of the current columns, passed by reference
571
         * @param integer $section_id
572
         * The current Section ID
573
         */
574
        Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumn', '/publish/', array('tableHead' => &$aTableHead, 'section_id' => $section->get('id')));
575
576
        // Table Body
577
        $aTableBody = array();
578
579
        if (!is_array($entries['records']) || empty($entries['records'])) {
580
            $aTableBody = array(
581
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
582
            );
583
        } else {
584
            $field_pool = array();
585
586
            if (is_array($visible_columns) && !empty($visible_columns)) {
587
                foreach ($visible_columns as $column) {
588
                    $field_pool[$column->get('id')] = $column;
589
                }
590
            }
591
592
            $link_column = array_reverse($visible_columns);
593
            $link_column = end($link_column);
594
            reset($visible_columns);
595
596
            foreach ($entries['records'] as $entry) {
597
                $tableData = array();
598
599
                // Setup each cell
600
                if (!is_array($visible_columns) || empty($visible_columns)) {
601
                    $tableData[] = Widget::TableData(Widget::Anchor($entry->get('id'), Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'));
602
                } else {
603
                    $link = Widget::Anchor(
604
                        '',
605
                        Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'.($filter_querystring ? '?' . $prepopulate_querystring : ''),
606
                        $entry->get('id'),
607
                        'content'
608
                    );
609
610
                    foreach ($visible_columns as $position => $column) {
611
                        $data = $entry->getData($column->get('id'));
612
                        $field = $field_pool[$column->get('id')];
613
614
                        $value = $field->prepareTableValue($data, ($column == $link_column) ? $link : null, $entry->get('id'));
615
616
                        if (!is_object($value) && (strlen(trim($value)) == 0 || $value == __('None'))) {
617
                            $value = ($position == 0 ? $link->generate() : __('None'));
618
                        }
619
620
                        if ($value == __('None')) {
621
                            $tableData[] = Widget::TableData($value, 'inactive field-' . $column->get('type') . ' field-' . $column->get('id'));
622
                        } else {
623
                            $tableData[] = Widget::TableData($value, 'field-' . $column->get('type') . ' field-' . $column->get('id'));
624
                        }
625
626
                        unset($field);
627
                    }
628
                }
629
630
                if (is_array($child_sections) && !empty($child_sections)) {
631
                    foreach ($child_sections as $key => $as) {
632
                        $field = FieldManager::fetch((int)$associated_sections[$key]['child_section_field_id']);
633
                        $parent_section_field_id = (int)$associated_sections[$key]['parent_section_field_id'];
634
635
                        if (!is_null($parent_section_field_id)) {
636
                            $search_value = $field->fetchAssociatedEntrySearchValue(
637
                                $entry->getData($parent_section_field_id),
638
                                $parent_section_field_id,
639
                                $entry->get('id')
640
                            );
641
                        } else {
642
                            $search_value = $entry->get('id');
643
                        }
644
645
                        if (!is_array($search_value)) {
646
                            $associated_entry_count = $field->fetchAssociatedEntryCount($search_value);
647
648
                            $tableData[] = Widget::TableData(
649
                                Widget::Anchor(
650
                                    sprintf('%d &rarr;', max(0, intval($associated_entry_count))),
651
                                    sprintf(
652
                                        '%s/publish/%s/?filter[%s]=%s',
653
                                        SYMPHONY_URL,
654
                                        $as->get('handle'),
655
                                        $field->get('element_name'),
656
                                        rawurlencode($search_value)
657
                                    ),
658
                                    $entry->get('id'),
659
                                    'content'
660
                                )
661
                            );
662
                        }
663
664
                        unset($field);
665
                    }
666
                }
667
668
                /**
669
                 * Allows Extensions to inject custom table data for each Entry
670
                 * into the Publish Index
671
                 *
672
                 * @delegate AddCustomPublishColumnData
673
                 * @since Symphony 2.2
674
                 * @param string $context
675
                 * '/publish/'
676
                 * @param array $tableData
677
                 *  An array of `Widget::TableData`, passed by reference
678
                 * @param integer $section_id
679
                 *  The current Section ID
680
                 * @param Entry $entry_id
681
                 *  The entry object, please note that this is by error and this will
682
                 *  be removed in Symphony 2.4. The entry object is available in
683
                 *  the 'entry' key as of Symphony 2.3.1.
684
                 * @param Entry $entry
685
                 *  The entry object for this row
686
                 */
687
                Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumnData', '/publish/', array(
688
                    'tableData' => &$tableData,
689
                    'section_id' => $section->get('id'),
690
                    'entry_id' => $entry,
691
                    'entry' => $entry
692
                ));
693
694
                $lastCol = $tableData[count($tableData) - 1];
695
                $lastCol->appendChild(Widget::Label(__('Select Entry %d', array($entry->get('id'))), null, 'accessible', null, array(
696
                    'for' => 'entry-' . $entry->get('id')
697
                )));
698
                $lastCol->appendChild(Widget::Input('items['.$entry->get('id').']', $entry->get('modification_date'), 'checkbox', array(
699
                    'id' => 'entry-' . $entry->get('id')
700
                )));
701
702
                // Add a row to the body array, assigning each cell to the row
703
                $aTableBody[] = Widget::TableRow($tableData, null, 'id-' . $entry->get('id'));
704
            }
705
        }
706
707
        $table = Widget::Table(
708
            Widget::TableHead($aTableHead),
709
            null,
710
            Widget::TableBody($aTableBody),
711
            'selectable',
712
            null,
713
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading', 'data-interactive' => 'data-interactive')
714
        );
715
716
        $this->Form->appendChild($table);
717
718
        $tableActions = new XMLElement('div');
719
        $tableActions->setAttribute('class', 'actions');
720
721
        $options = array(
722
            array(null, false, __('With Selected...')),
723
            array('delete', false, __('Delete'), 'confirm', null, array(
724
                'data-message' => __('Are you sure you want to delete the selected entries?')
725
            ))
726
        );
727
728
        $toggable_fields = $section->fetchToggleableFields();
729
730
        if (is_array($toggable_fields) && !empty($toggable_fields)) {
731
            $index = 2;
732
733
            foreach ($toggable_fields as $field) {
734
                $toggle_states = $field->getToggleStates();
735
736
                if (is_array($toggle_states)) {
737
                    $options[$index] = array('label' => __('Set %s', array($field->get('label'))), 'options' => array());
738
739
                    foreach ($toggle_states as $value => $state) {
740
                        $options[$index]['options'][] = array('toggle-' . $field->get('id') . '-' . $value, false, $state);
741
                    }
742
                }
743
744
                $index++;
745
            }
746
        }
747
748
        /**
749
         * Allows an extension to modify the existing options for this page's
750
         * With Selected menu. If the `$options` parameter is an empty array,
751
         * the 'With Selected' menu will not be rendered.
752
         *
753
         * @delegate AddCustomActions
754
         * @since Symphony 2.3.2
755
         * @param string $context
756
         * '/publish/'
757
         * @param array $options
758
         *  An array of arrays, where each child array represents an option
759
         *  in the With Selected menu. Options should follow the same format
760
         *  expected by `Widget::__SelectBuildOption`. Passed by reference.
761
         */
762
        Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/publish/', array(
763
            'options' => &$options
764
        ));
765
766
        if (!empty($options)) {
767
            $tableActions->appendChild(Widget::Apply($options));
768
            $this->Form->appendChild($tableActions);
769
        }
770
771
        if ($entries['total-pages'] > 1) {
772
            $ul = new XMLElement('ul');
773
            $ul->setAttribute('class', 'page');
774
775
            // First
776
            $li = new XMLElement('li');
777
778
            if ($current_page > 1) {
779
                $li->appendChild(Widget::Anchor(__('First'), Administration::instance()->getCurrentPageURL(). '?pg=1'.($filter_querystring ? "&amp;" . $filter_querystring : '')));
780
            } else {
781
                $li->setValue(__('First'));
782
            }
783
784
            $ul->appendChild($li);
785
786
            // Previous
787
            $li = new XMLElement('li');
788
789
            if ($current_page > 1) {
790
                $li->appendChild(Widget::Anchor(__('&larr; Previous'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page - 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
791
            } else {
792
                $li->setValue(__('&larr; Previous'));
793
            }
794
795
            $ul->appendChild($li);
796
797
            // Summary
798
            $li = new XMLElement('li');
799
800
            $li->setAttribute('title', __('Viewing %1$s - %2$s of %3$s entries', array(
801
                $entries['start'],
802
                ($current_page != $entries['total-pages']) ? $current_page * Symphony::Configuration()->get('pagination_maximum_rows', 'symphony') : $entries['total-entries'],
803
                $entries['total-entries']
804
            )));
805
806
            $pgform = Widget::Form(Administration::instance()->getCurrentPageURL(), 'get', 'paginationform');
807
808
            $pgmax = max($current_page, $entries['total-pages']);
809
            $pgform->appendChild(Widget::Input('pg', null, 'text', array(
810
                'data-active' => __('Go to page …'),
811
                'data-inactive' => __('Page %1$s of %2$s', array((string)$current_page, $pgmax)),
812
                'data-max' => $pgmax
813
            )));
814
815
            $li->appendChild($pgform);
816
            $ul->appendChild($li);
817
818
            // Next
819
            $li = new XMLElement('li');
820
821
            if ($current_page < $entries['total-pages']) {
822
                $li->appendChild(Widget::Anchor(__('Next &rarr;'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page + 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
823
            } else {
824
                $li->setValue(__('Next &rarr;'));
825
            }
826
827
            $ul->appendChild($li);
828
829
            // Last
830
            $li = new XMLElement('li');
831
832
            if ($current_page < $entries['total-pages']) {
833
                $li->appendChild(Widget::Anchor(__('Last'), Administration::instance()->getCurrentPageURL(). '?pg=' . $entries['total-pages'].($filter_querystring ? "&amp;" . $filter_querystring : '')));
834
            } else {
835
                $li->setValue(__('Last'));
836
            }
837
838
            $ul->appendChild($li);
839
840
            $this->Contents->appendChild($ul);
841
        }
842
    }
843
844
    public function __actionIndex()
845
    {
846
        $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
847
848
        if (is_array($checked) && !empty($checked)) {
849
            /**
850
             * Extensions can listen for any custom actions that were added
851
             * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
852
             * delegates.
853
             *
854
             * @delegate CustomActions
855
             * @since Symphony 2.3.2
856
             * @param string $context
857
             *  '/publish/'
858
             * @param array $checked
859
             *  An array of the selected rows. The value is usually the ID of the
860
             *  the associated object.
861
             */
862
            Symphony::ExtensionManager()->notifyMembers('CustomActions', '/publish/', array(
863
                'checked' => $checked
864
            ));
865
866
            switch ($_POST['with-selected']) {
867
                case 'delete':
868
                    /**
869
                     * Prior to deletion of entries. An array of Entry ID's is provided which
870
                     * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
871
                     * in Symphony 2.3.
872
                     *
873
                     * @delegate EntryPreDelete
874
                     * @param string $context
875
                     * '/publish/'
876
                     * @param array $entry_id
877
                     *  An array of Entry ID's passed by reference
878
                     */
879
                    Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
880
881
                    EntryManager::delete($checked);
882
883
                    /**
884
                     * After the deletion of entries, this delegate provides an array of Entry ID's
885
                     * that were deleted.
886
                     *
887
                     * @since Symphony 2.3
888
                     * @delegate EntryPostDelete
889
                     * @param string $context
890
                     * '/publish/'
891
                     * @param array $entry_id
892
                     *  An array of Entry ID's that were deleted.
893
                     */
894
                    Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
895
896
                    redirect(server_safe('REQUEST_URI'));
897
                    break;
898
                default:
899
                    list($option, $field_id, $value) = explode('-', $_POST['with-selected'], 3);
900
901
                    if ($option == 'toggle') {
902
                        $field = FieldManager::fetch($field_id);
0 ignored issues
show
Bug introduced by
$field_id of type string is incompatible with the type integer|array expected by parameter $id of FieldManager::fetch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

902
                        $field = FieldManager::fetch(/** @scrutinizer ignore-type */ $field_id);
Loading history...
903
                        $fields = array($field->get('element_name') => $value);
904
905
                        $section = SectionManager::fetch($field->get('parent_section'));
906
907
                        foreach ($checked as $entry_id) {
908
                            $entry = EntryManager::fetch($entry_id);
909
                            $existing_data = $entry[0]->getData($field_id);
910
                            $entry[0]->setData($field_id, $field->toggleFieldData(is_array($existing_data) ? $existing_data : array(), $value, $entry_id));
911
912
                            /**
913
                             * Just prior to editing of an Entry
914
                             *
915
                             * @delegate EntryPreEdit
916
                             * @param string $context
917
                             * '/publish/edit/'
918
                             * @param Section $section
919
                             * @param Entry $entry
920
                             * @param array $fields
921
                             */
922
                            Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array(
923
                                'section' => $section,
924
                                'entry' => &$entry[0],
925
                                'fields' => $fields
926
                            ));
927
928
                            $entry[0]->commit();
929
930
                            /**
931
                             * Editing an entry. Entry object is provided.
932
                             *
933
                             * @delegate EntryPostEdit
934
                             * @param string $context
935
                             * '/publish/edit/'
936
                             * @param Section $section
937
                             * @param Entry $entry
938
                             * @param array $fields
939
                             */
940
                            Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array(
941
                                'section' => $section,
942
                                'entry' => $entry[0],
943
                                'fields' => $fields
944
                            ));
945
                        }
946
947
                        unset($field);
948
                        redirect(server_safe('REQUEST_URI'));
949
                    }
950
            }
951
        }
952
    }
953
954
    public function __viewNew()
955
    {
956
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
957
            Administration::instance()->throwCustomError(
958
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
959
                __('Unknown Section'),
960
                Page::HTTP_STATUS_NOT_FOUND
961
            );
962
        }
963
964
        $section = SectionManager::fetch($section_id);
965
966
        $this->setPageType('form');
967
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
0 ignored issues
show
Bug introduced by
It seems like $section->get('name') can also be of type array; however, parameter $source of General::sanitize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

967
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize(/** @scrutinizer ignore-type */ $section->get('name')), __('Symphony'))));
Loading history...
968
969
        // Ensure errored entries still maintain any prepopulated values [#2211]
970
        $this->Form->setAttribute('action', $this->Form->getAttribute('action') . $this->getPrepopulateString());
971
        $this->Form->setAttribute('enctype', 'multipart/form-data');
972
973
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
974
        $main_fields = $section->fetchFields(null, 'main');
975
976
        if (!empty($sidebar_fields) && !empty($main_fields)) {
977
            $this->Form->setAttribute('class', 'two columns');
978
        } else {
979
            $this->Form->setAttribute('class', 'columns');
980
        }
981
982
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
983
        if (Symphony::Author()->isDeveloper()) {
984
            $this->appendSubheading(__('Untitled'),
985
                Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button')
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
986
            );
987
        } else {
988
            $this->appendSubheading(__('Untitled'));
989
        }
990
991
        // Build filtered breadcrumb [#1378}
992
        $this->insertBreadcrumbs(array(
993
            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
994
        ));
995
996
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
0 ignored issues
show
Bug introduced by
It seems like Symphony::Configuration(..._upload_size', 'admin') can also be of type array; however, parameter $value of Widget::Input() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

996
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', /** @scrutinizer ignore-type */ Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
Loading history...
997
998
        // If there is post data floating around, due to errors, create an entry object
999
        if (isset($_POST['fields'])) {
1000
            $entry = EntryManager::create();
1001
            $entry->set('section_id', $section_id);
1002
            $entry->setDataFromPost($_POST['fields'], $error, true);
1003
1004
            // Brand new entry, so need to create some various objects
1005
        } else {
1006
            $entry = EntryManager::create();
1007
            $entry->set('section_id', $section_id);
1008
        }
1009
1010
        // Check if there is a field to prepopulate
1011
        if (isset($_REQUEST['prepopulate'])) {
1012
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1013
                $this->Form->prependChild(Widget::Input(
1014
                    "prepopulate[{$field_id}]",
1015
                    rawurlencode($value),
1016
                    'hidden'
1017
                ));
1018
1019
                // The actual pre-populating should only happen if there is not existing fields post data
1020
                // and if the field allows it
1021
                if (!isset($_POST['fields']) && ($field = FieldManager::fetch($field_id)) && $field->canPrePopulate()) {
1022
                    $entry->setData(
1023
                        $field->get('id'),
1024
                        $field->processRawFieldData($value, $error, $message, true)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $error does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $message seems to be never defined.
Loading history...
1025
                    );
1026
                    unset($field);
1027
                }
1028
            }
1029
        }
1030
1031
        $primary = new XMLElement('fieldset');
1032
        $primary->setAttribute('class', 'primary column');
1033
1034
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1035
            $message = __('Fields must be added to this section before an entry can be created.');
1036
1037
            if (Symphony::Author()->isDeveloper()) {
1038
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
0 ignored issues
show
Bug introduced by
Are you sure $section->get('id') of type string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1038
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . /** @scrutinizer ignore-type */ $section->get('id') . '/" accesskey="c">'
Loading history...
1039
                . __('Add fields')
1040
                . '</a>';
1041
            }
1042
1043
            $this->pageAlert($message, Alert::ERROR);
1044
        } else {
1045
            if (is_array($main_fields) && !empty($main_fields)) {
1046
                foreach ($main_fields as $field) {
1047
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1048
                }
1049
1050
                $this->Form->appendChild($primary);
1051
            }
1052
1053
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1054
                $sidebar = new XMLElement('fieldset');
1055
                $sidebar->setAttribute('class', 'secondary column');
1056
1057
                foreach ($sidebar_fields as $field) {
1058
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1059
                }
1060
1061
                $this->Form->appendChild($sidebar);
1062
            }
1063
1064
            $div = new XMLElement('div');
1065
            $div->setAttribute('class', 'actions');
1066
            $div->appendChild(Widget::Input('action[save]', __('Create Entry'), 'submit', array('accesskey' => 's')));
1067
1068
            $this->Form->appendChild($div);
1069
1070
            // Create a Drawer for Associated Sections
1071
            $this->prepareAssociationsDrawer($section);
0 ignored issues
show
Bug introduced by
It seems like $section can also be of type array; however, parameter $section of contentPublish::prepareAssociationsDrawer() does only seem to accept Section, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1071
            $this->prepareAssociationsDrawer(/** @scrutinizer ignore-type */ $section);
Loading history...
1072
        }
1073
    }
1074
1075
    public function __actionNew()
1076
    {
1077
        if (is_array($_POST['action']) && (array_key_exists('save', $_POST['action']) || array_key_exists('done', $_POST['action']))) {
1078
            $section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle']);
1079
1080
            if (!$section = SectionManager::fetch($section_id)) {
1081
                Administration::instance()->throwCustomError(
1082
                    __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1083
                    __('Unknown Section'),
1084
                    Page::HTTP_STATUS_NOT_FOUND
1085
                );
1086
            }
1087
1088
            $entry = EntryManager::create();
1089
            $entry->set('author_id', Symphony::Author()->get('id'));
1090
            $entry->set('section_id', $section_id);
1091
            $entry->set('creation_date', DateTimeObj::get('c'));
1092
            $entry->set('modification_date', DateTimeObj::get('c'));
1093
1094
            $fields = $_POST['fields'];
1095
1096
            // Combine FILES and POST arrays, indexed by their custom field handles
1097
            if (isset($_FILES['fields'])) {
1098
                $filedata = General::processFilePostData($_FILES['fields']);
1099
1100
                foreach ($filedata as $handle => $data) {
1101
                    if (!isset($fields[$handle])) {
1102
                        $fields[$handle] = $data;
1103
                    } elseif (isset($data['error']) && $data['error'] == UPLOAD_ERR_NO_FILE) {
1104
                        $fields[$handle] = null;
1105
                    } else {
1106
                        foreach ($data as $ii => $d) {
1107
                            if (isset($d['error']) && $d['error'] == UPLOAD_ERR_NO_FILE) {
1108
                                $fields[$handle][$ii] = null;
1109
                            } elseif (is_array($d) && !empty($d)) {
1110
                                foreach ($d as $key => $val) {
1111
                                    $fields[$handle][$ii][$key] = $val;
1112
                                }
1113
                            }
1114
                        }
1115
                    }
1116
                }
1117
            }
1118
1119
            // Initial checks to see if the Entry is ok
1120
            if (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
1121
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1122
1123
                // Secondary checks, this will actually process the data and attempt to save
1124
            } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
1125
                foreach ($errors as $field_id => $message) {
1126
                    $this->pageAlert($message, Alert::ERROR);
1127
                }
1128
1129
                // Everything is awesome. Dance.
1130
            } else {
1131
                /**
1132
                 * Just prior to creation of an Entry
1133
                 *
1134
                 * @delegate EntryPreCreate
1135
                 * @param string $context
1136
                 * '/publish/new/'
1137
                 * @param Section $section
1138
                 * @param Entry $entry
1139
                 * @param array $fields
1140
                 */
1141
                Symphony::ExtensionManager()->notifyMembers('EntryPreCreate', '/publish/new/', array('section' => $section, 'entry' => &$entry, 'fields' => &$fields));
1142
1143
                $entry->set('modification_author_id', Symphony::Author()->get('id'));
1144
1145
                // Check to see if the dancing was premature
1146
                if (!$entry->commit()) {
1147
                    $this->pageAlert(null, Alert::ERROR);
1148
                } else {
1149
                    /**
1150
                     * Creation of an Entry. New Entry object is provided.
1151
                     *
1152
                     * @delegate EntryPostCreate
1153
                     * @param string $context
1154
                     * '/publish/new/'
1155
                     * @param Section $section
1156
                     * @param Entry $entry
1157
                     * @param array $fields
1158
                     */
1159
                    Symphony::ExtensionManager()->notifyMembers('EntryPostCreate', '/publish/new/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1160
1161
                    $prepopulate_querystring = $this->getPrepopulateString();
1162
                    redirect(sprintf(
1163
                        '%s/publish/%s/edit/%d/created/%s',
1164
                        SYMPHONY_URL,
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1165
                        $this->_context['section_handle'],
1166
                        $entry->get('id'),
0 ignored issues
show
Bug introduced by
It seems like $entry->get('id') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1166
                        /** @scrutinizer ignore-type */ $entry->get('id'),
Loading history...
1167
                        (!empty($prepopulate_querystring) ? $prepopulate_querystring : null)
1168
                    ));
1169
                }
1170
            }
1171
        }
1172
    }
1173
1174
    public function __viewEdit()
1175
    {
1176
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
1177
            Administration::instance()->throwCustomError(
1178
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1179
                __('Unknown Section'),
1180
                Page::HTTP_STATUS_NOT_FOUND
1181
            );
1182
        }
1183
1184
        $section = SectionManager::fetch($section_id);
1185
        $entry_id = intval($this->_context['entry_id']);
1186
        $base = '/publish/'.$this->_context['section_handle'] . '/';
1187
        $new_link = $base . 'new/';
1188
        $filter_link = $base;
1189
        $canonical_link = $base . 'edit/' . $entry_id . '/';
1190
1191
        EntryManager::setFetchSorting('id', 'DESC');
0 ignored issues
show
Bug introduced by
'id' of type string is incompatible with the type integer expected by parameter $field_id of EntryManager::setFetchSorting(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1191
        EntryManager::setFetchSorting(/** @scrutinizer ignore-type */ 'id', 'DESC');
Loading history...
1192
1193
        $existingEntry = EntryManager::fetch($entry_id);
1194
        if (empty($existingEntry)) {
1195
            Administration::instance()->throwCustomError(
1196
                __('Unknown Entry'),
1197
                __('The Entry, %s, could not be found.', array($entry_id)),
1198
                Page::HTTP_STATUS_NOT_FOUND
1199
            );
1200
        }
1201
        $existingEntry = $existingEntry[0];
1202
1203
        // If there is post data floating around, due to errors, create an entry object
1204
        if (isset($_POST['fields'])) {
1205
            $fields = $_POST['fields'];
1206
1207
            $entry = EntryManager::create();
1208
            $entry->set('id', $entry_id);
1209
            $entry->set('author_id', $existingEntry->get('author_id'));
1210
            $entry->set('modification_author_id', $existingEntry->get('modification_author_id'));
1211
            $entry->set('section_id', $existingEntry->get('section_id'));
1212
            $entry->set('creation_date', $existingEntry->get('creation_date'));
1213
            $entry->set('modification_date', $existingEntry->get('modification_date'));
1214
            $entry->setDataFromPost($fields, $errors, true);
1215
1216
            $timestamp = isset($_POST['action']['timestamp'])
1217
                ? $_POST['action']['timestamp']
1218
                : $entry->get('modification_date');
1219
1220
            // Editing an entry, so need to create some various objects
1221
        } else {
1222
            $entry = $existingEntry;
1223
            $fields = array();
1224
1225
            if (!$section) {
1226
                $section = SectionManager::fetch($entry->get('section_id'));
1227
            }
1228
1229
            $timestamp = $entry->get('modification_date');
1230
        }
1231
1232
        /**
1233
         * Just prior to rendering of an Entry edit form.
1234
         *
1235
         * @delegate EntryPreRender
1236
         * @param string $context
1237
         * '/publish/edit/'
1238
         * @param Section $section
1239
         * @param Entry $entry
1240
         * @param array $fields
1241
         */
1242
        Symphony::ExtensionManager()->notifyMembers('EntryPreRender', '/publish/edit/', array(
1243
            'section' => $section,
1244
            'entry' => &$entry,
1245
            'fields' => $fields
1246
        ));
1247
1248
        // Iterate over the `prepopulate` parameters to build a URL
1249
        // to remember this state for Create New, View all Entries and
1250
        // Breadcrumb links. If `prepopulate` doesn't exist, this will
1251
        // just use the standard pages (ie. no filtering)
1252
        if (isset($_REQUEST['prepopulate'])) {
1253
            $new_link .= $this->getPrepopulateString();
1254
            $filter_link .= $this->getFilterString();
1255
            $canonical_link .= $this->getPrepopulateString();
1256
        }
1257
1258
        if (isset($this->_context['flag'])) {
1259
            // These flags are only relevant if there are no errors
1260
            if (empty($this->_errors)) {
1261
                $time = Widget::Time();
1262
1263
                switch ($this->_context['flag']) {
1264
                    case 'saved':
1265
                        $message = __('Entry updated at %s.', array($time->generate()));
1266
                        break;
1267
                    case 'created':
1268
                        $message = __('Entry created at %s.', array($time->generate()));
1269
                }
1270
1271
                $this->pageAlert(
1272
                    $message
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $message does not seem to be defined for all execution paths leading up to this point.
Loading history...
1273
                    . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1274
                    . __('Create another?')
1275
                    . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
1276
                    . __('View all Entries')
1277
                    . '</a>',
1278
                    Alert::SUCCESS
1279
                );
1280
            }
1281
        }
1282
1283
        // Determine the page title
1284
        $field_id = Symphony::Database()->fetchVar('id', 0, sprintf("
1285
            SELECT `id`
1286
            FROM `tbl_fields`
1287
            WHERE `parent_section` = %d
1288
            ORDER BY `sortorder` LIMIT 1",
1289
            $section->get('id')
0 ignored issues
show
Bug introduced by
It seems like $section->get('id') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1289
            /** @scrutinizer ignore-type */ $section->get('id')
Loading history...
1290
        ));
1291
        if (!is_null($field_id)) {
1292
            $field = FieldManager::fetch($field_id);
0 ignored issues
show
Bug introduced by
$field_id of type string is incompatible with the type integer|array expected by parameter $id of FieldManager::fetch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1292
            $field = FieldManager::fetch(/** @scrutinizer ignore-type */ $field_id);
Loading history...
1293
        }
1294
1295
        if ($field) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $field does not seem to be defined for all execution paths leading up to this point.
Loading history...
1296
            $title = $field->prepareReadableValue($existingEntry->getData($field->get('id')), $entry_id, true);
1297
        } else {
1298
            $title = '';
1299
        }
1300
1301
        if (trim($title) == '') {
1302
            $title = __('Untitled');
1303
        }
1304
1305
        // Check if there is a field to prepopulate
1306
        if (isset($_REQUEST['prepopulate'])) {
1307
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1308
                $this->Form->prependChild(Widget::Input(
1309
                    "prepopulate[{$field_id}]",
1310
                    rawurlencode($value),
1311
                    'hidden'
1312
                ));
1313
            }
1314
        }
1315
1316
        $this->setPageType('form');
1317
        $this->Form->setAttribute('enctype', 'multipart/form-data');
1318
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, General::sanitize($section->get('name')), __('Symphony'))));
0 ignored issues
show
Bug introduced by
It seems like $section->get('name') can also be of type array; however, parameter $source of General::sanitize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1318
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, General::sanitize(/** @scrutinizer ignore-type */ $section->get('name')), __('Symphony'))));
Loading history...
1319
        $this->addElementToHead(new XMLElement('link', null, array(
1320
            'rel' => 'canonical',
1321
            'href' => SYMPHONY_URL . $canonical_link,
1322
        )));
1323
1324
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
1325
        $main_fields = $section->fetchFields(null, 'main');
1326
1327
        if (!empty($sidebar_fields) && !empty($main_fields)) {
1328
            $this->Form->setAttribute('class', 'two columns');
1329
        } else {
1330
            $this->Form->setAttribute('class', 'columns');
1331
        }
1332
1333
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
1334
        if (Symphony::Author()->isDeveloper()) {
1335
            $this->appendSubheading($title, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
1336
        } else {
1337
            $this->appendSubheading($title);
1338
        }
1339
1340
        $this->insertBreadcrumbs(array(
1341
            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
1342
        ));
1343
1344
        $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') can also be of type array; however, parameter $value of Widget::Input() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1344
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', /** @scrutinizer ignore-type */ Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
Loading history...
1345
1346
        $primary = new XMLElement('fieldset');
1347
        $primary->setAttribute('class', 'primary column');
1348
1349
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1350
            $message = __('Fields must be added to this section before an entry can be created.');
1351
1352
            if (Symphony::Author()->isDeveloper()) {
1353
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
0 ignored issues
show
Bug introduced by
Are you sure $section->get('id') of type string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1353
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . /** @scrutinizer ignore-type */ $section->get('id') . '/" accesskey="c">'
Loading history...
1354
                . __('Add fields')
1355
                . '</a>';
1356
            }
1357
1358
            $this->pageAlert($message, Alert::ERROR);
1359
        } else {
1360
            if (is_array($main_fields) && !empty($main_fields)) {
1361
                foreach ($main_fields as $field) {
1362
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1363
                }
1364
1365
                $this->Form->appendChild($primary);
1366
            }
1367
1368
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1369
                $sidebar = new XMLElement('fieldset');
1370
                $sidebar->setAttribute('class', 'secondary column');
1371
1372
                foreach ($sidebar_fields as $field) {
1373
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1374
                }
1375
1376
                $this->Form->appendChild($sidebar);
1377
            }
1378
1379
            $div = new XMLElement('div');
1380
            $div->setAttribute('class', 'actions');
1381
            $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));
1382
1383
            $button = new XMLElement('button', __('Delete'));
1384
            $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?')));
1385
            $div->appendChild($button);
1386
1387
            $div->appendChild(Widget::Input('action[timestamp]', $timestamp, 'hidden'));
1388
            $div->appendChild(Widget::Input('action[ignore-timestamp]', 'yes', 'checkbox', array('class' => 'irrelevant')));
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, parameter $section of contentPublish::prepareAssociationsDrawer() does only seem to accept Section, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1393
            $this->prepareAssociationsDrawer(/** @scrutinizer ignore-type */ $section);
Loading history...
1394
        }
1395
    }
1396
1397
    public function __actionEdit()
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
            $ret = EntryManager::fetch($entry_id);
1403
            if (empty($ret)) {
1404
                Administration::instance()->throwCustomError(
1405
                    __('The Entry, %s, could not be found.', array($entry_id)),
1406
                    __('Unknown Entry'),
1407
                    Page::HTTP_STATUS_NOT_FOUND
1408
                );
1409
            }
1410
1411
            $entry = $ret[0];
1412
1413
            $section = SectionManager::fetch($entry->get('section_id'));
1414
1415
            $post = General::getPostData();
1416
            $fields = $post['fields'];
1417
1418
            $canProceed = $this->validateTimestamp($entry_id, true);
1419
1420
            // Timestamp validation
1421
            if (!$canProceed) {
1422
                $this->addTimestampValidationPageAlert($this->_errors['timestamp'], $entry, 'save');
1423
1424
                // Initial checks to see if the Entry is ok
1425
            } elseif (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
1426
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1427
1428
                // Secondary checks, this will actually process the data and attempt to save
1429
            } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $errors seems to be never defined.
Loading history...
1430
                foreach ($errors as $field_id => $message) {
1431
                    $this->pageAlert($message, Alert::ERROR);
1432
                }
1433
1434
                // Everything is awesome. Dance.
1435
            } else {
1436
                /**
1437
                 * Just prior to editing of an Entry.
1438
                 *
1439
                 * @delegate EntryPreEdit
1440
                 * @param string $context
1441
                 * '/publish/edit/'
1442
                 * @param Section $section
1443
                 * @param Entry $entry
1444
                 * @param array $fields
1445
                 */
1446
                Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array('section' => $section, 'entry' => &$entry, 'fields' => $fields));
1447
1448
                $entry->set('modification_author_id', Symphony::Author()->get('id'));
1449
1450
                // Check to see if the dancing was premature
1451
                if (!$entry->commit()) {
1452
                    $this->pageAlert(null, Alert::ERROR);
1453
                } else {
1454
                    /**
1455
                     * Just after the editing of an Entry
1456
                     *
1457
                     * @delegate EntryPostEdit
1458
                     * @param string $context
1459
                     * '/publish/edit/'
1460
                     * @param Section $section
1461
                     * @param Entry $entry
1462
                     * @param array $fields
1463
                     */
1464
                    Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1465
1466
                    redirect(sprintf(
1467
                        '%s/publish/%s/edit/%d/saved/%s',
1468
                        SYMPHONY_URL,
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1469
                        $this->_context['section_handle'],
1470
                        $entry->get('id'),
1471
                        $this->getPrepopulateString()
1472
                    ));
1473
                }
1474
            }
1475
        } elseif (is_array($_POST['action']) && array_key_exists('delete', $_POST['action']) && is_numeric($entry_id)) {
1476
            /**
1477
             * Prior to deletion of entries. An array of Entry ID's is provided which
1478
             * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
1479
             * in Symphony 2.3.
1480
             *
1481
             * @delegate EntryPreDelete
1482
             * @param string $context
1483
             * '/publish/'
1484
             * @param array $entry_id
1485
             *    An array of Entry ID's passed by reference
1486
             */
1487
            $checked = array($entry_id);
1488
            Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
1489
1490
            $canProceed = $this->validateTimestamp($entry_id);
1491
1492
            if ($canProceed) {
1493
                EntryManager::delete($checked);
1494
1495
                /**
1496
                 * After the deletion of entries, this delegate provides an array of Entry ID's
1497
                 * that were deleted.
1498
                 *
1499
                 * @since Symphony 2.3
1500
                 * @delegate EntryPostDelete
1501
                 * @param string $context
1502
                 * '/publish/'
1503
                 * @param array $entry_id
1504
                 *  An array of Entry ID's that were deleted.
1505
                 */
1506
                Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
1507
1508
                redirect(SYMPHONY_URL . '/publish/'.$this->_context['section_handle'].'/');
1509
            } else {
1510
                $ret = EntryManager::fetch($entry_id);
1511
                if (!empty($ret)) {
1512
                    $entry = $ret[0];
1513
                    $this->addTimestampValidationPageAlert($this->_errors['timestamp'], $entry, 'delete');
1514
                }
1515
            }
1516
        }
1517
    }
1518
1519
    /**
1520
     * Given a Field and Entry object, this function will wrap
1521
     * the Field's displayPublishPanel result with a div that
1522
     * contains some contextual information such as the Field ID,
1523
     * the Field handle and whether it is required or not.
1524
     *
1525
     * @param Field $field
1526
     * @param Entry $entry
1527
     * @return XMLElement
1528
     */
1529
    private function __wrapFieldWithDiv(Field $field, Entry $entry)
1530
    {
1531
        $is_hidden = $this->isFieldHidden($field);
1532
        $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' : '')));
0 ignored issues
show
Bug introduced by
Are you sure $field->get('id') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1532
        $div = new XMLElement('div', null, array('id' => 'field-' . /** @scrutinizer ignore-type */ $field->get('id'), 'class' => 'field field-'.$field->handle().($field->get('required') == 'yes' ? ' required' : '').($is_hidden === true ? ' irrelevant' : '')));
Loading history...
1533
1534
        $field->setAssociationContext($div);
1535
1536
        $field->displayPublishPanel(
1537
            $div, $entry->getData($field->get('id')),
0 ignored issues
show
Bug introduced by
It seems like $field->get('id') can also be of type array; however, parameter $field_id of Entry::getData() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1537
            $div, $entry->getData(/** @scrutinizer ignore-type */ $field->get('id')),
Loading history...
Bug introduced by
It seems like $entry->getData($field->get('id')) can also be of type object; however, parameter $data of Field::displayPublishPanel() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1537
            $div, /** @scrutinizer ignore-type */ $entry->getData($field->get('id')),
Loading history...
1538
            (isset($this->_errors[$field->get('id')]) ? $this->_errors[$field->get('id')] : null),
1539
            null, null, (is_numeric($entry->get('id')) ? $entry->get('id') : null)
0 ignored issues
show
Bug introduced by
It seems like is_numeric($entry->get('...entry->get('id') : null can also be of type array; however, parameter $entry_id of Field::displayPublishPanel() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1539
            null, null, /** @scrutinizer ignore-type */ (is_numeric($entry->get('id')) ? $entry->get('id') : null)
Loading history...
1540
        );
1541
1542
        /**
1543
         * Allows developers modify the field before it is rendered in the publish
1544
         * form. Passes the `Field` object, `Entry` object, the `XMLElement` div and
1545
         * any errors for the entire `Entry`. Only the `$div` element
1546
         * will be altered before appending to the page, the rest are read only.
1547
         *
1548
         * @since Symphony 2.5.0
1549
         * @delegate ModifyFieldPublishWidget
1550
         * @param string $context
1551
         * '/backend/'
1552
         * @param Field $field
1553
         * @param Entry $entry
1554
         * @param array $errors
1555
         * @param Widget $widget
1556
         */
1557
        Symphony::ExtensionManager()->notifyMembers('ModifyFieldPublishWidget', '/backend/', array(
1558
            'field' => $field,
1559
            'entry' => $entry,
1560
            'errors' => $this->_errors,
1561
            'widget' => &$div
1562
        ));
1563
1564
        return $div;
1565
    }
1566
1567
    /**
1568
     * Check whether the given `$field` will be hidden because it's been
1569
     * prepopulated.
1570
     *
1571
     * @param  Field  $field
1572
     * @return boolean
1573
     */
1574
    public function isFieldHidden(Field $field)
1575
    {
1576
        if ($field->get('hide_when_prepopulated') == 'yes') {
1577
            if (isset($_REQUEST['prepopulate'])) {
1578
                foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1579
                    if ($field_id == $field->get('id')) {
1580
                        return true;
1581
                    }
1582
                }
1583
            }
1584
        }
1585
1586
        return false;
1587
    }
1588
1589
    /**
1590
     * Prepare a Drawer to visualize section associations
1591
     *
1592
     * @param  Section $section The current Section object
1593
     * @throws InvalidArgumentException
1594
     * @throws Exception
1595
     */
1596
    private function prepareAssociationsDrawer($section)
1597
    {
1598
        $entry_id = (!is_null($this->_context['entry_id'])) ? $this->_context['entry_id'] : null;
1599
        $show_entries = Symphony::Configuration()->get('association_maximum_rows', 'symphony');
1600
1601
        if (is_null($entry_id) && !isset($_GET['prepopulate']) || is_null($show_entries) || $show_entries == 0) {
1602
            return;
1603
        }
1604
1605
        $parent_associations = SectionManager::fetchParentAssociations($section->get('id'), true);
0 ignored issues
show
Bug introduced by
$section->get('id') of type string|array is incompatible with the type integer expected by parameter $section_id of SectionManager::fetchParentAssociations(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1605
        $parent_associations = SectionManager::fetchParentAssociations(/** @scrutinizer ignore-type */ $section->get('id'), true);
Loading history...
1606
        $child_associations = SectionManager::fetchChildAssociations($section->get('id'), true);
0 ignored issues
show
Bug introduced by
$section->get('id') of type string|array is incompatible with the type integer expected by parameter $section_id of SectionManager::fetchChildAssociations(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1606
        $child_associations = SectionManager::fetchChildAssociations(/** @scrutinizer ignore-type */ $section->get('id'), true);
Loading history...
1607
        $content = null;
1608
        $drawer_position = 'vertical-right';
1609
1610
        /**
1611
         * Prepare Associations Drawer from an Extension
1612
         *
1613
         * @since Symphony 2.3.3
1614
         * @delegate PrepareAssociationsDrawer
1615
         * @param string $context
1616
         * '/publish/'
1617
         * @param integer $entry_id
1618
         *  The entry ID or null
1619
         * @param array $parent_associations
1620
         *  Array of Sections
1621
         * @param array $child_associations
1622
         *  Array of Sections
1623
         * @param string $drawer_position
1624
         *  The position of the Drawer, defaults to `vertical-right`. Available
1625
         *  values of `vertical-left, `vertical-right` and `horizontal`
1626
         */
1627
        Symphony::ExtensionManager()->notifyMembers('PrepareAssociationsDrawer', '/publish/', array(
1628
            'entry_id' => $entry_id,
1629
            'parent_associations' => &$parent_associations,
1630
            'child_associations' => &$child_associations,
1631
            'content' => &$content,
1632
            'drawer-position' => &$drawer_position
1633
        ));
1634
1635
        // If there are no associations, return now.
1636
        if (
1637
            (is_null($parent_associations) || empty($parent_associations))
1638
            &&
1639
            (is_null($child_associations) || empty($child_associations))
1640
        ) {
1641
            return;
1642
        }
1643
1644
        if (!($content instanceof XMLElement)) {
1645
            $content = new XMLElement('div', null, array('class' => 'content'));
1646
            $content->setSelfClosingTag(false);
1647
1648
            // backup global sorting
1649
            $sorting = EntryManager::getFetchSorting();
1650
1651
            // Process Parent Associations
1652
            if (!is_null($parent_associations) && !empty($parent_associations)) {
1653
                $title = new XMLElement('h2', __('Linked to') . ':', array('class' => 'association-title'));
1654
                $content->appendChild($title);
1655
1656
                foreach ($parent_associations as $as) {
1657
                    if (empty($as['parent_section_field_id'])) {
1658
                        continue;
1659
                    }
1660
                    if ($field = FieldManager::fetch($as['parent_section_field_id'])) {
1661
                        // Get the related section
1662
                        $parent_section = SectionManager::fetch($as['parent_section_id']);
1663
1664
                        if (!($parent_section instanceof Section)) {
1665
                            continue;
1666
                        }
1667
1668
                        // set global sorting for associated section
1669
                        EntryManager::setFetchSorting(
1670
                            $parent_section->getSortingField(),
0 ignored issues
show
Bug introduced by
$parent_section->getSortingField() of type string is incompatible with the type integer expected by parameter $field_id of EntryManager::setFetchSorting(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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