summaryrefslogtreecommitdiffstats
path: root/library/Toplevelview/ViewConfig.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Toplevelview/ViewConfig.php')
-rw-r--r--library/Toplevelview/ViewConfig.php488
1 files changed, 488 insertions, 0 deletions
diff --git a/library/Toplevelview/ViewConfig.php b/library/Toplevelview/ViewConfig.php
new file mode 100644
index 0000000..dc73b7f
--- /dev/null
+++ b/library/Toplevelview/ViewConfig.php
@@ -0,0 +1,488 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview;
+
+use Icinga\Application\Benchmark;
+use Icinga\Application\Icinga;
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Exception\NotImplementedError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\NotWritableError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Toplevelview\Tree\TLVTree;
+use Icinga\Util\DirectoryIterator;
+use Icinga\Web\Session;
+
+class ViewConfig
+{
+ const FORMAT_YAML = 'yml';
+ const SESSION_PREFIX = 'toplevelview_view_';
+
+ protected $config_dir;
+
+ protected $name;
+
+ protected $format;
+
+ protected $file_path;
+
+ protected $view;
+
+ protected $raw;
+
+ protected $tree;
+
+ protected $hasBeenLoaded = false;
+ protected $hasBeenLoadedFromSession = false;
+
+ /**
+ * Content of the file
+ *
+ * @var string
+ */
+ protected $text;
+
+ protected $textChecksum;
+
+ /**
+ * @param $name
+ * @param string|null $config_dir
+ * @param string $format
+ *
+ * @return static
+ */
+ public static function loadByName($name, $config_dir = null, $format = self::FORMAT_YAML)
+ {
+ $object = new static;
+ $object
+ ->setName($name)
+ ->setConfigDir($config_dir)
+ ->setFormat($format)
+ ->load();
+
+ return $object;
+ }
+
+ /**
+ * @param string|null $config_dir
+ * @param string $format
+ *
+ * @return static[]
+ */
+ public static function loadAll($config_dir = null, $format = self::FORMAT_YAML)
+ {
+ $suffix = '.' . $format;
+
+ $config_dir = static::configDir($config_dir);
+ $directory = new DirectoryIterator($config_dir, $suffix);
+
+ $views = array();
+ foreach ($directory as $name => $path) {
+ if (is_dir($path)) {
+ // no not descend and ignore directories
+ continue;
+ }
+ $name = basename($name, $suffix);
+ $views[$name] = static::loadByName($name, $config_dir, $format);
+ }
+
+ // try to load from session
+ $len = strlen(self::SESSION_PREFIX);
+ foreach (static::session()->getAll() as $k => $v) {
+ if (substr($k, 0, $len) === self::SESSION_PREFIX) {
+ $name = substr($k, $len);
+ if (! array_key_exists($name, $views)) {
+ $views[$name] = static::loadByName($name, $config_dir, $format);
+ }
+ }
+ }
+
+ ksort($views);
+
+ return $views;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFilePath()
+ {
+ if ($this->file_path === null) {
+ if ($this->format === null) {
+ throw new ProgrammingError('format not set!');
+ }
+ $this->file_path = $this->getConfigDir() . DIRECTORY_SEPARATOR . $this->name . '.' . $this->format;
+ }
+ return $this->file_path;
+ }
+
+ /**
+ * @param string $file_path
+ *
+ * @return $this
+ */
+ public function setFilePath($file_path)
+ {
+ $this->file_path = $file_path;
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function load()
+ {
+ if ($this->text === null) {
+ $this->loadFromSession();
+ }
+ if ($this->text === null) {
+ $this->loadFromFile();
+ }
+ return $this;
+ }
+
+ public function loadFromFile()
+ {
+ $file_path = $this->getFilePath();
+ $this->text = file_get_contents($file_path);
+ if ($this->text === false) {
+ throw new NotReadableError('Could not read file %s', $file_path);
+ }
+ $this->view = null;
+ $this->hasBeenLoadedFromSession = false;
+ $this->hasBeenLoaded = true;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ public function getTextChecksum()
+ {
+ if ($this->textChecksum === null) {
+ $this->textChecksum = sha1($this->text);
+ }
+ return $this->textChecksum;
+ }
+
+ /**
+ * @param $text
+ *
+ * @return $this
+ */
+ public function setText($text)
+ {
+ $this->text = $text;
+ $this->textChecksum = null;
+ $this->raw = null;
+ $this->tree = null;
+ return $this;
+ }
+
+ protected function writeFile($path, $content, $mode = '0660')
+ {
+ $existing = file_exists($path);
+ if (file_put_contents($path, $content) === false) {
+ throw new NotWritableError('Could not save to %s', $path);
+ }
+
+ if ($existing === false) {
+ $octalMode = intval($mode, 8);
+ if ($mode !== null && false === @chmod($path, $octalMode)) {
+ throw new NotWritableError('Failed to set file mode "%s" on file "%s"', $mode, $path);
+ }
+ }
+ }
+
+ protected function storeBackup($force = false)
+ {
+ $backupDir = $this->getConfigBackupDir();
+
+ $this->ensureConfigDir($backupDir);
+
+ $ts = (string) time();
+ $backup = $backupDir . DIRECTORY_SEPARATOR . $ts . '.' . $this->format;
+
+ if (file_exists($backup)) {
+ throw new ProgrammingError('History file with timestamp already present: %s', $backup);
+ }
+
+ $existingFile = $this->getFilePath();
+ $oldText = file_get_contents($existingFile);
+ if ($oldText === false) {
+ throw new NotReadableError('Could not read file %s', $existingFile);
+ }
+
+ // only save backup if changed or forced
+ if ($force || $oldText !== $this->text) {
+ $this->writeFile($backup, $oldText);
+ }
+ }
+
+ public function store()
+ {
+ $config_dir = $this->getConfigDir();
+ $file_path = $this->getFilePath();
+
+ $this->ensureConfigDir($config_dir);
+
+ // ensure to save history
+ if (file_exists($file_path)) {
+ $this->storeBackup();
+ }
+
+ $this->writeFile($file_path, $this->text);
+
+ $this->clearSession();
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @throws ProgrammingError When dir is not yet set
+ */
+ public function getConfigDir()
+ {
+ if ($this->config_dir === null) {
+ throw new ProgrammingError('config_dir not yet set!');
+ }
+ return $this->config_dir;
+ }
+
+ /**
+ * @return string
+ */
+ public function getConfigBackupDir()
+ {
+ return $this->getConfigDir() . DIRECTORY_SEPARATOR . $this->name;
+ }
+
+ /**
+ * @param string $config_dir
+ *
+ * @return $this
+ * @throws NotReadableError
+ */
+ public function setConfigDir($config_dir = null)
+ {
+ $this->config_dir = static::configDir($config_dir);
+ $this->file_path = null;
+ return $this;
+ }
+
+ protected static function ensureConfigDir($path, $mode = '2770')
+ {
+ if (! file_exists($path)) {
+ if (mkdir($path) !== true) {
+ throw new NotWritableError(
+ 'Config path did not exit, and it could not be created: %s',
+ $path
+ );
+ }
+
+ $octalMode = intval($mode, 8);
+ if ($mode !== null && false === @chmod($path, $octalMode)) {
+ throw new NotWritableError('Failed to set file mode "%s" on file "%s"', $mode, $path);
+ }
+ }
+ }
+
+ public static function configDir($config_dir = null)
+ {
+ $config_dir_module = Icinga::app()->getModuleManager()->getModule('toplevelview')->getConfigDir();
+ if ($config_dir === null) {
+ $config_dir = $config_dir_module . DIRECTORY_SEPARATOR . 'views';
+ }
+
+ static::ensureConfigDir($config_dir_module);
+ static::ensureConfigDir($config_dir);
+
+ return $config_dir;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param mixed $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ $this->file_path = null;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * @param string $format
+ *
+ * @return $this
+ */
+ public function setFormat($format)
+ {
+ $this->format = $format;
+ $this->file_path = null;
+ return $this;
+ }
+
+ public function getMeta($key)
+ {
+ $this->ensureParsed();
+ if ($key !== 'children' && array_key_exists($key, $this->raw)) {
+ return $this->raw[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function setMeta($key, $value)
+ {
+ if ($key === 'children') {
+ throw new ProgrammingError('You can not edit children here!');
+ }
+ $this->raw[$key] = $value;
+ return $this;
+ }
+
+ public function getMetaData()
+ {
+ $this->ensureParsed();
+ $data = array();
+ foreach ($this->raw as $key => $value) {
+ if ($key !== 'children') {
+ $data[$key] = $value;
+ }
+ }
+ return $data;
+ }
+
+ protected function ensureParsed()
+ {
+ if ($this->raw === null) {
+ Benchmark::measure('Begin parsing YAML document');
+
+ $text = $this->getText();
+ if ($text === null) {
+ // new ViewConfig
+ $this->raw = array();
+ } elseif ($this->format == self::FORMAT_YAML) {
+ // TODO: use stdClass instead of Array?
+ $this->raw = yaml_parse($text);
+ if (! is_array($this->raw)) {
+ throw new InvalidPropertyException('Could not parse YAML config!');
+ }
+ } else {
+ throw new NotImplementedError("Unknown format '%s'", $this->format);
+ }
+
+ Benchmark::measure('Finished parsing YAML document');
+ }
+ }
+
+ /**
+ * Loads the Tree for this configuration
+ *
+ * @return TLVTree
+ */
+ public function getTree()
+ {
+ if ($this->tree === null) {
+ $this->ensureParsed();
+ $this->tree = $tree = TLVTree::fromArray($this->raw);
+ $tree->setConfig($this);
+ }
+ return $this->tree;
+ }
+
+ protected function getSessionVarName()
+ {
+ return self::SESSION_PREFIX . $this->name;
+ }
+
+ public static function session()
+ {
+ // TODO: is this CLI safe?
+ return Session::getSession();
+ }
+
+ public function loadFromSession()
+ {
+ if (($sessionConfig = $this->session()->get($this->getSessionVarName())) !== null) {
+ $this->text = $sessionConfig;
+ $this->hasBeenLoadedFromSession = true;
+ $this->hasBeenLoaded = true;
+ }
+ return $this;
+ }
+
+ public function clearSession()
+ {
+ $this->session()->delete($this->getSessionVarName());
+ }
+
+ public function storeToSession()
+ {
+ $this->session()->set($this->getSessionVarName(), $this->text);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasBeenLoadedFromSession()
+ {
+ return $this->hasBeenLoadedFromSession;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasBeenLoaded()
+ {
+ return $this->hasBeenLoaded;
+ }
+
+ public function __clone()
+ {
+ $this->name = null;
+ $this->raw = null;
+ $this->tree = null;
+
+ $this->hasBeenLoaded = false;
+ $this->hasBeenLoadedFromSession = false;
+ }
+
+ public function delete()
+ {
+ $file_path = $this->getFilePath();
+
+ $this->clearSession();
+
+ if (file_exists($file_path)) {
+ $this->storeBackup(true);
+ unlink($file_path);
+ }
+
+ return $this;
+ }
+}