summaryrefslogtreecommitdiffstats
path: root/library/Businessprocess/Storage
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:42:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:42:35 +0000
commit18db984057b83ca4962c89b6b79bdce6a660b58f (patch)
tree2c9f23c086b4dfcb3e7eb2ec69210206b0782d3c /library/Businessprocess/Storage
parentInitial commit. (diff)
downloadicingaweb2-module-businessprocess-upstream/2.4.0.tar.xz
icingaweb2-module-businessprocess-upstream/2.4.0.zip
Adding upstream version 2.4.0.upstream/2.4.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Businessprocess/Storage')
-rw-r--r--library/Businessprocess/Storage/ConfigDiff.php91
-rw-r--r--library/Businessprocess/Storage/LegacyConfigParser.php409
-rw-r--r--library/Businessprocess/Storage/LegacyConfigRenderer.php255
-rw-r--r--library/Businessprocess/Storage/LegacyStorage.php207
-rw-r--r--library/Businessprocess/Storage/Storage.php107
5 files changed, 1069 insertions, 0 deletions
diff --git a/library/Businessprocess/Storage/ConfigDiff.php b/library/Businessprocess/Storage/ConfigDiff.php
new file mode 100644
index 0000000..495151e
--- /dev/null
+++ b/library/Businessprocess/Storage/ConfigDiff.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Storage;
+
+use Diff;
+use Diff_Renderer_Html_Inline;
+use Diff_Renderer_Html_SideBySide;
+use Diff_Renderer_Text_Context;
+use Diff_Renderer_Text_Unified;
+use ipl\Html\ValidHtml;
+
+class ConfigDiff implements ValidHtml
+{
+ protected $a;
+
+ protected $b;
+
+ protected $diff;
+ protected $opcodes;
+
+ protected function __construct($a, $b)
+ {
+ $this->requireVendorLib('Diff.php');
+
+ if (empty($a)) {
+ $this->a = array();
+ } else {
+ $this->a = explode("\n", (string) $a);
+ }
+
+ if (empty($b)) {
+ $this->b = array();
+ } else {
+ $this->b = explode("\n", (string) $b);
+ }
+
+ $options = array(
+ 'context' => 5,
+ // 'ignoreWhitespace' => true,
+ // 'ignoreCase' => true,
+ );
+ $this->diff = new Diff($this->a, $this->b, $options);
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ return $this->renderHtmlSideBySide();
+ }
+
+ public function renderHtmlSideBySide()
+ {
+ $this->requireVendorLib('Diff/Renderer/Html/SideBySide.php');
+ $renderer = new Diff_Renderer_Html_SideBySide;
+ return $this->diff->render($renderer);
+ }
+
+ public function renderHtmlInline()
+ {
+ $this->requireVendorLib('Diff/Renderer/Html/Inline.php');
+ $renderer = new Diff_Renderer_Html_Inline;
+ return $this->diff->render($renderer);
+ }
+
+ public function renderTextContext()
+ {
+ $this->requireVendorLib('Diff/Renderer/Text/Context.php');
+ $renderer = new Diff_Renderer_Text_Context;
+ return $this->diff->render($renderer);
+ }
+
+ public function renderTextUnified()
+ {
+ $this->requireVendorLib('Diff/Renderer/Text/Unified.php');
+ $renderer = new Diff_Renderer_Text_Unified;
+ return $this->diff->render($renderer);
+ }
+
+ protected function requireVendorLib($file)
+ {
+ require_once dirname(dirname(__DIR__)) . '/vendor/php-diff/lib/' . $file;
+ }
+
+ public static function create($a, $b)
+ {
+ $diff = new static($a, $b);
+ return $diff;
+ }
+}
diff --git a/library/Businessprocess/Storage/LegacyConfigParser.php b/library/Businessprocess/Storage/LegacyConfigParser.php
new file mode 100644
index 0000000..17fc8a5
--- /dev/null
+++ b/library/Businessprocess/Storage/LegacyConfigParser.php
@@ -0,0 +1,409 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Storage;
+
+use Icinga\Application\Benchmark;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\SystemPermissionException;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\BpNode;
+use Icinga\Module\Businessprocess\Metadata;
+
+class LegacyConfigParser
+{
+ /** @var int */
+ protected $currentLineNumber;
+
+ /** @var string */
+ protected $currentFilename;
+
+ protected $name;
+
+ /** @var BpConfig */
+ protected $config;
+
+ /** @var array */
+ protected $missingNodes = [];
+
+ /**
+ * LegacyConfigParser constructor
+ *
+ * @param $name
+ */
+ private function __construct($name)
+ {
+ $this->name = $name;
+ $this->config = new BpConfig();
+ $this->config->setName($name);
+ }
+
+ /**
+ * @return BpConfig
+ */
+ public function getParsedConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * @param $name
+ * @param $filename
+ *
+ * @return BpConfig
+ */
+ public static function parseFile($name, $filename)
+ {
+ Benchmark::measure('Loading business process ' . $name);
+ $parser = new static($name);
+ $parser->reallyParseFile($filename);
+ Benchmark::measure('Business process ' . $name . ' loaded');
+ return $parser->getParsedConfig();
+ }
+
+ /**
+ * @param $name
+ * @param $string
+ *
+ * @return BpConfig
+ */
+ public static function parseString($name, $string)
+ {
+ Benchmark::measure('Loading BP config from file: ' . $name);
+ $parser = new static($name);
+
+ $config = $parser->getParsedConfig();
+ $config->setMetadata(
+ static::readMetadataFromString($name, $string)
+ );
+
+ foreach (preg_split('/\r?\n/', $string) as $line) {
+ $parser->parseLine($line);
+ }
+
+ $parser->resolveMissingNodes();
+
+ Benchmark::measure('Business process ' . $name . ' loaded');
+ return $config;
+ }
+
+ protected function reallyParseFile($filename)
+ {
+ $file = $this->currentFilename = $filename;
+ $fh = @fopen($file, 'r');
+ if (! $fh) {
+ throw new SystemPermissionException('Could not open "%s"', $filename);
+ }
+
+ $config = $this->config;
+ $config->setMetadata(
+ $this::readMetadataFromFileHeader($config->getName(), $filename)
+ );
+
+ $this->currentLineNumber = 0;
+ while ($line = fgets($fh)) {
+ $this->parseLine($line);
+ }
+
+ $this->resolveMissingNodes();
+
+ fclose($fh);
+ unset($this->currentLineNumber);
+ unset($this->currentFilename);
+ }
+
+ /**
+ * Resolve previously missed business process nodes
+ *
+ * @throws ConfigurationError In case a referenced process does not exist
+ */
+ protected function resolveMissingNodes()
+ {
+ foreach ($this->missingNodes as $name => $parents) {
+ foreach ($parents as $parent) {
+ /** @var BpNode $parent */
+ $parent->addChild($this->config->getNode($name));
+ }
+ }
+ }
+
+ public static function readMetadataFromFileHeader($name, $filename)
+ {
+ $metadata = new Metadata($name);
+ $fh = fopen($filename, 'r');
+ $cnt = 0;
+ while ($cnt < 15 && false !== ($line = fgets($fh))) {
+ $cnt++;
+ static::parseHeaderLine($line, $metadata);
+ }
+
+ fclose($fh);
+ return $metadata;
+ }
+
+ public static function readMetadataFromString($name, &$string)
+ {
+ $metadata = new Metadata($name);
+
+ $lines = preg_split('/\r?\n/', substr($string, 0, 8092));
+ foreach ($lines as $line) {
+ static::parseHeaderLine($line, $metadata);
+ }
+
+ return $metadata;
+ }
+
+ protected function splitCommaSeparated($string)
+ {
+ return preg_split('/\s*,\s*/', $string, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ protected function readHeaderString($string, Metadata $metadata)
+ {
+ foreach (preg_split('/\r?\n/', $string) as $line) {
+ $this->parseHeaderLine($line, $metadata);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @return array
+ */
+ protected function emptyHeader()
+ {
+ return array(
+ 'Title' => null,
+ 'Description' => null,
+ 'Owner' => null,
+ 'AllowedUsers' => null,
+ 'AllowedGroups' => null,
+ 'AllowedRoles' => null,
+ 'Backend' => null,
+ 'Statetype' => 'soft',
+ 'SLAHosts' => null
+ );
+ }
+
+ /**
+ * @param $line
+ * @param Metadata $metadata
+ */
+ protected static function parseHeaderLine($line, Metadata $metadata)
+ {
+ if (preg_match('/^\s*#\s+(.+?)\s*:\s*(.+)$/', trim($line), $m)) {
+ if ($metadata->hasKey($m[1])) {
+ $metadata->set($m[1], $m[2]);
+ }
+ }
+ }
+
+ /**
+ * @param $line
+ * @param BpConfig $bp
+ */
+ protected function parseDisplay(&$line, BpConfig $bp)
+ {
+ list($display, $name, $desc) = preg_split('~\s*;\s*~', substr($line, 8), 3);
+ $bp->getBpNode($name)->setAlias($desc)->setDisplay($display);
+ if ($display > 0) {
+ $bp->addRootNode($name);
+ }
+ }
+
+ /**
+ * @param $line
+ * @param BpConfig $bp
+ */
+ protected function parseExternalInfo(&$line, BpConfig $bp)
+ {
+ list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2);
+ $bp->getBpNode($name)->setInfoCommand($script);
+ }
+
+ protected function parseExtraInfo(&$line, BpConfig $bp)
+ {
+ // TODO: Not yet
+ // list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2);
+ // $this->getNode($name)->setExtraInfo($script);
+ }
+
+ protected function parseInfoUrl(&$line, BpConfig $bp)
+ {
+ list($name, $url) = preg_split('~\s*;\s*~', substr($line, 9), 2);
+ $bp->getBpNode($name)->setInfoUrl($url);
+ }
+
+ protected function parseStateOverrides(&$line, BpConfig $bp)
+ {
+ // state_overrides <bp-node>!<child>|n-n[,n-n]!<child>|n-n[,n-n]
+ $segments = preg_split('~\s*!\s*~', substr($line, 16));
+ $node = $bp->getNode(array_shift($segments));
+ foreach ($segments as $overrideDef) {
+ list($childName, $overrides) = preg_split('~\s*\|\s*~', $overrideDef, 2);
+
+ $stateOverrides = [];
+ foreach (preg_split('~\s*,\s*~', $overrides) as $override) {
+ list($from, $to) = preg_split('~\s*-\s*~', $override, 2);
+ $stateOverrides[(int) $from] = (int) $to;
+ }
+
+ $node->setStateOverrides($stateOverrides, $childName);
+ }
+ }
+
+ protected function parseExtraLine(&$line, $typeLength, BpConfig $bp)
+ {
+ $type = substr($line, 0, $typeLength);
+ if (substr($type, 0, 7) === 'display') {
+ $this->parseDisplay($line, $bp);
+ return true;
+ }
+
+ switch ($type) {
+ case 'external_info':
+ $this->parseExternalInfo($line, $bp);
+ break;
+ case 'extra_info':
+ $this->parseExtraInfo($line, $bp);
+ break;
+ case 'info_url':
+ $this->parseInfoUrl($line, $bp);
+ break;
+ case 'state_overrides':
+ $this->parseStateOverrides($line, $bp);
+ break;
+ case 'template':
+ // compat, ignoring for now
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Parses a single line
+ *
+ * Adds eventual new knowledge to the given Business Process config
+ *
+ * @param $line
+ *
+ * @throws ConfigurationError
+ */
+ protected function parseLine(&$line)
+ {
+ $bp = $this->config;
+ $line = trim($line);
+
+ $this->currentLineNumber++;
+
+ // Skip empty or comment-only lines
+ if (empty($line) || $line[0] === '#') {
+ return;
+ }
+
+ // Space found in the first 16 cols? Might be a line with extra information
+ $pos = strpos($line, ' ');
+ if ($pos !== false && $pos < 16) {
+ if ($this->parseExtraLine($line, $pos, $bp)) {
+ return;
+ }
+ }
+
+ if (strpos($line, '=') === false) {
+ $this->parseError('Got invalid line');
+ }
+
+ list($name, $value) = preg_split('~\s*=\s*~', $line, 2);
+
+ if (strpos($name, ';') !== false) {
+ $this->parseError('No semicolon allowed in varname');
+ }
+
+ $op = '&';
+ if (preg_match_all('~(?<!\\\\)([\|\+&\!\%])~', $value, $m)) {
+ $op = implode('', $m[1]);
+ for ($i = 1; $i < strlen($op); $i++) {
+ if ($op[$i] !== $op[$i - 1]) {
+ $this->parseError('Mixing operators is not allowed');
+ }
+ }
+ }
+ $op = $op[0];
+ $op_name = $op;
+
+ if ($op === '+') {
+ if (! preg_match('~^(\d+)(?::(\d+))?\s*of:\s*(.+?)$~', $value, $m)) {
+ $this->parseError('syntax: <var> = <num> of: <var1> + <var2> [+ <varn>]*');
+ }
+ $op_name = $m[1];
+ // New feature: $minWarn = $m[2];
+ $value = $m[3];
+ }
+
+ $node = new BpNode((object) array(
+ 'name' => $name,
+ 'operator' => $op_name,
+ 'child_names' => []
+ ));
+ $node->setBpConfig($bp);
+
+ $cmps = preg_split('~\s*(?<!\\\\)\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($cmps as $val) {
+ $val = preg_replace('~(\\\\([\|\+&\!\%]))~', '$2', $val);
+ if (strpos($val, ';') !== false) {
+ if ($bp->hasNode($val)) {
+ $node->addChild($bp->getNode($val));
+ } else {
+ list($host, $service) = preg_split('~;~', $val, 2);
+ if ($service === 'Hoststatus') {
+ $node->addChild($bp->createHost($host));
+ } else {
+ $node->addChild($bp->createService($host, $service));
+ }
+ }
+ } elseif ($val[0] === '@') {
+ if (strpos($val, ':') === false) {
+ throw new ConfigurationError(
+ "I'm unable to import full external configs, a node needs to be provided for '%s'",
+ $val
+ );
+ } else {
+ list($config, $nodeName) = preg_split('~:\s*~', substr($val, 1), 2);
+ $node->addChild($bp->createImportedNode($config, $nodeName));
+ }
+ } elseif ($bp->hasNode($val)) {
+ $node->addChild($bp->getNode($val));
+ } else {
+ $this->missingNodes[$val][] = $node;
+ }
+ }
+
+ $bp->addNode($name, $node);
+ }
+
+ /**
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->currentFilename ?: '[given string]';
+ }
+
+ /**
+ * @param $msg
+ * @throws ConfigurationError
+ */
+ protected function parseError($msg)
+ {
+ throw new ConfigurationError(
+ sprintf(
+ 'Parse error on %s:%s: %s',
+ $this->getFilename(),
+ $this->currentLineNumber,
+ $msg
+ )
+ );
+ }
+}
diff --git a/library/Businessprocess/Storage/LegacyConfigRenderer.php b/library/Businessprocess/Storage/LegacyConfigRenderer.php
new file mode 100644
index 0000000..7e2e0b2
--- /dev/null
+++ b/library/Businessprocess/Storage/LegacyConfigRenderer.php
@@ -0,0 +1,255 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Storage;
+
+use Icinga\Module\Businessprocess\BpNode;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\ImportedNode;
+
+class LegacyConfigRenderer
+{
+ /** @var array */
+ protected $renderedNodes;
+
+ /**
+ * LecagyConfigRenderer constructor
+ *
+ * @param BpConfig $config
+ */
+ public function __construct(BpConfig $config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ return $this->renderHeader() . $this->renderNodes();
+ }
+
+ /**
+ * @param BpConfig $config
+ * @return mixed
+ */
+ public static function renderConfig(BpConfig $config)
+ {
+ $renderer = new static($config);
+ return $renderer->render();
+ }
+
+ /**
+ * @return string
+ */
+ public function renderHeader()
+ {
+ $str = "### Business Process Config File ###\n#\n";
+
+ $meta = $this->config->getMetadata();
+ foreach ($meta->getProperties() as $key => $value) {
+ if ($value === null) {
+ continue;
+ }
+
+ $str .= sprintf("# %-15s : %s\n", $key, $value);
+ }
+
+ $str .= "#\n###################################\n\n";
+
+ return $str;
+ }
+
+ /**
+ * @return string
+ */
+ public function renderNodes()
+ {
+ $this->renderedNodes = array();
+
+ $config = $this->config;
+ $str = '';
+
+ foreach ($config->getRootNodes() as $node) {
+ $str .= $this->requireRenderedBpNode($node);
+ }
+
+ foreach ($config->getUnboundNodes() as $name => $node) {
+ $str .= $this->requireRenderedBpNode($node);
+ }
+
+ return $str . "\n";
+ }
+
+ /**
+ * Rendered node definition, empty string if already rendered
+ *
+ * @param BpNode $node
+ *
+ * @return string
+ */
+ protected function requireRenderedBpNode(BpNode $node)
+ {
+ $name = $node->getName();
+
+ if (array_key_exists($name, $this->renderedNodes)) {
+ return '';
+ } else {
+ $this->renderedNodes[$name] = true;
+ return $this->renderBpNode($node);
+ }
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ protected function renderBpNode(BpNode $node)
+ {
+ $name = $node->getName();
+ // Doing this before rendering children allows us to store loops
+ $cfg = '';
+
+ foreach ($node->getChildBpNodes() as $name => $child) {
+ if ($child instanceof ImportedNode) {
+ continue;
+ }
+
+ $cfg .= $this->requireRenderedBpNode($child) . "\n";
+ }
+
+ $cfg .= static::renderSingleBpNode($node);
+
+ return $cfg;
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderEqualSign(BpNode $node)
+ {
+ $op = $node->getOperator();
+ if (is_numeric($op)) {
+ return '= ' . $op . ' of:';
+ } else {
+ return '=';
+ }
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderOperator(BpNode $node)
+ {
+ $op = $node->getOperator();
+ if (is_numeric($op)) {
+ return '+';
+ } else {
+ return $op;
+ }
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderSingleBpNode(BpNode $node)
+ {
+ return static::renderExpression($node)
+ . static::renderStateOverrides($node)
+ . static::renderDisplay($node)
+ . static::renderInfoUrl($node);
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderExpression(BpNode $node)
+ {
+ return sprintf(
+ "%s %s %s\n",
+ $node->getName(),
+ static::renderEqualSign($node),
+ static::renderChildNames($node)
+ );
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderChildNames(BpNode $node)
+ {
+ $op = static::renderOperator($node);
+ $children = $node->getChildNames();
+ $str = implode(' ' . $op . ' ', array_map(function ($val) {
+ return preg_replace('~([\|\+&\!\%])~', '\\\\$1', $val);
+ }, $children));
+
+ if ((count($children) < 2) && $op !== '&') {
+ return $op . ' ' . $str;
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderDisplay(BpNode $node)
+ {
+ if ($node->hasAlias() || $node->getDisplay() > 0) {
+ $prio = $node->getDisplay();
+ return sprintf(
+ "display %s;%s;%s\n",
+ $prio,
+ $node->getName(),
+ $node->getAlias()
+ );
+ } else {
+ return '';
+ }
+ }
+
+ public static function renderStateOverrides(BpNode $node)
+ {
+ $stateOverrides = '';
+ foreach ($node->getStateOverrides() as $childName => $overrideRules) {
+ $overrides = [];
+ foreach ($overrideRules as $from => $to) {
+ $overrides[] = sprintf('%d-%d', $from, $to);
+ }
+
+ if (! empty($overrides)) {
+ $stateOverrides .= '!' . $childName . '|' . join(',', $overrides);
+ }
+ }
+
+ if (! $stateOverrides) {
+ return '';
+ }
+
+ return 'state_overrides ' . $node->getName() . $stateOverrides . "\n";
+ }
+
+ /**
+ * @param BpNode $node
+ * @return string
+ */
+ public static function renderInfoUrl(BpNode $node)
+ {
+ if ($node->hasInfoUrl()) {
+ return sprintf(
+ "info_url %s;%s\n",
+ $node->getName(),
+ $node->getInfoUrl()
+ );
+ } else {
+ return '';
+ }
+ }
+}
diff --git a/library/Businessprocess/Storage/LegacyStorage.php b/library/Businessprocess/Storage/LegacyStorage.php
new file mode 100644
index 0000000..6582ebd
--- /dev/null
+++ b/library/Businessprocess/Storage/LegacyStorage.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Storage;
+
+use DirectoryIterator;
+use Icinga\Application\Hook\AuditHook;
+use Icinga\Application\Icinga;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Exception\SystemPermissionException;
+
+class LegacyStorage extends Storage
+{
+ /**
+ * All parsed configurations
+ *
+ * @var BpConfig[]
+ */
+ protected $configs = [];
+
+ /** @var string */
+ protected $configDir;
+
+ public function getConfigDir()
+ {
+ if ($this->configDir === null) {
+ $this->prepareDefaultConfigDir();
+ }
+
+ return $this->configDir;
+ }
+
+ protected function prepareDefaultConfigDir()
+ {
+ $dir = Icinga::app()
+ ->getModuleManager()
+ ->getModule('businessprocess')
+ ->getConfigDir();
+
+ // TODO: This is silly. We need Config::requireDirectory().
+ if (! is_dir($dir)) {
+ if (! is_dir(dirname($dir))) {
+ if (! @mkdir(dirname($dir))) {
+ throw new SystemPermissionException('Could not create config directory "%s"', dirname($dir));
+ }
+ }
+ if (! mkdir($dir)) {
+ throw new SystemPermissionException('Could not create config directory "%s"', $dir);
+ }
+ }
+ $dir = $dir . '/processes';
+ if (! is_dir($dir)) {
+ if (! mkdir($dir)) {
+ throw new SystemPermissionException('Could not create config directory "%s"', $dir);
+ }
+ }
+
+ $this->configDir = $dir;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listProcesses()
+ {
+ $files = array();
+
+ foreach ($this->listAllProcessNames() as $name) {
+ $meta = $this->loadMetadata($name);
+ if (! $meta->canRead()) {
+ continue;
+ }
+
+ $files[$name] = $meta->getExtendedTitle();
+ }
+
+ natcasesort($files);
+ return $files;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listProcessNames()
+ {
+ $files = array();
+
+ foreach ($this->listAllProcessNames() as $name) {
+ $meta = $this->loadMetadata($name);
+ if (! $meta->canRead()) {
+ continue;
+ }
+
+ $files[$name] = $name;
+ }
+
+ natcasesort($files);
+ return $files;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function listAllProcessNames()
+ {
+ $files = array();
+
+ foreach (new DirectoryIterator($this->getConfigDir()) as $file) {
+ if ($file->isDot()) {
+ continue;
+ }
+
+ $filename = $file->getFilename();
+ if (substr($filename, -5) === '.conf') {
+ $files[] = substr($filename, 0, -5);
+ }
+ }
+
+ natcasesort($files);
+ return $files;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function loadProcess($name)
+ {
+ if (! isset($this->configs[$name])) {
+ $this->configs[$name] = LegacyConfigParser::parseFile(
+ $name,
+ $this->getFilename($name)
+ );
+ }
+
+ return $this->configs[$name];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function storeProcess(BpConfig $process)
+ {
+ AuditHook::logActivity('businessprocess/store', "Business Process \"{$process->getName()}\" stored");
+ file_put_contents(
+ $this->getFilename($process->getName()),
+ LegacyConfigRenderer::renderConfig($process)
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteProcess($name)
+ {
+ AuditHook::logActivity('businessprocess/delete', "Business Process \"{$name}\" deleted");
+ return @unlink($this->getFilename($name));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function loadMetadata($name)
+ {
+ if (isset($this->configs[$name])) {
+ return $this->configs[$name]->getMetadata();
+ }
+
+ return LegacyConfigParser::readMetadataFromFileHeader(
+ $name,
+ $this->getFilename($name)
+ );
+ }
+
+ public function getSource($name)
+ {
+ return file_get_contents($this->getFilename($name));
+ }
+
+ public function getFilename($name)
+ {
+ return $this->getConfigDir() . '/' . $name . '.conf';
+ }
+
+ /**
+ * @param $name
+ * @param $string
+ *
+ * @return BpConfig
+ */
+ public function loadFromString($name, $string)
+ {
+ return LegacyConfigParser::parseString($name, $string);
+ }
+
+ /**
+ * @param $name
+ * @return bool
+ */
+ public function hasProcess($name)
+ {
+ $file = $this->getFilename($name);
+ if (! is_file($file)) {
+ return false;
+ }
+
+ return $this->loadMetadata($name)->canRead();
+ }
+}
diff --git a/library/Businessprocess/Storage/Storage.php b/library/Businessprocess/Storage/Storage.php
new file mode 100644
index 0000000..c8a07ba
--- /dev/null
+++ b/library/Businessprocess/Storage/Storage.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Storage;
+
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Metadata;
+
+abstract class Storage
+{
+ /**
+ * @var static
+ */
+ protected static $instance;
+
+ /**
+ * @var ConfigObject
+ */
+ protected $config;
+
+ /**
+ * Storage constructor.
+ * @param ConfigObject $config
+ */
+ public function __construct(ConfigObject $config)
+ {
+ $this->config = $config;
+ $this->init();
+ }
+
+ protected function init()
+ {
+ }
+
+ public static function getInstance()
+ {
+ if (static::$instance === null) {
+ static::$instance = new static(Config::module('businessprocess')->getSection('global'));
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * All processes readable by the current user
+ *
+ * The returned array has the form <process name> => <nice title>, sorted
+ * by title
+ *
+ * @return array
+ */
+ abstract public function listProcesses();
+
+ /**
+ * All process names readable by the current user
+ *
+ * The returned array has the form <process name> => <process name> and is
+ * sorted
+ *
+ * @return array
+ */
+ abstract public function listProcessNames();
+
+ /**
+ * All available process names, regardless of eventual restrictions
+ *
+ * @return array
+ */
+ abstract public function listAllProcessNames();
+
+ /**
+ * Whether a configuration with the given name exists
+ *
+ * @param $name
+ *
+ * @return bool
+ */
+ abstract public function hasProcess($name);
+
+ /**
+ * @param $name
+ * @return BpConfig
+ */
+ abstract public function loadProcess($name);
+
+ /**
+ * Store eventual changes applied to the given configuration
+ *
+ * @param BpConfig $config
+ *
+ * @return mixed
+ */
+ abstract public function storeProcess(BpConfig $config);
+
+ /**
+ * @param $name
+ * @return bool Whether the process has been deleted
+ */
+ abstract public function deleteProcess($name);
+
+ /**
+ * @param string $name
+ * @return Metadata
+ */
+ abstract public function loadMetadata($name);
+}