2013-06-07 11:44:37 +02:00
|
|
|
<?php
|
2013-08-14 12:42:32 +02:00
|
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
|
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
2013-06-07 11:44:37 +02:00
|
|
|
|
|
|
|
namespace Icinga\Application\Modules;
|
|
|
|
|
2013-08-14 12:53:20 +02:00
|
|
|
use \Icinga\Application\ApplicationBootstrap;
|
|
|
|
use \Icinga\Application\Icinga;
|
|
|
|
use \Icinga\Application\Logger;
|
|
|
|
use \Icinga\Exception\ConfigurationError;
|
|
|
|
use \Icinga\Exception\SystemPermissionException;
|
|
|
|
use \Icinga\Exception\ProgrammingError;
|
2013-06-07 11:44:37 +02:00
|
|
|
|
2013-08-12 15:58:26 +02:00
|
|
|
/**
|
|
|
|
* Module manager that handles detecting, enabling and disabling of modules
|
|
|
|
*
|
|
|
|
* Modules can have 3 states:
|
|
|
|
* - installed Means that the module exists, but could be deactivated (see enabled and loaded)
|
|
|
|
* - enabled Means that the module is marked as being enabled and should be used
|
|
|
|
* - loaded Means that the module has been registered in the autoloader and is being used
|
|
|
|
*
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
class Manager
|
|
|
|
{
|
2013-08-12 15:58:26 +02:00
|
|
|
/**
|
|
|
|
* Array of all installed module's base directories
|
|
|
|
*
|
|
|
|
* null if modules haven't been scanned yet
|
|
|
|
*
|
|
|
|
* @var array|null
|
|
|
|
*/
|
2013-08-14 12:42:32 +02:00
|
|
|
private $installedBaseDirs = null;
|
2013-08-12 15:58:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of all enabled modules base dirs
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2013-08-14 12:42:32 +02:00
|
|
|
private $enabledDirs = array();
|
2013-08-12 15:58:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of all module names that have been loaded
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2013-08-14 12:42:32 +02:00
|
|
|
private $loadedModules = array();
|
2013-08-12 15:58:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reference to Icinga::app
|
|
|
|
*
|
|
|
|
* @var Icinga
|
|
|
|
*/
|
2013-08-14 12:42:32 +02:00
|
|
|
private $app;
|
2013-08-12 15:58:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The directory that is used to detect enabled modules
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2013-08-14 12:42:32 +02:00
|
|
|
private $enableDir;
|
2013-08-12 15:58:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* All paths to look for installed modules that can be enabled
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2013-08-14 12:42:32 +02:00
|
|
|
private $modulePaths = array();
|
2013-08-12 15:58:26 +02:00
|
|
|
|
2013-06-20 17:01:13 +02:00
|
|
|
/**
|
2013-08-14 12:42:32 +02:00
|
|
|
* Create a new instance of the module manager
|
2013-08-12 15:58:26 +02:00
|
|
|
*
|
2013-08-14 12:42:32 +02:00
|
|
|
* @param ApplicationBootstrap $app The application bootstrap. This one needs a properly defined interface
|
|
|
|
* In order to test it correctly, the application now only requires a stdClass
|
|
|
|
* @param string $enabledDir The path of the dir used for adding symlinks to enabled modules
|
|
|
|
* ( must be writable )
|
|
|
|
* @param array $availableDirs An array containing all paths where the modulemanager can look for available
|
|
|
|
* modules
|
|
|
|
**/
|
2013-06-20 17:01:13 +02:00
|
|
|
public function __construct($app, $enabledDir = null, array $availableDirs = array())
|
2013-06-07 11:44:37 +02:00
|
|
|
{
|
|
|
|
$this->app = $app;
|
2013-06-20 17:01:13 +02:00
|
|
|
if (empty($availableDirs)) {
|
|
|
|
$availableDirs = array(ICINGA_APPDIR."/../modules");
|
|
|
|
}
|
|
|
|
$this->modulePaths = $availableDirs;
|
|
|
|
if ($enabledDir === null) {
|
|
|
|
$enabledDir = $this->app->getConfig()->getConfigDir()
|
2013-06-20 14:06:02 +02:00
|
|
|
. '/enabledModules';
|
|
|
|
}
|
2013-06-20 17:01:13 +02:00
|
|
|
$this->prepareEssentials($enabledDir);
|
2013-06-07 11:44:37 +02:00
|
|
|
$this->detectEnabledModules();
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Set the module dir and checks for existence
|
|
|
|
*
|
|
|
|
* @param string $moduleDir The module directory to set for the module manager
|
|
|
|
* @throws \Icinga\Exception\ProgrammingError
|
|
|
|
*/
|
|
|
|
private function prepareEssentials($moduleDir)
|
2013-06-07 11:44:37 +02:00
|
|
|
{
|
2013-06-20 14:06:02 +02:00
|
|
|
$this->enableDir = $moduleDir;
|
2013-06-07 11:44:37 +02:00
|
|
|
|
|
|
|
if (! file_exists($this->enableDir) || ! is_dir($this->enableDir)) {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
sprintf(
|
|
|
|
'Missing module directory: %s',
|
|
|
|
$this->enableDir
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Query interface for the module manager
|
|
|
|
*
|
|
|
|
* @return \Icinga\Data\ArrayQuery
|
|
|
|
*/
|
2013-06-20 17:01:13 +02:00
|
|
|
public function select()
|
|
|
|
{
|
2013-06-26 16:05:01 +02:00
|
|
|
$source = new \Icinga\Data\ArrayDatasource($this->getModuleInfo());
|
2013-06-20 17:01:13 +02:00
|
|
|
return $source->select();
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Check for enabled modules and update the internal $enabledDirs property with the enabled modules
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
private function detectEnabledModules()
|
2013-06-07 11:44:37 +02:00
|
|
|
{
|
|
|
|
$fh = opendir($this->enableDir);
|
|
|
|
|
2013-06-20 17:01:13 +02:00
|
|
|
$this->enabledDirs = array();
|
2013-06-07 11:44:37 +02:00
|
|
|
while (false !== ($file = readdir($fh))) {
|
|
|
|
|
|
|
|
if ($file[0] === '.') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$link = $this->enableDir . '/' . $file;
|
|
|
|
if (! is_link($link)) {
|
2013-08-14 12:42:32 +02:00
|
|
|
Logger::warn(
|
|
|
|
'Found invalid module in enabledModule directory "%s": "%s" is not a symlink',
|
|
|
|
$this->enableDir,
|
|
|
|
$link
|
|
|
|
);
|
2013-06-07 11:44:37 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$dir = realpath($link);
|
|
|
|
if (! file_exists($dir) || ! is_dir($dir)) {
|
2013-08-14 12:42:32 +02:00
|
|
|
Logger::warn(
|
|
|
|
'Found invalid module in enabledModule directory "%s": "%s" points to non existing path "%s"',
|
|
|
|
$this->enableDir,
|
|
|
|
$link,
|
|
|
|
$dir
|
|
|
|
);
|
2013-06-07 11:44:37 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->enabledDirs[$file] = $dir;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Try to set all enabled modules in loaded sate
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
* @see Manager::loadModule()
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function loadEnabledModules()
|
|
|
|
{
|
|
|
|
foreach ($this->listEnabledModules() as $name) {
|
|
|
|
$this->loadModule($name);
|
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Try to load the module and register it in the application
|
|
|
|
*
|
|
|
|
* @param string $name The name of the module to load
|
|
|
|
* @param null|mixed $moduleBase An alternative class to use instead of @see Module, used for testing
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
*/
|
2013-06-20 17:01:13 +02:00
|
|
|
public function loadModule($name, $moduleBase = null)
|
2013-06-07 11:44:37 +02:00
|
|
|
{
|
|
|
|
if ($this->hasLoaded($name)) {
|
|
|
|
return $this;
|
|
|
|
}
|
2013-06-20 17:01:13 +02:00
|
|
|
|
|
|
|
$module = null;
|
|
|
|
if ($moduleBase === null) {
|
|
|
|
$module = new Module($this->app, $name, $this->getModuleDir($name));
|
|
|
|
} else {
|
|
|
|
$module = new $moduleBase($this->app, $name, $this->getModuleDir($name));
|
|
|
|
}
|
2013-06-07 11:44:37 +02:00
|
|
|
$module->register();
|
|
|
|
$this->loadedModules[$name] = $module;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Set the given module to the enabled state
|
|
|
|
*
|
|
|
|
* @param string $name The module to enable
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
* @throws \Icinga\Exception\ConfigurationError When trying to enable a module that is not installed
|
|
|
|
* @throws \Icinga\Exception\SystemPermissionException When insufficient permissions for the application exist
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function enableModule($name)
|
|
|
|
{
|
|
|
|
if (! $this->hasInstalled($name)) {
|
|
|
|
throw new ConfigurationError(
|
|
|
|
sprintf(
|
|
|
|
"Cannot enable module '%s' as it isn't installed",
|
|
|
|
$name
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return $this;
|
|
|
|
}
|
2013-06-20 17:01:13 +02:00
|
|
|
clearstatcache(true);
|
2013-06-07 11:44:37 +02:00
|
|
|
$target = $this->installedBaseDirs[$name];
|
|
|
|
$link = $this->enableDir . '/' . $name;
|
|
|
|
if (! is_writable($this->enableDir)) {
|
2013-06-20 14:06:02 +02:00
|
|
|
throw new SystemPermissionException(
|
|
|
|
"Insufficient system permissions for enabling modules",
|
|
|
|
"write",
|
|
|
|
$this->enableDir
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (file_exists($link) && is_link($link)) {
|
2013-06-07 11:44:37 +02:00
|
|
|
return $this;
|
|
|
|
}
|
2013-06-20 14:06:02 +02:00
|
|
|
if (!@symlink($target, $link)) {
|
|
|
|
$error = error_get_last();
|
|
|
|
if (strstr($error["message"], "File exists") === false) {
|
|
|
|
throw new SystemPermissionException($error["message"], "symlink", $link);
|
|
|
|
}
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
2013-06-20 17:01:13 +02:00
|
|
|
$this->enabledDirs[$name] = $link;
|
2013-06-07 11:44:37 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Disable the given module and remove it's enabled state
|
|
|
|
*
|
|
|
|
* @param string $name The name of the module to disable
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
* @throws \Icinga\Exception\ConfigurationError When the module is not installed or it's not symlinked
|
|
|
|
* @throws \Icinga\Exception\SystemPermissionException When the module can't be disabled
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function disableModule($name)
|
|
|
|
{
|
|
|
|
if (! $this->hasEnabled($name)) {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
if (! is_writable($this->enableDir)) {
|
2013-06-20 14:06:02 +02:00
|
|
|
throw new SystemPermissionException("Can't write the module directory", "write", $this->enableDir);
|
2013-06-07 11:44:37 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
$link = $this->enableDir . '/' . $name;
|
2013-06-20 14:06:02 +02:00
|
|
|
if (!file_exists($link)) {
|
|
|
|
throw new ConfigurationError("The module $name could not be found, can't disable it");
|
|
|
|
}
|
|
|
|
if (!is_link($link)) {
|
|
|
|
throw new ConfigurationError(
|
|
|
|
"The module $name can't be disabled as this would delete the whole module. ".
|
|
|
|
"It looks like you have installed this module manually and moved it to your module folder.".
|
|
|
|
"In order to dynamically enable and disable modules, you have to create a symlink to ".
|
|
|
|
"the enabled_modules folder"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-06-07 11:44:37 +02:00
|
|
|
if (file_exists($link) && is_link($link)) {
|
2013-06-20 14:06:02 +02:00
|
|
|
if (!@unlink($link)) {
|
|
|
|
$error = error_get_last();
|
|
|
|
throw new SystemPermissionException($error["message"], "unlink", $link);
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
2013-06-20 14:06:02 +02:00
|
|
|
} else {
|
|
|
|
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
2013-06-20 17:01:13 +02:00
|
|
|
unset($this->enabledDirs[$name]);
|
2013-06-07 11:44:37 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return the directory of the given module as a string, optionally with a given sub directoy
|
|
|
|
*
|
|
|
|
* @param string $name The module name to return the module directory of
|
|
|
|
* @param string $subdir The sub directory to append to the path
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
* @throws \Icinga\Exception\ProgrammingError When the module is not installed or existing
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function getModuleDir($name, $subdir = '')
|
|
|
|
{
|
|
|
|
if ($this->hasEnabled($name)) {
|
|
|
|
return $this->enabledDirs[$name]. $subdir;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->hasInstalled($name)) {
|
|
|
|
return $this->installedBaseDirs[$name] . $subdir;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new ProgrammingError(
|
|
|
|
sprintf(
|
|
|
|
'Trying to access uninstalled module dir: %s',
|
|
|
|
$name
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return true when the module with the given name is installed, otherwise false
|
|
|
|
*
|
|
|
|
* @param string $name The module to check for being installed
|
|
|
|
* @return bool
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function hasInstalled($name)
|
|
|
|
{
|
|
|
|
if ($this->installedBaseDirs === null) {
|
|
|
|
$this->detectInstalledModules();
|
|
|
|
}
|
|
|
|
return array_key_exists($name, $this->installedBaseDirs);
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return true when the given module is in enabled state, otherwise false
|
|
|
|
*
|
|
|
|
* @param string $name The module to check for being enabled
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function hasEnabled($name)
|
|
|
|
{
|
|
|
|
return array_key_exists($name, $this->enabledDirs);
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return true when the module is in loaded state, otherwise false
|
|
|
|
*
|
|
|
|
* @param string $name The module to check for being loaded
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function hasLoaded($name)
|
|
|
|
{
|
|
|
|
return array_key_exists($name, $this->loadedModules);
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return an array containing all loaded modules
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
* @see Module
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function getLoadedModules()
|
|
|
|
{
|
|
|
|
return $this->loadedModules;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return the module instance of the given module when it is loaded
|
|
|
|
*
|
|
|
|
* @param string $name The module name to return
|
|
|
|
* @return Module
|
|
|
|
*
|
|
|
|
* @throws \Icinga\Exception\ProgrammingError Thrown when the module hasn't been loaded
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function getModule($name)
|
|
|
|
{
|
|
|
|
if (! $this->hasLoaded($name)) {
|
|
|
|
throw new ProgrammingError(
|
|
|
|
sprintf(
|
|
|
|
'Cannot access module %s as it hasn\'t been loaded',
|
|
|
|
$name
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $this->loadedModules[$name];
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return an array containing information objects for each available module
|
|
|
|
*
|
|
|
|
* Each entry has the following fields
|
|
|
|
* - name: The name of the module as a string
|
|
|
|
* - path: The path where the module is located as a string
|
|
|
|
* - enabled: Whether the module is enabled or not as a boolean
|
|
|
|
* - loaded: Whether the module is loaded or not as a boolean
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function getModuleInfo()
|
|
|
|
{
|
|
|
|
$installed = $this->listInstalledModules();
|
2013-06-26 16:05:01 +02:00
|
|
|
|
2013-06-07 11:44:37 +02:00
|
|
|
$info = array();
|
2013-06-26 16:05:01 +02:00
|
|
|
if ($installed === null) {
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
2013-06-07 11:44:37 +02:00
|
|
|
foreach ($installed as $name) {
|
|
|
|
$info[] = (object) array(
|
|
|
|
'name' => $name,
|
|
|
|
'path' => $this->installedBaseDirs[$name],
|
|
|
|
'enabled' => $this->hasEnabled($name),
|
|
|
|
'loaded' => $this->hasLoaded($name)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return an array containing all enabled module names as strings
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function listEnabledModules()
|
|
|
|
{
|
|
|
|
return array_keys($this->enabledDirs);
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return an array containing all loaded module names as strings
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function listLoadedModules()
|
|
|
|
{
|
|
|
|
return array_keys($this->loadedModules);
|
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Return an array containing all installled module names as strings
|
|
|
|
*
|
|
|
|
* Calls @see Manager::detectInstalledModules if no module discovery has
|
|
|
|
* been performed yet
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function listInstalledModules()
|
|
|
|
{
|
|
|
|
if ($this->installedBaseDirs === null) {
|
|
|
|
$this->detectInstalledModules();
|
|
|
|
}
|
2013-06-26 16:05:01 +02:00
|
|
|
|
|
|
|
if ($this->installedBaseDirs !== null) {
|
|
|
|
return array_keys($this->installedBaseDirs);
|
|
|
|
}
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
|
|
|
|
2013-08-14 12:42:32 +02:00
|
|
|
/**
|
|
|
|
* Detect installed modules from every path provided in modulePaths
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
*/
|
2013-06-07 11:44:37 +02:00
|
|
|
public function detectInstalledModules()
|
|
|
|
{
|
2013-06-20 17:01:13 +02:00
|
|
|
foreach ($this->modulePaths as $basedir) {
|
|
|
|
$fh = opendir($basedir);
|
|
|
|
if ($fh === false) {
|
|
|
|
return $this;
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
2013-06-20 17:01:13 +02:00
|
|
|
|
|
|
|
while ($name = readdir($fh)) {
|
|
|
|
if ($name[0] === '.') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (is_dir($basedir . '/' . $name)) {
|
|
|
|
$this->installedBaseDirs[$name] = $basedir . '/' . $name;
|
|
|
|
}
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
|
|
|
}
|
2013-08-14 12:42:32 +02:00
|
|
|
return $this;
|
2013-06-07 11:44:37 +02:00
|
|
|
}
|
|
|
|
}
|