GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 0e9349...d2bdfd )
by Nicolas
08:54 queued 04:10
created

symphony/content/content.publish.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

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

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

    return array();
}

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

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

Loading history...
1045
        }
1046
    }
1047
1048
    public function __actionNew()
1049
    {
1050
        if (array_key_exists('save', $_POST['action']) || array_key_exists("done", $_POST['action'])) {
1051
            $section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle']);
1052
1053
            if (!$section = SectionManager::fetch($section_id)) {
1054
                Administration::instance()->throwCustomError(
1055
                    __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1056
                    __('Unknown Section'),
1057
                    Page::HTTP_STATUS_NOT_FOUND
1058
                );
1059
            }
1060
1061
            $entry = EntryManager::create();
1062
            $entry->set('author_id', Symphony::Author()->get('id'));
1063
            $entry->set('section_id', $section_id);
1064
            $entry->set('creation_date', DateTimeObj::get('c'));
1065
            $entry->set('modification_date', DateTimeObj::get('c'));
1066
1067
            $fields = $_POST['fields'];
1068
1069
            // Combine FILES and POST arrays, indexed by their custom field handles
1070
            if (isset($_FILES['fields'])) {
1071
                $filedata = General::processFilePostData($_FILES['fields']);
1072
1073
                foreach ($filedata as $handle => $data) {
1074
                    if (!isset($fields[$handle])) {
1075
                        $fields[$handle] = $data;
1076
                    } elseif (isset($data['error']) && $data['error'] == UPLOAD_ERR_NO_FILE) {
1077
                        $fields[$handle] = null;
1078
                    } else {
1079
                        foreach ($data as $ii => $d) {
1080
                            if (isset($d['error']) && $d['error'] == UPLOAD_ERR_NO_FILE) {
1081
                                $fields[$handle][$ii] = null;
1082
                            } elseif (is_array($d) && !empty($d)) {
1083
                                foreach ($d as $key => $val) {
1084
                                    $fields[$handle][$ii][$key] = $val;
1085
                                }
1086
                            }
1087
                        }
1088
                    }
1089
                }
1090
            }
1091
1092
            // Initial checks to see if the Entry is ok
1093
            if (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
1094
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1095
1096
                // Secondary checks, this will actually process the data and attempt to save
1097 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
1098
                foreach ($errors as $field_id => $message) {
1099
                    $this->pageAlert($message, Alert::ERROR);
1100
                }
1101
1102
                // Everything is awesome. Dance.
1103
            } else {
1104
                /**
1105
                 * Just prior to creation of an Entry
1106
                 *
1107
                 * @delegate EntryPreCreate
1108
                 * @param string $context
1109
                 * '/publish/new/'
1110
                 * @param Section $section
1111
                 * @param Entry $entry
1112
                 * @param array $fields
1113
                 */
1114
                Symphony::ExtensionManager()->notifyMembers('EntryPreCreate', '/publish/new/', array('section' => $section, 'entry' => &$entry, 'fields' => &$fields));
1115
1116
                // Check to see if the dancing was premature
1117
                if (!$entry->commit()) {
1118
                    $this->pageAlert(null, Alert::ERROR);
1119
                } else {
1120
                    /**
1121
                     * Creation of an Entry. New Entry object is provided.
1122
                     *
1123
                     * @delegate EntryPostCreate
1124
                     * @param string $context
1125
                     * '/publish/new/'
1126
                     * @param Section $section
1127
                     * @param Entry $entry
1128
                     * @param array $fields
1129
                     */
1130
                    Symphony::ExtensionManager()->notifyMembers('EntryPostCreate', '/publish/new/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1131
1132
                    $prepopulate_querystring = $this->getPrepopulateString();
1133
                    redirect(sprintf(
1134
                        '%s/publish/%s/edit/%d/created/%s',
1135
                        SYMPHONY_URL,
1136
                        $this->_context['section_handle'],
1137
                        $entry->get('id'),
1138
                        (!empty($prepopulate_querystring) ? $prepopulate_querystring : null)
1139
                    ));
1140
                }
1141
            }
1142
        }
1143
    }
1144
1145
    public function __viewEdit()
1146
    {
1147 View Code Duplication
        if (!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
1148
            Administration::instance()->throwCustomError(
1149
                __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
1150
                __('Unknown Section'),
1151
                Page::HTTP_STATUS_NOT_FOUND
1152
            );
1153
        }
1154
1155
        $section = SectionManager::fetch($section_id);
1156
        $entry_id = intval($this->_context['entry_id']);
1157
        $base = '/publish/'.$this->_context['section_handle'] . '/';
1158
        $new_link = $base . 'new/';
1159
        $filter_link = $base;
1160
1161
        EntryManager::setFetchSorting('id', 'DESC');
1162
1163 View Code Duplication
        if (!$existingEntry = EntryManager::fetch($entry_id)) {
1164
            Administration::instance()->throwCustomError(
1165
                __('Unknown Entry'),
1166
                __('The Entry, %s, could not be found.', array($entry_id)),
1167
                Page::HTTP_STATUS_NOT_FOUND
1168
            );
1169
        }
1170
        $existingEntry = $existingEntry[0];
1171
1172
        // If there is post data floating around, due to errors, create an entry object
1173
        if (isset($_POST['fields'])) {
1174
            $fields = $_POST['fields'];
1175
1176
            $entry = EntryManager::create();
1177
            $entry->set('id', $entry_id);
1178
            $entry->set('author_id', $existingEntry->get('author_id'));
1179
            $entry->set('section_id', $existingEntry->get('section_id'));
1180
            $entry->set('creation_date', $existingEntry->get('creation_date'));
1181
            $entry->set('modification_date', $existingEntry->get('modification_date'));
1182
            $entry->setDataFromPost($fields, $errors, true);
1183
1184
            // Editing an entry, so need to create some various objects
1185
        } else {
1186
            $entry = $existingEntry;
1187
            $fields = array();
1188
1189
            if (!$section) {
1190
                $section = SectionManager::fetch($entry->get('section_id'));
1191
            }
1192
        }
1193
1194
        /**
1195
         * Just prior to rendering of an Entry edit form.
1196
         *
1197
         * @delegate EntryPreRender
1198
         * @param string $context
1199
         * '/publish/edit/'
1200
         * @param Section $section
1201
         * @param Entry $entry
1202
         * @param array $fields
1203
         */
1204
        Symphony::ExtensionManager()->notifyMembers('EntryPreRender', '/publish/edit/', array(
1205
            'section' => $section,
1206
            'entry' => &$entry,
1207
            'fields' => $fields
1208
        ));
1209
1210
        // Iterate over the `prepopulate` parameters to build a URL
1211
        // to remember this state for Create New, View all Entries and
1212
        // Breadcrumb links. If `prepopulate` doesn't exist, this will
1213
        // just use the standard pages (ie. no filtering)
1214
        if (isset($_REQUEST['prepopulate'])) {
1215
            $new_link .= $this->getPrepopulateString();
1216
            $filter_link .= $this->getFilterString();
1217
        }
1218
1219
        if (isset($this->_context['flag'])) {
1220
            // These flags are only relevant if there are no errors
1221
            if (empty($this->_errors)) {
1222
                $time = Widget::Time();
1223
1224
                switch ($this->_context['flag']) {
1225
                    case 'saved':
1226
                        $message = __('Entry updated at %s.', array($time->generate()));
1227
                        break;
1228
                    case 'created':
1229
                        $message = __('Entry created at %s.', array($time->generate()));
1230
                }
1231
1232
                $this->pageAlert(
1233
                    $message
1234
                    . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
1235
                    . __('Create another?')
1236
                    . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
1237
                    . __('View all Entries')
1238
                    . '</a>',
1239
                    Alert::SUCCESS
1240
                );
1241
            }
1242
        }
1243
1244
        // Determine the page title
1245
        $field_id = Symphony::Database()->fetchVar('id', 0, sprintf("
1246
            SELECT `id`
1247
            FROM `tbl_fields`
1248
            WHERE `parent_section` = %d
1249
            ORDER BY `sortorder` LIMIT 1",
1250
            $section->get('id')
1251
        ));
1252
        if (!is_null($field_id)) {
1253
            $field = FieldManager::fetch($field_id);
1254
        }
1255
1256
        if ($field) {
1257
            $title = $field->prepareReadableValue($existingEntry->getData($field->get('id')), $entry_id, true);
1258
        } else {
1259
            $title = '';
1260
        }
1261
1262
        if (trim($title) == '') {
1263
            $title = __('Untitled');
1264
        }
1265
1266
        // Check if there is a field to prepopulate
1267
        if (isset($_REQUEST['prepopulate'])) {
1268
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1269
                $this->Form->prependChild(Widget::Input(
1270
                    "prepopulate[{$field_id}]",
1271
                    rawurlencode($value),
1272
                    'hidden'
1273
                ));
1274
            }
1275
        }
1276
1277
        $this->setPageType('form');
1278
        $this->Form->setAttribute('enctype', 'multipart/form-data');
1279
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, $section->get('name'), __('Symphony'))));
1280
1281
        $sidebar_fields = $section->fetchFields(null, 'sidebar');
