Completed
Push — master ( 281eb8...04fe01 )
by Sebastian
10:51
created

Xml::setCleanup()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 16
loc 16
c 0
b 0
f 0
rs 9.4285
ccs 7
cts 7
cp 1
cc 3
eloc 10
nc 3
nop 2
crap 3
1
<?php
2
namespace phpbu\App\Configuration\Loader;
3
4
use DOMElement;
5
use DOMXPath;
6
use phpbu\App\Configuration;
7
use phpbu\App\Configuration\Loader;
8
use phpbu\App\Exception;
9
use phpbu\App\Util\Str;
10
11
/**
12
 *
13
 * Loader for a phpbu XML configuration file.
14
 *
15
 * Example XML configuration file:
16
 * <code>
17
 * <?xml version="1.0" encoding="UTF-8" ?>
18
 * <phpbu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19
 *        xsi:noNamespaceSchemaLocation="http://schema.phpbu.de/1.1/phpbu.xsd"
20
 *        bootstrap="backup/bootstrap.php"
21
 *        verbose="true">
22
 *
23
 *   <php>
24
 *     <includePath>.</includePath>
25
 *     <ini name="max_execution_time" value="0" />
26
 *   </php>
27
 *
28
 *   <logging>
29
 *     <log type="json" target="/tmp/logfile.json" />
30
 *   </logging>
31
 *
32
 *   <backups>
33
 *     <backup>
34
 *       <source type="mysql">
35
 *         <option name="databases" value="dbname" />
36
 *         <option name="tables" value="" />
37
 *         <option name="ignoreTables" value="" />
38
 *         <option name="structureOnly" value="dbname.table1,dbname.table2" />
39
 *       </source>
40
 *
41
 *       <target dirname="/tmp/backup" filename="mysqldump-%Y%m%d-%H%i.sql" compress="bzip2" />
42
 *
43
 *       <check type="sizemin" value="10MB" />
44
 *
45
 *       <crypt type="mcrypt">
46
 *         <option name="algorithm" value="blowfish"/>
47
 *         <option name="key" value="myKey"/>
48
 *       </crypt>
49
 *
50
 *       <sync type="sftp" skipOnFailure="true">
51
 *         <option name="host" value="example.com" />
52
 *         <option name="user" value="user.name" />
53
 *         <option name="password" value="topsecret" />
54
 *         <option name="path" value="backup" />
55
 *       </sync>
56
 *
57
 *       <cleanup type="Outdated" skipOnFailure="true">
58
 *         <option name="older" value="2W" />
59
 *       </cleanup>
60
 *     </backup>
61
 *   </backups>
62
 * </phpbu>
63
 * </code>
64
 *
65
 * @package    phpbu
66
 * @subpackage App
67
 * @author     Sebastian Feldmann <[email protected]>
68
 * @copyright  Sebastian Feldmann <[email protected]>
69
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
70
 * @link       http://phpbu.de/
71
 * @since      Class available since Release 2.0.0
72
 */
