icingaweb2/library/Icinga/Application/Config.php

498 lines
13 KiB
PHP

<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
namespace Icinga\Application;
use Iterator;
use Countable;
use LogicException;
use UnexpectedValueException;
use Icinga\Util\File;
use Icinga\Data\ConfigObject;
use Icinga\Data\Selectable;
use Icinga\Data\SimpleQuery;
use Icinga\File\Ini\IniWriter;
use Icinga\File\Ini\IniParser;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotReadableError;
use Icinga\Web\Navigation\Navigation;
/**
* Container for INI like configuration and global registry of application and module related configuration.
*/
class Config implements Countable, Iterator, Selectable
{
/**
* Configuration directory where ALL (application and module) configuration is located
*
* @var string
*/
public static $configDir;
/**
* Application config instances per file
*
* @var array
*/
protected static $app = array();
/**
* Module config instances per file
*
* @var array
*/
protected static $modules = array();
/**
* Navigation config instances per type
*
* @var array
*/
protected static $navigation = array();
/**
* The internal ConfigObject
*
* @var ConfigObject
*/
protected $config;
/**
* The INI file this config has been loaded from or should be written to
*
* @var string
*/
protected $configFile;
/**
* Create a new config
*
* @param ConfigObject $config The config object to handle
*/
public function __construct(ConfigObject $config = null)
{
$this->config = $config !== null ? $config : new ConfigObject();
}
/**
* Return this config's file path
*
* @return string
*/
public function getConfigFile()
{
return $this->configFile;
}
/**
* Set this config's file path
*
* @param string $filepath The path to the ini file
*
* @return $this
*/
public function setConfigFile($filepath)
{
$this->configFile = $filepath;
return $this;
}
/**
* Return the internal ConfigObject
*
* @return ConfigObject
*/
public function getConfigObject()
{
return $this->config;
}
/**
* Provide a query for the internal config object
*
* @return SimpleQuery
*/
public function select()
{
return $this->config->select();
}
/**
* Return the count of available sections
*
* @return int
*/
public function count()
{
return $this->select()->count();
}
/**
* Reset the current position of the internal config object
*
* @return ConfigObject
*/
public function rewind()
{
return $this->config->rewind();
}
/**
* Return the section of the current iteration
*
* @return ConfigObject
*/
public function current()
{
return $this->config->current();
}
/**
* Return whether the position of the current iteration is valid
*
* @return bool
*/
public function valid()
{
return $this->config->valid();
}
/**
* Return the section's name of the current iteration
*
* @return string
*/
public function key()
{
return $this->config->key();
}
/**
* Advance the position of the current iteration and return the new section
*
* @return ConfigObject
*/
public function next()
{
return $this->config->next();
}
/**
* Return whether this config has any sections
*
* @return bool
*/
public function isEmpty()
{
return $this->config->isEmpty();
}
/**
* Return this config's section names
*
* @return array
*/
public function keys()
{
return $this->config->keys();
}
/**
* Return this config's data as associative array
*
* @return array
*/
public function toArray()
{
return $this->config->toArray();
}
/**
* Return the value from a section's property
*
* @param string $section The section where the given property can be found
* @param string $key The section's property to fetch the value from
* @param mixed $default The value to return in case the section or the property is missing
*
* @return mixed
*
* @throws UnexpectedValueException In case the given section does not hold any configuration
*/
public function get($section, $key, $default = null)
{
$value = $this->config->$section;
if ($value instanceof ConfigObject) {
$value = $value->$key;
} elseif ($value !== null) {
throw new UnexpectedValueException(
sprintf('Value "%s" is not of type "%s" or a sub-type of it', $value, get_class($this->config))
);
}
if ($value === null && $default !== null) {
$value = $default;
}
return $value;
}
/**
* Return the given section
*
* @param string $name The section's name
*
* @return ConfigObject
*/
public function getSection($name)
{
$section = $this->config->get($name);
return $section !== null ? $section : new ConfigObject();
}
/**
* Set or replace a section
*
* @param string $name
* @param array|ConfigObject $config
*
* @return $this
*/
public function setSection($name, $config = null)
{
if ($config === null) {
$config = new ConfigObject();
} elseif (! $config instanceof ConfigObject) {
$config = new ConfigObject($config);
}
$this->config->$name = $config;
return $this;
}
/**
* Remove a section
*
* @param string $name
*
* @return $this
*/
public function removeSection($name)
{
unset($this->config->$name);
return $this;
}
/**
* Return whether the given section exists
*
* @param string $name
*
* @return bool
*/
public function hasSection($name)
{
return isset($this->config->$name);
}
/**
* Initialize a new config using the given array
*
* The returned config has no file associated to it.
*
* @param array $array The array to initialize the config with
*
* @return Config
*/
public static function fromArray(array $array)
{
return new static(new ConfigObject($array));
}
/**
* Load configuration from the given INI file
*
* @param string $file The file to parse
*
* @throws NotReadableError When the file cannot be read
*/
public static function fromIni($file)
{
$emptyConfig = new static();
$filepath = realpath($file);
if ($filepath === false) {
$emptyConfig->setConfigFile($file);
} elseif (is_readable($filepath)) {
return IniParser::parseIniFile($filepath);
} elseif (@file_exists($filepath)) {
throw new NotReadableError(t('Cannot read config file "%s". Permission denied'), $filepath);
}
return $emptyConfig;
}
/**
* Save configuration to the given INI file
*
* @param string|null $filePath The path to the INI file or null in case this config's path should be used
* @param int $fileMode The file mode to store the file with
*
* @throws LogicException In case this config has no path and none is passed in either
* @throws NotWritableError In case the INI file cannot be written
*
* @todo create basepath and throw NotWritableError in case its not possible
*/
public function saveIni($filePath = null, $fileMode = 0660)
{
if ($filePath === null && $this->configFile) {
$filePath = $this->configFile;
} elseif ($filePath === null) {
throw new LogicException('You need to pass $filePath or set a path using Config::setConfigFile()');
}
if (! file_exists($filePath)) {
File::create($filePath, $fileMode);
}
$this->getIniWriter($filePath, $fileMode)->write();
}
/**
* Return a IniWriter for this config
*
* @param string|null $filePath
* @param int $fileMode
*
* @return IniWriter
*/
protected function getIniWriter($filePath = null, $fileMode = null)
{
return new IniWriter($this, $filePath, $fileMode);
}
/**
* Prepend configuration base dir to the given relative path
*
* @param string $path A relative path
*
* @return string
*/
public static function resolvePath($path)
{
return self::$configDir . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
}
/**
* Retrieve a application config
*
* @param string $configname The configuration name (without ini suffix) to read and return
* @param bool $fromDisk When set true, the configuration will be read from disk, even
* if it already has been read
*
* @return Config The requested configuration
*/
public static function app($configname = 'config', $fromDisk = false)
{
if (! isset(self::$app[$configname]) || $fromDisk) {
self::$app[$configname] = static::fromIni(static::resolvePath($configname . '.ini'));
}
return self::$app[$configname];
}
/**
* Retrieve a module config
*
* @param string $modulename The name of the module where to look for the requested configuration
* @param string $configname The configuration name (without ini suffix) to read and return
* @param string $fromDisk When set true, the configuration will be read from disk, even
* if it already has been read
*
* @return Config The requested configuration
*/
public static function module($modulename, $configname = 'config', $fromDisk = false)
{
if (! isset(self::$modules[$modulename])) {
self::$modules[$modulename] = array();
}
if (! isset(self::$modules[$modulename][$configname]) || $fromDisk) {
self::$modules[$modulename][$configname] = static::fromIni(
static::resolvePath('modules/' . $modulename . '/' . $configname . '.ini')
);
}
return self::$modules[$modulename][$configname];
}
/**
* Retrieve a navigation config
*
* @param string $type The type identifier of the navigation item for which to return its config
* @param string $username A user's name or null if the shared config is desired
* @param bool $fromDisk If true, the configuration will be read from disk
*
* @return Config The requested configuration
*/
public static function navigation($type, $username = null, $fromDisk = false)
{
if (! isset(self::$navigation[$type])) {
self::$navigation[$type] = array();
}
$branch = $username ?: 'shared';
$typeConfigs = self::$navigation[$type];
if (! isset($typeConfigs[$branch]) || $fromDisk) {
$typeConfigs[$branch] = static::fromIni(static::getNavigationConfigPath($type, $username));
}
return $typeConfigs[$branch];
}
/**
* Return the path to the configuration file for the given navigation item type and user
*
* @param string $type
* @param string $username
*
* @return string
*
* @throws IcingaException In case the given type is unknown
*/
protected static function getNavigationConfigPath($type, $username = null)
{
$itemTypeConfig = Navigation::getItemTypeConfiguration();
if (! isset($itemTypeConfig[$type])) {
throw new IcingaException('Invalid navigation item type %s provided', $type);
}
if (isset($itemTypeConfig[$type]['config'])) {
$filename = $itemTypeConfig[$type]['config'] . '.ini';
} else {
$filename = $type . 's.ini';
}
if ($username) {
$path = static::resolvePath(implode(DIRECTORY_SEPARATOR, array('preferences', $username, $filename)));
if (realpath($path) === false) {
$path = static::resolvePath(implode(
DIRECTORY_SEPARATOR,
array('preferences', strtolower($username), $filename)
));
}
} else {
$path = static::resolvePath('navigation' . DIRECTORY_SEPARATOR . $filename);
}
return $path;
}
/**
* Return this config rendered as a INI structured string
*
* @return string
*/
public function __toString()
{
return $this->getIniWriter()->render();
}
}