From 3e02d5aff85babc3ffbfcf52313f2108e313aa23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:46:43 +0200 Subject: Adding upstream version 2.12.1. Signed-off-by: Daniel Baumann --- .../forms/Config/UserGroup/AddMemberForm.php | 183 ++++++++++ .../Config/UserGroup/DbUserGroupBackendForm.php | 79 +++++ .../Config/UserGroup/LdapUserGroupBackendForm.php | 370 +++++++++++++++++++++ .../Config/UserGroup/UserGroupBackendForm.php | 314 +++++++++++++++++ .../forms/Config/UserGroup/UserGroupForm.php | 158 +++++++++ 5 files changed, 1104 insertions(+) create mode 100644 application/forms/Config/UserGroup/AddMemberForm.php create mode 100644 application/forms/Config/UserGroup/DbUserGroupBackendForm.php create mode 100644 application/forms/Config/UserGroup/LdapUserGroupBackendForm.php create mode 100644 application/forms/Config/UserGroup/UserGroupBackendForm.php create mode 100644 application/forms/Config/UserGroup/UserGroupForm.php (limited to 'application/forms/Config/UserGroup') diff --git a/application/forms/Config/UserGroup/AddMemberForm.php b/application/forms/Config/UserGroup/AddMemberForm.php new file mode 100644 index 0000000..cda9d52 --- /dev/null +++ b/application/forms/Config/UserGroup/AddMemberForm.php @@ -0,0 +1,183 @@ +ds = $ds; + return $this; + } + + /** + * Set the user group backend to use + * + * @param Extensible $backend + * + * @return $this + */ + public function setBackend(Extensible $backend) + { + $this->backend = $backend; + return $this; + } + + /** + * Set the group to add members for + * + * @param string $groupName + * + * @return $this + */ + public function setGroupName($groupName) + { + $this->groupName = $groupName; + return $this; + } + + /** + * Create and add elements to this form + * + * @param array $formData The data sent by the user + */ + public function createElements(array $formData) + { + // TODO(jom): Fetching already existing members to prevent the user from mistakenly creating duplicate + // memberships (no matter whether the data source permits it or not, a member does never need to be + // added more than once) should be kept at backend level (GroupController::fetchUsers) but this does + // not work currently as our ldap protocol stuff is unable to handle our filter implementation.. + $members = $this->backend + ->select() + ->from('group_membership', array('user_name')) + ->where('group_name', $this->groupName) + ->fetchColumn(); + $filter = empty($members) ? Filter::matchAll() : Filter::not(Filter::where('user_name', $members)); + + $users = $this->ds->select()->from('user', array('user_name'))->applyFilter($filter)->fetchColumn(); + if (! empty($users)) { + $this->addElement( + 'multiselect', + 'user_name', + array( + 'multiOptions' => array_combine($users, $users), + 'label' => $this->translate('Backend Users'), + 'description' => $this->translate( + 'Select one or more users (fetched from your user backends) to add as group member' + ), + 'class' => 'grant-permissions' + ) + ); + } + + $this->addElement( + 'textarea', + 'users', + array( + 'required' => empty($users), + 'label' => $this->translate('Users'), + 'description' => $this->translate( + 'Provide one or more usernames separated by comma to add as group member' + ) + ) + ); + + $this->setTitle(sprintf($this->translate('Add members for group %s'), $this->groupName)); + $this->setSubmitLabel($this->translate('Add')); + } + + /** + * Insert the members for the group + * + * @return bool + */ + public function onSuccess() + { + $userNames = $this->getValue('user_name') ?: array(); + if (($users = $this->getValue('users'))) { + $userNames = array_merge($userNames, array_map('trim', explode(',', $users))); + } + + if (empty($userNames)) { + $this->info($this->translate( + 'Please provide at least one username, either by choosing one ' + . 'in the list or by manually typing one in the text box below' + )); + return false; + } + + $single = null; + $userName = null; + foreach ($userNames as $userName) { + try { + $this->backend->insert( + 'group_membership', + array( + 'group_name' => $this->groupName, + 'user_name' => $userName + ) + ); + } catch (NotFoundError $e) { + throw $e; // Trigger 404, the group name is initially accessed as GET parameter + } catch (Exception $e) { + Notification::error(sprintf( + $this->translate('Failed to add "%s" as group member for "%s"'), + $userName, + $this->groupName + )); + $this->error($e->getMessage()); + return false; + } + + $single = $single === null; + } + + if ($single) { + Notification::success(sprintf($this->translate('Group member "%s" added successfully'), $userName)); + } else { + Notification::success($this->translate('Group members added successfully')); + } + + return true; + } +} diff --git a/application/forms/Config/UserGroup/DbUserGroupBackendForm.php b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php new file mode 100644 index 0000000..daea8de --- /dev/null +++ b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php @@ -0,0 +1,79 @@ +setName('form_config_dbusergroupbackend'); + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Backend Name'), + 'description' => $this->translate( + 'The name of this user group backend that is used to differentiate it from others' + ) + ) + ); + + $resourceNames = $this->getDatabaseResourceNames(); + $this->addElement( + 'select', + 'resource', + array( + 'required' => true, + 'label' => $this->translate('Database Connection'), + 'description' => $this->translate('The database connection to use for this backend'), + 'multiOptions' => empty($resourceNames) ? array() : array_combine($resourceNames, $resourceNames) + ) + ); + + $this->addElement( + 'hidden', + 'backend', + array( + 'disabled' => true, // Prevents the element from being submitted, see #7717 + 'value' => 'db' + ) + ); + } + + /** + * Return the names of all configured database resources + * + * @return array + */ + protected function getDatabaseResourceNames() + { + $names = array(); + foreach (ResourceFactory::getResourceConfigs() as $name => $config) { + if (strtolower($config->type) === 'db') { + $names[] = $name; + } + } + + return $names; + } +} diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php new file mode 100644 index 0000000..10c069a --- /dev/null +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -0,0 +1,370 @@ +setName('form_config_ldapusergroupbackend'); + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Backend Name'), + 'description' => $this->translate( + 'The name of this user group backend that is used to differentiate it from others' + ) + ) + ); + + $resourceNames = $this->getLdapResourceNames(); + $this->addElement( + 'select', + 'resource', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('LDAP Connection'), + 'description' => $this->translate('The LDAP connection to use for this backend.'), + 'multiOptions' => array_combine($resourceNames, $resourceNames) + ) + ); + $resource = ResourceFactory::create( + isset($formData['resource']) && in_array($formData['resource'], $resourceNames) + ? $formData['resource'] + : $resourceNames[0] + ); + + $userBackendNames = $this->getLdapUserBackendNames($resource); + if (! empty($userBackendNames)) { + $userBackends = array_combine($userBackendNames, $userBackendNames); + $userBackends['none'] = $this->translate('None', 'usergroupbackend.ldap.user_backend'); + } else { + $userBackends = array('none' => $this->translate('None', 'usergroupbackend.ldap.user_backend')); + } + $this->addElement( + 'select', + 'user_backend', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('User Backend'), + 'description' => $this->translate('The user backend to link with this user group backend.'), + 'multiOptions' => $userBackends + ) + ); + + $groupBackend = new LdapUserGroupBackend($resource); + if ($formData['type'] === 'ldap') { + $defaults = $groupBackend->getOpenLdapDefaults(); + $groupConfigDisabled = $userConfigDisabled = null; // MUST BE null, do NOT change this to false! + } else { // $formData['type'] === 'msldap' + $defaults = $groupBackend->getActiveDirectoryDefaults(); + $groupConfigDisabled = $userConfigDisabled = true; + } + + if ($formData['type'] === 'msldap') { + $this->addElement( + 'checkbox', + 'nested_group_search', + array( + 'description' => $this->translate( + 'Check this box for nested group search in Active Directory based on the user' + ), + 'label' => $this->translate('Nested Group Search') + ) + ); + } else { + // This is required to purge already present options + $this->addElement('hidden', 'nested_group_search', array('disabled' => true)); + } + + $this->createGroupConfigElements($defaults, $groupConfigDisabled); + if (count($userBackends) === 1 || (isset($formData['user_backend']) && $formData['user_backend'] === 'none')) { + $this->createUserConfigElements($defaults, $userConfigDisabled); + } else { + $this->createHiddenUserConfigElements(); + } + + $this->addElement( + 'hidden', + 'backend', + array( + 'disabled' => true, // Prevents the element from being submitted, see #7717 + 'value' => $formData['type'] + ) + ); + } + + /** + * Create and add all elements to this form required for the group configuration + * + * @param ConfigObject $defaults + * @param null|bool $disabled + */ + protected function createGroupConfigElements(ConfigObject $defaults, $disabled) + { + $this->addElement( + 'text', + 'group_class', + array( + 'preserveDefault' => true, + 'ignore' => $disabled, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Object Class'), + 'description' => $this->translate('The object class used for storing groups on the LDAP server.'), + 'value' => $defaults->group_class + ) + ); + $this->addElement( + 'text', + 'group_filter', + array( + 'preserveDefault' => true, + 'allowEmpty' => true, + 'label' => $this->translate('LDAP Group Filter'), + 'description' => $this->translate( + 'An additional filter to use when looking up groups using the specified connection. ' + . 'Leave empty to not to use any additional filter rules.' + ), + 'requirement' => $this->translate( + 'The filter needs to be expressed as standard LDAP expression, without' + . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)' + ), + 'validators' => array( + array( + 'Callback', + false, + array( + 'callback' => function ($v) { + return strpos($v, '(') !== 0; + }, + 'messages' => array( + 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.') + ) + ) + ) + ), + 'value' => $defaults->group_filter + ) + ); + $this->addElement( + 'text', + 'group_name_attribute', + array( + 'preserveDefault' => true, + 'ignore' => $disabled, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Name Attribute'), + 'description' => $this->translate( + 'The attribute name used for storing a group\'s name on the LDAP server.' + ), + 'value' => $defaults->group_name_attribute + ) + ); + $this->addElement( + 'text', + 'group_member_attribute', + array( + 'preserveDefault' => true, + 'ignore' => $disabled, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Member Attribute'), + 'description' => $this->translate('The attribute name used for storing a group\'s members.'), + 'value' => $defaults->group_member_attribute + ) + ); + $this->addElement( + 'text', + 'base_dn', + array( + 'preserveDefault' => true, + 'label' => $this->translate('LDAP Group Base DN'), + 'description' => $this->translate( + 'The path where groups can be found on the LDAP server. Leave ' . + 'empty to select all users available using the specified connection.' + ), + 'value' => $defaults->base_dn + ) + ); + } + + /** + * Create and add all elements to this form required for the user configuration + * + * @param ConfigObject $defaults + * @param null|bool $disabled + */ + protected function createUserConfigElements(ConfigObject $defaults, $disabled) + { + $this->addElement( + 'text', + 'user_class', + array( + 'preserveDefault' => true, + 'ignore' => $disabled, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP User Object Class'), + 'description' => $this->translate('The object class used for storing users on the LDAP server.'), + 'value' => $defaults->user_class + ) + ); + $this->addElement( + 'text', + 'user_filter', + array( + 'preserveDefault' => true, + 'allowEmpty' => true, + 'label' => $this->translate('LDAP User Filter'), + 'description' => $this->translate( + 'An additional filter to use when looking up users using the specified connection. ' + . 'Leave empty to not to use any additional filter rules.' + ), + 'requirement' => $this->translate( + 'The filter needs to be expressed as standard LDAP expression, without' + . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)' + ), + 'validators' => array( + array( + 'Callback', + false, + array( + 'callback' => function ($v) { + return strpos($v, '(') !== 0; + }, + 'messages' => array( + 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.') + ) + ) + ) + ), + 'value' => $defaults->user_filter + ) + ); + $this->addElement( + 'text', + 'user_name_attribute', + array( + 'preserveDefault' => true, + 'ignore' => $disabled, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP User Name Attribute'), + 'description' => $this->translate( + 'The attribute name used for storing a user\'s name on the LDAP server.' + ), + 'value' => $defaults->user_name_attribute + ) + ); + $this->addElement( + 'text', + 'user_base_dn', + array( + 'preserveDefault' => true, + 'label' => $this->translate('LDAP User Base DN'), + 'description' => $this->translate( + 'The path where users can be found on the LDAP server. Leave ' . + 'empty to select all users available using the specified connection.' + ), + 'value' => $defaults->user_base_dn + ) + ); + $this->addElement( + 'text', + 'domain', + array( + 'label' => $this->translate('Domain'), + 'description' => $this->translate( + 'The domain the LDAP server is responsible for.' + ) + ) + ); + } + + /** + * Create and add all elements for the user configuration as hidden inputs + * + * This is required to purge already present options when unlinking a group backend with a user backend. + */ + protected function createHiddenUserConfigElements() + { + $this->addElement('hidden', 'user_class', array('disabled' => true)); + $this->addElement('hidden', 'user_filter', array('disabled' => true)); + $this->addElement('hidden', 'user_name_attribute', array('disabled' => true)); + $this->addElement('hidden', 'user_base_dn', array('disabled' => true)); + $this->addElement('hidden', 'domain', array('disabled' => true)); + } + + /** + * Return the names of all configured LDAP resources + * + * @return array + */ + protected function getLdapResourceNames() + { + $names = array(); + foreach (ResourceFactory::getResourceConfigs() as $name => $config) { + if (in_array(strtolower($config->type), array('ldap', 'msldap'))) { + $names[] = $name; + } + } + + if (empty($names)) { + Notification::error( + $this->translate('No LDAP resources available. Please configure an LDAP resource first.') + ); + $this->getResponse()->redirectAndExit('config/createresource'); + } + + return $names; + } + + /** + * Return the names of all configured LDAP user backends + * + * @param LdapConnection $resource + * + * @return array + */ + protected function getLdapUserBackendNames(LdapConnection $resource) + { + $names = array(); + foreach (UserBackend::getBackendConfigs() as $name => $config) { + if (in_array(strtolower($config->backend), array('ldap', 'msldap'))) { + $backendResource = ResourceFactory::create($config->resource); + if ($backendResource->getHostname() === $resource->getHostname() + && $backendResource->getPort() === $resource->getPort() + ) { + $names[] = $name; + } + } + } + + return $names; + } +} diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php new file mode 100644 index 0000000..9ee4032 --- /dev/null +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -0,0 +1,314 @@ +getValues())); + if ($backend instanceof Inspectable) { + return $backend->inspect(); + } + } + + /** + * Initialize this form + */ + public function init() + { + $this->setName('form_config_usergroupbackend'); + $this->setSubmitLabel($this->translate('Save Changes')); + $this->customBackends = UserGroupBackend::getCustomBackendConfigForms(); + } + + /** + * Return a form object for the given backend type + * + * @param string $type The backend type for which to return a form + * + * @return Form + * + * @throws InvalidArgumentException In case the given backend type is invalid + */ + public function getBackendForm($type) + { + switch ($type) { + case 'db': + return new DbUserGroupBackendForm(); + case 'ldap': + case 'msldap': + return new LdapUserGroupBackendForm(); + default: + if (isset($this->customBackends[$type])) { + return new $this->customBackends[$type](); + } + + throw new InvalidArgumentException( + sprintf($this->translate('Invalid backend type "%s" provided'), $type) + ); + } + } + + /** + * Populate the form with the given backend's config + * + * @param string $name + * + * @return $this + * + * @throws NotFoundError In case no backend with the given name is found + */ + public function load($name) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No user group backend called "%s" found', $name); + } + + $this->backendToLoad = $name; + return $this; + } + + /** + * Add a new user group backend + * + * The backend to add is identified by the array-key `name'. + * + * @param array $data + * + * @return $this + * + * @throws InvalidArgumentException In case $data does not contain a backend name + * @throws IcingaException In case a backend with the same name already exists + */ + public function add(array $data) + { + if (! isset($data['name'])) { + throw new InvalidArgumentException('Key \'name\' missing'); + } + + $backendName = $data['name']; + if ($this->config->hasSection($backendName)) { + throw new IcingaException('A user group backend with the name "%s" does already exist', $backendName); + } + + unset($data['name']); + $this->config->setSection($backendName, $data); + return $this; + } + + /** + * Edit a user group backend + * + * @param string $name + * @param array $data + * + * @return $this + * + * @throws NotFoundError In case no backend with the given name is found + */ + public function edit($name, array $data) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No user group backend called "%s" found', $name); + } + + $backendConfig = $this->config->getSection($name); + if (isset($data['name'])) { + if ($data['name'] !== $name) { + $this->config->removeSection($name); + $name = $data['name']; + } + + unset($data['name']); + } + + $this->config->setSection($name, $backendConfig->merge($data)); + return $this; + } + + /** + * Remove a user group backend + * + * @param string $name + * + * @return $this + */ + public function delete($name) + { + $this->config->removeSection($name); + return $this; + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + $backendTypes = array( + 'db' => $this->translate('Database'), + 'ldap' => 'LDAP', + 'msldap' => 'ActiveDirectory' + ); + + $customBackendTypes = array_keys($this->customBackends); + $backendTypes += array_combine($customBackendTypes, $customBackendTypes); + + $backendType = isset($formData['type']) ? $formData['type'] : null; + if ($backendType === null) { + $backendType = key($backendTypes); + } + + $this->addElement( + 'select', + 'type', + array( + 'ignore' => true, + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Backend Type'), + 'description' => $this->translate('The type of this user group backend'), + 'multiOptions' => $backendTypes + ) + ); + + $this->addSubForm($this->getBackendForm($backendType)->create($formData), 'backend_form'); + } + + /** + * Populate the configuration of the backend to load + */ + public function onRequest() + { + if ($this->backendToLoad) { + $data = $this->config->getSection($this->backendToLoad)->toArray(); + $data['type'] = $data['backend']; + $data['name'] = $this->backendToLoad; + $this->populate($data); + } + } + + /** + * Run the configured backend's inspection checks and show the result, if necessary + * + * This will only run any validation if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + $inspection = static::inspectUserBackend($this); + if ($inspection !== null) { + $join = function ($e) use (&$join) { + return is_array($e) ? join("\n", array_map($join, $e)) : $e; + }; + $this->addElement( + 'note', + 'inspection_output', + array( + 'order' => 0, + 'value' => '' . $this->translate('Validation Log') . "\n\n" + . join("\n", array_map($join, $inspection->toArray())), + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')), + ) + ) + ); + + if ($inspection->hasError()) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $inspection->getError() + )); + return false; + } + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } + + return true; + } + + /** + * Add a submit button to this form and one to manually validate the configuration + * + * Calls parent::addSubmitButton() to add the submit button. + * + * @return $this + */ + public function addSubmitButton() + { + parent::addSubmitButton() + ->getElement('btn_submit') + ->setDecorators(array('ViewHelper')); + + $this->addElement( + 'submit', + 'backend_validation', + array( + 'ignore' => true, + 'label' => $this->translate('Validate Configuration'), + 'data-progress-label' => $this->translate('Validation In Progress'), + 'decorators' => array('ViewHelper') + ) + ); + $this->addDisplayGroup( + array('btn_submit', 'backend_validation'), + 'submit_validation', + array( + 'decorators' => array( + 'FormElements', + array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls')) + ) + ) + ); + + return $this; + } +} diff --git a/application/forms/Config/UserGroup/UserGroupForm.php b/application/forms/Config/UserGroup/UserGroupForm.php new file mode 100644 index 0000000..b944e97 --- /dev/null +++ b/application/forms/Config/UserGroup/UserGroupForm.php @@ -0,0 +1,158 @@ +addElement( + 'text', + 'group_name', + array( + 'required' => true, + 'label' => $this->translate('Group Name') + ) + ); + + if ($this->shouldInsert()) { + $this->setTitle($this->translate('Add a new group')); + $this->setSubmitLabel($this->translate('Add')); + } else { // $this->shouldUpdate() + $this->setTitle(sprintf($this->translate('Edit group %s'), $this->getIdentifier())); + $this->setSubmitLabel($this->translate('Save')); + } + } + + /** + * Update a group + * + * @return bool + */ + protected function onUpdateSuccess() + { + if (parent::onUpdateSuccess()) { + if (($newName = $this->getValue('group_name')) !== $this->getIdentifier()) { + $this->getRedirectUrl()->setParam('group', $newName); + } + + return true; + } + + return false; + } + + /** + * Create and add elements to this form to delete a group + * + * @param array $formData The data sent by the user + */ + protected function createDeleteElements(array $formData) + { + $this->setTitle(sprintf($this->translate('Remove group %s?'), $this->getIdentifier())); + $this->addDescription($this->translate( + 'Note that all users that are currently a member of this group will' + . ' have their membership cleared automatically.' + )); + $this->setSubmitLabel($this->translate('Yes')); + $this->setAttrib('class', 'icinga-form icinga-controls'); + } + + /** + * Create and return a filter to use when updating or deleting a group + * + * @return Filter + */ + protected function createFilter() + { + return Filter::where('group_name', $this->getIdentifier()); + } + + /** + * Return a notification message to use when inserting a group + * + * @param bool $success true or false, whether the operation was successful + * + * @return string + */ + protected function getInsertMessage($success) + { + if ($success) { + return $this->translate('Group added successfully'); + } else { + return $this->translate('Failed to add group'); + } + } + + /** + * Return a notification message to use when updating a group + * + * @param bool $success true or false, whether the operation was successful + * + * @return string + */ + protected function getUpdateMessage($success) + { + if ($success) { + return sprintf($this->translate('Group "%s" has been edited'), $this->getIdentifier()); + } else { + return sprintf($this->translate('Failed to edit group "%s"'), $this->getIdentifier()); + } + } + + /** + * Return a notification message to use when deleting a group + * + * @param bool $success true or false, whether the operation was successful + * + * @return string + */ + protected function getDeleteMessage($success) + { + if ($success) { + return sprintf($this->translate('Group "%s" has been removed'), $this->getIdentifier()); + } else { + return sprintf($this->translate('Failed to remove group "%s"'), $this->getIdentifier()); + } + } + + 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 (ConfigFormEventsHook::runOnSuccess($this) === false) { + Notification::error($this->translate( + 'Configuration successfully stored. Though, one or more module hooks failed to run.' + . ' See logs for details' + )); + } + } +} -- cgit v1.2.3