1282
        $main_fields = $section->fetchFields(null, 'main');
1283
1284 View Code Duplication
        if (!empty($sidebar_fields) && !empty($main_fields)) {
1285
            $this->Form->setAttribute('class', 'two columns');
1286
        } else {
1287
            $this->Form->setAttribute('class', 'columns');
1288
        }
1289
1290
        // Only show the Edit Section button if the Author is a developer. #938 ^BA
1291
        if (Symphony::Author()->isDeveloper()) {
1292
            $this->appendSubheading($title, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
1293
        } else {
1294
            $this->appendSubheading($title);
1295
        }
1296
1297
        $this->insertBreadcrumbs(array(
1298
            Widget::Anchor($section->get('name'), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
1299
        ));
1300
1301
        $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
1302
1303
        $primary = new XMLElement('fieldset');
1304
        $primary->setAttribute('class', 'primary column');
1305
1306
        if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
1307
            $message = __('Fields must be added to this section before an entry can be created.');
1308
1309 View Code Duplication
            if (Symphony::Author()->isDeveloper()) {
1310
                $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
1311
                . __('Add fields')
1312
                . '</a>';
1313
            }
1314
1315
            $this->pageAlert($message, Alert::ERROR);
1316
        } else {
1317 View Code Duplication
            if (is_array($main_fields) && !empty($main_fields)) {
1318
                foreach ($main_fields as $field) {
1319
                    $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
1320
                }
1321
1322
                $this->Form->appendChild($primary);
1323
            }
1324
1325 View Code Duplication
            if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
1326
                $sidebar = new XMLElement('fieldset');
1327
                $sidebar->setAttribute('class', 'secondary column');
1328
1329
                foreach ($sidebar_fields as $field) {
1330
                    $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
1331
                }
1332
1333
                $this->Form->appendChild($sidebar);
1334
            }
1335
1336
            $div = new XMLElement('div');
1337
            $div->setAttribute('class', 'actions');
1338
            $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));
