summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Application/Config.php
blob: 80fe3b87ca63d0f077c2c1a4ec936ce78811cf0c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */

namespace Icinga\Application;

use Icinga\Exception\NotWritableError;
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(): int
    {
        return $this->select()->count();
    }

    /**
     * Reset the current position of the internal config object
     *
     * @return  void
     */
    public function rewind(): void
    {
        $this->config->rewind();
    }

    /**
     * Return the section of the current iteration
     *
     * @return  ConfigObject
     */
    public function current(): ConfigObject
    {
        return $this->config->current();
    }

    /**
     * Return whether the position of the current iteration is valid
     *
     * @return  bool
     */
    public function valid(): bool
    {
        return $this->config->valid();
    }

    /**
     * Return the section's name of the current iteration
     *
     * @return  string
     */
    public function key(): string
    {
        return $this->config->key();
    }

    /**
     * Advance the position of the current iteration and return the new section
     *
     * @return  void
     */
    public function next(): void
    {
        $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   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 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();
    }
}