Completed
Push — master ( 58b546...f86624 )
by Sebastian
02:38
created

Cmd::createConfiguration()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 8.6897
c 0
b 0
f 0
cc 6
nc 2
nop 2
1
<?php
2
/**
3
 * phpbu
4
 *
5
 * Copyright (c) 2014 - 2017 Sebastian Feldmann <[email protected]>
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package    phpbu
26
 * @author     Sebastian Feldmann <[email protected]>
27
 * @copyright  Sebastian Feldmann
28
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
29
 * @link       http://phpbu.de/
30
 * @since      Class available since Release 1.0.0
31
 */
32
namespace phpbu\App;
33
34
use Phar;
35
use phpbu\App\Cmd\Args;
36
use phpbu\App\Configuration\Bootstrapper;
37
use phpbu\App\Util\Arr;
38
39
/**
40
 * Main application class.
41
 *
42
 * @package    phpbu
43
 * @author     Sebastian Feldmann <[email protected]>
44
 * @copyright  Sebastian Feldmann <[email protected]>
45
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
46
 * @link       http://phpbu.de/
47
 * @since      Class available since Release 1.0.0
48
 */
49
class Cmd
50
{
51
    const EXIT_SUCCESS   = 0;
52
    const EXIT_FAILURE   = 1;
53
    const EXIT_EXCEPTION = 2;
54
55
    /**
56
     * Is cmd executed from phar.
57
     *
58
     * @var bool
59
     */
60
    private $isPhar;
61
62
    /**
63
     * Is version string printed already.
64
     *
65
     * @var bool
66
     */
67
    private $isVersionStringPrinted = false;
68
69
    /**
70
     * List of given arguments
71
     *
72
     * @var array
73
     */
74
    private $arguments = [];
75
76
    /**
77
     * Runs the application.
78
     *
79
     * @param array $args
80
     */
81
    public function run(array $args)
82
    {
83
        $this->isPhar = defined('__PHPBU_PHAR__');
84
        $this->handleOpt($args);
85
86
        $ret        = self::EXIT_FAILURE;
87
        $factory    = new Factory();
88
        $runner     = new Runner($factory);
89
        $configFile = $this->findConfiguration();
90
91
        try {
92
            $this->printVersionString();
93
            $result = $runner->run($this->createConfiguration($configFile, $factory));
94
95
            if ($result->wasSuccessful()) {
96
                $ret = self::EXIT_SUCCESS;
97
            } elseif ($result->errorCount() > 0) {
98
                $ret = self::EXIT_EXCEPTION;
99
            }
100
        } catch (\Exception $e) {
101
            echo $e->getMessage() . PHP_EOL;
102
            $ret = self::EXIT_EXCEPTION;
103
        }
104
105
        exit($ret);
106
    }
107
108
    /**
109
     * Check arguments and load configuration file.
110
     *
111
     * @param array $args
112
     */
113
    protected function handleOpt(array $args)
114
    {
115
        try {
116
            $parser  = new Args($this->isPhar);
117
            $options = $parser->getOptions($args);
118
            $this->handleArgs($options);
119
        } catch (Exception $e) {
120
            $this->printError($e->getMessage(), true);
121
        }
122
    }
123
124
    /**
125
     * Handle the parsed command line options
126
     *
127
     * @param  array $options
128
     * @return void
129
     */
130
    protected function handleArgs(array $options)
131
    {
132
        foreach ($options as $option => $argument) {
133
            switch ($option) {
134
                case '-V':
135
                case '--version':
136
                    $this->printVersionString();
137
                    exit(self::EXIT_SUCCESS);
138
                case '--self-update':
139
                    $this->handleSelfUpdate();
140
                    exit(self::EXIT_SUCCESS);
141
                case '--generate-configuration':
142
                    $this->handleConfigGeneration();
143
                    exit(self::EXIT_SUCCESS);
144
                case '--version-check':
145
                    $this->handleVersionCheck();
146
                    exit(self::EXIT_SUCCESS);
147
                case '-h':
148
                case '--help':
149
                    $this->printHelp();
150
                    exit(self::EXIT_SUCCESS);
151
                case '-v':
152
                    $this->arguments['verbose'] = true;
153
                    break;
154
                default:
155
                    $this->arguments[trim($option, '-')] = $argument;
156
                    break;
157
            }
158
        }
159
    }
160
161
    /**
162
     * Try to find a configuration file.
163
     */
164
    protected function findConfiguration() : string
165
    {
166
        $configOption = $this->arguments['configuration'] ?? '';
167
168
        try {
169
            $finder = new Configuration\Finder();
170
            return $finder->findConfiguration($configOption);
171
        } catch (\Exception $e) {
172
            // config option given but still no config found
173
            if (!empty($configOption)) {
174
                $this->printError('Can\'t find configuration file.');
175
            }
176
            $this->printHelp();
177
            exit(self::EXIT_EXCEPTION);
178
        }
179
    }
180
181
    /**
182
     * Create a application configuration.
183
     *
184
     * @param  string             $configurationFile
185
     * @param  \phpbu\App\Factory $factory
186
     * @return \phpbu\App\Configuration
187
     * @throws \phpbu\App\Exception
188
     */
189
    protected function createConfiguration(string $configurationFile, Factory $factory) : Configuration
190
    {
191
        // setup bootstrapper with --bootstrap option that has precedence over the config file value
192
        $bootstrapper  = new Bootstrapper(Arr::getValue($this->arguments, 'bootstrap', ''));
193
        $configLoader  = Configuration\Loader\Factory::createLoader($configurationFile, $bootstrapper);
194
195
        if ($configLoader->hasValidationErrors()) {
196
            echo "  Warning - The configuration file did not pass validation!" . \PHP_EOL .
197
                 "  The following problems have been detected:" . \PHP_EOL;
198
199
            foreach ($configLoader->getValidationErrors() as $line => $errors) {
200
                echo \sprintf("\n  Line %d:\n", $line);
201
                foreach ($errors as $msg) {
202
                    echo \sprintf("  - %s\n", $msg);
203
                }
204
            }
205
            echo \PHP_EOL;
206
        }
207
208
        $configuration = $configLoader->getConfiguration($factory);
209
210
        // command line arguments overrule the config file settings
211
        $this->overrideConfigWithArguments($configuration);
212
213
        // check for command line limit option
214
        $limitOption = Arr::getValue($this->arguments, 'limit');
215
        $configuration->setLimit(!empty($limitOption) ? explode(',', $limitOption) : []);
216
217
        // add a cli printer for some output
218
        $configuration->addLogger(
219
            new Result\PrinterCli(
220
                $configuration->getVerbose(),
221
                $configuration->getColors(),
222
                ($configuration->getDebug() || $configuration->isSimulation())
223
            )
224
        );
225
        return $configuration;
226
    }
227
228
    /**
229
     * Override configuration settings with command line arguments.
230
     *
231
     * @param \phpbu\App\Configuration $configuration
232
     */
233
    protected function overrideConfigWithArguments(Configuration $configuration)
234
    {
235
        $settingsToOverride = ['verbose', 'colors', 'debug', 'simulate', 'restore'];
236
        foreach ($settingsToOverride as $arg) {
237
            $value = Arr::getValue($this->arguments, $arg);
238
            if (!empty($value)) {
239
                $setter = 'set' . ucfirst($arg);
240
                $configuration->{$setter}($value);
241
            }
242
        }
243
    }
244
245
    /**
246
     * Handle the phar self-update.
247
     */
248
    protected function handleSelfUpdate()
249
    {
250
        $this->printVersionString();
251
252
        // check if upgrade is necessary
253
        $latestVersion = $this->getLatestVersion();
254
        if (!$this->isPharOutdated($latestVersion)) {
255
            echo 'You already have the latest version of phpbu installed.' . PHP_EOL;
256
            exit(self::EXIT_SUCCESS);
257
        }
258
259
        $remoteFilename = 'http://phar.phpbu.de/phpbu.phar';
260
        $localFilename  = realpath($_SERVER['argv'][0]);
261
        $tempFilename   = basename($localFilename, '.phar') . '-temp.phar';
262
263
        echo 'Updating the phpbu PHAR to version ' . $latestVersion . ' ... ';
264
265
        $old  = error_reporting(0);
266
        $phar = file_get_contents($remoteFilename);
267
        error_reporting($old);
268
        if (!$phar) {
269
            echo ' failed' . PHP_EOL . 'Could not reach phpbu update site' . PHP_EOL;
270
            exit(self::EXIT_EXCEPTION);
271
        }
272
        file_put_contents($tempFilename, $phar);
273
274
        chmod($tempFilename, 0777 & ~umask());
275
276
        // check downloaded phar
277
        try {
278
            $phar = new Phar($tempFilename);
279
            unset($phar);
280
            // replace current phar with the new one
281
            rename($tempFilename, $localFilename);
282
        } catch (\Exception $e) {
283
            // cleanup crappy phar
284
            unlink($tempFilename);
285
            echo 'failed' . PHP_EOL . $e->getMessage() . PHP_EOL;
286
            exit(self::EXIT_EXCEPTION);
287
        }
288
289
        echo 'done' . PHP_EOL;
290
    }
291
292
    /**
293
     * Handle phar version-check.
294
     */
295
    protected function handleVersionCheck()
296
    {
297
        $this->printVersionString();
298
299
        $latestVersion = $this->getLatestVersion();
300
        if ($this->isPharOutdated($latestVersion)) {
301
            print 'You are not using the latest version of phpbu.' . PHP_EOL
302
                . 'Use "phpbu --self-update" to install phpbu ' . $latestVersion . PHP_EOL;
303
        } else {
304
            print 'You are using the latest version of phpbu.' . PHP_EOL;
305
        }
306
    }
307
308
    /**
309
     * Create a configuration file by asking the user some questions
310
     *
311
     * @return void
312
     */
313
    private function handleConfigGeneration()
314
    {
315
        $this->printVersionString();
316
317
        print 'Configuration file format: xml|json (default: xml): ';
318
        $format = \trim(\fgets(\STDIN)) === 'json' ? 'json' : 'xml';
319
        $file   = 'phpbu.' . $format;
320
321
        if (file_exists($file)) {
322
            echo '  FAILED: The configuration file already exists.' . \PHP_EOL;
323
            exit(self::EXIT_EXCEPTION);
324
        }
325
326
        print \PHP_EOL . 'Generating ' . $file . ' in ' . \getcwd() . \PHP_EOL . \PHP_EOL;
327
328
        print 'Bootstrap script (relative to path shown above; e.g: vendor/autoload.php): ';
329
        $bootstrapScript = \trim(\fgets(\STDIN));
330
331
        $generator = new Configuration\Generator;
332
333
        \file_put_contents(
334
            $file,
335
            $generator->generateDefaultConfiguration(
336
                Version::minor(),
337
                $format,
338
                $bootstrapScript
339
            )
340
        );
341
342
        print \PHP_EOL . 'Generated ' . $file . ' in ' . \getcwd() . \PHP_EOL . \PHP_EOL .
343
            'ATTENTION:' . \PHP_EOL .
344
            'The created configuration is just a skeleton. You have to finish the configuration manually.' . \PHP_EOL;
345
    }
346
347
    /**
348
     * Returns latest released phpbu version.
349
     *
350
     * @return string
351
     * @throws \RuntimeException
352
     */
353
    protected function getLatestVersion() : string
354
    {
355
        $old     = error_reporting(0);
356
        $version = file_get_contents('https://phar.phpbu.de/latest-version-of/phpbu');
357
        error_reporting($old);
358
        if (!$version) {
359
            echo 'Network-Error: Could not check latest version.' . PHP_EOL;
360
            exit(self::EXIT_EXCEPTION);
361
        }
362
        return $version;
363
    }
364
365
    /**
366
     * Check if current phar is outdated.
367
     *
368
     * @param  string $latestVersion
369
     * @return bool
370
     */
371
    protected function isPharOutdated(string $latestVersion) : bool
372
    {
373
        return version_compare($latestVersion, Version::id(), '>');
374
    }
375
376
    /**
377
     * Shows the current application version.
378
     */
379
    protected function printVersionString()
380
    {
381
        if ($this->isVersionStringPrinted) {
382
            return;
383
        }
384
385
        echo Version::getVersionString() . PHP_EOL . PHP_EOL;
386
        $this->isVersionStringPrinted = true;
387
    }
388
389
    /**
390
     * Show the help message.
391
     */
392
    protected function printHelp()
393
    {
394
        $this->printVersionString();
395
        echo <<<EOT
396
Usage: phpbu [option]
397
398
  --bootstrap=<file>       A "bootstrap" PHP file that is included before the backup.
399
  --configuration=<file>   A phpbu configuration file.
400
  --colors                 Use colors in output.
401
  --debug                  Display debugging information during backup generation.
402
  --generate-configuration Create a new configuration skeleton.
403
  --limit=<subset>         Limit backup execution to a subset.
404
  --simulate               Perform a trial run with no changes made.
405
  -h, --help               Print this usage information.
406
  -v, --verbose            Output more verbose information.
407
  -V, --version            Output version information and exit.
408
409
EOT;
410
        if ($this->isPhar) {
411
            echo '  --version-check        Check whether phpbu is up to date.' . PHP_EOL;
412
            echo '  --self-update          Upgrade phpbu to the latest version.' . PHP_EOL;
413
        }
414
    }
415
416
    /**
417
     * Shows some given error message.
418
     *
419
     * @param string $message
420
     * @param bool   $hint
421
     */
422
    private function printError($message, $hint = false)
423
    {
424
        $help = $hint ? ', use "phpbu -h" for help' : '';
425
        $this->printVersionString();
426
        echo $message . $help . PHP_EOL;
427
        exit(self::EXIT_EXCEPTION);
428
    }
429
430
    /**
431
     * Main method, is called by phpbu command and the phar file.
432
     */
433
    public static function main()
434
    {
435
        $app = new static();
436
        $app->run($_SERVER['argv']);
437
    }
438
}
439