1339
1340
            $button = new XMLElement('button', __('Delete'));
1341
            $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?')));
1342
            $div->appendChild($button);
1343
1344
            $this->Form->appendChild($div);
1345
1346
            // Create a Drawer for Associated Sections
1347
            $this->prepareAssociationsDrawer($section);
1348
        }
1349
    }
1350
1351
    public function __actionEdit()
1352
    {
1353
        $entry_id = intval($this->_context['entry_id']);
1354
1355
        if (@array_key_exists('save', $_POST['action']) || @array_key_exists("done", $_POST['action'])) {
1356 View Code Duplication
            if (!$ret = EntryManager::fetch($entry_id)) {
1357
                Administration::instance()->throwCustomError(
1358
                    __('The Entry, %s, could not be found.', array($entry_id)),
1359
                    __('Unknown Entry'),
1360
                    Page::HTTP_STATUS_NOT_FOUND
1361
                );
1362
            }
1363
1364
            $entry = $ret[0];
1365
1366
            $section = SectionManager::fetch($entry->get('section_id'));
1367
1368
            $post = General::getPostData();
1369
            $fields = $post['fields'];
1370
1371
            // Initial checks to see if the Entry is ok
1372
            if (Entry::__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
1373
                $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
1374
1375
                // Secondary checks, this will actually process the data and attempt to save
1376 View Code Duplication
            } elseif (Entry::__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
1377
                foreach ($errors as $field_id => $message) {
1378
                    $this->pageAlert($message, Alert::ERROR);
1379
                }
1380
1381
                // Everything is awesome. Dance.
1382
            } else {
1383
                /**
1384
                 * Just prior to editing of an Entry.
1385
                 *
1386
                 * @delegate EntryPreEdit
1387
                 * @param string $context
1388
                 * '/publish/edit/'
1389
                 * @param Section $section
1390
                 * @param Entry $entry
1391
                 * @param array $fields
1392
                 */
1393
                Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array('section' => $section, 'entry' => &$entry, 'fields' => $fields));
1394
1395
                // Check to see if the dancing was premature
1396
                if (!$entry->commit()) {
1397
                    $this->pageAlert(null, Alert::ERROR);
1398
                } else {
1399
                    /**
1400
                     * Just after the editing of an Entry
1401
                     *
1402
                     * @delegate EntryPostEdit
1403
                     * @param string $context
1404
                     * '/publish/edit/'
1405
                     * @param Section $section
1406
                     * @param Entry $entry
1407
                     * @param array $fields
1408
                     */
1409
                    Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
1410
1411
                    redirect(sprintf(
1412
                        '%s/publish/%s/edit/%d/saved/%s',
1413
                        SYMPHONY_URL,
1414
                        $this->_context['section_handle'],
1415
                        $entry->get('id'),
1416
                        $this->getPrepopulateString()
1417
                    ));
1418
                }
1419
            }
1420
        } elseif (@array_key_exists('delete', $_POST['action']) && is_numeric($entry_id)) {
1421
            /**
1422
             * Prior to deletion of entries. An array of Entry ID's is provided which
1423
             * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
1424
             * in Symphony 2.3.
1425
             *
1426
             * @delegate EntryPreDelete
1427
             * @param string $context
1428
             * '/publish/'
1429
             * @param array $entry_id
1430
             *    An array of Entry ID's passed by reference
1431
             */
1432
            $checked = array($entry_id);
1433
            Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
1434
1435
            EntryManager::delete($checked);
1436
1437
            /**
1438
             * After the deletion of entries, this delegate provides an array of Entry ID's
1439
             * that were deleted.
1440
             *
1441
             * @since Symphony 2.3
1442
             * @delegate EntryPostDelete
1443
             * @param string $context
1444
             * '/publish/'
1445
             * @param array $entry_id
1446
             *  An array of Entry ID's that were deleted.
1447
             */
1448
            Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
1449
1450
            redirect(SYMPHONY_URL . '/publish/'.$this->_context['section_handle'].'/');
1451
        }
