Passed
Push — release-2.x ( e7ff42...5f707c )
by Slye
01:21
created

System::checkRequirements()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 17
nc 1
nop 0
dl 0
loc 19
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
namespace NDC\DatabaseBackup;
4
5
use const DIRECTORY_SEPARATOR;
6
use function dirname;
7
use Dotenv\{
8
    Dotenv, Validator
9
};
10
use Exception;
11
use Ifsnop\Mysqldump\Mysqldump;
12
use JBZoo\Lang\Lang;
13
use PDO;
14
use RuntimeException;
15
use Swift_Attachment;
16
use Swift_Mailer;
17
use Swift_Message;
18
use Swift_SmtpTransport;
19
20
/**
21
 * Class System
22
 * @package NDC\DatabaseBackup
23
 */
24
class System
25
{
26
    /**
27
     * @var Dotenv
28
     */
29
    private $env;
30
    /**
31
     * @var array
32
     */
33
    protected $errors = [];
34
    /**
35
     * @var bool
36
     */
37
    private $isCli;
38
    /**
39
     * @var array
40
     */
41
    protected $files = [];
42
    /**
43
     * @var Lang
44
     */
45
    protected $l10n;
46
47
    /**
48
     * @var System|null
49
     */
50
    private static $_instance;
51
52
    /**
53
     * @return System|null
54
     */
55
    public static function getInstance(): ?System
56
    {
57
58
        if (self::$_instance === null) {
59
            self::$_instance = new System();
60
        }
61
        return self::$_instance;
62
    }
63
64
    /**
65
     * System constructor.
66
     */
67
    private function __construct()
68
    {
69
        $this->isCli = PHP_SAPI === 'cli';
70
        try {
71
            $this->loadConfigurationEnvironment();
72
        } catch (\JBZoo\Lang\Exception $e) {
73
            throw new RuntimeException($e);
74
        } catch (\JBZoo\Path\Exception $e) {
75
            throw new RuntimeException($e);
76
        }
77
        FileManager::getInstance();
78
    }
79
80
    /**
81
     * Start System initialization
82
     * @return void
83
     * @throws RuntimeException
84
     * @throws \JBZoo\Lang\Exception
85
     * @throws \JBZoo\Path\Exception
86
     */
87
    private function loadConfigurationEnvironment(): void
88
    {
89
        if (getenv('APP_TEST') !== '1') {
90
            if (!file_exists(dirname(__DIR__) . DIRECTORY_SEPARATOR . '.env')) {
91
                throw new RuntimeException('Please configure this script with .env file');
92
            }
93
            $this->env = Dotenv::create(dirname(__DIR__), '.env');
94
            $this->env->overload();
95
            $this->checkRequirements();
96
        }
97
        $this->l10n = new Lang(env('LANGUAGE', 'en'));
98
        $this->l10n->load(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'i18n', null, 'yml');
99
        if (!$this->isCli && !(bool)env('ALLOW_EXECUTE_IN_WEB_BROWSER', false)) {
100
            die($this->l10n->translate('unauthorized_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...
101
        }
102
        if ((PHP_MAJOR_VERSION . PHP_MINOR_VERSION) < 72) {
103
            die($this->l10n->translate('unsupported_php_version'));
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...
104
        }
105
    }
106
107
    /**
108
     * @return Validator
109
     */
110
    private function checkRequirements(): Validator
111
    {
112
        return $this->env->required([
113
            'DB_HOST',
114
            'DB_USER',
115
            'DB_PASSWORD',
116
            'MAIL_FROM',
117
            'MAIL_FROM_NAME',
118
            'MAIL_TO',
119
            'MAIL_TO_NAME',
120
            'MAIL_SEND_ON_ERROR',
121
            'MAIL_SEND_ON_SUCCESS',
122
            'MAIL_SMTP_HOST',
123
            'MAIL_SMTP_PORT',
124
            'FILES_DAYS_HISTORY',
125
            'FILES_PATH_TO_SAVE_BACKUP',
126
            'LANGUAGE',
127
            'ALLOW_EXECUTE_IN_WEB_BROWSER'
128
        ])->notEmpty();
129
    }
130
131
    /**
132
     * @return array|string
133
     */
134
    private function getExcludedDatabases()
135
    {
136
        if (empty(trim(env('DB_EXCLUDE_DATABASES', 'information_schema,mysql,performance_schema')))) {
137
            return [];
138
        }
139
        return $this->parseAndSanitize(env('DB_EXCLUDE_DATABASES', 'information_schema,mysql,performance_schema'));
0 ignored issues
show
Bug introduced by
It seems like env('DB_EXCLUDE_DATABASE...ql,performance_schema') can also be of type boolean and null; however, parameter $data of NDC\DatabaseBackup\System::parseAndSanitize() does only seem to accept string, 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

139
        return $this->parseAndSanitize(/** @scrutinizer ignore-type */ env('DB_EXCLUDE_DATABASES', 'information_schema,mysql,performance_schema'));
Loading history...
140
    }
141
142
    /**
143
     * @return array
144
     */
145
    private function getDatabases(): array
146
    {
147
        $pdo = new PDO('mysql:host=' . env('DB_HOST', 'localhost') . ';charset=UTF8', env('DB_USER', 'root'),
148
            env('DB_PASSWORD', 'root'), [
149
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
150
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
151
            ]);
152
        return $pdo->query('SHOW DATABASES')->fetchAll();
153
    }
154
155
    /**
156
     * Process to backup databases
157
     */
158
    public function processBackup(): void
159
    {
160
        foreach ($this->getDatabases() as $database) {
161
            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

161
            if (!\in_array($database->Database, /** @scrutinizer ignore-type */ $this->getExcludedDatabases(), true)) {
Loading history...
162
                $file_format = $database->Database . '-' . date($this->l10n->translate('date_format')) . '.sql';
163
                try {
164
                    $dumper = new Mysqldump('mysql:host=' . env('DB_HOST',
165
                            'localhost') . ';dbname=' . $database->Database . ';charset=UTF8',
166
                        env('DB_USER', 'root'), env('DB_PASSWORD', ''));
167
                    $dumper->start(env('FILES_PATH_TO_SAVE_BACKUP', './Backups') . DIRECTORY_SEPARATOR . $file_format);
168
                    $this->files[] = env('FILES_PATH_TO_SAVE_BACKUP', './Backups') . DIRECTORY_SEPARATOR . $file_format;
169
                } catch (Exception $e) {
170
                    $this->errors[] = [
171
                        'dbname' => $database->Database,
172
                        'error_message' => $e->getMessage(),
173
                        'error_code' => $e->getCode()
174
                    ];
175
                }
176
            }
177
        }
178
        $this->sendMail();
179
    }
180
181
    /**
182
     * @param string $data
183
     * @return array|string
184
     */
185
    private function parseAndSanitize(string $data)
186
    {
187
        $results = explode(',', $data);
188
        if (\count($results) > 1) {
189
            foreach ($results as $k => $v) {
190
                $results[$k] = trim($v);
191
                if (empty($v)) {
192
                    unset($results[$k]);
193
                }
194
            }
195
            return $results;
196
        }
197
        return trim($results[0]);
198
    }
199
200
    /**
201
     * Send a mail if error or success backup database
202
     */
203
    private function sendMail(): void
204
    {
205
        $smtpTransport = new Swift_SmtpTransport(env('MAIL_SMTP_HOST', 'localhost'), env('MAIL_SMTP_PORT', 25));
0 ignored issues
show
Bug introduced by
It seems like env('MAIL_SMTP_PORT', 25) can also be of type boolean and string; however, parameter $port of Swift_SmtpTransport::__construct() does only seem to accept integer, 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

205
        $smtpTransport = new Swift_SmtpTransport(env('MAIL_SMTP_HOST', 'localhost'), /** @scrutinizer ignore-type */ env('MAIL_SMTP_PORT', 25));
Loading history...
206
        $smtpTransport->setUsername(env('MAIL_SMTP_USER', ''))->setPassword(env('MAIL_SMTP_PASSWORD', ''));
207
        $mailer = new Swift_Mailer($smtpTransport);
208
        if (empty($this->errors)) {
209
            if ((bool)env('MAIL_SEND_ON_SUCCESS', false)) {
210
                $body = "<strong>{$this->l10n->translate('mail_db_backup_successfull')}</strong>";
211
                if ((bool)env('MAIL_SEND_WITH_BACKUP_FILE', false)) {
212
                    $body .= "<br><br>{$this->l10n->translate('mail_db_backup_file')}";
213
                }
214
                $message = (new Swift_Message($this->l10n->translate('mail_subject_on_success')))->setFrom(env('MAIL_FROM',
215
                    '[email protected]'),
216
                    env('MAIL_FROM_NAME', 'Website Mailer for Database Backup'))
217
                    ->setTo(env('MAIL_TO'),
218
                        env('MAIL_TO_NAME', 'Webmaster of my website'))
219
                    ->setBody($body)
220
                    ->setCharset('utf-8')
221
                    ->setContentType('text/html');
222
                if ((bool)env('MAIL_SEND_WITH_BACKUP_FILE', false)) {
223
                    foreach ($this->files as $file) {
224
                        $attachment = Swift_Attachment::fromPath($file)->setContentType('application/sql');
225
                        $message->attach($attachment);
226
                    }
227
                }
228
                $mailer->send($message);
229
            }
230
        } elseif ((bool)env('MAIL_SEND_ON_ERROR', false)) {
231
            $body = "<strong>{$this->l10n->translate('mail_db_backup_failed')}}:</strong><br><br><ul>";
232
            foreach ($this->errors as $error) {
233
                $body .= "<li>
234
                        <ul>
235
                            <li>{$this->l10n->translate('database')}: {$error['dbname']}</li>
236
                            <li>{$this->l10n->translate('error_code')}: {$error['error_code']}</li>
237
                            <li>{$this->l10n->translate('error_message')}: {$error['error_message']}</li>
238
                        </ul>
239
                       </li>";
240
            }
241
            $body .= '</ul>';
242
            $message = (new Swift_Message($this->l10n->translate('mail_subject_on_error')))->setFrom(env('MAIL_FROM',
243
                '[email protected]'),
244
                env('MAIL_FROM_NAME', 'Website Mailer for Database Backup'))
245
                ->setTo(env('MAIL_TO'),
246
                    env('MAIL_TO_NAME', 'Webmaster of my website'))
247
                ->setBody($body)
248
                ->setCharset('utf-8')
249
                ->setContentType('text/html');
250
            $mailer->send($message);
251
        }
252
    }
253
}
254