setAttrib('class', self::DEFAULT_CLASSES . ' role-form');
        list($this->providedPermissions, $this->providedRestrictions) = static::collectProvidedPrivileges();
    }
    protected function createFilter()
    {
        return Filter::where('name', $this->getIdentifier());
    }
    public function createInsertElements(array $formData = array())
    {
        $this->addElement(
            'text',
            'name',
            [
                'required'      => true,
                'label'         => $this->translate('Role Name'),
                'description'   => $this->translate('The name of the role')
            ]
        );
        $this->addElement(
            'select',
            'parent',
            [
                'label'         => $this->translate('Inherit From'),
                'description'   => $this->translate('Choose a role from which to inherit privileges'),
                'value'         => '',
                'multiOptions'  => array_merge(
                    ['' => $this->translate('None', 'parent role')],
                    $this->collectRoles()
                )
            ]
        );
        $this->addElement(
            'textarea',
            'users',
            [
                'label'         => $this->translate('Users'),
                'description'   => $this->translate('Comma-separated list of users that are assigned to the role')
            ]
        );
        $this->addElement(
            'textarea',
            'groups',
            [
                'label'         => $this->translate('Groups'),
                'description'   => $this->translate('Comma-separated list of groups that are assigned to the role')
            ]
        );
        $this->addElement(
            'checkbox',
            self::WILDCARD_NAME,
            [
                'autosubmit'    => true,
                'label'         => $this->translate('Administrative Access'),
                'description'   => $this->translate('Everything is allowed')
            ]
        );
        $this->addElement(
            'checkbox',
            'unrestricted',
            [
                'autosubmit'        => true,
                'uncheckedValue'    => null,
                'label'             => $this->translate('Unrestricted Access'),
                'description'       => $this->translate('Access to any data is completely unrestricted')
            ]
        );
        $hasAdminPerm = isset($formData[self::WILDCARD_NAME]) && $formData[self::WILDCARD_NAME];
        $isUnrestricted = isset($formData['unrestricted']) && $formData['unrestricted'];
        foreach ($this->providedPermissions as $moduleName => $permissionList) {
            $this->sortPermissions($permissionList);
            $anythingGranted = false;
            $anythingRefused = false;
            $anythingRestricted = false;
            $elements = [$moduleName . '_header'];
            // The actual element is added last
            $elements[] = 'permission_header';
            $this->addElement('note', 'permission_header', [
                'decorators'    => [['Callback', ['callback' => function () {
                    return '
' . $this->translate('Permissions') . '
'
                        . $this->getView()->icon('ok', $this->translate(
                            'Grant access by toggling a switch below'
                        ))
                        . $this->getView()->icon('cancel', $this->translate(
                            'Deny access by toggling a switch below'
                        ));
                }]], ['HtmlTag', ['tag' => 'div']]]
            ]);
            $hasFullPerm = false;
            foreach ($permissionList as $name => $spec) {
                $elementName = $this->filterName($name);
                if (isset($formData[$elementName]) && $formData[$elementName]) {
                    $anythingGranted = true;
                }
                if ($hasFullPerm || $hasAdminPerm) {
                    $elementName .= '_fake';
                }
                $denyCheckbox = null;
                if (! isset($spec['isFullPerm'])
                    && substr($name, 0, strlen(self::DENY_PREFIX)) !== self::DENY_PREFIX
                ) {
                    $denyCheckbox = $this->createElement('checkbox', self::DENY_PREFIX . $name, [
                        'decorators'    => ['ViewHelper']
                    ]);
                    $this->addElement($denyCheckbox);
                    $this->removeFromIteration($denyCheckbox->getName());
                    if (isset($formData[$denyCheckbox->getName()]) && $formData[$denyCheckbox->getName()]) {
                        $anythingRefused = true;
                    }
                }
                $elements[] = $elementName;
                $this->addElement(
                    'checkbox',
                    $elementName,
                    [
                        'ignore'        => $hasFullPerm || $hasAdminPerm,
                        'autosubmit'    => isset($spec['isFullPerm']),
                        'disabled'      => $hasFullPerm || $hasAdminPerm ?: null,
                        'value'         => $hasFullPerm || $hasAdminPerm,
                        'label'         => isset($spec['label'])
                            ? $spec['label']
                            : join('', iterator_to_array(call_user_func(function ($segments) {
                                foreach ($segments as $segment) {
                                    if ($segment[0] === '/') {
                                        // Adds a zero-width char after each slash to help browsers break onto newlines
                                        yield '/';
                                        yield '' . substr($segment, 1) . '';
                                    } else {
                                        yield '' . $segment . '';
                                    }
                                }
                            }, preg_split(
                                '~(/[^/]+)~',
                                $name,
                                -1,
                                PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
                            )))),
                        'description'   => isset($spec['description']) ? $spec['description'] : $name,
                        'decorators'    => array_merge(
                            array_slice(self::$defaultElementDecorators, 0, 3),
                            [['Callback', ['callback' => function () use ($denyCheckbox) {
                                return $denyCheckbox ? $denyCheckbox->render() : '';
                            }]]],
                            array_slice(self::$defaultElementDecorators, 3)
                        )
                    ]
                )
                    ->getElement($elementName)
                    ->getDecorator('Label')
                    ->setOption('escape', false);
                if ($hasFullPerm || $hasAdminPerm) {
                    // Add a hidden element to preserve the configured permission value
                    $this->addElement('hidden', $this->filterName($name));
                }
                if (isset($spec['isFullPerm'])) {
                    $filteredName = $this->filterName($name);
                    $hasFullPerm = isset($formData[$filteredName]) && $formData[$filteredName];
                }
            }
            if (isset($this->providedRestrictions[$moduleName])) {
                $elements[] = 'restriction_header';
                $this->addElement('note', 'restriction_header', [
                    'value'         => '' . $this->translate('Restrictions') . '
',
                    'decorators'    => ['ViewHelper']
                ]);
                foreach ($this->providedRestrictions[$moduleName] as $name => $spec) {
                    $elementName = $this->filterName($name);
                    if (isset($formData[$elementName]) && $formData[$elementName]) {
                        $anythingRestricted = true;
                    }
                    $elements[] = $elementName;
                    $this->addElement(
                        'text',
                        $elementName,
                        [
                            'label'         => isset($spec['label'])
                                ? $spec['label']
                                : join('', iterator_to_array(call_user_func(function ($segments) {
                                    foreach ($segments as $segment) {
                                        if ($segment[0] === '/') {
                                            // Add zero-width char after each slash to help browsers break onto newlines
                                            yield '/';
                                            yield '' . substr($segment, 1) . '';
                                        } else {
                                            yield '' . $segment . '';
                                        }
                                    }
                                }, preg_split(
                                    '~(/[^/]+)~',
                                    $name,
                                    -1,
                                    PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
                                )))),
                            'description'   => $spec['description'],
                            'style'         => $isUnrestricted ? 'text-decoration:line-through;' : '',
                            'readonly'      => $isUnrestricted ?: null
                        ]
                    )
                        ->getElement($elementName)
                        ->getDecorator('Label')
                        ->setOption('escape', false);
                }
            }
            $this->addElement(
                'note',
                $moduleName . '_header',
                [
                    'decorators'    => ['ViewHelper'],
                    'value'         => ''
                        . '' . ($moduleName !== 'application'
                            ? sprintf('%s %s', $moduleName, $this->translate('Module'))
                            :  'Icinga Web 2'
                        ) . ''
                        . ''
                        . ($hasAdminPerm || $anythingGranted ? new Icon('check-circle', ['class' => 'granted']) : '')
                        . ($anythingRefused ? new Icon('times-circle', ['class' => 'refused']) : '')
                        . (! $isUnrestricted && $anythingRestricted
                            ? new Icon('filter', ['class' => 'restricted'])
                            : ''
                        )
                        . ''
                        . '
'
                ]
            );
            $this->addDisplayGroup($elements, $moduleName . '_elements', [
                'decorators'    => [
                    'FormElements',
                    ['Fieldset', [
                        'class'                 => 'collapsible',
                        'data-toggle-element'   => 'h3',
                        'data-visible-height'   => 0
                    ]]
                ]
            ]);
        }
    }
    protected function createDeleteElements(array $formData)
    {
    }
    public function fetchEntry()
    {
        $role = parent::fetchEntry();
        if ($role === false) {
            return false;
        }
        $values = [
            'parent'            => $role->parent,
            'name'              => $role->name,
            'users'             => $role->users,
            'groups'            => $role->groups,
            'unrestricted'      => $role->unrestricted,
            self::WILDCARD_NAME => (bool) preg_match('~(?permissions)
        ];
        if (! empty($role->permissions) || ! empty($role->refusals)) {
            $permissions = StringHelper::trimSplit($role->permissions);
            $refusals = StringHelper::trimSplit($role->refusals);
            list($permissions, $newRefusals) = AdmissionLoader::migrateLegacyPermissions($permissions);
            if (! empty($newRefusals)) {
                array_push($refusals, ...$newRefusals);
            }
            foreach ($this->providedPermissions as $moduleName => $permissionList) {
                foreach ($permissionList as $name => $spec) {
                    if (in_array($name, $permissions, true)) {
                        $values[$this->filterName($name)] = 1;
                    }
                    if (in_array($name, $refusals, true)) {
                        $values[$this->filterName(self::DENY_PREFIX . $name)] = 1;
                    }
                }
            }
        }
        foreach ($this->providedRestrictions as $moduleName => $restrictionList) {
            foreach ($restrictionList as $name => $spec) {
                if (isset($role->$name)) {
                    $values[$this->filterName($name)] = $role->$name;
                }
            }
        }
        return (object) $values;
    }
    public function getValues($suppressArrayNotation = false)
    {
        $values = parent::getValues($suppressArrayNotation);
        foreach ($this->providedRestrictions as $moduleName => $restrictionList) {
            foreach ($restrictionList as $name => $spec) {
                $elementName = $this->filterName($name);
                if (isset($values[$elementName])) {
                    $values[$name] = $values[$elementName];
                    unset($values[$elementName]);
                }
            }
        }
        $permissions = [];
        if (isset($values[self::WILDCARD_NAME]) && $values[self::WILDCARD_NAME]) {
            $permissions[] = '*';
        }
        $refusals = [];
        foreach ($this->providedPermissions as $moduleName => $permissionList) {
            foreach ($permissionList as $name => $spec) {
                $elementName = $this->filterName($name);
                if (isset($values[$elementName]) && $values[$elementName]) {
                    $permissions[] = $name;
                }
                $denyName = $this->filterName(self::DENY_PREFIX . $name);
                if (isset($values[$denyName]) && $values[$denyName]) {
                    $refusals[] = $name;
                }
                unset($values[$elementName], $values[$denyName]);
            }
        }
        unset($values[self::WILDCARD_NAME]);
        $values['refusals'] = join(',', $refusals);
        $values['permissions'] = join(',', $permissions);
        return ConfigForm::transformEmptyValuesToNull($values);
    }
    protected function getInsertMessage($success)
    {
        return $success ? $this->translate('Role created') : $this->translate('Role creation failed');
    }
    protected function getUpdateMessage($success)
    {
        return $success ? $this->translate('Role updated') : $this->translate('Role update failed');
    }
    protected function getDeleteMessage($success)
    {
        return $success ? $this->translate('Role removed') : $this->translate('Role removal failed');
    }
    protected function sortPermissions(&$permissions)
    {
        return uksort($permissions, function ($a, $b) use ($permissions) {
            if (isset($permissions[$a]['isUsagePerm'])) {
                return isset($permissions[$b]['isFullPerm']) ? 1 : -1;
            } elseif (isset($permissions[$b]['isUsagePerm'])) {
                return isset($permissions[$a]['isFullPerm']) ? -1 : 1;
            }
            $aParts = explode('/', $a);
            $bParts = explode('/', $b);
            do {
                $a = array_shift($aParts);
                $b = array_shift($bParts);
            } while ($a === $b);
            return strnatcmp($a, $b);
        });
    }
    protected function collectRoles()
    {
        // Function to get all connected children. Used to avoid reference loops
        $getChildren = function ($name, $children = []) use (&$getChildren) {
            foreach ($this->repository->select()->where('parent', $name) as $child) {
                if (isset($children[$child->name])) {
                    // Don't follow already established loops here,
                    // the user should be able to solve such in the UI
                    continue;
                }
                $children[$child->name] = true;
                $children = $getChildren($child->name, $children);
            }
            return $children;
        };
        $children = $this->getIdentifier() !== null ? $getChildren($this->getIdentifier()) : [];
        $names = [];
        foreach ($this->repository->select() as $role) {
            if ($role->name !== $this->getIdentifier() && ! isset($children[$role->name])) {
                $names[] = $role->name;
            }
        }
        return array_combine($names, $names);
    }
    public function isValid($formData)
    {
        $valid = parent::isValid($formData);
        if ($valid && ConfigFormEventsHook::runIsValid($this) === false) {
            foreach (ConfigFormEventsHook::getLastErrors() as $msg) {
                $this->error($msg);
            }
            $valid = false;
        }
        return $valid;
    }
    public function onSuccess()
    {
        if (parent::onSuccess() === false) {
            return false;
        }
        if ($this->getIdentifier() && ($newName = $this->getValue('name')) !== $this->getIdentifier()) {
            $this->repository->update(
                $this->getBaseTable(),
                ['parent' => $newName],
                Filter::where('parent', $this->getIdentifier())
            );
        }
        if (ConfigFormEventsHook::runOnSuccess($this) === false) {
            Notification::error($this->translate(
                'Configuration successfully stored. Though, one or more module hooks failed to run.'
                . ' See logs for details'
            ));
        }
    }
    /**
     * Collect permissions and restrictions provided by Icinga Web 2 and modules
     *
     * @return array[$permissions, $restrictions]
     */
    public static function collectProvidedPrivileges()
    {
        $providedPermissions['application'] = [
            'application/announcements' => [
                'description' => t('Allow to manage announcements')
            ],
            'application/log' => [
                'description' => t('Allow to view the application log')
            ],
            'config/*' => [
                'description' => t('Allow full config access')
            ],
            'config/general' => [
                'description' => t('Allow to adjust the general configuration')
            ],
            'config/modules' => [
                'description' => t('Allow to enable/disable and configure modules')
            ],
            'config/resources' => [
                'description' => t('Allow to manage resources')
            ],
            'config/navigation' => [
                'description' => t('Allow to view and adjust shared navigation items')
            ],
            'config/access-control/*' => [
                'description' => t('Allow to fully manage access-control')
            ],
            'config/access-control/users' => [
                'description' => t('Allow to manage user accounts')
            ],
            'config/access-control/groups' => [
                'description' => t('Allow to manage user groups')
            ],
            'config/access-control/roles' => [
                'description' => t('Allow to manage roles')
            ],
            'user/*' => [
                'description' => t('Allow all account related functionalities')
            ],
            'user/password-change' => [
                'description' => t('Allow password changes in the account preferences')
            ],
            'user/application/stacktraces' => [
                'description' => t('Allow to adjust in the preferences whether to show stacktraces')
            ],
            'user/share/navigation' => [
                'description' => t('Allow to share navigation items')
            ]
        ];
        $providedRestrictions['application'] = [
            'application/share/users' => [
                'description'   => t('Restrict which users this role can share items and information with')
            ],
            'application/share/groups' => [
                'description'   => t('Restrict which groups this role can share items and information with')
            ]
        ];
        $mm = Icinga::app()->getModuleManager();
        foreach ($mm->listInstalledModules() as $moduleName) {
            $modulePermission = Manager::MODULE_PERMISSION_NS . $moduleName;
            $providedPermissions[$moduleName][$modulePermission] = [
                'isUsagePerm'   => true,
                'label'         => t('General Module Access'),
                'description'   => sprintf(t('Allow access to module %s'), $moduleName)
            ];
            $module = $mm->getModule($moduleName, false);
            $permissions = $module->getProvidedPermissions();
            $providedPermissions[$moduleName][$moduleName . '/*'] = [
                'isFullPerm'    => true,
                'label'         => t('Full Module Access')
            ];
            foreach ($permissions as $permission) {
                /** @var object $permission */
                $providedPermissions[$moduleName][$permission->name] = [
                    'description'   => $permission->description
                ];
            }
            foreach ($module->getProvidedRestrictions() as $restriction) {
                $providedRestrictions[$moduleName][$restriction->name] = [
                    'description'   => $restriction->description
                ];
            }
        }
        return [$providedPermissions, $providedRestrictions];
    }
}