1452
    }
1453
1454
    /**
1455
     * Given a Field and Entry object, this function will wrap
1456
     * the Field's displayPublishPanel result with a div that
1457
     * contains some contextual information such as the Field ID,
1458
     * the Field handle and whether it is required or not.
1459
     *
1460
     * @param Field $field
1461
     * @param Entry $entry
1462
     * @return XMLElement
1463
     */
1464
    private function __wrapFieldWithDiv(Field $field, Entry $entry)
1465
    {
1466
        $is_hidden = $this->isFieldHidden($field);
1467
        $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' : '')));
1468
1469
        $field->setAssociationContext($div);
1470
1471
        $field->displayPublishPanel(
1472
            $div, $entry->getData($field->get('id')),
1473
            (isset($this->_errors[$field->get('id')]) ? $this->_errors[$field->get('id')] : null),
1474
            null, null, (is_numeric($entry->get('id')) ? $entry->get('id') : null)
1475
        );
1476
1477
        /**
1478
         * Allows developers modify the field before it is rendered in the publish
1479
         * form. Passes the `Field` object, `Entry` object, the `XMLElement` div and
1480
         * any errors for the entire `Entry`. Only the `$div` element
1481
         * will be altered before appending to the page, the rest are read only.
1482
         *
1483
         * @since Symphony 2.5.0
1484
         * @delegate ModifyFieldPublishWidget
1485
         * @param string $context
1486
         * '/backend/'
1487
         * @param Field $field
1488
         * @param Entry $entry
1489
         * @param array $errors
1490
         * @param Widget $widget
1491
         */
1492
        Symphony::ExtensionManager()->notifyMembers('ModifyFieldPublishWidget', '/backend/', array(
1493
            'field' => $field,
1494
            'entry' => $entry,
1495
            'errors' => $this->_errors,
1496
            'widget' => &$div
1497
        ));
1498
1499
        return $div;
1500
    }
