src/Controller/DefaultController.php
protected function &getConfig(): array
    {
        if (!empty($this->aConfig)) {
            return $this->aConfig;
        }

        //  Ensure required constants are set
        $aRequiredConstants = [
            'CONFIG_MODEL_NAME',
            'CONFIG_MODEL_PROVIDER',
        ];
        foreach ($aRequiredConstants as $sConstant) {
            if (empty(constant('static::' . $sConstant))) {
                throw new NailsException(
                    'The constant "static::' . $sConstant . '" must be set in ' . get_called_class()
                );
            }
        }

        //  Build the config array
        $oModel  = static::getModel();
        $aConfig = [
            'MODEL_NAME'             => static::CONFIG_MODEL_NAME,
            'MODEL_PROVIDER'         => static::CONFIG_MODEL_PROVIDER,
            'MODEL_INSTANCE'         => $oModel,
            'CAN_CREATE'             => static::CONFIG_CAN_CREATE,
            'CAN_EDIT'               => static::CONFIG_CAN_EDIT,
            'CAN_VIEW'               => static::CONFIG_CAN_VIEW,
            'CAN_DELETE'             => static::CONFIG_CAN_DELETE,
            'CAN_RESTORE'            => static::CONFIG_CAN_RESTORE && !$oModel->isDestructiveDelete(),
            'PERMISSION'             => static::CONFIG_PERMISSION,
            'TITLE_SINGLE'           => static::getTitleSingle(),
            'TITLE_PLURAL'           => static::getTitlePlural(),
            'SIDEBAR_GROUP'          => static::getSidebarGroup(),
            'SIDEBAR_ICON'           => static::CONFIG_SIDEBAR_ICON,
            'BASE_URL'               => static::getBaseUrl(),
            'SORT_OPTIONS'           => static::CONFIG_SORT_OPTIONS,
            'SORT_DIRECTION'         => static::CONFIG_SORT_DIRECTION,
            'INDEX_FIELDS'           => static::CONFIG_INDEX_FIELDS,
            'INDEX_HEADER_BUTTONS'   => static::CONFIG_INDEX_HEADER_BUTTONS,
            'INDEX_ROW_BUTTONS'      => array_merge(static::$aConfigIndexRowButtons, static::CONFIG_INDEX_ROW_BUTTONS),
            'INDEX_DATA'             => static::CONFIG_INDEX_DATA,
            'INDEX_BOOL_FIELDS'      => static::CONFIG_INDEX_BOOL_FIELDS,
            'INDEX_USER_FIELDS'      => static::CONFIG_INDEX_USER_FIELDS,
            'INDEX_NUMERIC_FIELDS'   => static::CONFIG_INDEX_NUMERIC_FIELDS,
            'INDEX_CENTERED_FIELDS'  => static::CONFIG_INDEX_CENTERED_FIELDS,
            'CREATE_READONLY_FIELDS' => static::CONFIG_CREATE_READONLY_FIELDS,
            'CREATE_IGNORE_FIELDS'   => static::CONFIG_CREATE_IGNORE_FIELDS,
            'EDIT_HEADER_BUTTONS'    => static::CONFIG_EDIT_HEADER_BUTTONS,
            'EDIT_READONLY_FIELDS'   => static::CONFIG_EDIT_READONLY_FIELDS,
            'EDIT_IGNORE_FIELDS'     => static::CONFIG_EDIT_IGNORE_FIELDS,
            'EDIT_DATA'              => static::CONFIG_EDIT_DATA,
            'SORT_DATA'              => static::CONFIG_SORT_DATA,
            'SORT_LABEL'             => static::CONFIG_SORT_LABEL,
            'FIELDSET_ORDER'         => static::CONFIG_EDIT_FIELDSET_ORDER,
            'ENABLE_NOTES'           => static::EDIT_ENABLE_NOTES,
            'FIELDS'                 => $oModel->describeFields(),
        ];

        if (classUses($oModel, Sortable::class)) {
            $aConfig['SORT_OPTIONS']           = array_merge(['Defined Order' => 'order'], $aConfig['SORT_OPTIONS']);
            $aConfig['CREATE_IGNORE_FIELDS'][] = $oModel->getSortableColumn();
            $aConfig['EDIT_IGNORE_FIELDS'][]   = $oModel->getSortableColumn();
        }

        if (classUses($oModel, Nestable::class)) {
            $aConfig['CREATE_IGNORE_FIELDS'][] = $oModel->getBreadcrumbsColumn();
            $aConfig['EDIT_IGNORE_FIELDS'][]   = $oModel->getBreadcrumbsColumn();
        }

        if (classUses($oModel, Localised::class)) {
            $aConfig['CREATE_READONLY_FIELDS'][] = 'locale';
            $aConfig['EDIT_READONLY_FIELDS'][]   = 'locale';
            $aConfig['INDEX_FIELDS']             = array_merge(
                [
                    'Locale' => function ($oRow) {
                        $sFlag = $oRow->locale->getFlagEmoji();
                        return $sFlag ? '<span rel="tipsy" title="' . $oRow->locale->getDisplayLanguage() . '">' . $sFlag . '</span>' : $oRow->locale;
                    },
                ],
                $aConfig['INDEX_FIELDS']
            );
        }

        // --------------------------------------------------------------------------

        if ($aConfig['CAN_CREATE'] && static::userCan('create') && classUses($oModel, Localised::class)) {
            $oItem = $this->getItem([], null, false, false);
            if (!empty($oItem)) {
                $aVersions = [];
                foreach ($oItem->missing_locales as $oLocale) {
                    $aVersions['Create ' . $oLocale->getDisplayLanguage()] = $aConfig['BASE_URL'] . '/create/' . $oItem->id . '/' . $oLocale;
                }
                $aConfig['EDIT_HEADER_BUTTONS'][] = [
                    $aVersions,
                    'Create Version',
                    'btn btn-warning',
                ];
            }
        }

        if ($aConfig['CAN_CREATE'] && static::userCan('create')) {
            $aConfig['INDEX_HEADER_BUTTONS'][] = [
                $aConfig['BASE_URL'] . '/create',
                'Create',
            ];
            $aConfig['EDIT_HEADER_BUTTONS'][]  = [
                $aConfig['BASE_URL'] . '/create',
                'Create',
            ];
        }

        if ($aConfig['CAN_EDIT'] && static::userCan('edit') && classUses($oModel, Sortable::class)) {
            $aConfig['INDEX_HEADER_BUTTONS'][] = [
                $aConfig['BASE_URL'] . '/sort',
                'Set Order',
            ];
        }

        // --------------------------------------------------------------------------

        $bIsLocalised = classUses(static::getModel(), Localised::class);

        /** @var Locale $oLocale */
        $oLocale = Factory::service('Locale');

        $aConfig['INDEX_ROW_BUTTONS'] = array_merge(
            $aConfig['INDEX_ROW_BUTTONS'],
            array_filter([
                [
                    'url'     => '{{url}}',
                    'label'   => lang('action_view'),
                    'class'   => 'btn-default',
                    'attr'    => 'target="_blank"',
                    'enabled' => function ($oItem) {
                        return static::isViewButtonEnabled($oItem);
                    },
                ],

                //  Non-localised buttons
                !$bIsLocalised ? [
                    'url'     => 'edit/{{id}}',
                    'label'   => lang('action_edit'),
                    'class'   => 'btn-primary',
                    'enabled' => function ($oItem) {
                        return static::isEditButtonEnabled($oItem);
                    },
                ] : null,
                !$bIsLocalised ? [
                    'url'     => 'delete/{{id}}',
                    'label'   => lang('action_delete'),
                    'class'   => 'btn-danger confirm',
                    'enabled' => function ($oItem) {
                        return static::isDeleteButtonEnabled($oItem);
                    },
                ] : null,

                //  Localised buttons
                $bIsLocalised ? [
                    'url'     => 'edit/{{id}}/{{locale}}',
                    'label'   => lang('action_edit'),
                    'class'   => 'btn-primary',
                    'enabled' => function ($oItem) {
                        return static::isEditButtonEnabled($oItem);
                    },
                ] : null,
                $bIsLocalised ? [
                    'url'   => function ($oItem) {
                        $aOut = [];
                        foreach ($oItem->missing_locales as $oLocale) {
                            $aOut['Add ' . $oLocale->getDisplayLanguage()] = 'create/{{id}}/' . $oLocale;
                        }
                        return $aOut;
                    },
                    'label' => 'Create Version',
                    'class' => function ($oItem) {
                        if (!static::localisedItemCanBeCreated($oItem)) {
                            return 'btn-warning btn-disabled';
                        }
                        return 'btn-warning';
                    },
                    'attr'  => function ($oItem) {
                        if (!static::localisedItemCanBeCreated($oItem)) {
                            //  @todo (Pablo - 2019-04-17) - Explicitly state why
                            return 'rel="tipsy" title="A new version cannot be created; you may not have permission, ' .
                                'or all supported locales exist already"';
                        }
                        return '';
                    },
                ] : null,
                $bIsLocalised ? [
                    'url'     => 'delete/{{id}}/{{locale}}',
                    'label'   => lang('action_delete'),
                    'class'   => function ($oItem) {
                        if (!static::localisedItemCanBeDeleted($oItem)) {
                            return 'btn-danger btn-disabled';
                        }
                        return 'btn-danger confirm';
                    },
                    'attr'    => function ($oItem) {
                        if (!static::localisedItemCanBeDeleted($oItem)) {
                            //  @todo (Pablo - 2019-04-17) - Explicitly state why
                            return 'rel="tipsy" title="This item cannot be deleted; you may not have permission, ' .
                                'or other locales may exist which need to be deleted first"';
                        }
                        return '';
                    },
                    'enabled' => function ($oItem) use ($oLocale) {
                        return static::isDeleteButtonEnabled($oItem);
                    },
                ] : null,
            ])
        );

        // --------------------------------------------------------------------------

        $this->aConfig        =& $aConfig;
        $this->data['CONFIG'] =& $this->aConfig;
        return $this->aConfig;
    }
