summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Application/Modules/Module.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Application/Modules/Module.php')
-rw-r--r--library/Icinga/Application/Modules/Module.php1451
1 files changed, 1451 insertions, 0 deletions
diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php
new file mode 100644
index 0000000..6a5afb8
--- /dev/null
+++ b/library/Icinga/Application/Modules/Module.php
@@ -0,0 +1,1451 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Modules;
+
+use Exception;
+use Icinga\Application\ApplicationBootstrap;
+use Icinga\Application\Config;
+use Icinga\Application\Hook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Setup\SetupWizard;
+use Icinga\Util\File;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Widget;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\StaticTranslator;
+use ipl\I18n\Translation;
+use Zend_Controller_Router_Route;
+use Zend_Controller_Router_Route_Abstract;
+use Zend_Controller_Router_Route_Regex;
+
+/**
+ * Module handling
+ *
+ * Register modules and initialize it
+ */
+class Module
+{
+ use Translation {
+ translate as protected;
+ translatePlural as protected;
+ }
+
+ /**
+ * Module name
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * Base directory of module
+ *
+ * @var string
+ */
+ private $basedir;
+
+ /**
+ * Directory for styles
+ *
+ * @var string
+ */
+ private $cssdir;
+
+ /**
+ * Directory for Javascript
+ *
+ * @var string
+ */
+ private $jsdir;
+
+ /**
+ * Base application directory
+ *
+ * @var string
+ */
+ private $appdir;
+
+ /**
+ * Library directory
+ *
+ * @var string
+ */
+ private $libdir;
+
+ /**
+ * Config directory
+ *
+ * @var string
+ */
+ private $configdir;
+
+ /**
+ * Directory containing translations
+ *
+ * @var string
+ */
+ private $localedir;
+
+ /**
+ * Directory where controllers reside
+ *
+ * @var string
+ */
+ private $controllerdir;
+
+ /**
+ * Directory containing form implementations
+ *
+ * @var string
+ */
+ private $formdir;
+
+ /**
+ * Module bootstrapping script
+ *
+ * @var string
+ */
+ private $runScript;
+
+ /**
+ * Module configuration script
+ *
+ * @var string
+ */
+ private $configScript;
+
+ /**
+ * Module metadata filename
+ *
+ * @var string
+ */
+ private $metadataFile;
+
+ /**
+ * Module metadata (version...)
+ *
+ * @var object
+ */
+ private $metadata;
+
+ /**
+ * Whether we already tried to include the module configuration script
+ *
+ * @var bool
+ */
+ private $triedToLaunchConfigScript = false;
+
+ /**
+ * Whether the module's namespaces have been registered on our autoloader
+ *
+ * @var bool
+ */
+ protected $registeredAutoloader = false;
+
+ /**
+ * Whether this module has been registered
+ *
+ * @var bool
+ */
+ private $registered = false;
+
+ /**
+ * Provided permissions
+ *
+ * @var array
+ */
+ private $permissionList = array();
+
+ /**
+ * Provided restrictions
+ *
+ * @var array
+ */
+ private $restrictionList = array();
+
+ /**
+ * Provided config tabs
+ *
+ * @var array
+ */
+ private $configTabs = array();
+
+ /**
+ * Provided setup wizard
+ *
+ * @var string
+ */
+ private $setupWizard;
+
+ /**
+ * Icinga application
+ *
+ * @var \Icinga\Application\Web
+ */
+ private $app;
+
+ /**
+ * The CSS/LESS files this module provides
+ *
+ * @var array
+ */
+ protected $cssFiles = array();
+
+ /**
+ * The Javascript files this module provides
+ *
+ * @var array
+ */
+ protected $jsFiles = array();
+
+ /**
+ * Routes to add to the route chain
+ *
+ * @var array Array of name-route pairs
+ *
+ * @see addRoute()
+ */
+ protected $routes = array();
+
+ /**
+ * A set of menu elements
+ *
+ * @var MenuItemContainer[]
+ */
+ protected $menuItems = array();
+
+ /**
+ * A set of Pane elements
+ *
+ * @var array
+ */
+ protected $paneItems = array();
+
+ /**
+ * A set of objects representing a searchUrl configuration
+ *
+ * @var array
+ */
+ protected $searchUrls = array();
+
+ /**
+ * This module's user backends providing several authentication mechanisms
+ *
+ * @var array
+ */
+ protected $userBackends = array();
+
+ /**
+ * This module's user group backends
+ *
+ * @var array
+ */
+ protected $userGroupBackends = array();
+
+ /**
+ * This module's configurable navigation items
+ *
+ * @var array
+ */
+ protected $navigationItems = array();
+
+ /**
+ * Create a new module object
+ *
+ * @param ApplicationBootstrap $app
+ * @param string $name
+ * @param string $basedir
+ */
+ public function __construct(ApplicationBootstrap $app, $name, $basedir)
+ {
+ $this->app = $app;
+ $this->name = $name;
+ $this->basedir = $basedir;
+ $this->cssdir = $basedir . '/public/css';
+ $this->jsdir = $basedir . '/public/js';
+ $this->libdir = $basedir . '/library';
+ $this->configdir = $app->getConfigDir('modules/' . $name);
+ $this->appdir = $basedir . '/application';
+ $this->localedir = $basedir . '/application/locale';
+ $this->formdir = $basedir . '/application/forms';
+ $this->controllerdir = $basedir . '/application/controllers';
+ $this->runScript = $basedir . '/run.php';
+ $this->configScript = $basedir . '/configuration.php';
+ $this->metadataFile = $basedir . '/module.info';
+
+ $this->translationDomain = $name;
+ }
+
+ /**
+ * Provide a search URL
+ *
+ * @param string $title
+ * @param string $url
+ * @param int $priority
+ *
+ * @return $this
+ */
+ public function provideSearchUrl($title, $url, $priority = 0)
+ {
+ $this->searchUrls[] = (object) array(
+ 'title' => (string) $title,
+ 'url' => (string) $url,
+ 'priority' => (int) $priority
+ );
+
+ return $this;
+ }
+
+ /**
+ * Get this module's search urls
+ *
+ * @return array
+ */
+ public function getSearchUrls()
+ {
+ $this->launchConfigScript();
+ return $this->searchUrls;
+ }
+
+ /**
+ * Return this module's dashboard
+ *
+ * @return Navigation
+ */
+ public function getDashboard()
+ {
+ $this->launchConfigScript();
+ return $this->createDashboard($this->paneItems);
+ }
+
+ /**
+ * Create and return a new navigation for the given dashboard panes
+ *
+ * @param DashboardContainer[] $panes
+ *
+ * @return Navigation
+ */
+ public function createDashboard(array $panes)
+ {
+ $navigation = new Navigation();
+ foreach ($panes as $pane) {
+ /** @var DashboardContainer $pane */
+ $dashlets = [];
+ foreach ($pane->getDashlets() as $dashletName => $dashletConfig) {
+ $dashlets[$dashletName] = [
+ 'label' => $this->translate($dashletName),
+ 'url' => $dashletConfig['url'],
+ 'priority' => $dashletConfig['priority']
+ ];
+ }
+
+ $navigation->addItem(
+ $pane->getName(),
+ array_merge(
+ $pane->getProperties(),
+ array(
+ 'label' => $this->translate($pane->getName()),
+ 'type' => 'dashboard-pane',
+ 'children' => $dashlets
+ )
+ )
+ );
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Add or get a dashboard pane
+ *
+ * @param string $name
+ * @param array $properties
+ *
+ * @return DashboardContainer
+ */
+ protected function dashboard($name, array $properties = array())
+ {
+ if (array_key_exists($name, $this->paneItems)) {
+ $this->paneItems[$name]->setProperties($properties);
+ } else {
+ $this->paneItems[$name] = new DashboardContainer($name, $properties);
+ }
+
+ return $this->paneItems[$name];
+ }
+
+ /**
+ * Return this module's menu
+ *
+ * @return Navigation
+ */
+ public function getMenu()
+ {
+ $this->launchConfigScript();
+ return Navigation::fromArray($this->createMenu($this->menuItems));
+ }
+
+ /**
+ * Create and return an array structure for the given menu items
+ *
+ * @param MenuItemContainer[] $items
+ *
+ * @return array
+ */
+ private function createMenu(array $items)
+ {
+ $navigation = array();
+ foreach ($items as $item) {
+ /** @var MenuItemContainer $item */
+ $properties = $item->getProperties();
+ $properties['children'] = $this->createMenu($item->getChildren());
+ if (! isset($properties['label'])) {
+ $properties['label'] = $this->translate($item->getName());
+ }
+
+ $navigation[$item->getName()] = $properties;
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Add or get a menu section
+ *
+ * @param string $name
+ * @param array $properties
+ *
+ * @return MenuItemContainer
+ */
+ protected function menuSection($name, array $properties = array())
+ {
+ if (array_key_exists($name, $this->menuItems)) {
+ $this->menuItems[$name]->setProperties($properties);
+ } else {
+ $this->menuItems[$name] = new MenuItemContainer($name, $properties);
+ }
+
+ return $this->menuItems[$name];
+ }
+
+ /**
+ * Register module
+ *
+ * @return bool
+ */
+ public function register()
+ {
+ if ($this->registered) {
+ return true;
+ }
+
+ $this->registerAutoloader();
+ try {
+ $this->launchRunScript();
+ } catch (Exception $e) {
+ Logger::warning(
+ 'Launching the run script %s for module %s failed with the following exception: %s',
+ $this->runScript,
+ $this->name,
+ $e->getMessage()
+ );
+ return false;
+ }
+ $this->registerWebIntegration();
+ $this->registered = true;
+
+ return true;
+ }
+
+ /**
+ * Get whether this module has been registered
+ *
+ * @return bool
+ */
+ public function isRegistered()
+ {
+ return $this->registered;
+ }
+
+ /**
+ * Test for an enabled module by name
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public static function exists($name)
+ {
+ return Icinga::app()->getModuleManager()->hasEnabled($name);
+ }
+
+ /**
+ * Get a module by name
+ *
+ * @param string $name
+ * @param bool $autoload
+ *
+ * @return self
+ *
+ * @throws ProgrammingError When the module is not yet loaded
+ */
+ public static function get($name, $autoload = false)
+ {
+ $manager = Icinga::app()->getModuleManager();
+ if (!$manager->hasLoaded($name)) {
+ if ($autoload === true && $manager->hasEnabled($name)) {
+ $manager->loadModule($name);
+ }
+ }
+ // Throws ProgrammingError when the module is not yet loaded
+ return $manager->getModule($name);
+ }
+
+ /**
+ * Provide an additional CSS/LESS file
+ *
+ * @param string $path The path to the file, relative to self::$cssdir
+ *
+ * @return $this
+ */
+ protected function provideCssFile($path)
+ {
+ $this->cssFiles[] = $this->cssdir . DIRECTORY_SEPARATOR . $path;
+ return $this;
+ }
+
+ /**
+ * Test if module provides css
+ *
+ * @return bool
+ */
+ public function hasCss()
+ {
+ if (file_exists($this->getCssFilename())) {
+ return true;
+ }
+
+ $this->launchConfigScript();
+ return !empty($this->cssFiles);
+ }
+
+ /**
+ * Returns the complete less file name
+ *
+ * @return string
+ */
+ public function getCssFilename()
+ {
+ return $this->cssdir . '/module.less';
+ }
+
+ /**
+ * Return the CSS/LESS files this module provides
+ *
+ * @return array
+ */
+ public function getCssFiles()
+ {
+ $this->launchConfigScript();
+ $files = $this->cssFiles;
+ if (file_exists($this->getCssFilename())) {
+ $files[] = $this->getCssFilename();
+ }
+ return $files;
+ }
+
+ /**
+ * Provide an additional Javascript file
+ *
+ * @param string $path The path to the file, relative to self::$jsdir
+ *
+ * @return $this
+ */
+ protected function provideJsFile($path)
+ {
+ $this->jsFiles[] = $this->jsdir . DIRECTORY_SEPARATOR . $path;
+ return $this;
+ }
+
+ /**
+ * Test if module provides js
+ *
+ * @return bool
+ */
+ public function hasJs()
+ {
+ if (file_exists($this->getJsFilename())) {
+ return true;
+ }
+
+ $this->launchConfigScript();
+ return !empty($this->jsFiles);
+ }
+
+ /**
+ * Returns the complete js file name
+ *
+ * @return string
+ */
+ public function getJsFilename()
+ {
+ return $this->jsdir . '/module.js';
+ }
+
+ /**
+ * Return the Javascript files this module provides
+ *
+ * @return array
+ */
+ public function getJsFiles()
+ {
+ $this->launchConfigScript();
+ $files = $this->jsFiles;
+ $files[] = $this->getJsFilename();
+ return $files;
+ }
+
+ /**
+ * Get the module name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get the module namespace
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return 'Icinga\\Module\\' . ucfirst($this->getName());
+ }
+
+ /**
+ * Get the module version
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->metadata()->version;
+ }
+
+ /**
+ * Get the module description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->metadata()->description;
+ }
+
+ /**
+ * Get the module title (short description)
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->metadata()->title;
+ }
+
+ /**
+ * Get the module dependencies
+ *
+ * @return array
+ * @deprecated Use method getRequiredModules() instead
+ */
+ public function getDependencies()
+ {
+ return $this->metadata()->depends;
+ }
+
+ /**
+ * Get required libraries
+ *
+ * @return array
+ */
+ public function getRequiredLibraries()
+ {
+ $requiredLibraries = $this->metadata()->libraries;
+
+ // Register module requirements for ipl and reactbundle as library requirements
+ $requiredModules = $this->metadata()->modules ?: $this->metadata()->depends;
+ if (isset($requiredModules['ipl']) && ! isset($requiredLibraries['icinga-php-library'])) {
+ $requiredLibraries['icinga-php-library'] = $requiredModules['ipl'];
+ }
+
+ if (isset($requiredModules['reactbundle']) && ! isset($requiredLibraries['icinga-php-thirdparty'])) {
+ $requiredLibraries['icinga-php-thirdparty'] = $requiredModules['reactbundle'];
+ }
+
+ return $requiredLibraries;
+ }
+
+ /**
+ * Get required modules
+ *
+ * @return array
+ */
+ public function getRequiredModules()
+ {
+ $requiredModules = $this->metadata()->modules ?: $this->metadata()->depends;
+
+ $hasIcingadb = isset($requiredModules['icingadb']);
+ if (isset($requiredModules['monitoring']) && ($this->isSupportingIcingadb() || $hasIcingadb)) {
+ $requiredMods = [];
+ $icingadbVersion = true;
+ if ($hasIcingadb) {
+ $icingadbVersion = isset($requiredModules['icingadb']) ? $requiredModules['icingadb'] : true;
+ unset($requiredModules['icingadb']);
+ }
+
+ foreach ($requiredModules as $name => $version) {
+ $requiredMods[$name] = $version;
+ if ($name === 'monitoring') {
+ $requiredMods['icingadb'] = $icingadbVersion;
+ }
+ }
+
+ $requiredModules = $requiredMods;
+ }
+
+ // Both modules are deprecated and their successors are now dependencies of web itself
+ unset($requiredModules['ipl'], $requiredModules['reactbundle']);
+
+ return $requiredModules;
+ }
+
+ /**
+ * Check whether module supports icingadb
+ *
+ * @return bool
+ */
+ protected function isSupportingIcingadb()
+ {
+ $icingadbSupportingModules = [
+ 'cube' => '1.2.0',
+ 'jira' => '1.2.0',
+ 'graphite' => '1.2.0',
+ 'director' => '1.9.0',
+ 'toplevelview' => '0.4.0',
+ 'businessprocess' => '2.4.0'
+ ];
+
+ return array_key_exists($this->getName(), $icingadbSupportingModules)
+ && version_compare($this->getVersion(), $icingadbSupportingModules[$this->getName()], '>=');
+ }
+
+ /**
+ * Fetch module metadata
+ *
+ * @return object
+ */
+ protected function metadata()
+ {
+ if ($this->metadata === null) {
+ $metadata = (object) [
+ 'name' => $this->getName(),
+ 'version' => '0.0.0',
+ 'title' => null,
+ 'description' => '',
+ 'depends' => [],
+ 'libraries' => [],
+ 'modules' => []
+ ];
+
+ if (file_exists($this->metadataFile)) {
+ $key = null;
+ $simpleRequires = false;
+ $file = new File($this->metadataFile, 'r');
+ foreach ($file as $lineno => $line) {
+ $line = rtrim($line);
+
+ if ($key === 'description') {
+ if (empty($line)) {
+ $metadata->description .= "\n";
+ continue;
+ } elseif ($line[0] === ' ') {
+ $metadata->description .= $line;
+ continue;
+ }
+ } elseif (empty($line)) {
+ continue;
+ }
+
+ if (strpos($line, ':') === false) {
+ Logger::debug(
+ "Can't process line %d in %s: Line does not specify a key:value pair"
+ . " nor is it part of the description (indented with a single space)",
+ $lineno,
+ $this->metadataFile
+ );
+
+ break;
+ }
+
+ $parts = preg_split('/:\s+/', $line, 2);
+ if (count($parts) === 1) {
+ $parts[] = '';
+ }
+
+ list($key, $val) = $parts;
+
+ $key = strtolower($key);
+ switch ($key) {
+ case 'requires':
+ if ($val) {
+ $simpleRequires = true;
+ $key = 'libraries';
+ } else {
+ break;
+ }
+
+ // Shares the syntax with `Depends`
+ case ' libraries':
+ case ' modules':
+ if ($simpleRequires && $key[0] === ' ') {
+ Logger::debug(
+ 'Can\'t process line %d in %s: Requirements already registered by a previous line',
+ $lineno,
+ $this->metadataFile
+ );
+ break;
+ }
+
+ $key = ltrim($key);
+ // Shares the syntax with `Depends`
+ case 'depends':
+ if (strpos($val, ' ') === false) {
+ $metadata->{$key}[$val] = true;
+ continue 2;
+ }
+
+ $parts = preg_split('/,\s+/', $val);
+ foreach ($parts as $part) {
+ if (preg_match('/^([\w\-\/]+)\s+\((.+)\)$/', $part, $m)) {
+ $metadata->{$key}[$m[1]] = $m[2];
+ } else {
+ $metadata->{$key}[$part] = true;
+ }
+ }
+
+ break;
+ case 'description':
+ if ($metadata->title === null) {
+ $metadata->title = $val;
+ } else {
+ $metadata->description = $val;
+ }
+ break;
+
+ default:
+ $metadata->{$key} = $val;
+ }
+ }
+ }
+
+ if ($metadata->title === null) {
+ $metadata->title = $this->getName();
+ }
+
+ if ($metadata->description === '') {
+ $metadata->description = t(
+ 'This module has no description'
+ );
+ }
+
+ $this->metadata = $metadata;
+ }
+ return $this->metadata;
+ }
+
+ /**
+ * Get the module's CSS directory
+ *
+ * @return string
+ */
+ public function getCssDir()
+ {
+ return $this->cssdir;
+ }
+
+ /**
+ * Get the module's JS directory
+ *
+ * @return string
+ */
+ public function getJsDir()
+ {
+ return $this->jsdir;
+ }
+
+ /**
+ * Get the module's controller directory
+ *
+ * @return string
+ */
+ public function getControllerDir()
+ {
+ return $this->controllerdir;
+ }
+
+ /**
+ * Get the module's base directory
+ *
+ * @return string
+ */
+ public function getBaseDir()
+ {
+ return $this->basedir;
+ }
+
+ /**
+ * Get the module's application directory
+ *
+ * @return string
+ */
+ public function getApplicationDir()
+ {
+ return $this->appdir;
+ }
+
+ /**
+ * Get the module's library directory
+ *
+ * @return string
+ */
+ public function getLibDir()
+ {
+ return $this->libdir;
+ }
+
+ /**
+ * Get the module's configuration directory
+ *
+ * @return string
+ */
+ public function getConfigDir()
+ {
+ return $this->configdir;
+ }
+
+ /**
+ * Get the module's form directory
+ *
+ * @return string
+ */
+ public function getFormDir()
+ {
+ return $this->formdir;
+ }
+
+ /**
+ * Get the module config
+ *
+ * @param string $file
+ *
+ * @return Config
+ */
+ public function getConfig($file = 'config')
+ {
+ return $this->app->getConfig()->module($this->name, $file);
+ }
+
+ /**
+ * Get provided permissions
+ *
+ * @return array
+ */
+ public function getProvidedPermissions()
+ {
+ $this->launchConfigScript();
+ return $this->permissionList;
+ }
+
+ /**
+ * Get provided restrictions
+ *
+ * @return array
+ */
+ public function getProvidedRestrictions()
+ {
+ $this->launchConfigScript();
+ return $this->restrictionList;
+ }
+
+ /**
+ * Whether the module provides the given restriction
+ *
+ * @param string $name Restriction name
+ *
+ * @return bool
+ */
+ public function providesRestriction($name)
+ {
+ $this->launchConfigScript();
+ return array_key_exists($name, $this->restrictionList);
+ }
+
+ /**
+ * Whether the module provides the given permission
+ *
+ * @param string $name Permission name
+ *
+ * @return bool
+ */
+ public function providesPermission($name)
+ {
+ $this->launchConfigScript();
+ return array_key_exists($name, $this->permissionList);
+ }
+
+ /**
+ * Get the module configuration tabs
+ *
+ * @return \Icinga\Web\Widget\Tabs
+ */
+ public function getConfigTabs()
+ {
+ $this->launchConfigScript();
+ $tabs = Widget::create('tabs');
+ /** @var \Icinga\Web\Widget\Tabs $tabs */
+ $tabs->add('info', array(
+ 'url' => 'config/module',
+ 'urlParams' => array('name' => $this->getName()),
+ 'label' => 'Module: ' . $this->getName()
+ ));
+
+ if ($this->app->getModuleManager()->hasEnabled($this->name)) {
+ foreach ($this->configTabs as $name => $config) {
+ $tabs->add($name, $config);
+ }
+ }
+
+ return $tabs;
+ }
+
+ /**
+ * Whether the module provides a setup wizard
+ *
+ * @return bool
+ */
+ public function providesSetupWizard()
+ {
+ $this->launchConfigScript();
+ if ($this->setupWizard && class_exists($this->setupWizard)) {
+ $wizard = new $this->setupWizard;
+ return $wizard instanceof SetupWizard;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the module's setup wizard
+ *
+ * @return SetupWizard
+ */
+ public function getSetupWizard()
+ {
+ return new $this->setupWizard;
+ }
+
+ /**
+ * Get the module's user backends
+ *
+ * @return array
+ */
+ public function getUserBackends()
+ {
+ $this->launchConfigScript();
+ return $this->userBackends;
+ }
+
+ /**
+ * Get the module's user group backends
+ *
+ * @return array
+ */
+ public function getUserGroupBackends()
+ {
+ $this->launchConfigScript();
+ return $this->userGroupBackends;
+ }
+
+ /**
+ * Return this module's configurable navigation items
+ *
+ * @return array
+ */
+ public function getNavigationItems()
+ {
+ $this->launchConfigScript();
+ return $this->navigationItems;
+ }
+
+ /**
+ * Provide a named permission
+ *
+ * @param string $name Unique permission name
+ * @param string $description Permission description
+ *
+ * @throws IcingaException If the permission is already provided
+ */
+ protected function providePermission($name, $description)
+ {
+ if ($this->providesPermission($name)) {
+ throw new IcingaException(
+ 'Cannot provide permission "%s" twice',
+ $name
+ );
+ }
+ $this->permissionList[$name] = (object) array(
+ 'name' => $name,
+ 'description' => $description
+ );
+ }
+
+ /**
+ * Provide a named restriction
+ *
+ * @param string $name Unique restriction name
+ * @param string $description Restriction description
+ *
+ * @throws IcingaException If the restriction is already provided
+ */
+ protected function provideRestriction($name, $description)
+ {
+ if ($this->providesRestriction($name)) {
+ throw new IcingaException(
+ 'Cannot provide restriction "%s" twice',
+ $name
+ );
+ }
+ $this->restrictionList[$name] = (object) array(
+ 'name' => $name,
+ 'description' => $description
+ );
+ }
+
+ /**
+ * Provide a module config tab
+ *
+ * @param string $name Unique tab name
+ * @param array $config Tab config
+ *
+ * @return $this
+ * @throws ProgrammingError If $config lacks the key 'url'
+ */
+ protected function provideConfigTab($name, $config = array())
+ {
+ if (! array_key_exists('url', $config)) {
+ throw new ProgrammingError('A module config tab MUST provide a "url"');
+ }
+ $config['url'] = $this->getName() . '/' . ltrim($config['url'], '/');
+ $this->configTabs[$name] = $config;
+ return $this;
+ }
+
+ /**
+ * Provide a setup wizard
+ *
+ * @param string $className The name of the class
+ *
+ * @return $this
+ */
+ protected function provideSetupWizard($className)
+ {
+ $this->setupWizard = $className;
+ return $this;
+ }
+
+ /**
+ * Provide a user backend capable of authenticating users
+ *
+ * @param string $identifier The identifier of the new backend type
+ * @param string $className The name of the class
+ *
+ * @return $this
+ */
+ protected function provideUserBackend($identifier, $className)
+ {
+ $this->userBackends[strtolower($identifier)] = $className;
+ return $this;
+ }
+
+ /**
+ * Provide a user group backend
+ *
+ * @param string $identifier The identifier of the new backend type
+ * @param string $className The name of the class
+ *
+ * @return $this
+ */
+ protected function provideUserGroupBackend($identifier, $className)
+ {
+ $this->userGroupBackends[strtolower($identifier)] = $className;
+ return $this;
+ }
+
+ /**
+ * Provide a new type of configurable navigation item with a optional label and config filename
+ *
+ * @param string $type
+ * @param string $label
+ * @param string $config
+ *
+ * @return $this
+ */
+ protected function provideNavigationItem($type, $label = null, $config = null)
+ {
+ $this->navigationItems[$type] = array(
+ 'label' => $label,
+ 'config' => $config
+ );
+
+ return $this;
+ }
+
+ /**
+ * Register module namespaces on our class loader
+ *
+ * @return $this
+ */
+ protected function registerAutoloader()
+ {
+ if ($this->registeredAutoloader) {
+ return $this;
+ }
+
+ $moduleName = ucfirst($this->getName());
+
+ $this->app->getLoader()->registerNamespace(
+ 'Icinga\\Module\\' . $moduleName,
+ $this->getLibDir() . '/'. $moduleName,
+ $this->getApplicationDir()
+ );
+
+ $this->registeredAutoloader = true;
+
+ return $this;
+ }
+
+ /**
+ * Bind text domain for i18n
+ *
+ * @return $this
+ */
+ protected function registerLocales()
+ {
+ if ($this->hasLocales() && StaticTranslator::$instance instanceof GettextTranslator) {
+ StaticTranslator::$instance->addTranslationDirectory($this->localedir, $this->name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get whether the module has translations
+ */
+ public function hasLocales()
+ {
+ return file_exists($this->localedir) && is_dir($this->localedir);
+ }
+
+ /**
+ * List all available locales
+ *
+ * @return array Locale list
+ */
+ public function listLocales()
+ {
+ $locales = array();
+ if (! $this->hasLocales()) {
+ return $locales;
+ }
+
+ $dh = opendir($this->localedir);
+ while (false !== ($file = readdir($dh))) {
+ $filename = $this->localedir . DIRECTORY_SEPARATOR . $file;
+ if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) {
+ $locales[] = $file;
+ }
+ }
+ closedir($dh);
+ sort($locales);
+ return $locales;
+ }
+
+ /**
+ * Register web integration
+ *
+ * Add controller directory to mvc
+ *
+ * @return $this
+ */
+ protected function registerWebIntegration()
+ {
+ if (! $this->app->isWeb()) {
+ return $this;
+ }
+
+ return $this
+ ->registerLocales()
+ ->registerRoutes();
+ }
+
+ /**
+ * Add routes for static content and any route added via {@link addRoute()} to the route chain
+ *
+ * @return $this
+ */
+ protected function registerRoutes()
+ {
+ $router = $this->app->getFrontController()->getRouter();
+
+ // TODO: We should not be required to do this. Please check dispatch()
+ $this->app->getFrontController()->addControllerDirectory(
+ $this->getControllerDir(),
+ $this->getName()
+ );
+
+ /** @var \Zend_Controller_Router_Rewrite $router */
+ foreach ($this->routes as $name => $route) {
+ $router->addRoute($name, $route);
+ }
+ $router->addRoute(
+ $this->name . '_jsprovider',
+ new Zend_Controller_Router_Route(
+ 'js/' . $this->name . '/:file',
+ array(
+ 'action' => 'javascript',
+ 'controller' => 'static',
+ 'module' => 'default',
+ 'module_name' => $this->name
+ )
+ )
+ );
+ $router->addRoute(
+ $this->name . '_img',
+ new Zend_Controller_Router_Route_Regex(
+ 'img/' . $this->name . '/(.+)',
+ array(
+ 'action' => 'img',
+ 'controller' => 'static',
+ 'module' => 'default',
+ 'module_name' => $this->name
+ ),
+ array(
+ 1 => 'file'
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Run module bootstrap script
+ *
+ * @return $this
+ */
+ protected function launchRunScript()
+ {
+ return $this->includeScript($this->runScript);
+ }
+
+ /**
+ * Include a php script if it is readable
+ *
+ * @param string $file File to include
+ *
+ * @return $this
+ */
+ protected function includeScript($file)
+ {
+ if (file_exists($file) && is_readable($file)) {
+ include $file;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Run module config script
+ *
+ * @return $this
+ */
+ protected function launchConfigScript()
+ {
+ if ($this->triedToLaunchConfigScript) {
+ return $this;
+ }
+ $this->triedToLaunchConfigScript = true;
+ $this->registerAutoloader();
+ return $this->includeScript($this->configScript);
+ }
+
+ protected function slashesToNamespace($class)
+ {
+ $list = explode('/', $class);
+ foreach ($list as &$part) {
+ $part = ucfirst($part);
+ }
+
+ return implode('\\', $list);
+ }
+
+ /**
+ * Provide a hook implementation
+ *
+ * @param string $name Name of the hook for which to provide an implementation
+ * @param string $implementation Fully qualified name of the class providing the hook implementation.
+ * Defaults to the module's ProvidedHook namespace plus the hook's name for the
+ * class name
+ * @param bool $alwaysRun To run the hook always (e.g. without permission check)
+ *
+ * @return $this
+ */
+ protected function provideHook($name, $implementation = null, $alwaysRun = false)
+ {
+ if ($implementation === null) {
+ $implementation = $name;
+ }
+
+ if (strpos($implementation, '\\') === false) {
+ $class = $this->getNamespace()
+ . '\\ProvidedHook\\'
+ . $this->slashesToNamespace($implementation);
+ } else {
+ $class = $implementation;
+ }
+
+ Hook::register($name, $class, $class, $alwaysRun);
+ return $this;
+ }
+
+ /**
+ * Add a route which will be added to the route chain
+ *
+ * @param string $name Name of the route
+ * @param Zend_Controller_Router_Route_Abstract $route Instance of the route
+ *
+ * @return $this
+ * @see registerRoutes()
+ */
+ protected function addRoute($name, Zend_Controller_Router_Route_Abstract $route)
+ {
+ $this->routes[$name] = $route;
+ return $this;
+ }
+}