Merge pull request #3862 from Icinga/feature/config-form-events-hook-3768

Provide ConfigFormEventsHook
This commit is contained in:
Johannes Meyer 2019-07-15 08:03:26 +02:00 committed by GitHub
commit 0a55df0fcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 222 additions and 11 deletions

View File

@ -4,9 +4,10 @@
namespace Icinga\Forms;
use Exception;
use Icinga\Exception\ConfigurationError;
use Zend_Form_Decorator_Abstract;
use Icinga\Application\Config;
use Icinga\Application\Hook\ConfigFormEventsHook;
use Icinga\Exception\ConfigurationError;
use Icinga\Web\Form;
use Icinga\Web\Notification;
@ -52,9 +53,21 @@ class ConfigForm extends Form
return $this;
}
/**
* {@inheritdoc}
*/
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()
{
$sections = array();
@ -72,11 +85,15 @@ class ConfigForm extends Form
} else {
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'
));
}
}
/**
* {@inheritdoc}
*/
public function onRequest()
{
$values = array();

View File

@ -1,5 +1,5 @@
# Icinga Web 2 - Head for multiple monitoring backends.
# Copyright (C) 2018 Icinga Development Team
# Copyright (C) 2019 Icinga Development Team
# This file is distributed under the same license as Icinga Web 2.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Icinga Web 2 (None)\n"
"Report-Msgid-Bugs-To: dev@icinga.com\n"
"POT-Creation-Date: 2018-07-11 14:17+0000\n"
"PO-Revision-Date: 2018-07-11 16:30+0200\n"
"POT-Creation-Date: 2019-07-12 12:08-0700\n"
"PO-Revision-Date: 2019-07-12 14:10+0200\n"
"Last-Translator: Markus Frosch <markus.frosch@icinga.com>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
@ -17,7 +17,7 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-Basepath: .\n"
"Language-Team: \n"
"X-Generator: Poedit 2.0.6\n"
"X-Generator: Poedit 2.2.3\n"
"X-Poedit-SearchPath-0: .\n"
#: ../../../../library/Icinga/Web/Form/Validator/InArray.php:16
@ -534,6 +534,14 @@ msgstr "Komma separierte Liste von Nutzern, die dieser Rolle zugewiesen werden"
msgid "Configuration"
msgstr "Konfiguration"
#: ../../../../application/forms/ConfigForm.php:89
msgid ""
"Configuration successfully stored. Though, one or more module hooks failed "
"to run. See logs for details"
msgstr ""
"Konfiguration erfolgreich gespeichert. Es sind jedoch einer oder mehrere "
"Modul-Hooks fehlgeschlagen. Bitte die Logs prüfen"
#: ../../../../application/controllers/RoleController.php:155
#: ../../../../application/controllers/UserController.php:320
#: ../../../../application/controllers/GroupController.php:369

49
doc/60-Hooks.md Normal file
View File

@ -0,0 +1,49 @@
# Hooks
## ConfigFormEventsHook
The `ConfigFormEventsHook` allows developers to hook into the handling of configuration forms. It provides three methods:
* `appliesTo()`
* `isValid()`
* `onSuccess()`
`appliesTo()` determines whether the hook should run for a given configuration form.
Developers should use `instanceof` checks in order to decide whether the hook should run or not.
If `appliesTo()` returns `false`, `isValid()` and `onSuccess()` won't get called for this hook.
`isValid()` is called after the configuration form has been validated successfully.
An exception thrown here indicates form errors and prevents the config from being stored.
The exception's error message is shown in the frontend automatically.
If there are multiple hooks indicating errors, every error will be displayed.
`onSuccess()` is called after the configuration has been stored successfully.
Form handling can't be interrupted here. Any exception will be caught, logged and notified.
Hook example:
```php
namespace Icinga\Module\Acme\ProvidedHook;
use Icinga\Application\Hook\ConfigFormEventsHook;
use Icinga\Forms\ConfigForm;
use Icinga\Forms\Security\RoleForm;
class ConfigFormEvents extends ConfigFormEventsHook
{
public function appliesTo(ConfigForm $form)
{
return $form instanceof RoleForm;
}
public function onSuccess(ConfigForm $form)
{
$this->updateMyModuleConfig();
}
protected function updateMyModuleConfig()
{
// ...
}
}
```

View File

@ -0,0 +1,137 @@
<?php
/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
namespace Icinga\Application\Hook;
use Icinga\Application\Hook;
use Icinga\Application\Logger;
use Icinga\Exception\IcingaException;
use Icinga\Forms\ConfigForm;
/**
* Base class for config form event hooks
*/
abstract class ConfigFormEventsHook
{
/** @var array Array of errors found while processing the form event hooks */
private static $lastErrors = [];
/**
* Get whether the hook applies to the given config form
*
* @param ConfigForm $form
*
* @return bool
*/
public function appliesTo(ConfigForm $form)
{
return false;
}
/**
* isValid event hook
*
* Implement this method in order to run code after the form has been validated successfully.
* Throw an exception here if either the form is not valid or you want interrupt the form handling.
* The exception's message will be automatically added as form error message so that it will be
* displayed in the frontend.
*
* @param ConfigForm $form
*
* @throws \Exception If either the form is not valid or to interrupt the form handling
*/
public function isValid(ConfigForm $form)
{
}
/**
* onSuccess event hook
*
* Implement this method in order to run code after the configuration form has been stored successfully.
* You can't interrupt the form handling here. Any exception will be caught, logged and notified.
*
* @param ConfigForm $form
*/
public function onSuccess(ConfigForm $form)
{
}
/**
* Get an array of errors found while processing the form event hooks
*
* @return array
*/
final public static function getLastErrors()
{
return static::$lastErrors;
}
/**
* Run all isValid hooks
*
* @param ConfigForm $form
*
* @return bool Returns false if any hook threw an exception
*/
final public static function runIsValid(ConfigForm $form)
{
return self::runEventMethod('isValid', $form);
}
/**
* Run all onSuccess hooks
*
* @param ConfigForm $form
*
* @return bool Returns false if any hook threw an exception
*/
final public static function runOnSuccess(ConfigForm $form)
{
return self::runEventMethod('onSuccess', $form);
}
private static function runEventMethod($eventMethod, ConfigForm $form)
{
static::$lastErrors = [];
if (! Hook::has('ConfigFormEvents')) {
return true;
}
$success = true;
foreach (Hook::all('ConfigFormEvents') as $hook) {
/** @var self $hook */
if (! $hook->runAppliesTo($form)) {
continue;
}
try {
$hook->$eventMethod($form);
} catch (\Exception $e) {
static::$lastErrors[] = $e->getMessage();
Logger::error("%s\n%s", $e, IcingaException::getConfidentialTraceAsString($e));
$success = false;
}
}
return $success;
}
private function runAppliesTo(ConfigForm $form)
{
try {
$appliesTo = $this->appliesTo($form);
} catch (\Exception $e) {
// Don't save exception to last errors because we do not want to disturb the user for messed up
// appliesTo checks
Logger::error("%s\n%s", $e, IcingaException::getConfidentialTraceAsString($e));
$appliesTo = false;
}
return $appliesTo === true;
}
}