Passed
Push — master ( c783b2...7fb439 )
by Slye
06:40
created

System::getExcludedDatabases()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
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
use function getenv;
19
20
use const DIRECTORY_SEPARATOR;
21
22
/**
23
 * Class System
24
 * @package NDC
25
 */
26
class System
27
{
28
    /**
29
     * @var Dotenv
30
     */
31
    protected Dotenv $env;
32
    /**
33
     * @var array
34
     */
35
    protected array $errors = [];
36
    /**
37
     * @var bool
38
     */
39
    protected bool $isCli;
40
    /**
41
     * @var array
42
     */
43
    protected array $files = [];
44
45
    /**
46
     * System constructor.
47
     */
48
    public function __construct()
49
    {
50
        $this->isCli = PHP_SAPI === 'cli';
51
        $this->loadConfigurationEnvironment();
52
    }
53
54
    /**
55
     * Start System initialization
56
     * @return void
57
     * @throws RuntimeException
58
     */
59
    public function loadConfigurationEnvironment(): void
60
    {
61
        if (!file_exists(dirname(__DIR__) . DIRECTORY_SEPARATOR . '.env')) {
62
            throw new RuntimeException('Please configure this script with .env-dist to .env file');
63
        }
64
        $this->env = Dotenv::createImmutable(dirname(__DIR__));
65
        $this->env->load();
66
        if (!$this->isCli && !(bool)getenv('ALLOW_EXECUTE_IN_HTTP_BROWSER')) {
67
            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...
68
        }
69
        if (PHP_VERSION_ID < 70000) {
70
            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...
71
        }
72
    }
73
74
    /**
75
     * @return Validator
76
     */
77
    public function checkRequirements(): Validator
78
    {
79
        return $this->env->required(
80
            [
81
                'DB_HOST',
82
                'DB_USER',
83
                'DB_PASSWORD',
84
                'MAIL_FROM',
85
                'MAIL_FROM_NAME',
86
                'MAIL_TO',
87
                'MAIL_TO_NAME',
88
                'MAIL_SEND_ON_ERROR',
89
                'MAIL_SEND_ON_SUCCESS',
90
                'MAIL_SMTP_HOST',
91
                'MAIL_SMTP_PORT',
92
                'FILES_DAYS_HISTORY',
93
                'FILES_PATH_TO_SAVE_BACKUP',
94
                'ALLOW_EXECUTE_IN_HTTP_BROWSER'
95
            ]
96
        )->notEmpty();
97
    }
98
99
    /**
100
     * @return array|string
101
     */
102
    public function getExcludedDatabases()
103
    {
104
        if (empty(trim(getenv('DB_EXCLUDE_DATABASES')))) {
105
            return [];
106
        }
107
        return $this->parseAndSanitize(getenv('DB_EXCLUDE_DATABASES'));
108
    }
109
110
    /**
111
     * @return array
112
     */
113
    public function getDatabases(): array
114
    {
115
        $pdo = new PDO(
116
            'mysql:host=' . getenv('DB_HOST') . ';charset=UTF8', getenv('DB_USER'), getenv('DB_PASSWORD'),
117
            [
118
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
119
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
120
            ]
121
        );
122
        return $pdo->query('SHOW DATABASES')->fetchAll();
123
    }
124
125
    /**
126
     * Process to backup databases
127
     */
128
    public function process(): void
129
    {
130
        foreach ($this->getDatabases() as $database) {
131
            if (!\in_array($database->Database, $this->getExcludedDatabases(), true)) {
0 ignored issues
show
Bug introduced by
It seems like $this->getExcludedDatabases() can also be of type string; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
            if (!\in_array($database->Database, /** @scrutinizer ignore-type */ $this->getExcludedDatabases(), true)) {
Loading history...
132
                $file_format = $database->Database . '-' . time() . '.sql';
133
                try {
134
                    $dumper = new Mysqldump(
135
                        'mysql:host=' . getenv('DB_HOST') . ';dbname=' . $database->Database . ';charset=UTF8',
136
                        getenv('DB_USER'), getenv('DB_PASSWORD')
137
                    );
138
                    $dumper->start(getenv('FILES_PATH_TO_SAVE_BACKUP') . DIRECTORY_SEPARATOR . $file_format);
139
                    $this->files[] = getenv('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
                }
147
            }
148
        }
149
        $this->sendMail();
150
    }
151
152
    /**
153
     * @param string $data
154
     * @return array|string
155
     */
156
    private function parseAndSanitize(string $data)
157
    {
158
        $results = explode(',', $data);
159
        if (\count($results) > 1) {
160
            foreach ($results as $k => $v) {
161
                $results[$k] = trim($v);
162
                if (empty($v)) {
163
                    unset($results[$k]);
164
                }
165
            }
166
            return $results;
167
        }
168
        return trim($results[0]);
169
    }
170
171
    /**
172
     * Send a mail if error or success backup database
173
     */
174
    private function sendMail(): void
175
    {
176
        $smtpTransport = new Swift_SmtpTransport(getenv('MAIL_SMTP_HOST'), getenv('MAIL_SMTP_PORT'));
0 ignored issues
show
Bug introduced by
getenv('MAIL_SMTP_PORT') of type string is incompatible with the type integer expected by parameter $port of Swift_SmtpTransport::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

176
        $smtpTransport = new Swift_SmtpTransport(getenv('MAIL_SMTP_HOST'), /** @scrutinizer ignore-type */ getenv('MAIL_SMTP_PORT'));
Loading history...
177
        $smtpTransport->setUsername(getenv('MAIL_SMTP_USER'))->setPassword(getenv('MAIL_SMTP_PASSWORD'));
178
        $mailer = new Swift_Mailer($smtpTransport);
179
        if (empty($this->errors)) {
180
            if ((bool)getenv('MAIL_SEND_ON_SUCCESS')) {
181
                $body = "<strong>The backup of the databases has been successful!</strong>";
182
                if ((bool)getenv('MAIL_SEND_BACKUP_FILE')) {
183
                    $body .= "<br><br>You will find a copy of the backup attached to this email.";
184
                }
185
                $message = (new Swift_Message('Backup performed!'))
186
                    ->setFrom(getenv('MAIL_FROM'), getenv('MAIL_FROM_NAME'))
187
                    ->setTo(getenv('MAIL_TO'), getenv('MAIL_TO_NAME'))
188
                    ->setBody($body)
189
                    ->setCharset('utf-8')
190
                    ->setContentType('text/html');
191
                if ((bool)getenv('MAIL_SEND_BACKUP_FILE')) {
192
                    foreach ($this->files as $file) {
193
                        $attachment = Swift_Attachment::fromPath($file)->setContentType('application/sql');
194
                        $message->attach($attachment);
195
                    }
196
                }
197
                $mailer->send($message);
198
            }
199
        } else {
200
            if ((bool)getenv('MAIL_SEND_ON_ERROR')) {
201
                $body = "<strong>The backup of databases has encountered errors: </strong><br><br><ul>";
202
                foreach ($this->errors as $error) {
203
                    $body .= "<li>
204
                            <ul>
205
                                <li>Database: {$error['dbname']}</li>
206
                                <li>Error code: {$error['error_code']}</li>
207
                                <li>Error message: {$error['error_message']}</li>
208
                            </ul>
209
                           </li>";
210
                }
211
                $body .= '</ul>';
212
                $message = (new Swift_Message('Backup failed!'))
213
                    ->setFrom(getenv('MAIL_FROM'), getenv('MAIL_FROM_NAME'))
214
                    ->setTo(getenv('MAIL_TO'), getenv('MAIL_TO_NAME'))
215
                    ->setBody($body)
216
                    ->setCharset('utf-8')
217
                    ->setContentType('text/html');
218
                $mailer->send($message);
219
            }
220
        }
221
    }
222
}
223