PersistenceService::domainVhost()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.9332
c 0
b 0
f 0
cc 3
nc 2
nop 2
crap 12
1
<?php
2
3
/**
4
 * admin
5
 *
6
 * @category    Tollwerk
7
 * @package     Tollwerk\Admin
8
 * @subpackage  Tollwerk\Admin\Infrastructure\Service
9
 * @author      Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright   Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license     http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Tollwerk\Admin\Infrastructure\Service;
38
39
use Tollwerk\Admin\Application\Contract\PersistenceAdapterFactoryInterface;
40
use Tollwerk\Admin\Application\Contract\PersistenceServiceInterface;
41
use Tollwerk\Admin\Application\Contract\ServiceServiceInterface;
42
use Tollwerk\Admin\Domain\Account\AccountInterface;
43
use Tollwerk\Admin\Domain\Vhost\VhostInterface;
44
use Tollwerk\Admin\Infrastructure\App;
45
use Tollwerk\Admin\Infrastructure\Persistence\AccountHelper;
46
use Tollwerk\Admin\Infrastructure\Shell\Directory;
47
48
/**
49
 * Persistence service
50
 *
51
 * @package Tollwerk\Admin
52
 * @subpackage Tollwerk\Admin\Infrastructure
53
 */
54
class PersistenceService implements PersistenceServiceInterface
55
{
56
    /**
57
     * Persistence adapter factory
58
     *
59
     * @var PersistenceAdapterFactoryInterface
60
     */
61
    protected $persistenceAdapterFactory;
62
    /**
63
     * Webservers scheduled for reload
64
     *
65
     * @var array
66
     */
67
    protected $reloadWebserver = [];
68
    /**
69
     * PHP-Restart flag
70
     *
71
     * @var array
72
     */
73
    protected $reloadPhp = [];
74
    /**
75
     * Service service
76
     *
77
     * @var ServiceServiceInterface
78
     */
79
    protected $serviceService;
80
81
    /**
82
     * Constructor
83
     *
84
     * @param PersistenceAdapterFactoryInterface $persistenceAdapterFactory Persistence adapter factory
85
     * @param ServiceServiceInterface $serviceService Service service
86
     */
87
    public function __construct(
88
        PersistenceAdapterFactoryInterface $persistenceAdapterFactory,
89
        ServiceServiceInterface $serviceService
90
    ) {
91
        $this->persistenceAdapterFactory = $persistenceAdapterFactory;
92
        $this->serviceService = $serviceService;
93
    }
94
95
    /**
96
     * Create an account
97
     *
98
     * @param AccountInterface $account Account
99
     * @return void
100
     */
101
    public function createAccount(AccountInterface $account)
102
    {
103
    }
104
105
    /**
106
     * Delete an account
107
     *
108
     * @param AccountInterface $account Account
109
     * @return void
110
     */
111
    public function deleteAccount(AccountInterface $account)
112
    {
113
        // Delete all virtual hosts
114
        /** @var VhostInterface $vhost */
115
        foreach ($account->getVhosts() as $vhost) {
116
            $this->deleteVhost($account, $vhost);
117
        }
118
    }
119
120
    /**
121
     * Enable an account
122
     *
123
     * @param AccountInterface $account Account
124
     * @return void
125
     */
126
    public function enableAccount(AccountInterface $account)
127
    {
128
        // Enable all virtual hosts
129
        /** @var VhostInterface $vhost */
130
        foreach ($account->getVhosts() as $vhost) {
131
            $this->enableVhost($account, $vhost);
132
        }
133
    }
134
135
    /**
136
     * Disable an account
137
     *
138
     * @param AccountInterface $account Account
139
     * @return void
140
     */
141
    public function disableAccount(AccountInterface $account)
142
    {
143
        // Disable all virtual hosts
144
        /** @var VhostInterface $vhost */
145
        foreach ($account->getVhosts() as $vhost) {
146
            $this->disableVhost($account, $vhost);
147
        }
148
    }
149
150
    /**
151
     * Rename an account
152
     *
153
     * @param AccountInterface $account Account
154
     * @return void
155
     */
156
    public function renameAccount(AccountInterface $account)
157
    {
158
        // Rewrite all virtual hosts
159
        // Reload Apache
160
    }
161
162
    /**
163
     * Create a virtual host
164
     *
165
     * @param AccountInterface $account Account
166
     * @param VhostInterface $vhost Virtual host
167
     * @return void
168
     */
169
    public function createVhost(AccountInterface $account, VhostInterface $vhost)
170
    {
171
        $accountHelper = new AccountHelper($account);
172
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
173
        if (is_dir($availableVhost)) {
174
            throw new \RuntimeException(
175
                sprintf('Virtual host "%s" already exists', strval($vhost->getPrimaryDomain())),
176
                1476018313
177
            );
178
        }
179
180
        // Prepare the virtual host directory
181
        $this->prepareDirectory($availableVhost, $account->getName(), App::getConfig('general.group'));
182
183
        // Persist the virtual host
184
        $this->persistVhost($account, $vhost);
185
    }
186
187
    /**
188
     * Delete a virtual host
189
     *
190
     * @param AccountInterface $account Account
191
     * @param VhostInterface $vhost Virtual host
192
     * @return void
193
     */
194
    public function deleteVhost(AccountInterface $account, VhostInterface $vhost)
195
    {
196
        // Disable the virtual host first
197
        $this->disableVhost($account, $vhost);
198
199
        $accountHelper = new AccountHelper($account);
200
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
201
        if (is_dir($availableVhost)) {
202
            $this->deleteDirectory($availableVhost, $account->getName(), App::getConfig('general.group'));
203
        }
204
    }
205
206
    /**
207
     * Enable a virtual host
208
     *
209
     * @param AccountInterface $account Account
210
     * @param VhostInterface $vhost Virtual host
211
     * @return void
212
     * @throws \RuntimeException If the virtual host directory doesn't exist yet
213
     * @throws \RuntimeException If the virtual host is already enabled but cannot be renewed
214
     */
215
    public function enableVhost(AccountInterface $account, VhostInterface $vhost)
216
    {
217
        // If both the account and the virtual host are active: Enable the virtual host
218
        if ($account->isActive() && $vhost->isActive()) {
219
            $accountHelper = new AccountHelper($account);
220
            $availableVhost = $accountHelper->vhostDirectory($vhost, false);
221
222
            // If the virtual host directory doesn't exist yet: Error
223
            if (!is_dir($availableVhost)) {
224
                throw new \RuntimeException(
225
                    sprintf('Virtual host "%s" isn\'t persisted yet', strval($vhost->getPrimaryDomain())),
226
                    1476015113
227
                );
228
            }
229
230
            $enabledVhost = $accountHelper->vhostDirectory($vhost, true);
231
            $this->prepareDirectory(dirname($enabledVhost), $account->getName(), App::getConfig('general.group'));
232
233
            // If the virtual host is already enabled but cannot be renewed
234
            if (file_exists($enabledVhost) && !unlink($enabledVhost)) {
235
                throw new \RuntimeException(
236
                    sprintf(
237
                        'Virtual host "%s" is already enabled but cannot be renewed',
238
                        strval($vhost->getPrimaryDomain())
239
                    ),
240
                    1476016371
241
                );
242
            }
243
244
            $relativeEnabledVhost = '..'.substr($availableVhost, strlen($accountHelper->directory('config')));
245
            symlink($relativeEnabledVhost, $enabledVhost);
246
247
            // Reload the webserver if the account is active
248
            if ($account->isActive()) {
249
                $this->serviceService->reloadWebserver($vhost->getType());
250
            }
251
        }
252
    }
253
254
    /**
255
     * Disable a virtual host
256
     *
257
     * @param AccountInterface $account Account
258
     * @param VhostInterface $vhost Virtual host
259
     * @return void
260
     */
261
    public function disableVhost(AccountInterface $account, VhostInterface $vhost)
262
    {
263
        $accountHelper = new AccountHelper($account);
264
        $enabledVhost = $accountHelper->vhostDirectory($vhost, true);
265
266
        // If the virtual host cannot be disabled
267
        if (file_exists($enabledVhost) && !unlink($enabledVhost)) {
268
            throw new \RuntimeException(
269
                sprintf('Virtual host "%s" cannot be disabled', strval($vhost->getPrimaryDomain())),
270
                1476016872
271
            );
272
        }
273
274
        // Reload the webserver if the account is active
275
        if ($account->isActive()) {
276
            $this->serviceService->reloadWebserver($vhost->getType());
277
        }
278
    }
279
280
    /**
281
     * Redirect a virtual host
282
     *
283
     * @param AccountInterface $account Account
284
     * @param VhostInterface $vhost Virtual host
285
     * @return void
286
     */
287
    public function redirectVhost(AccountInterface $account, VhostInterface $vhost)
288
    {
289
        // Re-persist the virtual host
290
        $this->persistVhost($account, $vhost);
291
292
        // Reload the webserver if the account and virtual host are enabled
293
        if ($account->isActive() && $vhost->isActive()) {
294
            $this->serviceService->reloadWebserver($vhost->getType());
295
        }
296
    }
297
298
    /**
299
     * Configure the PHP version of a virtual host
300
     *
301
     * @param AccountInterface $account Account
302
     * @param VhostInterface $vhost Virtual host
303
     * @param string|null $oldPhpVersion Old PHP version
304
     * @return void
305
     * @throws \RuntimeException If the previous PHP configuration cannot be removed
306
     */
307
    public function phpVhost(AccountInterface $account, VhostInterface $vhost, $oldPhpVersion = null)
308
    {
309
        $accountHelper = new AccountHelper($account);
310
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
311
312
        // Find all existing PHP configurations
313
        foreach (glob($availableVhost.DIRECTORY_SEPARATOR.'{fpm-*.conf,*_fpm.include}', GLOB_BRACE) as $fpmConfig) {
314
            // If the previous PHP configuration cannot be removed
315
            if (!unlink($fpmConfig)) {
316
                throw new \RuntimeException(
317
                    sprintf('Cannot remove PHP configuration "%s"', basename($fpmConfig)),
318
                    1476019274
319
                );
320
            }
321
        }
322
323
        // Re-persist the virtual host
324
        $this->persistVhost($account, $vhost);
325
326
        // Reload PHP
327
        $newPhpVersion = $vhost->getPhp();
328
        if ($oldPhpVersion !== $newPhpVersion) {
329
            // If the old PHP version needs to be disabled
330
            if ($oldPhpVersion !== null) {
331
                $this->serviceService->reloadPhp($oldPhpVersion);
332
            }
333
334
            // If the new PHP version needs to be enabled
335
            if ($newPhpVersion !== null) {
336
                $this->serviceService->reloadPhp($newPhpVersion);
337
            }
338
        }
339
340
        // Reload the webserver if the account and virtual host are enabled
341
        if ($account->isActive() && $vhost->isActive()) {
342
            $this->serviceService->reloadWebserver($vhost->getType());
343
        }
344
    }
345
346
    /**
347
     * Configure a protocol port for a virtual host
348
     *
349
     * @param AccountInterface $account Account
350
     * @param VhostInterface $vhost Virtual host
351
     * @return void
352
     */
353
    public function portVhost(AccountInterface $account, VhostInterface $vhost)
354
    {
355
        $accountHelper = new AccountHelper($account);
356
        $availableVhost = $accountHelper->vhostDirectory($vhost, false);
357
358
        // Find all existing PHP configurations
359
        foreach (glob($availableVhost.DIRECTORY_SEPARATOR.'{certbot.ini,*_ssl.include}', GLOB_BRACE) as $sslConfig) {
360
            // If the previous SSL configuration cannot be removed
361
            if (!unlink($sslConfig)) {
362
                throw new \RuntimeException(
363
                    sprintf('Cannot remove SSL configuration "%s"', basename($sslConfig)),
364
                    1476545093
365
                );
366
            }
367
        }
368
369
        // Re-persist the virtual host
370
        $this->persistVhost($account, $vhost);
371
372
        // Reload the webserver if the account and virtual host are enabled
373
        if ($account->isActive() && $vhost->isActive()) {
374
            $this->serviceService->reloadWebserver($vhost->getType());
375
        }
376
    }
377
378
    /**
379
     * Add a secondary domain to a virtual host
380
     *
381
     * @param AccountInterface $account Account
382
     * @param VhostInterface $vhost Virtual host
383
     * @return void
384
     */
385
    public function domainVhost(AccountInterface $account, VhostInterface $vhost)
386
    {
387
        // Re-persist the virtual host
388
        $this->persistVhost($account, $vhost);
389
390
        // Reload the webserver if the account and virtual host are enabled
391
        if ($account->isActive() && $vhost->isActive()) {
392
            $this->serviceService->reloadWebserver($vhost->getType());
393
        }
394
    }
395
396
    /**
397
     * Certifiy a virtual host
398
     *
399
     * @param AccountInterface $account Account
400
     * @param VhostInterface $vhost Virtual host
401
     * @return void
402
     */
403
    public function certifyVhost(AccountInterface $account, VhostInterface $vhost)
404
    {
405
        // Issue an SSL certificate
406
        $accountHelper = new AccountHelper($account);
407
        $certificateConfig = $accountHelper->vhostDirectory($vhost).DIRECTORY_SEPARATOR.'certbot.ini';
408
409
        $this->serviceService->certify($certificateConfig);
410
411
        // Reload the webserver if the account and virtual host are enabled
412
        if ($account->isActive() && $vhost->isActive()) {
413
            $this->serviceService->reloadWebserver($vhost->getType());
414
        }
415
    }
416
417
    /**
418
     * Prepare a directory
419
     *
420
     * @param string $directory Directory
421
     * @param string $user Owner user
422
     * @param string $group Owner group
423
     * @return string Prepared directory
424
     */
425
    protected function prepareDirectory($directory, $user, $group)
426
    {
427
        if (!is_dir($directory) && !Directory::create($directory, $user, $group)) {
428
            throw new \RuntimeException(sprintf('Could not prepare directory "%s"', $directory), 1476014854);
429
        }
430
        return $directory;
431
    }
432
433
    /**
434
     * Recursively delete a directory
435
     *
436
     * @param string $directory Directory
437
     * @param string $user Owner user
438
     * @param string $group Owner group
439
     * @throws \RuntimeException If a file or directory cannot be deleted
440
     */
441
    protected function deleteDirectory($directory, $user, $group)
442
    {
443
        $files = array_diff(scandir($directory), array('.', '..'));
444
        foreach ($files as $file) {
445
            // Recursive call if it's a directory
446
            if (is_dir($directory.DIRECTORY_SEPARATOR.$file)) {
447
                $this->deleteDirectory($directory.DIRECTORY_SEPARATOR.$file, $user, $group);
448
                continue;
449
            }
450
451
            // Delete the file
452
            if (!unlink($directory.DIRECTORY_SEPARATOR.$file)) {
453
                throw new \RuntimeException(
454
                    sprintf('Could not delete file "%s"', $directory.DIRECTORY_SEPARATOR.$file),
455
                    1476017676
456
                );
457
            }
458
        }
459
460
        // Delete the directory
461
        if (!Directory::delete($directory, $user, $group)) {
462
            throw new \RuntimeException(sprintf('Could not delete directory "%s"', $directory), 1476017747);
463
        }
464
    }
465
466
    /**
467
     * Persist a virtual host
468
     *
469
     * @param AccountInterface $account Account
470
     * @param VhostInterface $vhost Virtual host
471
     */
472
    protected function persistVhost(AccountInterface $account, VhostInterface $vhost)
473
    {
474
        // Persist all virtual host files
475
        $this->persistenceAdapterFactory
476
            ->makeVhostPersistenceAdapterStrategy($vhost->getType())
477
            ->persist($account, $vhost);
478
    }
479
}
480