1501
1502
    /**
1503
     * Check whether the given `$field` will be hidden because it's been
1504
     * prepopulated.
1505
     *
1506
     * @param  Field  $field
1507
     * @return boolean
1508
     */
1509
    public function isFieldHidden(Field $field)
1510
    {
1511
        if ($field->get('hide_when_prepopulated') == 'yes') {
1512
            if (isset($_REQUEST['prepopulate'])) {
1513
                foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1514
                    if ($field_id == $field->get('id')) {
1515
                        return true;
1516
                    }
1517
                }
1518
            }
1519
        }
1520
1521
        return false;
1522
    }
1523
1524
    /**
1525
     * Prepare a Drawer to visualize section associations
1526
     *
1527
     * @param  Section $section The current Section object
1528
     * @throws InvalidArgumentException
1529
     * @throws Exception
1530
     */
1531
    private function prepareAssociationsDrawer($section)
1532
    {
1533
        $entry_id = (!is_null($this->_context['entry_id'])) ? $this->_context['entry_id'] : null;
1534
        $show_entries = Symphony::Configuration()->get('association_maximum_rows', 'symphony');
1535
1536
        if (is_null($entry_id) && !isset($_GET['prepopulate']) || is_null($show_entries) || $show_entries == 0) {
1537
            return;
1538
        }
1539
1540
        $parent_associations = SectionManager::fetchParentAssociations($section->get('id'), true);
1541
        $child_associations = SectionManager::fetchChildAssociations($section->get('id'), true);
1542
        $content = null;
1543
        $drawer_position = 'vertical-right';
1544
1545
        /**
1546
         * Prepare Associations Drawer from an Extension
1547
         *
1548
         * @since Symphony 2.3.3
1549
         * @delegate PrepareAssociationsDrawer
1550
         * @param string $context
1551
         * '/publish/'
1552
         * @param integer $entry_id
1553
         *  The entry ID or null
1554
         * @param array $parent_associations
1555
         *  Array of Sections
1556
         * @param array $child_associations
1557
         *  Array of Sections
1558
         * @param string $drawer_position
1559
         *  The position of the Drawer, defaults to `vertical-right`. Available
1560
         *  values of `vertical-left, `vertical-right` and `horizontal`
1561
         */
1562
        Symphony::ExtensionManager()->notifyMembers('PrepareAssociationsDrawer', '/publish/', array(
1563
            'entry_id' => $entry_id,
1564
            'parent_associations' => &$parent_associations,
1565
            'child_associations' => &$child_associations,
1566
            'content' => &$content,
1567
            'drawer-position' => &$drawer_position
1568
        ));
1569
1570
        // If there are no associations, return now.
1571
        if (
1572
            (is_null($parent_associations) || empty($parent_associations))
1573
            &&
1574
            (is_null($child_associations) || empty($child_associations))
1575
        ) {
1576
            return;
1577
        }
1578
1579
        if (!($content instanceof XMLElement)) {
1580
            $content = new XMLElement('div', null, array('class' => 'content'));
1581
            $content->setSelfClosingTag(false);
1582
1583
            // Process Parent Associations
1584
            if (!is_null($parent_associations) && !empty($parent_associations)) {
1585
                foreach ($parent_associations as $as) {
1586
                    if ($field = FieldManager::fetch($as['parent_section_field_id'])) {
1587
                        if (isset($_GET['prepopulate'])) {
1588
                            $prepopulate_field = key($_GET['prepopulate']);
1589
                        }
1590
1591
                        // get associated entries if entry exists,
1592
                        if ($entry_id) {
1593
                            $entry_ids = $field->findParentRelatedEntries($as['child_section_field_id'], $entry_id);
1594
1595
                            // get prepopulated entry otherwise
1596
                        } elseif (isset($_GET['prepopulate'])) {
1597
                            $entry_ids = array(intval(current($_GET['prepopulate'])));
1598
                        } else {
1599
                            $entry_ids = array();
1600
                        }
1601
1602
                        // Use $schema for perf reasons
1603
                        $schema = array($field->get('element_name'));
1604
                        $where = (!empty($entry_ids)) ? sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids)) : null;
1605
                        $entries = (!empty($entry_ids) || isset($_GET['prepopulate']) && $field->get('id') === $prepopulate_field)
1606
                            ? EntryManager::fetchByPage(1, $as['parent_section_id'], $show_entries, $where, null, false, false, true, $schema)
1607
                            : array();
1608
                        $has_entries = !empty($entries) && $entries['total-entries'] != 0;
1609
1610
                        if ($has_entries) {
1611
                            $element = new XMLElement('section', null, array('class' => 'association parent'));
1612
                            $header = new XMLElement('header');
1613
                            $header->appendChild(new XMLElement('p', __('Linked to %s in', array('<a class="association-section" href="' . SYMPHONY_URL . '/publish/' . $as['handle'] . '/">' . $as['name'] . '</a>'))));
1614
                            $element->appendChild($header);
1615
1616
                            $ul = new XMLElement('ul', null, array(
1617
                                'class' => 'association-links',
1618
                                'data-section-id' => $as['child_section_id'],
1619
                                'data-association-ids' => implode(', ', $entry_ids)
1620
                            ));
1621
1622
                            foreach ($entries['records'] as $e) {
1623
                                // let the field create the mark up
1624
                                $li = $field->prepareAssociationsDrawerXMLElement($e, $as);
1625
                                // add it to the unordered list
1626
                                $ul->appendChild($li);
1627
                            }
1628
1629
                            $element->appendChild($ul);
1630
                            $content->appendChild($element);
1631
                        }
1632
                    }
1633
                }
1634
            }
