System::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 1
b 1
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace NDC;
6
7
use Dotenv\{Dotenv, Validator};
8
use Exception;
9
use Ifsnop\Mysqldump\Mysqldump;
10
use PDO;
11
use RuntimeException;
12
use Swift_Attachment;
13
use Swift_Mailer;
14
use Swift_Message;
15
use Swift_SmtpTransport;
16
17
use function dirname;
18
19
use const DIRECTORY_SEPARATOR;
20
21
/**
22
 * Class System
23
 * @package NDC
24
 */
25
class System
26
{
27
    /**
28
     * @var Dotenv
29
     */
30
    protected Dotenv $env;
31
    /**
32
     * @var array
33
     */
34
    protected array $errors = [];
35
    /**
36
     * @var bool
37
     */
38
    protected bool $isCli;
39
    /**
40
     * @var array
41
     */
42
    protected array $files = [];
43
44
    /**
45
     * System constructor.
46
     */
47
    public function __construct()
48
    {
49
        $this->isCli = PHP_SAPI === 'cli';
50
        $this->loadConfigurationEnvironment();
51
    }
52
53
    /**
54
     * Start System initialization
55
     * @return void
56
     * @throws RuntimeException
57
     */
58
    public function loadConfigurationEnvironment(): void
59
    {
60
        if (!file_exists(dirname(__DIR__) . DIRECTORY_SEPARATOR . '.env')) {
61
            throw new RuntimeException('Please configure this script with .env-dist to .env file');
62
        }
63
        $this->env = Dotenv::createImmutable(dirname(__DIR__));
64
        $this->env->load();
65
        if (!$this->isCli && !(bool)$_ENV['ALLOW_EXECUTE_IN_HTTP_BROWSER']) {
66
            die('Unauthorized to execute this script in your browser !');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
67
        }
68
        if (PHP_VERSION_ID < 70400) {
69
            die('PHP VERSION IS NOT SUPPORTED, PLEASE USE THIS SCRIPT WITH TO PHP 7.4 VERSION OR HIGHTER');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
70
        }
71
    }
72
73
    /**
74
     * @return Validator
75
     */
76
    public function checkRequirements(): Validator
77
    {
78
        return $this->env->required(
79
            [
80
                'DB_HOST',
81
                'DB_USER',
82
                'DB_PASSWORD',
83
                'MAIL_FROM',
84
                'MAIL_FROM_NAME',
85
                'MAIL_TO',
86
                'MAIL_TO_NAME',
87
                'MAIL_SEND_ON_ERROR',
88
                'MAIL_SEND_ON_SUCCESS',
89
                'MAIL_SMTP_HOST',
90
                'MAIL_SMTP_PORT',
91
                'FILES_DAYS_HISTORY',
92
                'FILES_PATH_TO_SAVE_BACKUP',
93
                'ALLOW_EXECUTE_IN_HTTP_BROWSER'
94
            ]
95
        )->notEmpty();
96
    }
97
98
    /**
99
     * @return array
100
     */
101
    public function getExcludedDatabases(): array
102
    {
103
        if (empty(trim($_ENV['DB_EXCLUDE_DATABASES']))) {
104
            return [];
105
        }
106
        return (array)$this->parseAndSanitize($_ENV['DB_EXCLUDE_DATABASES']);
107
    }
108
109
    /**
110
     * @return array
111
     */
112
    public function getDatabases(): array
113
    {
114
        $pdo = new PDO(
115
            'mysql:host=' . $_ENV['DB_HOST'] . ';charset=UTF8', $_ENV['DB_USER'], $_ENV['DB_PASSWORD'],
116
            [
117
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
118
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
119
            ]
120
        );
121
        return $pdo->query('SHOW DATABASES')->fetchAll();
122
    }
123
124
    /**
125
     * Process to backup databases
126
     * @return bool
127
     */
128
    public function process(): bool
129
    {
130
        foreach ($this->getDatabases() as $database) {
131
            if (!\in_array($database->Database, $this->getExcludedDatabases(), true)) {
132
                $file_format = $database->Database . '-' . time() . '.sql';
133
                try {
134
                    $dumper = new Mysqldump(
135
                        'mysql:host=' . $_ENV['DB_HOST'] . ';dbname=' . $database->Database . ';charset=UTF8',
136
                        $_ENV['DB_USER'], $_ENV['DB_PASSWORD']
137
                    );
138
                    $dumper->start($_ENV['FILES_PATH_TO_SAVE_BACKUP'] . DIRECTORY_SEPARATOR . $file_format);
139
                    $this->files[] = $_ENV['FILES_PATH_TO_SAVE_BACKUP'] . DIRECTORY_SEPARATOR . $file_format;
140
                } catch (Exception $e) {
141
                    $this->errors[] = [
142
                        'dbname' => $database->Database,
143
                        'error_message' => $e->getMessage(),
144
                        'error_code' => $e->getCode()
145
                    ];
146
                    return false;
147
                }
148
            }
149
        }
150
        $this->sendMail();
151
        return true;
152
    }
153
154
    /**
155
     * @param string $data
156
     * @return array|string
157
     */
158
    private function parseAndSanitize(string $data)
159
    {
160
        $results = explode(',', $data);
161
        if (\count($results) > 1) {
162
            foreach ($results as $k => $v) {
163
                $results[$k] = trim($v);
164
                if (empty($v)) {
165
                    unset($results[$k]);
166
                }
167
            }
168
            return $results;
169
        }
170
        return trim($results[0]);
171
    }
172
173
    /**
174
     * Send a mail if error or success backup database
175
     */
176
    private function sendMail(): void
177
    {
178
        $smtpTransport = new Swift_SmtpTransport($_ENV['MAIL_SMTP_HOST'], (int)$_ENV['MAIL_SMTP_PORT']);
179
        $smtpTransport->setUsername($_ENV['MAIL_SMTP_USER'])->setPassword($_ENV['MAIL_SMTP_PASSWORD']);
180
        $mailer = new Swift_Mailer($smtpTransport);
181
        if (empty($this->errors)) {
182
            if ((bool)$_ENV['MAIL_SEND_ON_SUCCESS']) {
183
                $body = "<strong>The backup of the databases has been successful!</strong>";
184
                if ((bool)$_ENV['MAIL_SEND_BACKUP_FILE']) {
185
                    $body .= "<br><br>You will find a copy of the backup attached to this email.";
186
                }
187
                $message = (new Swift_Message('Backup performed!'))
188
                    ->setFrom($_ENV['MAIL_FROM'], $_ENV['MAIL_FROM_NAME'])
189
                    ->setTo($_ENV['MAIL_TO'], $_ENV['MAIL_TO_NAME'])
190
                    ->setBody($body)
191
                    ->setCharset('utf-8')
192
                    ->setContentType('text/html');
193
                if ((bool)$_ENV['MAIL_SEND_BACKUP_FILE']) {
194
                    foreach ($this->files as $file) {
195
                        $attachment = Swift_Attachment::fromPath($file)->setContentType('application/sql');
196
                        $message->attach($attachment);
197
                    }
198
                }
199
                $mailer->send($message);
200
            }
201
        } else {
202
            if ((bool)$_ENV['MAIL_SEND_ON_ERROR']) {
203
                $body = "<strong>The backup of databases has encountered errors: </strong><br><br><ul>";
204
                foreach ($this->errors as $error) {
205
                    $body .= "<li>
206
                            <ul>
207
                                <li>Database: {$error['dbname']}</li>
208
                                <li>Error code: {$error['error_code']}</li>
209
                                <li>Error message: {$error['error_message']}</li>
210
                            </ul>
211
                           </li>";
212
                }
213
                $body .= '</ul>';
214
                $message = (new Swift_Message('Backup failed!'))
215
                    ->setFrom($_ENV['MAIL_FROM'], $_ENV['MAIL_FROM_NAME'])
216
                    ->setTo($_ENV['MAIL_TO'], $_ENV['MAIL_TO_NAME'])
217
                    ->setBody($body)
218
                    ->setCharset('utf-8')
219
                    ->setContentType('text/html');
220
                $mailer->send($message);
221
            }
222
        }
223
    }
224
}
225