protected function &getConfig(): array
    {
        if (!empty($this->aConfig)) {
            return $this->aConfig;
        }

        //  Ensure required constants are set
        $aRequiredConstants = [
            'CONFIG_MODEL_NAME',
            'CONFIG_MODEL_PROVIDER',
        ];
        foreach ($aRequiredConstants as $sConstant) {
            if (empty(constant('static::' . $sConstant))) {
                throw new NailsException(
                    'The constant "static::' . $sConstant . '" must be set in ' . get_called_class()
                );
            }
        }

        //  Build the config array
        $oModel  = static::getModel();
        $aConfig = [
            'MODEL_NAME'             => static::CONFIG_MODEL_NAME,
            'MODEL_PROVIDER'         => static::CONFIG_MODEL_PROVIDER,
            'MODEL_INSTANCE'         => $oModel,
            'CAN_CREATE'             => static::CONFIG_CAN_CREATE,
            'CAN_EDIT'               => static::CONFIG_CAN_EDIT,
            'CAN_VIEW'               => static::CONFIG_CAN_VIEW,
            'CAN_DELETE'             => static::CONFIG_CAN_DELETE,
            'CAN_RESTORE'            => static::CONFIG_CAN_RESTORE && !$oModel->isDestructiveDelete(),
            'PERMISSION'             => static::CONFIG_PERMISSION,
            'TITLE_SINGLE'           => static::getTitleSingle(),
            'TITLE_PLURAL'           => static::getTitlePlural(),
            'SIDEBAR_GROUP'          => static::getSidebarGroup(),
            'SIDEBAR_ICON'           => static::CONFIG_SIDEBAR_ICON,
            'BASE_URL'               => static::getBaseUrl(),
            'SORT_OPTIONS'           => static::CONFIG_SORT_OPTIONS,
            'SORT_DIRECTION'         => static::CONFIG_SORT_DIRECTION,
            'INDEX_FIELDS'           => static::CONFIG_INDEX_FIELDS,
            'INDEX_HEADER_BUTTONS'   => static::CONFIG_INDEX_HEADER_BUTTONS,
            'INDEX_ROW_BUTTONS'      => array_merge(static::$aConfigIndexRowButtons, static::CONFIG_INDEX_ROW_BUTTONS),
            'INDEX_DATA'             => static::CONFIG_INDEX_DATA,
            'INDEX_BOOL_FIELDS'      => static::CONFIG_INDEX_BOOL_FIELDS,
            'INDEX_USER_FIELDS'      => static::CONFIG_INDEX_USER_FIELDS,
            'INDEX_NUMERIC_FIELDS'   => static::CONFIG_INDEX_NUMERIC_FIELDS,
            'INDEX_CENTERED_FIELDS'  => static::CONFIG_INDEX_CENTERED_FIELDS,
            'CREATE_READONLY_FIELDS' => static::CONFIG_CREATE_READONLY_FIELDS,
            'CREATE_IGNORE_FIELDS'   => static::CONFIG_CREATE_IGNORE_FIELDS,
            'EDIT_HEADER_BUTTONS'    => static::CONFIG_EDIT_HEADER_BUTTONS,
            'EDIT_READONLY_FIELDS'   => static::CONFIG_EDIT_READONLY_FIELDS,
            'EDIT_IGNORE_FIELDS'     => static::CONFIG_EDIT_IGNORE_FIELDS,
            'EDIT_DATA'              => static::CONFIG_EDIT_DATA,
            'SORT_DATA'              => static::CONFIG_SORT_DATA,
            'SORT_LABEL'             => static::CONFIG_SORT_LABEL,
            'FIELDSET_ORDER'         => static::CONFIG_EDIT_FIELDSET_ORDER,
            'ENABLE_NOTES'           => static::EDIT_ENABLE_NOTES,
            'FIELDS'                 => $oModel->describeFields(),
        ];

        if (classUses($oModel, Sortable::class)) {
            $aConfig['SORT_OPTIONS']           = array_merge(['Defined Order' => 'order'], $aConfig['SORT_OPTIONS']);
            $aConfig['CREATE_IGNORE_FIELDS'][] = $oModel->getSortableColumn();
            $aConfig['EDIT_IGNORE_FIELDS'][]   = $oModel->getSortableColumn();
        }

        if (classUses($oModel, Nestable::class)) {
            $aConfig['CREATE_IGNORE_FIELDS'][] = $oModel->getBreadcrumbsColumn();
            $aConfig['EDIT_IGNORE_FIELDS'][]   = $oModel->getBreadcrumbsColumn();
        }

        if (classUses($oModel, Localised::class)) {
            $aConfig['CREATE_READONLY_FIELDS'][] = 'locale';
            $aConfig['EDIT_READONLY_FIELDS'][]   = 'locale';
            $aConfig['INDEX_FIELDS']             = array_merge(
                [
                    'Locale' => function ($oRow) {
                        $sFlag = $oRow->locale->getFlagEmoji();
                        return $sFlag ? '<span rel="tipsy" title="' . $oRow->locale->getDisplayLanguage() . '">' . $sFlag . '</span>' : $oRow->locale;
                    },
                ],
                $aConfig['INDEX_FIELDS']
            );
        }

        // --------------------------------------------------------------------------

        if ($aConfig['CAN_CREATE'] && static::userCan('create') && classUses($oModel, Localised::class)) {
            $oItem = $this->getItem([], null, false, false);
            if (!empty($oItem)) {
                $aVersions = [];
                foreach ($oItem->missing_locales as $oLocale) {
                    $aVersions['Create ' . $oLocale->getDisplayLanguage()] = $aConfig['BASE_URL'] . '/create/' . $oItem->id . '/' . $oLocale;
                }
                $aConfig['EDIT_HEADER_BUTTONS'][] = [
                    $aVersions,
                    'Create Version',
                    'btn btn-warning',
                ];
            }
        }

        if ($aConfig['CAN_CREATE'] && static::userCan('create')) {
            $aConfig['INDEX_HEADER_BUTTONS'][] = [
                $aConfig['BASE_URL'] . '/create',
                'Create',
            ];
            $aConfig['EDIT_HEADER_BUTTONS'][]  = [
                $aConfig['BASE_URL'] . '/create',
                'Create',
            ];
        }

        if ($aConfig['CAN_EDIT'] && static::userCan('edit') && classUses($oModel, Sortable::class)) {
            $aConfig['INDEX_HEADER_BUTTONS'][] = [
                $aConfig['BASE_URL'] . '/sort',
                'Set Order',
            ];
        }

        // --------------------------------------------------------------------------

        $bIsLocalised = classUses(static::getModel(), Localised::class);

        /** @var Locale $oLocale */
        $oLocale = Factory::service('Locale');

        $aConfig['INDEX_ROW_BUTTONS'] = array_merge(
            $aConfig['INDEX_ROW_BUTTONS'],
            array_filter([
                [
                    'url'     => '{{url}}',
                    'label'   => lang('action_view'),
                    'class'   => 'btn-default',
                    'attr'    => 'target="_blank"',
                    'enabled' => function ($oItem) {
                        return static::isViewButtonEnabled($oItem);
                    },
                ],

                //  Non-localised buttons
                !$bIsLocalised ? [
                    'url'     => 'edit/{{id}}',
                    'label'   => lang('action_edit'),
                    'class'   => 'btn-primary',
                    'enabled' => function ($oItem) {
                        return static::isEditButtonEnabled($oItem);
                    },
                ] : null,
                !$bIsLocalised ? [
                    'url'     => 'delete/{{id}}',
                    'label'   => lang('action_delete'),
                    'class'   => 'btn-danger confirm',
                    'enabled' => function ($oItem) {
                        return static::isDeleteButtonEnabled($oItem);
                    },
                ] : null,

                //  Localised buttons
                $bIsLocalised ? [
                    'url'     => 'edit/{{id}}/{{locale}}',
                    'label'   => lang('action_edit'),
                    'class'   => 'btn-primary',
                    'enabled' => function ($oItem) {
                        return static::isEditButtonEnabled($oItem);
                    },
                ] : null,
                $bIsLocalised ? [
                    'url'   => function ($oItem) {
                        $aOut = [];
                        foreach ($oItem->missing_locales as $oLocale) {
                            $aOut['Add ' . $oLocale->getDisplayLanguage()] = 'create/{{id}}/' . $oLocale;
                        }
                        return $aOut;
                    },
                    'label' => 'Create Version',
                    'class' => function ($oItem) {
                        if (!static::localisedItemCanBeCreated($oItem)) {
                            return 'btn-warning btn-disabled';
                        }
                        return 'btn-warning';
                    },
                    'attr'  => function ($oItem) {
                        if (!static::localisedItemCanBeCreated($oItem)) {
                            //  @todo (Pablo - 2019-04-17) - Explicitly state why
                            return 'rel="tipsy" title="A new version cannot be created; you may not have permission, ' .
                                'or all supported locales exist already"';
                        }
                        return '';
                    },
                ] : null,
                $bIsLocalised ? [
                    'url'     => 'delete/{{id}}/{{locale}}',
                    'label'   => lang('action_delete'),
                    'class'   => function ($oItem) {
                        if (!static::localisedItemCanBeDeleted($oItem)) {
                            return 'btn-danger btn-disabled';
                        }
                        return 'btn-danger confirm';
                    },
                    'attr'    => function ($oItem) {
                        if (!static::localisedItemCanBeDeleted($oItem)) {
                            //  @todo (Pablo - 2019-04-17) - Explicitly state why
                            return 'rel="tipsy" title="This item cannot be deleted; you may not have permission, ' .
                                'or other locales may exist which need to be deleted first"';
                        }
                        return '';
                    },
                    'enabled' => function ($oItem) use ($oLocale) {
                        return static::isDeleteButtonEnabled($oItem);
                    },
                ] : null,
            ])
        );

        // --------------------------------------------------------------------------

        $this->aConfig        =& $aConfig;
        $this->data['CONFIG'] =& $this->aConfig;
        return $this->aConfig;
    }
Returns the controllers config

@return array
@throws NailsException
@throws FactoryException