1635
1636
            // Process Child Associations
1637
            if (!is_null($child_associations) && !empty($child_associations)) {
1638
                foreach ($child_associations as $as) {
1639
                    // Get the related section
1640
                    $child_section = SectionManager::fetch($as['child_section_id']);
1641
1642
                    if (!($child_section instanceof Section)) {
1643
                        continue;
1644
                    }
1645
1646
                    // Get the visible field instance (using the sorting field, this is more flexible than visibleColumns())
1647
                    // Get the link field instance
1648
                    $visible_field   = current($child_section->fetchVisibleColumns());
1649
                    $relation_field  = FieldManager::fetch($as['child_section_field_id']);
1650
1651
                    // Get entries, using $schema for performance reasons.
1652
                    $entry_ids = $relation_field->findRelatedEntries($entry_id, $as['parent_section_field_id']);
1653
                    $schema = $visible_field ? array($visible_field->get('element_name')) : array();
1654
                    $where = sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids));
1655
1656
                    $entries = (!empty($entry_ids)) ? EntryManager::fetchByPage(1, $as['child_section_id'], $show_entries, $where, null, false, false, true, $schema) : array();
1657
                    $has_entries = !empty($entries) && $entries['total-entries'] != 0;
1658
1659
                    // Build the HTML of the relationship
1660
                    $element = new XMLElement('section', null, array('class' => 'association child'));
1661
                    $header = new XMLElement('header');
1662
                    $filter = '?filter[' . $relation_field->get('element_name') . ']=' . $entry_id;
1663
                    $prepopulate = '?prepopulate[' . $as['child_section_field_id'] . ']=' . $entry_id;
1664
1665
                    // Create link with filter or prepopulate
1666
                    $link = SYMPHONY_URL . '/publish/' . $as['handle'] . '/' . $filter;
1667
                    $a = new XMLElement('a', $as['name'], array(
1668
                        'class' => 'association-section',
1669
                        'href' => $link
1670
                    ));
1671
1672
                    // Create new entries
1673
                    $create = new XMLElement('a', __('Create New'), array(
1674
                        'class' => 'button association-new',
1675
                        'href' => SYMPHONY_URL . '/publish/' . $as['handle'] . '/new/' . $prepopulate
1676
                    ));
1677
1678
                    // Display existing entries
