symphonycms/symphony-2

View on GitHub
symphony/content/content.blueprintssections.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php
/**
 * @package content
 */

/**
 * This page controls the creation and maintenance of Symphony
 * Sections through the Section Index and Section Editor.
 */

class contentBlueprintsSections extends AdministrationPage
{
    /**
     * The Sections page has /action/id/flag/ context.
     * eg. /edit/1/saved/
     *
     * @param array $context
     * @param array $parts
     * @return array
     */
    public function parseContext(array &$context, array $parts)
    {
        // Order is important!
        $params = array_fill_keys(array('action', 'id', 'flag'), null);

        if (isset($parts[2])) {
            $extras = preg_split('/\//', $parts[2], -1, PREG_SPLIT_NO_EMPTY);
            list($params['action'], $params['id'], $params['flag']) = $extras;
            $params['id'] = (int)$params['id'];
        }

        $context = array_filter($params);
    }

    public function build(array $context = [])
    {
        if (isset($context[1])) {
            $section_id = $context[1];
            $context['associations'] = array(
                'parent' => SectionManager::fetchParentAssociations($section_id),
                'child' => SectionManager::fetchChildAssociations($section_id)
            );
        }

        return parent::build($context);
    }

    public function __viewIndex()
    {
        $this->setPageType('table');
        $this->setTitle(__('%1$s &ndash; %2$s', array(__('Sections'), __('Symphony'))));
        $this->appendSubheading(__('Sections'), Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/', __('Create a section'), 'create button', null, array('accesskey' => 'c')));

        $sections = (new SectionManager)->select()->sort('sortorder')->execute()->rows();

        $aTableHead = array(
            array(__('Name'), 'col'),
            array(__('Entries'), 'col'),
            array(__('Navigation Group'), 'col')
        );

        $aTableBody = array();

        if (!is_array($sections) || empty($sections)) {
            $aTableBody = array(
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
            );
        } else {
            foreach ($sections as $s) {
                $entry_count = (new EntryManager)
                    ->select()
                    ->count()
                    ->section($s->get('id'))
                    ->execute()
                    ->integer(0);

                // Setup each cell
                $td1 = Widget::TableData(Widget::Anchor(General::sanitize($s->get('name')), Administration::instance()->getCurrentPageURL() . 'edit/' . $s->get('id') .'/', null, 'content'));
                $td1->appendChild(Widget::Label(__('Select Section %s', array(General::sanitize($s->get('name')))), null, 'accessible', null, array(
                    'for' => 'section-' . $s->get('id')
                )));
                $td1->appendChild(Widget::Input('items['.$s->get('id').']', $s->get('modification_date'), 'checkbox', array(
                    'id' => 'section-' . $s->get('id')
                )));

                $td2 = Widget::TableData(Widget::Anchor((string)$entry_count, SYMPHONY_URL . '/publish/' . $s->get('handle') . '/'));
                $td3 = Widget::TableData(General::sanitize($s->get('navigation_group')));

                // Create row
                $tr = Widget::TableRow(array($td1, $td2, $td3));

                if ($s->get('hidden') === 'yes') {
                    $tr->setAttribute('class', 'inactive');
                }

                $aTableBody[] = $tr;
            }
        }

        $table = Widget::Table(
            Widget::TableHead($aTableHead),
            null,
            Widget::TableBody($aTableBody),
            'orderable selectable',
            null,
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading', 'data-interactive' => 'data-interactive')
        );

        $this->Form->appendChild($table);

        $version = new XMLElement('p', 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'), array(
            'id' => 'version'
        ));

        $this->Form->appendChild($version);

        $tableActions = new XMLElement('div');
        $tableActions->setAttribute('class', 'actions');

        $options = array(
            array(null, false, __('With Selected...')),
            array('delete', false, __('Delete'), 'confirm', null, array(
                'data-message' => __('Are you sure you want to delete the selected sections?')
            )),
            array('delete-entries', false, __('Delete Entries'), 'confirm', null, array(
                'data-message' => __('Are you sure you want to delete all entries in the selected sections?')
            ))
        );

        if (is_array($sections) && !empty($sections)) {
            $index = 3;
            $options[$index] = array('label' => __('Set navigation group'), 'options' => array());

            $groups = array();

            foreach ($sections as $s) {
                if (in_array($s->get('navigation_group'), $groups)) {
                    continue;
                }

                $groups[] = $s->get('navigation_group');

                $value = 'set-navigation-group-' . urlencode($s->get('navigation_group'));
                $options[$index]['options'][] = array($value, false, General::sanitize($s->get('navigation_group')));
            }
        }

        /**
         * Allows an extension to modify the existing options for this page's
         * With Selected menu. If the `$options` parameter is an empty array,
         * the 'With Selected' menu will not be rendered.
         *
         * @delegate AddCustomActions
         * @since Symphony 2.3.2
         * @param string $context
         * '/blueprints/sections/'
         * @param array $options
         *  An array of arrays, where each child array represents an option
         *  in the With Selected menu. Options should follow the same format
         *  expected by `Widget::__SelectBuildOption`. Passed by reference.
         */
        Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/blueprints/sections/', array(
            'options' => &$options
        ));

        if (!empty($options)) {
            $tableActions->appendChild(Widget::Apply($options));
            $this->Form->appendChild($tableActions);
        }
    }

    public function __viewNew()
    {
        $this->setPageType('form');
        $this->setTitle(__('%1$s &ndash; %2$s', array(__('Sections'), __('Symphony'))));
        $this->appendSubheading(__('Untitled'));
        $this->insertBreadcrumbs(array(
            Widget::Anchor(__('Sections'), SYMPHONY_URL . '/blueprints/sections/'),
        ));

        $types = array();

        $fields = (isset($_POST['fields']) && is_array($_POST['fields'])) ? $_POST['fields'] : array();
        $meta = (isset($_POST['meta']) && is_array($_POST['meta'])) ? $_POST['meta'] : array('name'=>null);

        $formHasErrors = (is_array($this->_errors) && !empty($this->_errors));

        if ($formHasErrors) {
            $this->pageAlert(
                __('An error occurred while processing this form. See below for details.'),
                Alert::ERROR
            );
        }

        $showEmptyTemplate = (is_array($fields) && !empty($fields) ? false : true);

        if (!$showEmptyTemplate) {
            ksort($fields);
        }

        // Set navigation group, if not already set
        if (!isset($meta['navigation_group'])) {
            $meta['navigation_group'] = (isset($this->_navigation[0]['name']) ? $this->_navigation[0]['name'] : __('Content'));
        }

        $fieldset = new XMLElement('fieldset');
        $fieldset->setAttribute('class', 'settings');
        $fieldset->appendChild(new XMLElement('legend', __('Essentials')));

        $namediv = new XMLElement('div', null, array('class' => 'column'));

        $label = Widget::Label(__('Name'));
        $label->appendChild(Widget::Input('meta[name]', (isset($meta['name']) ? General::sanitize($meta['name']) : null)));

        if (isset($this->_errors['name'])) {
            $namediv->appendChild(Widget::Error($label, $this->_errors['name']));
        } else {
            $namediv->appendChild($label);
        }

        $fieldset->appendChild($namediv);

        $div = new XMLElement('div', null, array('class' => 'two columns'));

        $handlediv = new XMLElement('div', null, array('class' => 'column'));

        $label = Widget::Label(__('Handle'));
        $label->appendChild(Widget::Input('meta[handle]', (isset($meta['handle']) ? General::sanitize($meta['handle']) : null)));

        if (isset($this->_errors['handle'])) {
            $handlediv->appendChild(Widget::Error($label, $this->_errors['handle']));
        } else {
            $handlediv->appendChild($label);
        }

        $div->appendChild($handlediv);

        $navgroupdiv = new XMLElement('div', null, array('class' => 'column'));

        $sections = (new SectionManager)->select()->sort('sortorder')->execute()->rows();
        $label = Widget::Label(__('Navigation Group'));
        $label->appendChild(Widget::Input('meta[navigation_group]', (isset($meta['navigation_group']) ? General::sanitize($meta['navigation_group']) : null)));

        if (isset($this->_errors['navigation_group'])) {
            $navgroupdiv->appendChild(Widget::Error($label, $this->_errors['navigation_group']));
        } else {
            $navgroupdiv->appendChild($label);
        }

        if (is_array($sections) && !empty($sections)) {
            $ul = new XMLElement('ul', null, array('class' => 'tags singular', 'data-interactive' => 'data-interactive'));
            $groups = array();

            foreach ($sections as $s) {
                if (in_array($s->get('navigation_group'), $groups)) {
                    continue;
                }

                $ul->appendChild(new XMLElement('li', General::sanitize($s->get('navigation_group'))));
                $groups[] = $s->get('navigation_group');
            }

            $navgroupdiv->appendChild($ul);
        }

        $div->appendChild($navgroupdiv);
        $fieldset->appendChild($div);
        $this->Form->appendChild($fieldset);

        $this->addSectionOptions($meta);

        $fieldset = new XMLElement('fieldset');
        $fieldset->setAttribute('class', 'settings');

        $legend = new XMLElement('legend', __('Fields'));
        $legend->setAttribute('id', 'fields-legend');
        $fieldset->appendChild($legend);

        $div = new XMLElement('div', null, array('class' => 'frame', 'id' => 'fields-duplicator'));

        $ol = new XMLElement('ol');
        $ol->setAttribute('data-add', __('Add field'));
        $ol->setAttribute('data-remove', __('Remove field'));

        if (!$showEmptyTemplate) {
            foreach ($fields as $position => $data) {
                if ($input = FieldManager::create($data['type'])) {
                    $input->setArray($data);

                    $wrapper = new XMLElement('li');

                    $input->set('sortorder', $position);
                    $input->displaySettingsPanel($wrapper, (isset($this->_errors[$position]) ? $this->_errors[$position] : null));
                    $ol->appendChild($wrapper);
                }
            }
        }

        foreach (FieldManager::listAll() as $type) {
            if ($type = FieldManager::create($type)) {
                $types[] = $type;
            }
        }

        uasort($types, function (Field $a, Field $b) {
            return strnatcasecmp($a->name(), $b->name());
        });

        foreach ($types as $type) {
            $defaults = array();

            $type->findDefaults($defaults);
            $type->setArray($defaults);

            $wrapper = new XMLElement('li');
            $wrapper->setAttribute('class', 'template field-' . $type->handle() . ($type->mustBeUnique() ? ' unique' : null));
            $wrapper->setAttribute('data-type', $type->handle());

            $type->set('sortorder', '-1');
            $type->displaySettingsPanel($wrapper);

            $ol->appendChild($wrapper);
        }

        $div->appendChild($ol);
        $fieldset->appendChild($div);

        $this->Form->appendChild($fieldset);

        $div = new XMLElement('div');
        $div->setAttribute('class', 'actions');
        $div->appendChild(Widget::Input('action[save]', __('Create Section'), 'submit', array('accesskey' => 's')));

        $this->Form->appendChild($div);
    }

    public function __viewEdit()
    {
        $section_id = $this->_context['id'];
        $section = (new SectionManager)->select()->section($section_id)->execute()->next();

        if (!$section) {
            Administration::instance()->throwCustomError(
                __('The Section, %s, could not be found.', array($section_id)),
                __('Unknown Section'),
                Page::HTTP_STATUS_NOT_FOUND
            );
        }

        $meta = $section->get();
        $section_id = $meta['id'];
        $types = array();
        $canonical_link = '/blueprints/sections/edit/' . $section_id . '/';

        $formHasErrors = (is_array($this->_errors) && !empty($this->_errors));

        if ($formHasErrors) {
            $this->pageAlert(
                __('An error occurred while processing this form. See below for details.'),
                Alert::ERROR
            );

            // These alerts are only valid if the form doesn't have errors
        } elseif (isset($this->_context['flag'])) {
            $time = Widget::Time();

            switch ($this->_context['flag']) {
                case 'saved':
                    $message = __('Section updated at %s.', array($time->generate()));
                    break;
                case 'created':
                    $message = __('Section created at %s.', array($time->generate()));
            }

            $this->pageAlert(
                $message
                . ' <a href="' . SYMPHONY_URL . '/blueprints/sections/new/" accesskey="c">'
                . __('Create another?')
                . '</a> <a href="' . SYMPHONY_URL . '/blueprints/sections/" accesskey="a">'
                . __('View all Sections')
                . '</a>',
                Alert::SUCCESS
            );
        }

        if (isset($_POST['fields'])) {
            $fields = array();
            if (is_array($_POST['fields']) && !empty($_POST['fields'])) {
                foreach ($_POST['fields'] as $position => $data) {
                    if ($fields[$position] = FieldManager::create($data['type'])) {
                        $fields[$position]->setArray($data);
                        $fields[$position]->set('sortorder', $position);
                    }
                }
            }
            $timestamp = isset($_POST['action']['timestamp'])
                ? $_POST['action']['timestamp']
                : $section->get('modification_date');
        } else {
            $fields = (new FieldManager)->select()->section($section_id)->execute()->rows();
            $fields = array_values($fields);
            $timestamp = $section->get('modification_date');
        }

        if (isset($_POST['meta'])) {
            $meta = $_POST['meta'];
            if ($meta['name'] == '') {
                $meta['name'] = $section->get('name');
            }
        }

        $this->setPageType('form');
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array(General::sanitize($meta['name']), __('Sections'), __('Symphony'))));
        $this->addElementToHead(new XMLElement('link', null, array(
            'rel' => 'canonical',
            'href' => SYMPHONY_URL . $canonical_link,
        )));
        $this->appendSubheading(General::sanitize($meta['name']),
            Widget::Anchor(__('View Entries'), SYMPHONY_URL . '/publish/' . $section->get('handle') . '/', __('View Section Entries'), 'button')
        );
        $this->insertBreadcrumbs(array(
            Widget::Anchor(__('Sections'), SYMPHONY_URL . '/blueprints/sections/'),
        ));

        $fieldset = new XMLElement('fieldset');
        $fieldset->setAttribute('class', 'settings');
        $fieldset->appendChild(new XMLElement('legend', __('Essentials')));

        $namediv = new XMLElement('div', null, array('class' => 'column'));

        $label = Widget::Label(__('Name'));
        $label->appendChild(Widget::Input('meta[name]', (isset($meta['name']) ? General::sanitize($meta['name']) : null)));

        if (isset($this->_errors['name'])) {
            $namediv->appendChild(Widget::Error($label, $this->_errors['name']));
        } else {
            $namediv->appendChild($label);
        }

        $fieldset->appendChild($namediv);

        $div = new XMLElement('div', null, array('class' => 'two columns'));

        $handlediv = new XMLElement('div', null, array('class' => 'column'));

        $label = Widget::Label(__('Handle'));
        $label->appendChild(Widget::Input('meta[handle]', (isset($meta['handle']) ? General::sanitize($meta['handle']) : null)));

        if (isset($this->_errors['handle'])) {
            $handlediv->appendChild(Widget::Error($label, $this->_errors['handle']));
        } else {
            $handlediv->appendChild($label);
        }

        $div->appendChild($handlediv);

        $navgroupdiv = new XMLElement('div', null, array('class' => 'column'));

        $sections = (new SectionManager)->select()->sort('sortorder')->execute()->rows();
        $label = Widget::Label(__('Navigation Group'));
        $label->appendChild(Widget::Input('meta[navigation_group]', General::sanitize($meta['navigation_group'])));

        if (isset($this->_errors['navigation_group'])) {
            $navgroupdiv->appendChild(Widget::Error($label, $this->_errors['navigation_group']));
        } else {
            $navgroupdiv->appendChild($label);
        }

        if (is_array($sections) && !empty($sections)) {
            $ul = new XMLElement('ul', null, array('class' => 'tags singular', 'data-interactive' => 'data-interactive'));
            $groups = array();

            foreach ($sections as $s) {
                if (in_array($s->get('navigation_group'), $groups)) {
                    continue;
                }

                $ul->appendChild(new XMLElement('li', General::sanitize($s->get('navigation_group'))));
                $groups[] = $s->get('navigation_group');
            }

            $navgroupdiv->appendChild($ul);
        }

        $div->appendChild($navgroupdiv);
        $fieldset->appendChild($div);
        $this->Form->appendChild($fieldset);

        $this->addSectionOptions($meta);

        $fieldset = new XMLElement('fieldset');
        $fieldset->setAttribute('class', 'settings');

        $legend = new XMLElement('legend', __('Fields'));
        $legend->setAttribute('id', 'fields-legend');
        $fieldset->appendChild($legend);

        $div = new XMLElement('div', null, array('class' => 'frame', 'id' => 'fields-duplicator'));

        $ol = new XMLElement('ol');
        $ol->setAttribute('data-add', __('Add field'));
        $ol->setAttribute('data-remove', __('Remove field'));

        if (is_array($fields) && !empty($fields)) {
            foreach ($fields as $position => $field) {
                $wrapper = new XMLElement('li', null, array('class' => 'field-' . $field->handle() . ($field->mustBeUnique() ? ' unique' : null)));
                $wrapper->setAttribute('data-type', $field->handle());

                $field->set('sortorder', $position);
                $field->displaySettingsPanel($wrapper, (isset($this->_errors[$position]) ? $this->_errors[$position] : null));
                $ol->appendChild($wrapper);
            }
        }

        foreach (FieldManager::listAll() as $type) {
            if ($type = FieldManager::create($type)) {
                array_push($types, $type);
            }
        }

        uasort($types, function (Field $a, Field $b) {
            return strnatcasecmp($a->name(), $b->name());
        });

        foreach ($types as $type) {
            $defaults = array();

            $type->findDefaults($defaults);
            $type->setArray($defaults);

            $wrapper = new XMLElement('li');

            $wrapper->setAttribute('class', 'template field-' . $type->handle() . ($type->mustBeUnique() ? ' unique' : null));
            $wrapper->setAttribute('data-type', $type->handle());

            $type->set('sortorder', '-1');
            $type->displaySettingsPanel($wrapper);

            $ol->appendChild($wrapper);
        }

        $div->appendChild($ol);
        $fieldset->appendChild($div);

        $this->Form->appendChild($fieldset);

        $div = new XMLElement('div');
        $div->setAttribute('class', 'actions');
        $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));

        $button = new XMLElement('button', __('Delete'));
        $button->setAttributeArray(array('name' => 'action[delete]', 'class' => 'button confirm delete', 'title' => __('Delete this section'), 'type' => 'submit', 'accesskey' => 'd', 'data-message' => __('Are you sure you want to delete this section?')));
        $div->appendChild($button);

        $div->appendChild(Widget::Input('action[timestamp]', $timestamp, 'hidden'));
        $div->appendChild(Widget::Input('action[ignore-timestamp]', 'yes', 'checkbox', array('class' => 'irrelevant')));

        $this->Form->appendChild($div);
    }

    public function __actionIndex()
    {
        $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;

        if (is_array($checked) && !empty($checked)) {
            /**
             * Extensions can listen for any custom actions that were added
             * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
             * delegates.
             *
             * @delegate CustomActions
             * @since Symphony 2.3.2
             * @param string $context
             *  '/blueprints/sections/'
             * @param array $checked
             *  An array of the selected rows. The value is usually the ID of the
             *  the associated object.
             */
            Symphony::ExtensionManager()->notifyMembers('CustomActions', '/blueprints/sections/', array(
                'checked' => $checked
            ));

            if ($_POST['with-selected'] == 'delete') {
                /**
                 * Just prior to calling the Section Manager's delete function
                 *
                 * @delegate SectionPreDelete
                 * @since Symphony 2.2
                 * @param string $context
                 * '/blueprints/sections/'
                 * @param array $section_ids
                 *  An array of Section ID's passed by reference
                 */
                Symphony::ExtensionManager()->notifyMembers('SectionPreDelete', '/blueprints/sections/', array('section_ids' => &$checked));

                foreach ($checked as $section_id) {
                    SectionManager::delete($section_id);
                }

                redirect(SYMPHONY_URL . '/blueprints/sections/');
            } elseif ($_POST['with-selected'] == 'delete-entries') {
                foreach ($checked as $section_id) {
                    $entries = (new EntryManager)
                        ->select()
                        ->section($section_id)
                        ->disableDefaultSort()
                        ->execute()
                        ->rows();

                    $entry_ids = array();

                    foreach ($entries as $entry) {
                        $entry_ids[] = $entry['id'];
                    }

                    /**
                     * Prior to deletion of entries.
                     *
                     * @delegate Delete
                     * @param string $context
                     * '/publish/'
                     * @param array $entry_id
                     *  An array of Entry ID's that are about to be deleted, passed by reference
                     */
                    Symphony::ExtensionManager()->notifyMembers('Delete', '/publish/', array('entry_id' => &$entry_ids));

                    EntryManager::delete($entry_ids, $section_id);
                }

                redirect(SYMPHONY_URL . '/blueprints/sections/');
            } elseif (preg_match('/^set-navigation-group-/', $_POST['with-selected'])) {
                $navigation_group = preg_replace('/^set-navigation-group-/', null, $_POST['with-selected']);

                foreach ($checked as $section_id) {
                    SectionManager::edit($section_id, array(
                        'navigation_group' => urldecode($navigation_group),
                        'modification_author_id' => Symphony::Author()->get('id'),
                    ));
                }

                redirect(SYMPHONY_URL . '/blueprints/sections/');
            }
        }
    }

    public function __actionNew()
    {
        if (is_array($_POST['action']) && array_key_exists('save', $_POST['action'])) {
            $canProceed = true;
            $edit = ($this->_context['action'] === "edit");
            $this->_errors = [];

            $fields = isset($_POST['fields']) ? $_POST['fields'] : array();
            $meta = $_POST['meta'];

            if ($edit) {
                $section_id = $this->_context['id'];
                $existing_section = (new SectionManager)->select()->section($section_id)->execute()->next();
                $canProceed = $this->validateTimestamp($section_id, true);
                if (!$canProceed) {
                    $this->addTimestampValidationPageAlert($this->_errors['timestamp'], $existing_section, 'save');
                }
            }

            // Check handle to ensure it is unique
            $meta['handle'] = $_POST['meta']['handle'] = Lang::createHandle((isset($meta['handle']) && !empty($meta['handle']))
                ? $meta['handle']
                : $meta['name']);

            // Check to ensure all the required section fields are filled
            if (!isset($meta['name']) || strlen(trim($meta['name'])) == 0) {
                $this->_errors['name'] = __('This is a required field.');
                $canProceed = false;

            // Check name label starts with a letter
            } elseif (!preg_match('/^\p{L}/u', $meta['name'])) {
                $this->_errors['name'] = __('The name of the section must begin with a letter.');
                $canProceed = false;

            // Check for duplicate section handle during edit
            } elseif ($edit) {
                $s = SectionManager::fetchIDFromHandle(Lang::createHandle($meta['handle']));

                if (
                    $meta['handle'] !== $existing_section->get('handle')
                    && $s !== 0 && $s !== $section_id
                ) {
                    $this->_errors['handle'] = __('A Section with the handle %s already exists', array('<code>' . $meta['handle'] . '</code>'));
                    $canProceed = false;
                }

            // Existing section during creation
            } elseif (SectionManager::fetchIDFromHandle(Lang::createHandle($meta['handle'])) !== 0) {
                $this->_errors['handle'] = __('A Section with the handle %s already exists', array('<code>' . $meta['handle'] . '</code>'));
                $canProceed = false;
            }

            // Check to ensure all the required section fields are filled
            if (!isset($meta['navigation_group']) || strlen(trim($meta['navigation_group'])) == 0) {
                $this->_errors['navigation_group'] = __('This is a required field.');
                $canProceed = false;
            }

            // Basic custom field checking
            if (is_array($fields) && !empty($fields)) {
                // Check for duplicate CF names
                if ($canProceed) {
                    $name_list = [];

                    foreach ($fields as $position => $data) {
                        $data['element_name'] = trim($data['element_name']);
                        // Create element name if needed
                        if ($data['element_name'] === '') {
                            $data['element_name'] = $fields[$position]['element_name'] = $_POST['fields'][$position]['element_name'] = Lang::createHandle($data['label'], 255, '-', false, true, ['@^[\d-]+@i' => '']);
                        }

                        // Check unique element name
                        if (in_array($data['element_name'], $name_list)) {
                            $this->_errors[$position]['element_name'] = __('A field with this handle already exists. All handle must be unique.');
                            $canProceed = false;
                            break;
                        // Check label starts with a letter
                        } elseif (!preg_match('/^\p{L}/u', $data['label'])) {
                            $this->_errors[$position]['label'] = __('The label of the field must begin with a letter.');
                            $canProceed = false;
                            break;
                        }

                        $name_list[] = $data['element_name'];
                    }
                }

                if ($canProceed) {
                    $unique = array();

                    foreach ($fields as $position => $data) {
                        $field = FieldManager::create($data['type']);
                        $field->setFromPOST($data);

                        if (isset($existing_section)) {
                            $field->set('parent_section', $existing_section->get('id'));
                        }

                        if ($field->mustBeUnique() && !in_array($field->get('type'), $unique)) {
                            $unique[] = $field->get('type');
                        } elseif ($field->mustBeUnique() && in_array($field->get('type'), $unique)) {
                            // Warning. cannot have 2 of this field!
                            $canProceed = false;
                            $this->_errors[$position] = array('label' => __('There is already a field of type %s. There can only be one per section.', array('<code>' . $field->handle() . '</code>')));
                        }

                        $errors = array();

                        if (Field::__OK__ != $field->checkFields($errors, false) && !empty($errors)) {
                            $this->_errors[$position] = $errors;
                            $canProceed = false;
                        }
                    }
                }
            }

            if ($canProceed) {
                // If we are creating a new Section
                if (!$edit) {
                    $meta['sortorder'] = SectionManager::fetchNextSortOrder();

                    /**
                     * Just prior to saving the Section settings. Use with caution as
                     * there is no additional processing to ensure that Field's or Section's
                     * are unique.
                     *
                     * @delegate SectionPreCreate
                     * @since Symphony 2.2
                     * @param string $context
                     * '/blueprints/sections/'
                     * @param array $meta
                     *  The section's settings, passed by reference
                     * @param array $fields
                     *  An associative array of the fields that will be saved to this
                     *  section with the key being the position in the Section Editor
                     *  and the value being a Field object, passed by reference
                     */
                    Symphony::ExtensionManager()->notifyMembers('SectionPreCreate', '/blueprints/sections/', array('meta' => &$meta, 'fields' => &$fields));

                    $meta['author_id'] = Symphony::Author()->get('id');
                    $meta['modification_author_id'] = $meta['author_id'];

                    if (!$section_id = SectionManager::add($meta)) {
                        $this->pageAlert(__('An unknown database occurred while attempting to create the section.'), Alert::ERROR);
                    }

                    // We are editing a Section
                } else {

                    /**
                     * Just prior to updating the Section settings. Use with caution as
                     * there is no additional processing to ensure that Field's or Section's
                     * are unique.
                     *
                     * @delegate SectionPreEdit
                     * @since Symphony 2.2
                     * @param string $context
                     * '/blueprints/sections/'
                     * @param integer $section_id
                     *  The Section ID that is about to be edited.
                     * @param array $meta
                     *  The section's settings, passed by reference
                     * @param array $fields
                     *  An associative array of the fields that will be saved to this
                     *  section with the key being the position in the Section Editor
                     *  and the value being a Field object, passed by reference
                     */
                    Symphony::ExtensionManager()->notifyMembers('SectionPreEdit', '/blueprints/sections/', array('section_id' => $section_id, 'meta' => &$meta, 'fields' => &$fields));

                    $meta['modification_author_id'] = Symphony::Author()->get('id');

                    if (!SectionManager::edit($section_id, $meta)) {
                        $canProceed = false;
                        $this->pageAlert(__('An unknown database occurred while attempting to create the section.'), Alert::ERROR);
                    }
                }

                if ($section_id && $canProceed) {
                    if ($edit) {
                        // Delete missing CF's
                        $id_list = array();

                        if (is_array($fields) && !empty($fields)) {
                            foreach ($fields as $position => $data) {
                                if (isset($data['id'])) {
                                    $id_list[] = (int)$data['id'];
                                }
                            }
                        }

                        $missing_cfs = Symphony::Database()
                            ->select(['id'])
                            ->from('tbl_fields')
                            ->usePlaceholders()
                            ->where(['parent_section' => $section_id]);

                        if (!empty($id_list)) {
                            $missing_cfs->where(['id' => ['not in' => $id_list]]);
                        }

                        $missing_cfs = $missing_cfs->execute()->column('id');

                        if (is_array($missing_cfs) && !empty($missing_cfs)) {
                            foreach ($missing_cfs as $id) {
                                FieldManager::delete($id);
                            }
                        }
                    }

                    // Save each custom field
                    if (is_array($fields) && !empty($fields)) {
                        foreach ($fields as $position => $data) {
                            $field = FieldManager::create($data['type']);
                            $field->setFromPOST($data);
                            $field->set('sortorder', (string)$position);
                            $field->set('parent_section', $section_id);

                            // It is possible that the "existing" field has been deleted
                            // so we are dealing with an invalid id.
                            // First make sure the field table still exists and that
                            // there is a field with the corresponding id in it
                            $newField = !((boolean)$field->get('id') && $field->tableExists() && $field->exists());
                            // If the field has not been found, erase the id from $_POST
                            if ($newField) {
                                $field->set('id', false);
                            }

                            // Save data
                            $field->commit();
                            // Get the new id
                            $field_id = $field->get('id');

                            if ($field_id) {
                                if ($newField) {
                                    /**
                                     * After creation of a Field.
                                     *
                                     * @delegate FieldPostCreate
                                     * @param string $context
                                     * '/blueprints/sections/'
                                     * @param Field $field
                                     *  The Field object, passed by reference
                                     * @param array $data
                                     *  The settings for ths `$field`, passed by reference
                                     */
                                    Symphony::ExtensionManager()->notifyMembers('FieldPostCreate', '/blueprints/sections/', array('field' => &$field, 'data' => &$data));
                                } else {
                                    /**
                                     * After editing of a Field.
                                     *
                                     * @delegate FieldPostEdit
                                     * @param string $context
                                     * '/blueprints/sections/'
                                     * @param Field $field
                                     *  The Field object, passed by reference
                                     * @param array $data
                                     *  The settings for ths `$field`, passed by reference
                                     */
                                    Symphony::ExtensionManager()->notifyMembers('FieldPostEdit', '/blueprints/sections/', array('field' => &$field, 'data' => &$data));
                                }
                            }
                        }
                    }

                    if (!$edit) {
                        /**
                         * After the Section has been created, and all the Field's have been
                         * created for this section, but just before the redirect
                         *
                         * @delegate SectionPostCreate
                         * @since Symphony 2.2
                         * @param string $context
                         * '/blueprints/sections/'
                         * @param integer $section_id
                         *  The newly created Section ID.
                         */
                        Symphony::ExtensionManager()->notifyMembers('SectionPostCreate', '/blueprints/sections/', array('section_id' => $section_id));

                        redirect(SYMPHONY_URL . "/blueprints/sections/edit/$section_id/created/");
                    } else {
                        /**
                         * After the Section has been updated, and all the Field's have been
                         * updated for this section, but just before the redirect
                         *
                         * @delegate SectionPostEdit
                         * @since Symphony 2.2
                         * @param string $context
                         * '/blueprints/sections/'
                         * @param integer $section_id
                         *  The edited Section ID.
                         */
                        Symphony::ExtensionManager()->notifyMembers('SectionPostEdit', '/blueprints/sections/', array('section_id' => $section_id));

                        redirect(SYMPHONY_URL . "/blueprints/sections/edit/$section_id/saved/");
                    }
                }
            }
        }

        if (is_array($_POST['action']) && array_key_exists('delete', $_POST['action'])) {
            $section_id = $this->_context['id'];
            $canProceed = $this->validateTimestamp($section_id);

            if (!$canProceed) {
                $section = (new SectionManager)->select()->section($section_id)->execute()->next();
                $this->addTimestampValidationPageAlert(
                    $this->_errors['timestamp'],
                    $section,
                    'delete'
                );
                return;
            }

            $section_ids = array($section_id);

            /**
             * Just prior to calling the Section Manager's delete function
             *
             * @delegate SectionPreDelete
             * @since Symphony 2.2
             * @param string $context
             * '/blueprints/sections/'
             * @param array $section_ids
             *  An array of Section ID's passed by reference
             */
            Symphony::ExtensionManager()->notifyMembers('SectionPreDelete', '/blueprints/sections/', array('section_ids' => &$section_ids));

            foreach ($section_ids as $section) {
                SectionManager::delete($section);
            }

            redirect(SYMPHONY_URL . '/blueprints/sections/');
        }
    }

    public function __actionEdit()
    {
        return $this->__actionNew();
    }

    public function addSectionOptions(array &$meta = null)
    {
        $fieldset = new XMLElement('fieldset');
        $fieldset->setAttribute('class', 'settings');
        $fieldset->appendChild(new XMLElement('legend', __('Options')));

        $div = new XMLElement('div', null, array('class' => 'two columns'));

        $hidediv = new XMLElement('div', null, array('class' => 'column'));
        $label = Widget::Checkbox('meta[hidden]', $meta['hidden'], __('Hide this section from the back-end menu'));
        $hidediv->appendChild($label);
        $div->appendChild($hidediv);

        $filterdiv = new XMLElement('div', null, array('class' => 'column'));
        $label = Widget::Checkbox('meta[filter]', $meta['filter'], __('Allow filtering of section entries'));
        $filterdiv->appendChild($label);

        $div->appendChild($filterdiv);
        $fieldset->appendChild($div);
        $this->Form->appendChild($fieldset);

        /**
         * Allows extensions to add elements to the header of the Section Editor
         * form. Usually for section settings, this delegate is passed the current
         * `$meta` array and the `$this->_errors` array.
         *
         * @delegate AddSectionElements
         * @since Symphony 2.2
         * @param string $context
         * '/blueprints/sections/'
         * @param XMLElement $form
         *  An XMLElement of the current `$this->Form`, just after the Section
         *  settings have been appended, but before the Fields duplicator
         * @param array $meta
         *  The current $_POST['meta'] array
         * @param array $errors
         *  The current errors array
         */
        Symphony::ExtensionManager()->notifyMembers('AddSectionElements', '/blueprints/sections/', array(
            'form' => &$this->Form,
            'meta' => &$meta,
            'errors' => &$this->_errors
        ));
    }

    /**
     * Given $_POST values, this function will validate the current timestamp
     * and set the proper error messages.
     *
     * @since Symphony 2.7.0
     * @param  int $section_id
     *   The entry id to validate
     * @return boolean
     *   true if the timestamp is valid
     */
    protected function validateTimestamp($section_id, $checkMissing = false)
    {
        if (!isset($_POST['action']['ignore-timestamp'])) {
            if ($checkMissing && !isset($_POST['action']['timestamp'])) {
                if (isset($this->_errors) && is_array($this->_errors)) {
                    $this->_errors['timestamp'] = __('The section could not be saved due to conflicting changes');
                }
                return false;
            } elseif (isset($_POST['action']['timestamp'])) {
                $tv = new TimestampValidator('sections');
                if (!$tv->check($section_id, $_POST['action']['timestamp'])) {
                    if (isset($this->_errors) && is_array($this->_errors)) {
                        $this->_errors['timestamp'] = __('The section could not be saved due to conflicting changes');
                    }
                    return false;
                }
            }
        }
        return true;
    }
}