73
class Xml extends File implements Loader
74
{
75
    /**
76
     * Config file DOMDocument
77
     *
78
     * @var \DOMDocument
79
     */
80
    private $document;
81
82
    /**
83
     * Xpath to navigate the config DOM.
84
     *
85
     * @var \DOMXPath
86
     */
87
    private $xpath;
88
89
    /**
90
     * Constructor.
91
     *
92
     * @param  string $file
93
     * @throws \phpbu\App\Exception
94
     */
95 15
    public function __construct($file)
96
    {
97 15
        $this->filename = $file;
98 15
        $this->document = $this->loadXmlFile($file);
99 13
        $this->xpath    = new DOMXPath($this->document);
100 13
    }
101
102
    /**
103
     * Return list of adapter configs.
104
     *
105
     * @return array
106
     * @throws \phpbu\App\Exception
107 13
     */
108
    protected function getAdapterConfigs()
109 13
    {
110
        $adapters = [];
111 13
        /** @var \DOMElement $adapterNode */
112 13
        foreach ($this->xpath->query('adapters/adapter') as $adapterNode) {
113 13
            $type    = $adapterNode->getAttribute('type');
114 13
            $name    = $adapterNode->getAttribute('name');
115 13
            $options = $this->getOptions($adapterNode);
116 13
            if (!$type) {
117 13
                throw new Exception('invalid adapter configuration: attribute type missing');
118 13
            }
119 13
            if (!$name) {
120 13
                throw new Exception('invalid adapter configuration: attribute name missing');
121
            }
122
            $adapters[] = new Configuration\Adapter($type, $name, $options);
123
        }
124
        return $adapters;
125
    }
126
127
    /**
128 13
     * Set the phpbu application settings.
129
     *
130 13
     * @param  \phpbu\App\Configuration $configuration
131 11
     */
132 11
    public function setAppSettings(Configuration $configuration)
133 11
    {
134 11
        $root = $this->document->documentElement;
135 13
136 13
        if ($root->hasAttribute('bootstrap')) {
137
            $configuration->setBootstrap($this->toAbsolutePath($root->getAttribute('bootstrap')));
138 11
        }
139 11
        if ($root->hasAttribute('verbose')) {
140
            $configuration->setVerbose(Str::toBoolean($root->getAttribute('verbose'), false));
141 11
        }
142 13
        if ($root->hasAttribute('colors')) {
143 13
            $configuration->setColors(Str::toBoolean($root->getAttribute('colors'), false));
144
        }
145
    }
146
147
    /**
148
     * Set the php settings.
149
     * Checking for include_path and ini settings.
150
     *
151 13
     * @param  \phpbu\App\Configuration $configuration
152
     */
153
    public function setPhpSettings(Configuration $configuration)
154 13
    {
155 11
        foreach ($this->xpath->query('php/includePath') as $includePath) {
156 11
            $path = $includePath->nodeValue;
157 1
            if ($path) {
158
                $configuration->addIncludePath($this->toAbsolutePath($path));
159 10
            }
160 10
        }
161 1
        foreach ($this->xpath->query('php/ini') as $ini) {
162 1
            /** @var DOMElement $ini */
163
            $name  = $ini->getAttribute('name');
164 10
            $value = $ini->getAttribute('value');
165 10
166 9
            $configuration->addIniSetting($name, $value);
167 9
        }
168 10
    }
169 12
170 12
    /**
171
     * Set the log configuration.
172
     *
173
     * @param  \phpbu\App\Configuration $configuration
174
     * @throws \phpbu\App\Exception
175
     */
176
    public function setLoggers(Configuration $configuration)
177
    {
178 12
        /** @var \DOMElement $logNode */
179
        foreach ($this->xpath->query('logging/log') as $logNode) {
180 12
            $type = $logNode->getAttribute('type');
181 12
            if (!$type) {
182 6
                throw new Exception('invalid logger configuration: attribute type missing');
183 6
            }
184
            $options = $this->getOptions($logNode);
185
            if (isset($options['target'])) {
186
                $options['target'] = $this->toAbsolutePath($options['target']);
187
            }
188
            // search for target attribute to convert to option
189
            $target = $logNode->getAttribute('target');
190
            if (!empty($target)) {
191
                $options['target'] = $this->toAbsolutePath($target);
192 12
            }
193
            $configuration->addLogger(new Configuration\Logger($type, $options));
194 12
        }
195 12
    }
196 12
197
    /**
198 12
     * Set the backup configurations.
199 10
     *
200
     * @param  \phpbu\App\Configuration $configuration
201 9
     * @throws \phpbu\App\Exception
202 9
     */
203 8
    public function setBackups(Configuration $configuration)
204 7
    {
205
        foreach ($this->xpath->query('backups/backup') as $backupNode) {
206 6
            $configuration->addBackup($this->getBackupConfig($backupNode));
207
        }
208
    }
209
210
    /**
211
     * Get the config for a single backup node.
212
     *
213
     * @param  \DOMElement $backupNode
214
     * @throws \phpbu\App\Exception
215
     * @return \phpbu\App\Configuration\Backup
216 12
     */
217 View Code Duplication
    private function getBackupConfig(DOMElement $backupNode)
218 12
    {
219 12
        $stopOnFailure = Str::toBoolean($backupNode->getAttribute('stopOnFailure'), false);
220 1
        $backupName    = $backupNode->getAttribute('name');
221
        $backup        = new Configuration\Backup($backupName, $stopOnFailure);
222
223 11
        $backup->setSource($this->getSource($backupNode));
224 11
        $backup->setTarget($this->getTarget($backupNode));
225 11
226 1
        $this->setChecks($backup, $backupNode);
227
        $this->setCrypt($backup, $backupNode);
228
        $this->setSyncs($backup, $backupNode);
229 10
        $this->setCleanup($backup, $backupNode);
230
231
        return $backup;
232
    }
233
234
    /**
235
     * Get source configuration.
236
     *
237
     * @param  \DOMElement $node
238
     * @return \phpbu\App\Configuration\Backup\Source
239 10
     * @throws \phpbu\App\Exception
240
     */
241 10
    protected function getSource(DOMElement $node)
242 10
    {
243 1
        $sources = $node->getElementsByTagName('source');
244
        if ($sources->length !== 1) {
245
            throw new Exception('backup requires exactly one source config');
246 9
        }
247 9
        /** @var DOMElement $sourceNode */
248 9
        $sourceNode = $sources->item(0);
249 9
        $type       = $sourceNode->getAttribute('type');
250
        if (!$type) {
251 9
            throw new Exception('source requires type attribute');
252 9
        }
253 9
254
        return new Configuration\Backup\Source($type, $this->getOptions($sourceNode));
255 9
    }
256
257
    /**
258
     * Get Target configuration.
259
     *
260
     * @param  \DOMElement $node
261
     * @return \phpbu\App\Configuration\Backup\Target
262
     * @throws \phpbu\App\Exception
263
     */
264 9
    protected function getTarget(DOMElement $node)
265
    {
266
        $targets = $node->getElementsByTagName('target');
267 9
        if ($targets->length !== 1) {
268 9
            throw new Exception('backup requires exactly one target config');
269 9
        }
270
        /** @var DOMElement $targetNode */
271 9
        $targetNode = $targets->item(0);
272 1
        $compress   = $targetNode->getAttribute('compress');
273
        $filename   = $targetNode->getAttribute('filename');
274 8
        $dirname    = $targetNode->getAttribute('dirname');
275 9
276 9
        if ($dirname) {
277
            $dirname = $this->toAbsolutePath($dirname);
278
        }
279
280
        return new Configuration\Backup\Target($dirname, $filename, $compress);
281
    }
282
283
    /**
284
     * Set backup checks.
285 9
     *
286
     * @param \phpbu\App\Configuration\Backup $backup
287
     * @param \DOMElement                     $node
288 9
     */
289 9
    protected function setChecks(Configuration\Backup $backup, DOMElement $node)
290
    {
291 7
        /** @var DOMElement $checkNode */
292 7
        foreach ($node->getElementsByTagName('check') as $checkNode) {
293 7
            $type  = $checkNode->getAttribute('type');
294 1
            $value = $checkNode->getAttribute('value');
295
            // skip invalid sanity checks
296 6
            if (!$type || !$value) {
297 6
                continue;
298 6
            }
299 6
            $backup->addCheck(new Configuration\Backup\Check($type, $value));
300 8
        }
301
    }
302
303
    /**
304
     * Set the crypt configuration.
305
     *
306
     * @param  \phpbu\App\Configuration\Backup $backup
307
     * @param  \DOMElement                     $node
308
     * @throws \phpbu\App\Exception
309 8
     */
310 View Code Duplication
    protected function setCrypt(Configuration\Backup $backup, DOMElement $node)
311
    {
312 8
        /** @var \DOMNodeList $cryptNodes */
313 8
        $cryptNodes = $node->getElementsByTagName('crypt');
314 8
        if ($cryptNodes->length > 0) {
315 1
            /** @var \DOMElement $cryptNode */
316
            $cryptNode = $cryptNodes->item(0);
317 7
            $type = $cryptNode->getAttribute('type');
318 7
            if (!$type) {
319 7
                throw new Exception('invalid crypt configuration: attribute type missing');
320 7
            }
321 7
            $skip    = Str::toBoolean($cryptNode->getAttribute('skipOnFailure'), true);
322
            $options = $this->getOptions($cryptNode);
323
            $backup->setCrypt(new Configuration\Backup\Crypt($type, $skip, $options));
324
        }
325
    }
326
327
    /**
328
     * Set backup sync configurations.
329
     *
330 7
     * @param  \phpbu\App\Configuration\Backup $backup
331
     * @param  \DOMElement                     $node
332
     * @throws \phpbu\App\Exception
333 7
     */
334 7
    protected function setSyncs(Configuration\Backup $backup, DOMElement $node)
335
    {
336 7
        /** @var DOMElement $syncNode */
337 7
        foreach ($node->getElementsByTagName('sync') as $syncNode) {
338 7
            $type = $syncNode->getAttribute('type');
339 1
            if (!$type) {
340
                throw new Exception('invalid sync configuration: attribute type missing');
341 6
            }
342 6
            $skip    = Str::toBoolean($syncNode->getAttribute('skipOnFailure'), true);
343 6
            $options = $this->getOptions($syncNode);
344 6
            $backup->addSync(new Configuration\Backup\Sync($type, $skip, $options));
345 6
        }
346
    }
347
348
    /**
349
     * Set the cleanup configuration.
350
     *
351
     * @param  \phpbu\App\Configuration\Backup $backup
352
     * @param  \DOMElement                     $node
353 11
     * @throws \phpbu\App\Exception
354
     */
355 11 View Code Duplication
    protected function setCleanup(Configuration\Backup $backup, DOMElement $node)
356
    {
357 11
        /** @var \DOMNodeList $cleanupNodes */
358 11
        $cleanupNodes = $node->getElementsByTagName('cleanup');
359 11
        if ($cleanupNodes->length > 0) {
360 11
            /** @var \DOMElement $cleanupNode */
361 11
            $cleanupNode = $cleanupNodes->item(0);
362 11
            $type        = $cleanupNode->getAttribute('type');
363
            if (!$type) {
364
                throw new Exception('invalid cleanup configuration: attribute type missing');
365
            }
366
            $skip    = Str::toBoolean($cleanupNode->getAttribute('skipOnFailure'), true);
367
            $options = $this->getOptions($cleanupNode);
368
            $backup->setCleanup(new Configuration\Backup\Cleanup($type, $skip, $options));
369
        }
370
    }
371
372 15
    /**
373
     * Extracts all option tags.
374 15
     *
375 14
     * @param  DOMElement $node
376 14
     * @return array
377 14
     */
378 14
    protected function getOptions(DOMElement $node)
379
    {
380 14
        $options = [];
381 14
        /** @var \DOMElement $optionNode */
382
        foreach ($node->getElementsByTagName('option') as $optionNode) {
383 14
            $name           = $optionNode->getAttribute('name');
384 1
            $value          = $this->getOptionValue($optionNode->getAttribute('value'));
385 14
            $options[$name] = $value;
386
        }
387 14
        return $options;
388 14
    }
389
390 14
    /**
391 1
     * Load the XML-File.
392 1
     *
393 1
     * @param  string $filename
394 1
     * @throws \phpbu\App\Exception
395 1
     * @return \DOMDocument
396 1
     */
397 1
    private function loadXmlFile($filename)
398
    {
399 13
        $contents  = $this->loadFile($filename);
400
        $document  = new \DOMDocument;
401
        $message   = '';
402
        $internal  = libxml_use_internal_errors(true);
403
        $reporting = error_reporting(0);
404
405
        $document->documentURI = $filename;
406
        $loaded                = $document->loadXML($contents);
407
408
        foreach (libxml_get_errors() as $error) {
409
            $message .= "\n" . $error->message;
410
        }
411
412
        libxml_use_internal_errors($internal);
413
        error_reporting($reporting);
414
415
        if ($loaded === false || $message !== '') {
416
            throw new Exception(
417
                sprintf(
418
                    'Error loading file "%s".%s',
419
                    $filename,
420
                    $message != '' ? "\n" . $message : ''
421
                )
422
            );
423
        }
424
        return $document;
425
    }
426
}
427