1679
                    if ($has_entries) {
1680
                        $header->appendChild(new XMLElement('p', __('Links in %s', array($a->generate()))));
1681
1682
                        $ul = new XMLElement('ul', null, array(
1683
                            'class' => 'association-links',
1684
                            'data-section-id' => $as['child_section_id'],
1685
                            'data-association-ids' => implode(', ', $entry_ids)
1686
                        ));
1687
1688
                        foreach ($entries['records'] as $key => $e) {
1689
                            // let the first visible field create the mark up
1690
                            if ($visible_field) {
1691
                                $li = $visible_field->prepareAssociationsDrawerXMLElement($e, $as, $prepopulate);
1692
                            }
1693
                            // or use the system:id if no visible field exists.
1694
                            else {
1695
                                $li = Field::createAssociationsDrawerXMLElement($e->get('id'), $e, $as, $prepopulate);
1696
                            }
1697
1698
                            // add it to the unordered list
1699
                            $ul->appendChild($li);
1700
                        }
1701
1702
                        $element->appendChild($ul);
1703
1704
                        // If we are only showing 'some' of the entries, then show this on the UI
1705
                        if ($entries['total-entries'] > $show_entries) {
1706
                            $pagination = new XMLElement('li', null, array(
1707
                                'class' => 'association-more',
1708
                                'data-current-page' => '1',
1709
                                'data-total-pages' => ceil($entries['total-entries'] / $show_entries),
1710
                                'data-total-entries' => $entries['total-entries']
1711
                            ));
1712
                            $counts = new XMLElement('a', __('Show more entries'), array(
1713
                                'href' => $link
1714
                            ));
1715
1716
                            $pagination->appendChild($counts);
1717
                            $ul->appendChild($pagination);
1718
                        }
1719
1720
                        // No entries
1721
                    } else {
1722
                        $element->setAttribute('class', 'association child empty');
1723
                        $header->appendChild(new XMLElement('p', __('No links in %s', array($a->generate()))));
1724
                    }
1725
1726
                    $header->appendChild($create);
1727
                    $element->prependChild($header);
1728
                    $content->appendChild($element);
1729
                }
1730
            }
1731
        }
1732
1733
        $drawer = Widget::Drawer('section-associations', __('Show Associations'), $content);
1734
        $this->insertDrawer($drawer, $drawer_position, 'prepend');
1735
    }
1736
1737
    /**
1738
     * If this entry is being prepopulated, this function will return the prepopulated
1739
     * fields and values as a query string.
1740
     *
1741
     * @since Symphony 2.5.2
1742
     * @return string
1743
     */
1744 View Code Duplication
    public function getPrepopulateString()
1745
    {
1746
        $prepopulate_querystring = '';
1747
1748
        if (isset($_REQUEST['prepopulate'])) {
1749
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1750
                $prepopulate_querystring .= sprintf("prepopulate[%s]=%s&", $field_id, rawurldecode($value));
1751
            }
1752
            $prepopulate_querystring = trim($prepopulate_querystring, '&');
1753
        }
1754
1755
        // This is to prevent the value being interpreted as an additional GET
1756
        // parameter. eg. prepopulate[cat]=Minx&June, would come through as:
1757
        // $_GET['cat'] = Minx
1758
        // $_GET['June'] = ''
1759
        $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
1760
1761
        return $prepopulate_querystring ? '?' . $prepopulate_querystring : null;
1762
    }
1763
1764
    /**
1765
     * If the entry is being prepopulated, we may want to filter other views by this entry's
1766
     * value. This function will create that filter query string.
1767
     *
1768
     * @since Symphony 2.5.2
1769
     * @return string
1770
     */
1771 View Code Duplication
    public function getFilterString()
1772
    {
1773
        $filter_querystring = '';
1774
1775
        if (isset($_REQUEST['prepopulate'])) {
1776
            foreach ($_REQUEST['prepopulate'] as $field_id => $value) {
1777
                $handle = FieldManager::fetchHandleFromID($field_id);
1778
                $filter_querystring .= sprintf("filter[%s]=%s&", $handle, rawurldecode($value));
1779
            }
1780
            $filter_querystring = trim($filter_querystring, '&');
1781
        }
1782
1783
        // This is to prevent the value being interpreted as an additional GET
1784
        // parameter. eg. filter[cat]=Minx&June, would come through as:
1785
        // $_GET['cat'] = Minx
1786
        // $_GET['June'] = ''
1787
        $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
1788
1789
        return $filter_querystring ? '?' . $filter_querystring : null;
1790
    }
1791
}
1792