diff options
Diffstat (limited to 'library/Icinga/Application/Hook')
-rw-r--r-- | library/Icinga/Application/Hook/ApplicationStateHook.php | 90 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/AuditHook.php | 123 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/AuthenticationHook.php | 75 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/ConfigFormEventsHook.php | 137 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/GrapherHook.php | 111 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/HealthHook.php | 222 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/PdfexportHook.php | 25 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/ThemeLoaderHook.php | 22 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/Ticket/TicketPattern.php | 140 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/TicketHook.php | 210 | ||||
-rw-r--r-- | library/Icinga/Application/Hook/WebBaseHook.php | 54 |
11 files changed, 1209 insertions, 0 deletions
diff --git a/library/Icinga/Application/Hook/ApplicationStateHook.php b/library/Icinga/Application/Hook/ApplicationStateHook.php new file mode 100644 index 0000000..be973fe --- /dev/null +++ b/library/Icinga/Application/Hook/ApplicationStateHook.php @@ -0,0 +1,90 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook; + +use Icinga\Application\Hook; +use Icinga\Application\Logger; + +/** + * Application state hook base class + */ +abstract class ApplicationStateHook +{ + const ERROR = 'error'; + + private $messages = []; + + final public function hasMessages() + { + return ! empty($this->messages); + } + + final public function getMessages() + { + return $this->messages; + } + + /** + * Add an error message + * + * The timestamp of the message is used for deduplication and thus must refer to the time when the error first + * occurred. Don't use {@link time()} here! + * + * @param string $id ID of the message. The ID must be prefixed with the module name + * @param int $timestamp Timestamp when the error first occurred + * @param string $message Error message + * + * @return $this + */ + final public function addError($id, $timestamp, $message) + { + $id = trim($id); + $timestamp = (int) $timestamp; + + if (! strlen($id)) { + throw new \InvalidArgumentException('ID expected.'); + } + + if (! $timestamp) { + throw new \InvalidArgumentException('Timestamp expected.'); + } + + $this->messages[sha1($id . $timestamp)] = [self::ERROR, $timestamp, $message]; + + return $this; + } + + /** + * Override this method in order to provide application state messages + */ + abstract public function collectMessages(); + + final public static function getAllMessages() + { + $messages = []; + + if (! Hook::has('ApplicationState')) { + return $messages; + } + + foreach (Hook::all('ApplicationState') as $hook) { + /** @var self $hook */ + try { + $hook->collectMessages(); + } catch (\Exception $e) { + Logger::error( + "Failed to collect messages from hook '%s'. An error occurred: %s", + get_class($hook), + $e + ); + } + + if ($hook->hasMessages()) { + $messages += $hook->getMessages(); + } + } + + return $messages; + } +} diff --git a/library/Icinga/Application/Hook/AuditHook.php b/library/Icinga/Application/Hook/AuditHook.php new file mode 100644 index 0000000..e6209da --- /dev/null +++ b/library/Icinga/Application/Hook/AuditHook.php @@ -0,0 +1,123 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook; + +use Exception; +use InvalidArgumentException; +use Icinga\Authentication\Auth; +use Icinga\Application\Hook; +use Icinga\Application\Logger; + +abstract class AuditHook +{ + /** + * Log an activity to the audit log + * + * Propagates the given message details to all known hook implementations. + * + * @param string $type An arbitrary name identifying the type of activity + * @param string $message A detailed description possibly referencing parameters in $data + * @param array $data Additional information (How this is stored or used is up to each implementation) + * @param string $identity An arbitrary name identifying the responsible subject, defaults to the current user + * @param int $time A timestamp defining when the activity occurred, defaults to now + */ + public static function logActivity($type, $message, array $data = null, $identity = null, $time = null) + { + if (! Hook::has('audit')) { + return; + } + + if ($identity === null) { + $identity = Auth::getInstance()->getUser()->getUsername(); + } + + if ($time === null) { + $time = time(); + } + + foreach (Hook::all('audit') as $hook) { + /** @var self $hook */ + try { + $formattedMessage = $message; + if ($data !== null) { + // Calling formatMessage on each hook is intended and allows + // intercepting message formatting while keeping it implicit + $formattedMessage = $hook->formatMessage($message, $data); + } + + $hook->logMessage($time, $identity, $type, $formattedMessage, $data); + } catch (Exception $e) { + Logger::error( + 'Failed to propagate audit message to hook "%s". An error occurred: %s', + get_class($hook), + $e + ); + } + } + } + + /** + * Log a message to the audit log + * + * @param int $time A timestamp defining when the activity occurred + * @param string $identity An arbitrary name identifying the responsible subject + * @param string $type An arbitrary name identifying the type of activity + * @param string $message A detailed description of the activity + * @param array $data Additional activity information + */ + abstract public function logMessage($time, $identity, $type, $message, array $data = null); + + /** + * Substitute the given message with its accompanying data + * + * @param string $message + * @param array $messageData + * + * @return string + */ + public function formatMessage($message, array $messageData) + { + return preg_replace_callback('/{{(.+?)}}/', function ($match) use ($messageData) { + return $this->extractMessageValue(explode('.', $match[1]), $messageData); + }, $message); + } + + /** + * Extract the given value path from the given message data + * + * @param array $path + * @param array $messageData + * + * @return mixed + * + * @throws InvalidArgumentException In case of an invalid or missing format parameter + */ + protected function extractMessageValue(array $path, array $messageData) + { + $key = array_shift($path); + if (array_key_exists($key, $messageData)) { + $value = $messageData[$key]; + } else { + throw new InvalidArgumentException("Missing format parameter '$key'"); + } + + if (empty($path)) { + if (! is_scalar($value)) { + throw new InvalidArgumentException( + 'Invalid format parameter. Expected scalar for path "' . join('.', $path) . '".' + . ' Got "' . gettype($value) . '" instead' + ); + } + + return $value; + } elseif (! is_array($value)) { + throw new InvalidArgumentException( + 'Invalid format parameter. Expected array for path "'. join('.', $path) . '".' + . ' Got "' . gettype($value) . '" instead' + ); + } + + return $this->extractMessageValue($path, $value); + } +} diff --git a/library/Icinga/Application/Hook/AuthenticationHook.php b/library/Icinga/Application/Hook/AuthenticationHook.php new file mode 100644 index 0000000..41cc661 --- /dev/null +++ b/library/Icinga/Application/Hook/AuthenticationHook.php @@ -0,0 +1,75 @@ +<?php + +namespace Icinga\Application\Hook; + +use Icinga\User; +use Icinga\Web\Hook; +use Icinga\Application\Logger; + +/** + * Icinga Web Authentication Hook base class + * + * This hook can be used to authenticate the user in a third party application. + * Extend this class if you want to perform arbitrary actions during the login and logout. + */ +abstract class AuthenticationHook +{ + /** + * Name of the hook + */ + const NAME = 'authentication'; + + /** + * Triggered after login in Icinga Web and when calling login action even if already authenticated in Icinga Web + * + * @param User $user + */ + public function onLogin(User $user) + { + } + + /** + * Triggered before logout from Icinga Web + * + * @param User $user + */ + public function onLogout(User $user) + { + } + + /** + * Call the onLogin() method of all registered AuthHook(s) + * + * @param User $user + */ + public static function triggerLogin(User $user) + { + /** @var AuthenticationHook $hook */ + foreach (Hook::all(self::NAME) as $hook) { + try { + $hook->onLogin($user); + } catch (\Exception $e) { + // Avoid error propagation if login failed in third party application + Logger::error($e); + } + } + } + + /** + * Call the onLogout() method of all registered AuthHook(s) + * + * @param User $user + */ + public static function triggerLogout(User $user) + { + /** @var AuthenticationHook $hook */ + foreach (Hook::all(self::NAME) as $hook) { + try { + $hook->onLogout($user); + } catch (\Exception $e) { + // Avoid error propagation if login failed in third party application + Logger::error($e); + } + } + } +} diff --git a/library/Icinga/Application/Hook/ConfigFormEventsHook.php b/library/Icinga/Application/Hook/ConfigFormEventsHook.php new file mode 100644 index 0000000..aa0cadc --- /dev/null +++ b/library/Icinga/Application/Hook/ConfigFormEventsHook.php @@ -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\Web\Form; + +/** + * 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 Form $form + * + * @return bool + */ + public function appliesTo(Form $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 Form $form + * + * @throws \Exception If either the form is not valid or to interrupt the form handling + */ + public function isValid(Form $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 Form $form + */ + public function onSuccess(Form $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 Form $form + * + * @return bool Returns false if any hook threw an exception + */ + final public static function runIsValid(Form $form) + { + return self::runEventMethod('isValid', $form); + } + + /** + * Run all onSuccess hooks + * + * @param Form $form + * + * @return bool Returns false if any hook threw an exception + */ + final public static function runOnSuccess(Form $form) + { + return self::runEventMethod('onSuccess', $form); + } + + private static function runEventMethod($eventMethod, Form $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(Form $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; + } +} diff --git a/library/Icinga/Application/Hook/GrapherHook.php b/library/Icinga/Application/Hook/GrapherHook.php new file mode 100644 index 0000000..dfb2135 --- /dev/null +++ b/library/Icinga/Application/Hook/GrapherHook.php @@ -0,0 +1,111 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook; + +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Monitoring\Object\MonitoredObject; + +/** + * Icinga Web Grapher Hook base class + * + * Extend this class if you want to integrate your graphing solution nicely into + * Icinga Web. + */ +abstract class GrapherHook extends WebBaseHook +{ + /** + * Whether this grapher provides previews + * + * @var bool + */ + protected $hasPreviews = false; + + /** + * Whether this grapher provides tiny previews + * + * @var bool + */ + protected $hasTinyPreviews = false; + + /** + * Constructor must live without arguments right now + * + * Therefore the constructor is final, we might change our opinion about + * this one far day + */ + final public function __construct() + { + $this->init(); + } + + /** + * Overwrite this function if you want to do some initialization stuff + * + * @return void + */ + protected function init() + { + } + + /** + * Whether this grapher provides previews + * + * @return bool + */ + public function hasPreviews() + { + return $this->hasPreviews; + } + + /** + * Whether this grapher provides tiny previews + * + * @return bool + */ + public function hasTinyPreviews() + { + return $this->hasTinyPreviews; + } + + /** + * Whether a graph for the monitoring object exist + * + * @param MonitoredObject $object + * + * @return bool + */ + abstract public function has(MonitoredObject $object); + + /** + * Get a preview for the given object + * + * This function must return an empty string if no graph exists. + * + * @param MonitoredObject $object + * + * @return string + * @throws ProgrammingError + * + */ + public function getPreviewHtml(MonitoredObject $object) + { + throw new ProgrammingError('This hook provide previews but it is not implemented'); + } + + + /** + * Get a tiny preview for the given object + * + * This function must return an empty string if no graph exists. + * + * @param MonitoredObject $object + * + * @return string + * @throws ProgrammingError + */ + public function getTinyPreviewHtml(MonitoredObject $object) + { + throw new ProgrammingError('This hook provide tiny previews but it is not implemented'); + } +} diff --git a/library/Icinga/Application/Hook/HealthHook.php b/library/Icinga/Application/Hook/HealthHook.php new file mode 100644 index 0000000..f6420b5 --- /dev/null +++ b/library/Icinga/Application/Hook/HealthHook.php @@ -0,0 +1,222 @@ +<?php +/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Application\Hook; + +use Exception; +use Icinga\Application\Hook; +use Icinga\Application\Logger; +use Icinga\Data\DataArray\ArrayDatasource; +use Icinga\Exception\IcingaException; +use ipl\Web\Url; +use LogicException; + +abstract class HealthHook +{ + /** @var int */ + const STATE_OK = 0; + + /** @var int */ + const STATE_WARNING = 1; + + /** @var int */ + const STATE_CRITICAL = 2; + + /** @var int */ + const STATE_UNKNOWN = 3; + + /** @var int The overall state */ + protected $state; + + /** @var string Message describing the overall state */ + protected $message; + + /** @var array Available metrics */ + protected $metrics; + + /** @var Url Url to a graphical representation of the available metrics */ + protected $url; + + /** + * Get overall state + * + * @return int + */ + public function getState() + { + return $this->state; + } + + /** + * Set overall state + * + * @param int $state + * + * @return $this + */ + public function setState($state) + { + $this->state = $state; + + return $this; + } + + /** + * Get the message describing the overall state + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Set the message describing the overall state + * + * @param string $message + * + * @return $this + */ + public function setMessage($message) + { + $this->message = $message; + + return $this; + } + + /** + * Get available metrics + * + * @return array + */ + public function getMetrics() + { + return $this->metrics; + } + + /** + * Set available metrics + * + * @param array $metrics + * + * @return $this + */ + public function setMetrics(array $metrics) + { + $this->metrics = $metrics; + + return $this; + } + + /** + * Get the url to a graphical representation of the available metrics + * + * @return Url + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set the url to a graphical representation of the available metrics + * + * @param Url $url + * + * @return $this + */ + public function setUrl(Url $url) + { + $this->url = $url; + + return $this; + } + + /** + * Collect available health data from hooks + * + * @return ArrayDatasource + */ + final public static function collectHealthData() + { + $checks = []; + foreach (Hook::all('health') as $hook) { + /** @var self $hook */ + + try { + $hook->checkHealth(); + $url = $hook->getUrl(); + $state = $hook->getState(); + $message = $hook->getMessage(); + $metrics = $hook->getMetrics(); + } catch (Exception $e) { + Logger::error('Failed to check health: %s', $e); + + $state = self::STATE_UNKNOWN; + $message = IcingaException::describe($e); + $metrics = null; + $url = null; + } + + $checks[] = (object) [ + 'module' => $hook->getModuleName(), + 'name' => $hook->getName(), + 'url' => $url ? $url->getAbsoluteUrl() : null, + 'state' => $state, + 'message' => $message, + 'metrics' => (object) $metrics + ]; + } + + return (new ArrayDatasource($checks)) + ->setKeyColumn('name'); + } + + /** + * Get the name of the hook + * + * Only used in API responses to differentiate it from other hooks of the same module. + * + * @return string + */ + public function getName() + { + $classPath = get_class($this); + $parts = explode('\\', $classPath); + $className = array_pop($parts); + + if (substr($className, -4) === 'Hook') { + $className = substr($className, 1, -4); + } + + return strtolower($className[0]) . substr($className, 1); + } + + /** + * Get the name of the module providing this hook + * + * @return string + * + * @throws LogicException + */ + public function getModuleName() + { + $classPath = get_class($this); + if (substr($classPath, 0, 14) !== 'Icinga\\Module\\') { + throw new LogicException('Not a module hook'); + } + + $withoutPrefix = substr($classPath, 14); + return strtolower(substr($withoutPrefix, 0, strpos($withoutPrefix, '\\'))); + } + + /** + * Check health + * + * Implement this method and set the overall state, message, url and metrics. + * + * @return void + */ + abstract public function checkHealth(); +} diff --git a/library/Icinga/Application/Hook/PdfexportHook.php b/library/Icinga/Application/Hook/PdfexportHook.php new file mode 100644 index 0000000..36e9f51 --- /dev/null +++ b/library/Icinga/Application/Hook/PdfexportHook.php @@ -0,0 +1,25 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook; + +/** + * Base class for the PDF Export Hook + */ +abstract class PdfexportHook +{ + /** + * Get whether PDF export is supported + * + * @return bool + */ + abstract public function isSupported(); + + /** + * Render the specified HTML to PDF and stream it to the client + * + * @param string $html The HTML to render to PDF + * @param string $filename The filename for the generated PDF + */ + abstract public function streamPdfFromHtml($html, $filename); +} diff --git a/library/Icinga/Application/Hook/ThemeLoaderHook.php b/library/Icinga/Application/Hook/ThemeLoaderHook.php new file mode 100644 index 0000000..5320dd5 --- /dev/null +++ b/library/Icinga/Application/Hook/ThemeLoaderHook.php @@ -0,0 +1,22 @@ +<?php +/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +namespace Icinga\Application\Hook; + +/** + * Provide an implementation of this hook to dynamically provide themes. + * Note that only the first registered hook is utilized. Also note that + * for ordinary themes this hook is not required. Place such in your + * module's theme path: <module-path>/public/css/themes + */ +abstract class ThemeLoaderHook +{ + /** + * Get the path for the given theme + * + * @param ?string $theme + * + * @return ?string The path or NULL if the theme is unknown + */ + abstract public function getThemeFile(?string $theme): ?string; +} diff --git a/library/Icinga/Application/Hook/Ticket/TicketPattern.php b/library/Icinga/Application/Hook/Ticket/TicketPattern.php new file mode 100644 index 0000000..e37fcc1 --- /dev/null +++ b/library/Icinga/Application/Hook/Ticket/TicketPattern.php @@ -0,0 +1,140 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook\Ticket; + +use ArrayAccess; + +/** + * A ticket pattern + * + * This class should be used by modules which provide implementations for the Web 2 ticket hook. + * Have a look at the GenericTTS module for a possible use case. + */ +class TicketPattern implements ArrayAccess +{ + /** + * The result of a performed ticket match + * + * @var array + */ + protected $match = array(); + + /** + * The name of the TTS integration + * + * @var string + */ + protected $name; + + /** + * The ticket pattern + * + * @var string + */ + protected $pattern; + + public function offsetExists($offset): bool + { + return isset($this->match[$offset]); + } + + public function offsetGet($offset): ?string + { + return array_key_exists($offset, $this->match) ? $this->match[$offset] : null; + } + + public function offsetSet($offset, $value): void + { + if ($offset === null) { + $this->match[] = $value; + } else { + $this->match[$offset] = $value; + } + } + + public function offsetUnset($offset): void + { + unset($this->match[$offset]); + } + + + /** + * Get the result of a performed ticket match + * + * @return array + */ + public function getMatch() + { + return $this->match; + } + + /** + * Set the result of a performed ticket match + * + * @param array $match + * + * @return $this + */ + public function setMatch(array $match) + { + $this->match = $match; + return $this; + } + + /** + * Get the name of the TTS integration + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the name of the TTS integration + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Get the ticket pattern + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Set the ticket pattern + * + * @param string $pattern + * + * @return $this + */ + public function setPattern($pattern) + { + $this->pattern = $pattern; + return $this; + } + + /** + * Whether the integration is properly configured, i.e. the pattern and the URL are not empty + * + * @return bool + */ + public function isValid() + { + return ! empty($this->pattern); + } +} diff --git a/library/Icinga/Application/Hook/TicketHook.php b/library/Icinga/Application/Hook/TicketHook.php new file mode 100644 index 0000000..ceb3738 --- /dev/null +++ b/library/Icinga/Application/Hook/TicketHook.php @@ -0,0 +1,210 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook; + +use ArrayIterator; +use ErrorException; +use Exception; +use Icinga\Application\Hook\Ticket\TicketPattern; +use Icinga\Application\Logger; +use Icinga\Exception\IcingaException; + +/** + * Base class for ticket hooks + * + * Extend this class if you want to integrate your ticketing solution into Icinga Web 2. + */ +abstract class TicketHook +{ + /** + * Last error, if any + * + * @var string|null + */ + protected $lastError; + + /** + * Create a new ticket hook + * + * @see init() For hook initialization. + */ + final public function __construct() + { + $this->init(); + } + + /** + * Overwrite this function for hook initialization, e.g. loading the hook's config + */ + protected function init() + { + } + + /** + * Create a link for each matched element in the subject text + * + * @param array|TicketPattern $match Matched element according to {@link getPattern()} + * + * @return string Replacement string + */ + abstract public function createLink($match); + + /** + * Get the pattern(s) to search for + * + * Return an array of TicketPattern instances here to support multiple TTS integrations. + * + * @return string|TicketPattern[] + */ + abstract public function getPattern(); + + /** + * Apply ticket patterns to the given text + * + * @param string $text + * @param TicketPattern[] $ticketPatterns + * + * @return string + */ + private function applyTicketPatterns($text, array $ticketPatterns) + { + $out = ''; + $start = 0; + + $iterator = new ArrayIterator($ticketPatterns); + $iterator->rewind(); + + while ($iterator->valid()) { + $ticketPattern = $iterator->current(); + + try { + preg_match($ticketPattern->getPattern(), $text, $match, PREG_OFFSET_CAPTURE, $start); + } catch (ErrorException $e) { + $this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e)); + $iterator->next(); + continue; + } + + if (empty($match)) { + $iterator->next(); + continue; + } + + // Remove preg_offset from match for the ticket pattern + $carry = array(); + array_walk($match, function ($value, $key) use (&$carry) { + $carry[$key] = $value[0]; + }, $carry); + $ticketPattern->setMatch($carry); + + $offsetLeft = $match[0][1]; + $matchLength = strlen($match[0][0]); + + $out .= substr($text, $start, $offsetLeft - $start); + + try { + $out .= $this->createLink($ticketPattern); + } catch (Exception $e) { + $this->fail('Can\'t create ticket links: %s', IcingaException::describe($e)); + return $text; + } + + $start = $offsetLeft + $matchLength; + } + + $out .= substr($text, $start); + + return $out; + } + + /** + * Helper function to create a TicketPattern instance + * + * @param string $name Name of the TTS integration + * @param string $pattern Ticket pattern + * + * @return TicketPattern + */ + protected function createTicketPattern($name, $pattern) + { + $ticketPattern = new TicketPattern(); + $ticketPattern + ->setName($name) + ->setPattern($pattern); + return $ticketPattern; + } + + /** + * Set the hook as failed w/ the given message + * + * @param string $message Error message or error format string + * @param mixed ...$arg Format string argument + */ + private function fail($message) + { + $args = array_slice(func_get_args(), 1); + $lastError = vsprintf($message, $args); + Logger::debug($lastError); + $this->lastError = $lastError; + } + + /** + * Get the last error, if any + * + * @return string|null + */ + public function getLastError() + { + return $this->lastError; + } + + /** + * Create links w/ {@link createLink()} in the given text that matches to the subject from {@link getPattern()} + * + * In case of errors a debug message is recorded to the log and any subsequent call to {@link createLinks()} will + * be a no-op. + * + * @param string $text + * + * @return string + */ + final public function createLinks($text) + { + if ($this->lastError !== null) { + return $text; + } + + try { + $pattern = $this->getPattern(); + } catch (Exception $e) { + $this->fail('Can\'t create ticket links: Retrieving the pattern failed: %s', IcingaException::describe($e)); + return $text; + } + + if (empty($pattern)) { + $this->fail('Can\'t create ticket links: Pattern is empty'); + return $text; + } + + if (is_array($pattern)) { + $text = $this->applyTicketPatterns($text, $pattern); + } else { + try { + $text = preg_replace_callback( + $pattern, + array($this, 'createLink'), + $text + ); + } catch (ErrorException $e) { + $this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e)); + return $text; + } catch (Exception $e) { + $this->fail('Can\'t create ticket links: %s', IcingaException::describe($e)); + return $text; + } + } + + return $text; + } +} diff --git a/library/Icinga/Application/Hook/WebBaseHook.php b/library/Icinga/Application/Hook/WebBaseHook.php new file mode 100644 index 0000000..09e8f4f --- /dev/null +++ b/library/Icinga/Application/Hook/WebBaseHook.php @@ -0,0 +1,54 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Application\Hook; + +use Zend_Controller_Action_HelperBroker; +use Zend_View; + +/** + * Base class for web hooks + * + * The class provides access to the view + */ +class WebBaseHook +{ + /** + * View instance + * + * @var Zend_View + */ + private $view; + + /** + * Set the view instance + * + * @param Zend_View $view + * + * @return $this + */ + public function setView(Zend_View $view) + { + $this->view = $view; + + return $this; + } + + /** + * Get the view instance + * + * @return Zend_View + */ + public function getView() + { + if ($this->view === null) { + $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + if ($viewRenderer->view === null) { + $viewRenderer->initView(); + } + $this->view = $viewRenderer->view; + } + + return $this->view; + } +} |