2013-06-03 17:02:08 +02:00
|
|
|
<?php
|
2016-02-08 15:41:00 +01:00
|
|
|
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
|
2013-08-20 19:33:15 +02:00
|
|
|
|
2015-11-12 17:44:29 +01:00
|
|
|
namespace Icinga\Application;
|
2013-06-07 13:29:11 +02:00
|
|
|
|
2014-04-24 14:40:49 +02:00
|
|
|
use Exception;
|
2016-11-11 16:15:16 +01:00
|
|
|
use Icinga\Authentication\Auth;
|
|
|
|
use Icinga\Application\Modules\Manager;
|
2014-02-18 18:48:13 +01:00
|
|
|
use Icinga\Exception\ProgrammingError;
|
2013-06-03 17:02:08 +02:00
|
|
|
|
2013-06-27 13:42:48 +02:00
|
|
|
/**
|
2015-11-12 17:58:19 +01:00
|
|
|
* Icinga Hook registry
|
2013-06-27 13:42:48 +02:00
|
|
|
*
|
2013-06-27 13:33:46 +02:00
|
|
|
* Modules making use of predefined hooks have to use this registry
|
2013-06-27 13:42:48 +02:00
|
|
|
*
|
2013-06-27 13:33:46 +02:00
|
|
|
* Usage:
|
|
|
|
* <code>
|
|
|
|
* Hook::register('grapher', 'My\\Grapher\\Class');
|
|
|
|
* </code>
|
2013-06-27 13:42:48 +02:00
|
|
|
*/
|
2013-06-03 17:02:08 +02:00
|
|
|
class Hook
|
|
|
|
{
|
2013-06-07 13:29:11 +02:00
|
|
|
/**
|
2013-06-27 13:33:46 +02:00
|
|
|
* Our hook name registry
|
|
|
|
*
|
2013-06-07 13:29:11 +02:00
|
|
|
* @var array
|
|
|
|
*/
|
2013-06-03 17:02:08 +02:00
|
|
|
protected static $hooks = array();
|
2013-06-07 13:29:11 +02:00
|
|
|
|
|
|
|
/**
|
2013-06-27 13:33:46 +02:00
|
|
|
* Hooks that have already been instantiated
|
|
|
|
*
|
2013-06-07 13:29:11 +02:00
|
|
|
* @var array
|
|
|
|
*/
|
2013-06-03 17:02:08 +02:00
|
|
|
protected static $instances = array();
|
2013-06-07 13:29:11 +02:00
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
/**
|
2013-08-20 19:33:15 +02:00
|
|
|
* Namespace prefix
|
|
|
|
*
|
2013-06-28 11:16:35 +02:00
|
|
|
* @var string
|
|
|
|
*/
|
2015-11-12 17:58:19 +01:00
|
|
|
public static $BASE_NS = 'Icinga\\Application\\Hook\\';
|
2013-06-28 11:16:35 +02:00
|
|
|
|
2014-08-27 11:13:41 +02:00
|
|
|
/**
|
|
|
|
* Append this string to base class
|
|
|
|
*
|
|
|
|
* All base classes renamed to *Hook
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public static $classSuffix = 'Hook';
|
|
|
|
|
2013-08-20 19:33:15 +02:00
|
|
|
/**
|
|
|
|
* Reset object state
|
|
|
|
*/
|
2013-06-28 11:16:35 +02:00
|
|
|
public static function clean()
|
|
|
|
{
|
|
|
|
self::$hooks = array();
|
|
|
|
self::$instances = array();
|
2015-11-12 17:58:19 +01:00
|
|
|
self::$BASE_NS = 'Icinga\\Application\\Hook\\';
|
2013-06-28 11:16:35 +02:00
|
|
|
}
|
|
|
|
|
2013-06-07 13:29:11 +02:00
|
|
|
/**
|
2013-06-27 13:33:46 +02:00
|
|
|
* Whether someone registered itself for the given hook name
|
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @param string $name One of the predefined hook names
|
2013-06-07 13:29:11 +02:00
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @return bool
|
2013-06-07 13:29:11 +02:00
|
|
|
*/
|
2013-06-27 13:33:46 +02:00
|
|
|
public static function has($name)
|
2013-06-03 17:02:08 +02:00
|
|
|
{
|
2015-11-12 19:52:20 +01:00
|
|
|
$name = self::normalizeHookName($name);
|
2018-07-26 11:51:23 +02:00
|
|
|
|
|
|
|
if (! array_key_exists($name, self::$hooks)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-08-16 17:03:42 +02:00
|
|
|
foreach (self::$hooks[$name] as $hook) {
|
|
|
|
if (self::hasPermission($hook)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2018-07-26 11:51:23 +02:00
|
|
|
|
2018-08-16 17:03:42 +02:00
|
|
|
return false;
|
2013-06-03 17:02:08 +02:00
|
|
|
}
|
|
|
|
|
2015-11-12 19:52:20 +01:00
|
|
|
protected static function normalizeHookName($name)
|
|
|
|
{
|
|
|
|
if (strpos($name, '\\') === false) {
|
|
|
|
$parts = explode('/', $name);
|
|
|
|
foreach ($parts as & $part) {
|
|
|
|
$part = ucfirst($part);
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode('\\', $parts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
2013-06-07 13:29:11 +02:00
|
|
|
/**
|
2014-04-24 14:40:49 +02:00
|
|
|
* Create or return an instance of a given hook
|
2013-06-27 13:33:46 +02:00
|
|
|
*
|
|
|
|
* TODO: Should return some kind of a hook interface
|
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @param string $name One of the predefined hook names
|
|
|
|
* @param string $key The identifier of a specific subtype
|
2013-08-20 19:33:15 +02:00
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @return mixed
|
2013-06-28 11:16:35 +02:00
|
|
|
*/
|
|
|
|
public static function createInstance($name, $key)
|
|
|
|
{
|
2015-11-12 19:52:20 +01:00
|
|
|
$name = self::normalizeHookName($name);
|
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
if (!self::has($name, $key)) {
|
|
|
|
return null;
|
|
|
|
}
|
2014-04-24 14:40:49 +02:00
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
if (isset(self::$instances[$name][$key])) {
|
|
|
|
return self::$instances[$name][$key];
|
|
|
|
}
|
2014-04-24 14:40:49 +02:00
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
$class = self::$hooks[$name][$key];
|
2015-11-17 17:04:04 +01:00
|
|
|
|
|
|
|
if (! class_exists($class)) {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
'Erraneous hook implementation, class "%s" does not exist',
|
|
|
|
$class
|
|
|
|
);
|
|
|
|
}
|
2013-06-28 11:16:35 +02:00
|
|
|
try {
|
|
|
|
$instance = new $class();
|
2014-04-24 14:40:49 +02:00
|
|
|
} catch (Exception $e) {
|
2014-02-26 11:19:52 +01:00
|
|
|
Logger::debug(
|
2013-06-28 11:16:35 +02:00
|
|
|
'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
|
|
|
|
$name,
|
|
|
|
$key,
|
|
|
|
$class,
|
|
|
|
$e->getMessage()
|
|
|
|
);
|
2014-04-24 14:40:49 +02:00
|
|
|
// TODO: Persist unloading for "some time" or "current session"
|
2013-06-28 11:16:35 +02:00
|
|
|
unset(self::$hooks[$name][$key]);
|
|
|
|
return null;
|
|
|
|
}
|
2014-04-24 14:40:49 +02:00
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
self::assertValidHook($instance, $name);
|
|
|
|
self::$instances[$name][$key] = $instance;
|
|
|
|
return $instance;
|
|
|
|
}
|
|
|
|
|
2015-01-14 10:58:36 +01:00
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-11-11 16:15:16 +01:00
|
|
|
/**
|
|
|
|
* Extract the Icinga module name from a given namespaced class name
|
|
|
|
*
|
|
|
|
* Does no validation, prefix must have been checked before
|
|
|
|
*
|
|
|
|
* Shameless copy of ClassLoader::extractModuleName()
|
|
|
|
*
|
|
|
|
* @param string $class The hook's class path
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected static function extractModuleName($class)
|
|
|
|
{
|
|
|
|
return lcfirst(
|
|
|
|
substr(
|
|
|
|
$class,
|
|
|
|
ClassLoader::MODULE_PREFIX_LENGTH,
|
|
|
|
strpos(
|
|
|
|
$class,
|
|
|
|
ClassLoader::NAMESPACE_SEPARATOR,
|
|
|
|
ClassLoader::MODULE_PREFIX_LENGTH + 1
|
|
|
|
) - ClassLoader::MODULE_PREFIX_LENGTH
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return whether the user has the permission to access the module which provides the given hook
|
|
|
|
*
|
|
|
|
* @param string $class The hook's class path
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected static function hasPermission($class)
|
|
|
|
{
|
2016-12-19 21:14:32 +01:00
|
|
|
if (Icinga::app()->isCli()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-11 16:15:16 +01:00
|
|
|
return Auth::getInstance()->hasPermission(
|
|
|
|
Manager::MODULE_PERMISSION_NS . self::extractModuleName($class)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
/**
|
|
|
|
* Test for a valid class name
|
2013-08-20 19:33:15 +02:00
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @param mixed $instance
|
|
|
|
* @param string $name
|
|
|
|
*
|
|
|
|
* @throws ProgrammingError
|
2013-06-28 11:16:35 +02:00
|
|
|
*/
|
2014-04-24 14:40:49 +02:00
|
|
|
private static function assertValidHook($instance, $name)
|
2013-06-28 11:16:35 +02:00
|
|
|
{
|
2015-11-12 19:52:20 +01:00
|
|
|
$name = self::normalizeHookName($name);
|
|
|
|
|
2015-01-14 10:58:36 +01:00
|
|
|
$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)
|
2015-11-12 17:58:19 +01:00
|
|
|
. '\\Hook\\'
|
2015-01-14 10:58:36 +01:00
|
|
|
. ucfirst($name)
|
|
|
|
. $suffix;
|
2014-09-04 12:15:24 +02:00
|
|
|
}
|
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
if (!$instance instanceof $base_class) {
|
2015-11-12 18:30:18 +01:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
throw new ProgrammingError(
|
2014-08-26 11:15:19 +02:00
|
|
|
'%s is not an instance of %s',
|
|
|
|
get_class($instance),
|
|
|
|
$base_class
|
2013-06-28 11:16:35 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return all instances of a specific name
|
2013-08-20 19:33:15 +02:00
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @param string $name One of the predefined hook names
|
2013-08-20 19:33:15 +02:00
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @return array
|
2013-06-28 11:16:35 +02:00
|
|
|
*/
|
|
|
|
public static function all($name)
|
|
|
|
{
|
2015-11-13 14:27:46 +01:00
|
|
|
$name = self::normalizeHookName($name);
|
2016-11-11 16:15:16 +01:00
|
|
|
if (! self::has($name)) {
|
2013-06-28 11:16:35 +02:00
|
|
|
return array();
|
|
|
|
}
|
2014-04-24 14:40:49 +02:00
|
|
|
|
2013-06-28 11:16:35 +02:00
|
|
|
foreach (self::$hooks[$name] as $key => $hook) {
|
2017-01-17 14:56:13 +01:00
|
|
|
if (self::hasPermission($hook)) {
|
2016-11-11 16:15:16 +01:00
|
|
|
if (self::createInstance($name, $key) === null) {
|
|
|
|
return array();
|
|
|
|
}
|
2013-06-28 11:16:35 +02:00
|
|
|
}
|
|
|
|
}
|
2014-04-24 14:40:49 +02:00
|
|
|
|
2016-11-11 16:15:16 +01:00
|
|
|
return isset(self::$instances[$name]) ? self::$instances[$name] : array();
|
2013-06-28 11:16:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-08-20 19:33:15 +02:00
|
|
|
* Get the first hook
|
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @param string $name One of the predefined hook names
|
2013-08-20 19:33:15 +02:00
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @return null|mixed
|
2013-06-28 11:16:35 +02:00
|
|
|
*/
|
|
|
|
public static function first($name)
|
|
|
|
{
|
2015-11-12 19:52:20 +01:00
|
|
|
$name = self::normalizeHookName($name);
|
|
|
|
|
2014-04-24 14:40:49 +02:00
|
|
|
if (self::has($name)) {
|
2016-11-11 16:15:16 +01:00
|
|
|
foreach (self::$hooks[$name] as $key => $hook) {
|
2017-01-17 14:56:13 +01:00
|
|
|
if (self::hasPermission($hook)) {
|
2016-11-11 16:15:16 +01:00
|
|
|
return self::createInstance($name, $key);
|
|
|
|
}
|
|
|
|
}
|
2014-04-24 14:40:49 +02:00
|
|
|
}
|
2013-06-28 11:16:35 +02:00
|
|
|
}
|
|
|
|
|
2013-06-27 13:42:48 +02:00
|
|
|
/**
|
|
|
|
* Register a class
|
|
|
|
*
|
2014-04-24 14:40:49 +02:00
|
|
|
* @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
|
2015-11-12 17:58:19 +01:00
|
|
|
* classes in the Icinga/Application/Hook folder
|
2013-06-27 13:42:48 +02:00
|
|
|
*/
|
2014-09-12 09:53:38 +02:00
|
|
|
public static function register($name, $key, $class)
|
2013-06-27 13:42:48 +02:00
|
|
|
{
|
2015-11-12 19:52:20 +01:00
|
|
|
$name = self::normalizeHookName($name);
|
|
|
|
|
2013-06-27 13:42:48 +02:00
|
|
|
if (!isset(self::$hooks[$name])) {
|
|
|
|
self::$hooks[$name] = array();
|
|
|
|
}
|
|
|
|
|
2017-01-17 14:56:13 +01:00
|
|
|
$class = ltrim($class, ClassLoader::NAMESPACE_SEPARATOR);
|
|
|
|
|
2013-06-03 17:02:08 +02:00
|
|
|
self::$hooks[$name][$key] = $class;
|
|
|
|
}
|
|
|
|
}
|