diff --git a/library/Icinga/Application/Hook.php b/library/Icinga/Application/Hook.php
new file mode 100644
index 000000000..ed11cf458
--- /dev/null
+++ b/library/Icinga/Application/Hook.php
@@ -0,0 +1,256 @@
+
+ * Hook::register('grapher', 'My\\Grapher\\Class');
+ *
+ */
+class Hook
+{
+ /**
+ * Our hook name registry
+ *
+ * @var array
+ */
+ protected static $hooks = array();
+
+ /**
+ * Hooks that have already been instantiated
+ *
+ * @var array
+ */
+ protected static $instances = array();
+
+ /**
+ * Namespace prefix
+ *
+ * @var string
+ */
+ public static $BASE_NS = 'Icinga\\Application\\Hook\\';
+
+ /**
+ * Append this string to base class
+ *
+ * All base classes renamed to *Hook
+ *
+ * @var string
+ */
+ public static $classSuffix = 'Hook';
+
+ /**
+ * Reset object state
+ */
+ public static function clean()
+ {
+ self::$hooks = array();
+ self::$instances = array();
+ self::$BASE_NS = 'Icinga\\Application\\Hook\\';
+ }
+
+ /**
+ * Whether someone registered itself for the given hook name
+ *
+ * @param string $name One of the predefined hook names
+ *
+ * @return bool
+ */
+ public static function has($name)
+ {
+ $name = self::normalizeHookName($name);
+ return array_key_exists($name, self::$hooks);
+ }
+
+ protected static function normalizeHookName($name)
+ {
+ if (strpos($name, '\\') === false) {
+ $parts = explode('/', $name);
+ foreach ($parts as & $part) {
+ $part = ucfirst($part);
+ }
+
+ return implode('\\', $parts);
+ }
+
+ return $name;
+ }
+
+ /**
+ * Create or return an instance of a given hook
+ *
+ * TODO: Should return some kind of a hook interface
+ *
+ * @param string $name One of the predefined hook names
+ * @param string $key The identifier of a specific subtype
+ *
+ * @return mixed
+ */
+ public static function createInstance($name, $key)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (!self::has($name, $key)) {
+ return null;
+ }
+
+ if (isset(self::$instances[$name][$key])) {
+ return self::$instances[$name][$key];
+ }
+
+ $class = self::$hooks[$name][$key];
+ try {
+ $instance = new $class();
+ } catch (Exception $e) {
+ Logger::debug(
+ 'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
+ $name,
+ $key,
+ $class,
+ $e->getMessage()
+ );
+ // TODO: Persist unloading for "some time" or "current session"
+ unset(self::$hooks[$name][$key]);
+ return null;
+ }
+
+ self::assertValidHook($instance, $name);
+ self::$instances[$name][$key] = $instance;
+ return $instance;
+ }
+
+ protected static function splitHookName($name)
+ {
+ $sep = '\\';
+ if (false === $module = strpos($name, $sep)) {
+ return array(null, $name);
+ }
+ return array(
+ substr($name, 0, $module),
+ substr($name, $module + 1)
+ );
+ }
+
+ /**
+ * Test for a valid class name
+ *
+ * @param mixed $instance
+ * @param string $name
+ *
+ * @throws ProgrammingError
+ */
+ private static function assertValidHook($instance, $name)
+ {
+ $name = self::normalizeHookName($name);
+
+ $suffix = self::$classSuffix; // 'Hook'
+ $base = self::$BASE_NS; // 'Icinga\\Web\\Hook\\'
+
+ list($module, $name) = self::splitHookName($name);
+
+ if ($module === null) {
+ $base_class = $base . ucfirst($name) . 'Hook';
+
+ // I'm unsure whether this makes sense. Unused and Wrong.
+ if (strpos($base_class, $suffix) === false) {
+ $base_class .= $suffix;
+ }
+ } else {
+ $base_class = 'Icinga\\Module\\'
+ . ucfirst($module)
+ . '\\Hook\\'
+ . ucfirst($name)
+ . $suffix;
+ }
+
+ if (!$instance instanceof $base_class) {
+
+ // This is a compatibility check. Should be removed one far day:
+ if ($module !== null) {
+ $compat_class = 'Icinga\\Module\\'
+ . ucfirst($module)
+ . '\\Web\\Hook\\'
+ . ucfirst($name)
+ . $suffix;
+
+ if ($instance instanceof $compat_class) {
+ return;
+ }
+ }
+
+ throw new ProgrammingError(
+ '%s is not an instance of %s',
+ get_class($instance),
+ $base_class
+ );
+ }
+ }
+
+ /**
+ * Return all instances of a specific name
+ *
+ * @param string $name One of the predefined hook names
+ *
+ * @return array
+ */
+ public static function all($name)
+ {
+ $name = self::normalizeHookName($name);
+ if (!self::has($name)) {
+ return array();
+ }
+
+ foreach (self::$hooks[$name] as $key => $hook) {
+ if (self::createInstance($name, $key) === null) {
+ return array();
+ }
+ }
+
+ return self::$instances[$name];
+ }
+
+ /**
+ * Get the first hook
+ *
+ * @param string $name One of the predefined hook names
+ *
+ * @return null|mixed
+ */
+ public static function first($name)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (self::has($name)) {
+ return self::createInstance($name, key(self::$hooks[$name]));
+ }
+ }
+
+ /**
+ * Register a class
+ *
+ * @param string $name One of the predefined hook names
+ * @param string $key The identifier of a specific subtype
+ * @param string $class Your class name, must inherit one of the
+ * classes in the Icinga/Application/Hook folder
+ */
+ public static function register($name, $key, $class)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (!isset(self::$hooks[$name])) {
+ self::$hooks[$name] = array();
+ }
+
+ self::$hooks[$name][$key] = $class;
+ }
+}
diff --git a/library/Icinga/Application/Hook/GrapherHook.php b/library/Icinga/Application/Hook/GrapherHook.php
new file mode 100644
index 000000000..b923d2978
--- /dev/null
+++ b/library/Icinga/Application/Hook/GrapherHook.php
@@ -0,0 +1,111 @@
+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/TicketHook.php b/library/Icinga/Application/Hook/TicketHook.php
new file mode 100644
index 000000000..8e0aa4cc2
--- /dev/null
+++ b/library/Icinga/Application/Hook/TicketHook.php
@@ -0,0 +1,124 @@
+init();
+ }
+
+ /**
+ * Overwrite this function for hook initialization, e.g. loading the hook's config
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get the pattern
+ *
+ * @return string
+ */
+ abstract public function getPattern();
+
+ /**
+ * Create a link for each matched element in the subject text
+ *
+ * @param array $match Array of matched elements according to {@link getPattern()}
+ *
+ * @return string Replacement string
+ */
+ abstract public function createLink($match);
+
+ /**
+ * 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;
+ }
+ 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/Web/Hook/WebBaseHook.php b/library/Icinga/Application/Hook/WebBaseHook.php
similarity index 96%
rename from library/Icinga/Web/Hook/WebBaseHook.php
rename to library/Icinga/Application/Hook/WebBaseHook.php
index 544d2afa3..826b52a33 100644
--- a/library/Icinga/Web/Hook/WebBaseHook.php
+++ b/library/Icinga/Application/Hook/WebBaseHook.php
@@ -1,7 +1,7 @@
name;
}
+ /**
+ * Get the module namespace
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return 'Icinga\\Module\\' . ucfirst($this->getName());
+ }
+
/**
* Get the module version
*
@@ -1238,12 +1248,35 @@ class Module
*/
protected function registerHook($name, $class, $key = null)
{
- if ($key === null) {
- $key = $this->name;
+ return $this->provideHook($name, $class, $key);
+ }
+
+ protected function slashesToNamespace($class)
+ {
+ $list = explode('/', $class);
+ foreach ($list as &$part) {
+ $part = ucfirst($part);
}
- Hook::register($name, $key, $class);
+ return implode('\\', $list);
+ }
+ // deprecate $key
+ protected function provideHook($name, $implementation = null, $key = null)
+ {
+ if ($implementation === null) {
+ $implementation = $name;
+ }
+
+ if (strpos($implementation, '\\') === false) {
+ $class = $this->getNamespace()
+ . '\\ProvidedHook\\'
+ . $this->slashesToNamespace($implementation);
+ } else {
+ $class = $implementation;
+ }
+
+ Hook::register($name, $implementation, $class);
return $this;
}
diff --git a/library/Icinga/Web/Hook.php b/library/Icinga/Web/Hook.php
index 3b9700f27..e3fee951d 100644
--- a/library/Icinga/Web/Hook.php
+++ b/library/Icinga/Web/Hook.php
@@ -1,218 +1,14 @@
- * Hook::register('grapher', 'My\\Grapher\\Class');
- *
+ * Deprecated, please use Icinga\Application\Hook instead
*/
-class Hook
+class Hook extends NewHookImplementation
{
- /**
- * Our hook name registry
- *
- * @var array
- */
- protected static $hooks = array();
-
- /**
- * Hooks that have already been instantiated
- *
- * @var array
- */
- protected static $instances = array();
-
- /**
- * Namespace prefix
- *
- * @var string
- */
- public static $BASE_NS = 'Icinga\\Web\\Hook\\';
-
- /**
- * Append this string to base class
- *
- * All base classes renamed to *Hook
- *
- * @var string
- */
- public static $classSuffix = 'Hook';
-
- /**
- * Reset object state
- */
- public static function clean()
- {
- self::$hooks = array();
- self::$instances = array();
- self::$BASE_NS = 'Icinga\\Web\\Hook\\';
- }
-
- /**
- * Whether someone registered itself for the given hook name
- *
- * @param string $name One of the predefined hook names
- *
- * @return bool
- */
- public static function has($name)
- {
- return array_key_exists($name, self::$hooks);
- }
-
- /**
- * Create or return an instance of a given hook
- *
- * TODO: Should return some kind of a hook interface
- *
- * @param string $name One of the predefined hook names
- * @param string $key The identifier of a specific subtype
- *
- * @return mixed
- */
- public static function createInstance($name, $key)
- {
- if (!self::has($name, $key)) {
- return null;
- }
-
- if (isset(self::$instances[$name][$key])) {
- return self::$instances[$name][$key];
- }
-
- $class = self::$hooks[$name][$key];
- try {
- $instance = new $class();
- } catch (Exception $e) {
- Logger::debug(
- 'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
- $name,
- $key,
- $class,
- $e->getMessage()
- );
- // TODO: Persist unloading for "some time" or "current session"
- unset(self::$hooks[$name][$key]);
- return null;
- }
-
- self::assertValidHook($instance, $name);
- self::$instances[$name][$key] = $instance;
- return $instance;
- }
-
- protected static function splitHookName($name)
- {
- $sep = '\\';
- if (false === $module = strpos($name, $sep)) {
- return array(null, $name);
- }
- return array(
- substr($name, 0, $module),
- substr($name, $module + 1)
- );
- }
-
- /**
- * Test for a valid class name
- *
- * @param mixed $instance
- * @param string $name
- *
- * @throws ProgrammingError
- */
- private static function assertValidHook($instance, $name)
- {
- $suffix = self::$classSuffix; // 'Hook'
- $base = self::$BASE_NS; // 'Icinga\\Web\\Hook\\'
-
- list($module, $name) = self::splitHookName($name);
-
- if ($module === null) {
- $base_class = $base . ucfirst($name) . 'Hook';
-
- // I'm unsure whether this makes sense. Unused and Wrong.
- if (strpos($base_class, $suffix) === false) {
- $base_class .= $suffix;
- }
- } else {
- $base_class = 'Icinga\\Module\\'
- . ucfirst($module)
- . '\\Web\\Hook\\'
- . ucfirst($name)
- . $suffix;
- }
-
- if (!$instance instanceof $base_class) {
- throw new ProgrammingError(
- '%s is not an instance of %s',
- get_class($instance),
- $base_class
- );
- }
- }
-
- /**
- * Return all instances of a specific name
- *
- * @param string $name One of the predefined hook names
- *
- * @return array
- */
- public static function all($name)
- {
- if (!self::has($name)) {
- return array();
- }
-
- foreach (self::$hooks[$name] as $key => $hook) {
- if (self::createInstance($name, $key) === null) {
- return array();
- }
- }
-
- return self::$instances[$name];
- }
-
- /**
- * Get the first hook
- *
- * @param string $name One of the predefined hook names
- *
- * @return null|mixed
- */
- public static function first($name)
- {
- if (self::has($name)) {
- return self::createInstance($name, key(self::$hooks[$name]));
- }
- }
-
- /**
- * Register a class
- *
- * @param string $name One of the predefined hook names
- * @param string $key The identifier of a specific subtype
- * @param string $class Your class name, must inherit one of the
- * classes in the Icinga/Web/Hook folder
- */
- public static function register($name, $key, $class)
- {
- if (!isset(self::$hooks[$name])) {
- self::$hooks[$name] = array();
- }
-
- self::$hooks[$name][$key] = $class;
- }
}
diff --git a/library/Icinga/Web/Hook/GrapherHook.php b/library/Icinga/Web/Hook/GrapherHook.php
index 39edf86e4..377ed24b2 100644
--- a/library/Icinga/Web/Hook/GrapherHook.php
+++ b/library/Icinga/Web/Hook/GrapherHook.php
@@ -3,109 +3,11 @@
namespace Icinga\Web\Hook;
-use Icinga\Exception\ProgrammingError;
-use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Application\Hook\GrapherHook as BaseHook;
/**
- * Icinga Web Grapher Hook base class
+ * Deprecated, compat only.
*
- * Extend this class if you want to integrate your graphing solution nicely into
- * Icinga Web.
+ * Please implement hooks in Icinga\Application\Hook
*/
-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');
- }
-}
+abstract class GrapherHook extends BaseHook {}
diff --git a/library/Icinga/Web/Hook/TicketHook.php b/library/Icinga/Web/Hook/TicketHook.php
index 74af1a91f..63a4aa75c 100644
--- a/library/Icinga/Web/Hook/TicketHook.php
+++ b/library/Icinga/Web/Hook/TicketHook.php
@@ -3,122 +3,11 @@
namespace Icinga\Web\Hook;
-use ErrorException;
-use Exception;
-use Icinga\Application\Logger;
-use Icinga\Exception\IcingaException;
+use Icinga\Application\Hook\TicketHook as BaseHook;
/**
- * Base class for ticket hooks
+ * Deprecated, compat only.
*
- * Extend this class if you want to integrate your ticketing solution Icinga Web 2
+ * Please implement hooks in Icinga\Application\Hook
*/
-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()
- {
- }
-
- /**
- * 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;
- }
-
- /**
- * Get the pattern
- *
- * @return string
- */
- abstract public function getPattern();
-
- /**
- * Create a link for each matched element in the subject text
- *
- * @param array $match Array of matched elements according to {@link getPattern()}
- *
- * @return string Replacement string
- */
- abstract public function createLink($match);
-
- /**
- * 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;
- }
- 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;
- }
-}
+abstract class TicketHook extends BaseHook {}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
index 5edc8f6f1..f615ece11 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
use Zend_Db_Expr;
use Icinga\Application\Icinga;
+use Icinga\Application\Hook;
use Icinga\Application\Logger;
use Icinga\Data\Db\DbQuery;
use Icinga\Data\Filter\Filter;
@@ -119,6 +120,15 @@ abstract class IdoQuery extends DbQuery
*/
protected $joinedVirtualTables = array();
+ /**
+ * A map of virtual table names and corresponding hook instances
+ *
+ * Joins for those tables will be delegated to them
+ *
+ * @var array
+ */
+ protected $hookedVirtualTables = array();
+
/**
* List of column aliases used for sorting the result
*
@@ -593,6 +603,18 @@ abstract class IdoQuery extends DbQuery
return isset($this->caseInsensitiveColumns[$table][$alias]);
}
+ /**
+ * Return our column map
+ *
+ * Might be useful for hooks
+ *
+ * @return array
+ */
+ public function getColumnMap()
+ {
+ return $this->columnMap;
+ }
+
/**
* Apply oracle specific query initialization
*/
@@ -651,6 +673,23 @@ abstract class IdoQuery extends DbQuery
{
parent::init();
$this->prefix = $this->ds->getTablePrefix();
+
+ foreach (Hook::all('monitoring/idoQueryExtension') as $hook) {
+ $extensions = $hook->extendColumnMap($this);
+ if (! is_array($extensions)) continue;
+
+ foreach ($extensions as $vTable => $cols) {
+ if (! array_key_exists($vTable, $this->columnMap)) {
+ $this->hookedVirtualTables[$vTable] = $hook;
+ $this->columMap[$vTable] = array();
+ }
+
+ foreach ($cols as $k => $v) {
+ $this->columnMap[$vTable][$k] = $v;
+ }
+ }
+ }
+
$dbType = $this->ds->getDbType();
if ($dbType === 'oracle') {
$this->initializeForOracle();
@@ -787,7 +826,24 @@ abstract class IdoQuery extends DbQuery
if ($this->hasJoinedVirtualTable($name)) {
return $this;
}
- return $this->joinVirtualTable($name);
+
+ if ($this->virtualTableIsHooked($name)) {
+ return $this->joinHookedVirtualTable($this, $name);
+ } else {
+ return $this->joinVirtualTable($name);
+ }
+ }
+
+ /**
+ * Whether a given virtual table name has been provided by a hook
+ *
+ * @param string $name Virtual table name
+ *
+ * @return boolean
+ */
+ protected function virtualTableIsHooked($name)
+ {
+ return array_key_exists($name, $this->hookedVirtualTables);
}
protected function conflictsWithVirtualTable($name)
@@ -826,6 +882,19 @@ abstract class IdoQuery extends DbQuery
return $this;
}
+ /**
+ * Tell a hook to join a virtual table
+ *
+ * @param String $table
+ * @return $this
+ */
+ protected function joinHookedVirtualTable($table)
+ {
+ $this->hookedVirtualTables[$table]->joinVirtualTable($table);
+ $this->joinedVirtualTables[$table] = true;
+ return $this;
+ }
+
/**
* Get the table for a specific alias
*
diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php
index 50cd436fd..2afb046d5 100644
--- a/modules/monitoring/library/Monitoring/DataView/DataView.php
+++ b/modules/monitoring/library/Monitoring/DataView/DataView.php
@@ -4,6 +4,7 @@
namespace Icinga\Module\Monitoring\DataView;
use IteratorAggregate;
+use Icinga\Application\Hook;
use Icinga\Data\ConnectionInterface;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterMatch;
@@ -122,6 +123,18 @@ abstract class DataView implements QueryInterface, SortRules, FilterColumns, Ite
return $view;
}
+ protected function getHookedColumns()
+ {
+ $columns = array();
+ foreach (Hook::all('monitoring/dataviewExtension') as $hook) {
+ foreach ($hook->getAdditionalQueryColumns($this->getQueryName()) as $col) {
+ $columns[] = $col;
+ }
+ }
+
+ return $columns;
+ }
+
// TODO: This is not the right place for this, move it away
protected function applyUrlFilter($request = null)
{
diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
index 0ab9dd92e..220ce2226 100644
--- a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
+++ b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
@@ -10,7 +10,7 @@ class HostStatus extends DataView
*/
public function getColumns()
{
- return array(
+ return array_merge($this->getHookedColumns(), array(
'instance_name',
'host_name',
'host_display_name',
@@ -63,7 +63,7 @@ class HostStatus extends DataView
'host_problem',
'host_ipv4',
'host_acknowledgement_type'
- );
+ ));
}
/**
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
index db44f62c6..389bc9ae0 100644
--- a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
+++ b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
@@ -10,7 +10,7 @@ class ServiceStatus extends DataView
*/
public function getColumns()
{
- return array(
+ return array_merge($this->getHookedColumns(), array(
'instance_name',
'host_name',
'host_display_name',
@@ -98,7 +98,7 @@ class ServiceStatus extends DataView
'service_modified_service_attributes',
'service_host_name',
'service_acknowledgement_type',
- );
+ ));
}
/**
diff --git a/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php
new file mode 100644
index 000000000..c50d1d747
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php
@@ -0,0 +1,20 @@
+provideAdditionalQueryColumns($queryName);
+
+ if (! is_array($cols)) {
+ return array();
+ }
+
+ return $cols;
+ }
+
+ abstract public function provideAdditionalQueryColumns($queryName);
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php
new file mode 100644
index 000000000..b7923bcc8
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php
@@ -0,0 +1,46 @@
+ url, where title will
+ * be used as link caption. Url should be an Icinga\Web\Url object when
+ * the link should point to an Icinga Web url - otherwise a string would
+ * be fine.
+ *
+ * Mixed example:
+ *
+ * return array(
+ * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($host->host_name),
+ * 'Logstash' => Url::fromPath(
+ * 'logstash/search/syslog',
+ * array('host' => $host->host_name)
+ * )
+ * );
+ *
+ *
+ * One might also provide ssh:// or rdp:// urls if equipped with fitting
+ * (safe) URL handlers for his browser(s).
+ *
+ * TODO: I'd love to see some kind of a Link/LinkSet object implemented
+ * for this and similar hooks.
+ *
+ * @param Host $host Monitoring host object
+ *
+ * @return array An array containing a list of host action links
+ */
+ abstract public function getActionsForHost(Host $host);
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php
new file mode 100644
index 000000000..ea7f663f1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php
@@ -0,0 +1,15 @@
+ url, where title will
+ * be used as link caption. Url should be an Icinga\Web\Url object when
+ * the link should point to an Icinga Web url - otherwise a string would
+ * be fine.
+ *
+ * Mixed example:
+ *
+ * return array(
+ * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($service->service_name),
+ * 'Logstash' => Url::fromPath(
+ * 'logstash/search/syslog',
+ * array('service' => $service->host_name)
+ * )
+ * );
+ *
+ *
+ * One might also provide ssh:// or rdp:// urls if equipped with fitting
+ * (safe) URL handlers for his browser(s).
+ *
+ * TODO: I'd love to see some kind of a Link/LinkSet object implemented
+ * for this and similar hooks.
+ *
+ * @param Service $service Monitoring service object
+ *
+ * @return array An array containing a list of service action links
+ */
+ abstract public function getActionsForService(Service $service);
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php
new file mode 100644
index 000000000..2b9524828
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php
@@ -0,0 +1,37 @@
+ url, where title will
- * be used as link caption. Url should be an Icinga\Web\Url object when
- * the link should point to an Icinga Web url - otherwise a string would
- * be fine.
- *
- * Mixed example:
- *
- * return array(
- * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($host->host_name),
- * 'Logstash' => Url::fromPath(
- * 'logstash/search/syslog',
- * array('host' => $host->host_name)
- * )
- * );
- *
- *
- * One might also provide ssh:// or rdp:// urls if equipped with fitting
- * (safe) URL handlers for his browser(s).
- *
- * TODO: I'd love to see some kind of a Link/LinkSet object implemented
- * for this and similar hooks.
- *
- * @param Host $host Monitoring host object
- *
- * @return array An array containing a list of host action links
- */
- abstract public function getActionsForHost(Host $host);
-}
+abstract class HostActionsHook extends BaseHook {}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
index 2df3c4f55..b118f8d58 100644
--- a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
+++ b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
@@ -3,44 +3,11 @@
namespace Icinga\Module\Monitoring\Web\Hook;
-use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Module\Monitoring\Hook\ServiceActionsHook as BaseHook;
/**
- * Base class for host action hooks
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
*/
-abstract class ServiceActionsHook
-{
- /**
- * Implementors of this method should return an array containing
- * additional action links for a specific host. You get a full Service
- * object, which allows you to return specific links only for nodes
- * with specific properties.
- *
- * The result array should be in the form title => url, where title will
- * be used as link caption. Url should be an Icinga\Web\Url object when
- * the link should point to an Icinga Web url - otherwise a string would
- * be fine.
- *
- * Mixed example:
- *
- * return array(
- * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($service->service_name),
- * 'Logstash' => Url::fromPath(
- * 'logstash/search/syslog',
- * array('service' => $service->host_name)
- * )
- * );
- *
- *
- * One might also provide ssh:// or rdp:// urls if equipped with fitting
- * (safe) URL handlers for his browser(s).
- *
- * TODO: I'd love to see some kind of a Link/LinkSet object implemented
- * for this and similar hooks.
- *
- * @param Service $service Monitoring service object
- *
- * @return array An array containing a list of service action links
- */
- abstract public function getActionsForService(Service $service);
-}
+abstract class ServiceActionsHook extends BaseHook {}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
index 0af65d303..ba01d1e03 100644
--- a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
+++ b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
@@ -3,35 +3,11 @@
namespace Icinga\Module\Monitoring\Web\Hook;
-use Icinga\Module\Monitoring\Timeline\TimeRange;
+use Icinga\Module\Monitoring\Hook\TimelineProviderHook as BaseHook;
/**
- * Base class for TimeLine providers
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
*/
-abstract class TimelineProviderHook
-{
- /**
- * Return the names by which to group entries
- *
- * @return array An array with the names as keys and their attribute-lists as values
- */
- abstract public function getIdentifiers();
-
- /**
- * Return the visible entries supposed to be shown on the timeline
- *
- * @param TimeRange $range The range of time for which to fetch entries
- *
- * @return array The entries to display on the timeline
- */
- abstract public function fetchEntries(TimeRange $range);
-
- /**
- * Return the entries supposed to be used to calculate forecasts
- *
- * @param TimeRange $range The range of time for which to fetch forecasts
- *
- * @return array The entries to calculate forecasts with
- */
- abstract public function fetchForecasts(TimeRange $range);
-}
+abstract class TimelineProviderHook extends BaseHook {}