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->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'; } /** * 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 = array(); foreach ($pane->getDashlets() as $dashletName => $dashletUrl) { $dashlets[$this->translate($dashletName)] = $dashletUrl; } $navigation->addItem( $pane->getName(), array_merge( $pane->getProperties(), array( 'label' => $this->translate($pane->getName()), 'type' => 'dashboard-pane', 'dashlets' => $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 $this->createMenu($this->menuItems); } /** * Create and return a new navigation for the given menu items * * @param MenuItemContainer[] $items * * @return Navigation */ private function createMenu(array $items) { $navigation = new Navigation(); foreach ($items as $item) { /** @var MenuItemContainer $item */ $navigationItem = $navigation->createItem($item->getName(), $item->getProperties()); $navigationItem->setChildren($this->createMenu($item->getChildren())); $navigationItem->setLabel($this->translate($item->getName())); $navigation->addItem($navigationItem); } 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 mixed * * @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; $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 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 */ public function getDependencies() { return $this->metadata()->depends; } /** * Fetch module metadata * * @return object */ protected function metadata() { if ($this->metadata === null) { $metadata = (object) array( 'name' => $this->getName(), 'version' => '0.0.0', 'title' => null, 'description' => '', 'depends' => array(), ); if (file_exists($this->metadataFile)) { $key = null; $file = new File($this->metadataFile, 'r'); foreach ($file as $line) { $line = rtrim($line); if ($key === 'description') { if (empty($line)) { $metadata->description .= "\n"; continue; } elseif ($line[0] === ' ') { $metadata->description .= $line; continue; } } list($key, $val) = preg_split('/:\s+/', $line, 2); $key = lcfirst($key); switch ($key) { case 'depends': if (strpos($val, ' ') === false) { $metadata->depends[$val] = true; continue; } $parts = preg_split('/,\s+/', $val); foreach ($parts as $part) { if (preg_match('/^(\w+)\s+\((.+)\)$/', $part, $m)) { $metadata->depends[$m[1]] = $m[2]; } else { // TODO: FAIL? continue; } } 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 === '') { // TODO: Check whether the translation module is able to // extract this $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 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 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() )); 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 (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; } $loader = $this->app->getLoader(); $moduleName = ucfirst($this->getName()); $moduleLibraryDir = $this->getLibDir(). '/'. $moduleName; if (is_dir($moduleLibraryDir)) { $loader->registerNamespace('Icinga\\Module\\' . $moduleName, $moduleLibraryDir); } $moduleFormDir = $this->getFormDir(); if (is_dir($moduleFormDir)) { $loader->registerNamespace('Icinga\\Module\\' . $moduleName. '\\Forms', $moduleFormDir); } $this->registeredAutoloader = true; return $this; } /** * Bind text domain for i18n * * @return $this */ protected function registerLocales() { if ($this->hasLocales()) { Translator::registerDomain($this->name, $this->localedir); } 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; } $moduleControllerDir = $this->getControllerDir(); if (is_dir($moduleControllerDir)) { $this->app->getfrontController()->addControllerDirectory( $moduleControllerDir, $this->getName() ); $this->app->getLoader()->registerNamespace( 'Icinga\\Module\\' . ucfirst($this->getName()) . '\\' . Dispatcher::CONTROLLER_NAMESPACE, $moduleControllerDir ); } $this ->registerLocales() ->registerRoutes(); return $this; } /** * 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(); /** @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); } /** * Register hook * * @param string $name * @param string $class * @param string $key * * @return $this */ protected function registerHook($name, $class, $key = null) { if ($key === null) { $key = $this->name; } Hook::register($name, $key, $class); 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; } /** * (non-PHPDoc) * @see Translator::translate() For the function documentation. */ protected function translate($string, $context = null) { return mt($this->name, $string, $context); } /** * (non-PHPDoc) * @see Translator::translatePlural() For the function documentation. */ protected function translatePlural($textSingular, $textPlural, $number, $context = null) { return mtp($this->name, $textSingular, $textPlural, $number, $context); } }