PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.4.6
Backup Migration v1.4.6
2.1.6 2.1.5.2 trunk 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.7 1.4.8 1.4.9 1.4.9.1 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.5.1
backup-backup / includes / check / compatibility.php
backup-backup / includes / check Last commit date
checker.php 1 year ago compatibility.php 1 year ago system_info.php 1 year ago
compatibility.php
490 lines
1 <?php
2 /**
3 * This PHP code is designed to check the compatibility of the server environment with certain requirements.
4 * It uses the Strategy Design Pattern to encapsulate the different compatibility checks into separate classes.
5 */
6
7 // Namespace
8 namespace BMI\Plugin\Checker;
9
10 if (!defined('ABSPATH')) exit;
11
12 // Use
13 use BMI\Plugin\Backup_Migration_Plugin as BMP;
14
15
16
17 /**
18 * The Comparision interface defines the method for comparing a value with a recommendation.
19 */
20 interface Comparision {
21
22 /**
23 * Compare the value with the recommendation.
24 * @param mixed $value The value to compare.
25 * @param mixed $recommendation The recommendation to compare with.
26 * @return bool True if the value is compatible with the recommendation, false otherwise.
27 */
28 public function compare($value, $recommendation);
29 }
30
31 /**
32 * The Version_Comparision class implements the Comparision interface for version comparisons.
33 */
34 class Version_Comparision implements Comparision {
35 public function compare($value, $recommendation, $operator = '>=') {
36 return version_compare($value, $recommendation, '>=');
37 }
38 }
39
40 /**
41 * The String_Comparision class implements the Comparision interface for string comparisons.
42 */
43 class String_Comparision implements Comparision {
44 public function compare($value, $recommendation)
45 {
46 return strpos($value, $recommendation) !== false;
47 }
48 }
49
50 /**
51 * The In_Comparision class implements the Comparision interface for checking if a value is in a set of recommendations.
52 */
53 class In_Comparision implements Comparision {
54 public function compare($value, $recommendation)
55 {
56 foreach ($recommendation as $rec) {
57 if (strpos($value, $rec) !== false) {
58 return true;
59 }
60 }
61 return false;
62 }
63 }
64
65 /**
66 * The Int_Comparision class implements the Comparision interface for integer comparisons.
67 */
68 class Int_Comparision implements Comparision {
69 public function compare($value, $recommendation)
70 {
71 if (is_string($value)) {
72 $value = intval($value);
73 $recommendation = intval($recommendation);
74 }
75 return $value >= $recommendation;
76 }
77 }
78
79 /**
80 * The Bool_Comparision class implements the Comparision interface for boolean comparisons.
81 */
82 class Bool_Comparision implements Comparision {
83 public function compare($value, $recommendation)
84 {
85 return boolval($value) === boolval($recommendation);
86 }
87 }
88
89 /**
90 * The Compatibility_Attribute abstract class defines the structure for a compatibility attribute.
91 * It uses a Comparision object to compare a system value with a recommendation.
92 */
93 abstract class Compatibility_Attribute {
94
95 /**
96 * The key to access the system value.
97 * @var string
98 */
99 protected $key;
100
101 /**
102 * The recommendation to compare with.
103 * @var mixed
104 */
105 protected $recommendation;
106
107 /**
108 * The Comparision object to use for the comparison.
109 * @var Comparision
110 */
111 protected $comparision;
112
113 /**
114 * The error message for the compatibility check.
115 * @var string
116 */
117 protected $error_message;
118
119 /**
120 * Constructor
121 * @param mixed $recommendation The recommendation to compare with.
122 * @param string|null $key The key to access the system value. (if null, the compatability check will be determined by the checkValue method)
123 * @param Comparision $comparision The Comparision object to use for the comparison.
124 */
125 public function __construct($recommendation, $key, $comparision)
126 {
127 $this->recommendation = $recommendation;
128 $this->key = $key;
129 $this->comparision = $comparision;
130 }
131
132 /**
133 * Check if the system value is compatible with the recommendation.
134 * @param array $system The system information.
135 * @return bool True if the system value is compatible with the recommendation, false otherwise.
136 */
137 public function isCompatible($system){
138 if ($this->keyExists($system)) {
139 return $this->comparision->compare($system[$this->key], $this->recommendation);
140 }else{
141 if (method_exists($this, 'checkValue')) {
142 return $this->checkValue();
143 }
144 }
145 return false;
146 }
147
148 /**
149 * Check if the key exists in the system information.
150 * @param array $system The system information.
151 * @return bool True if the key exists, false otherwise.
152 */
153 protected function keyExists($system) {
154 return isset($system[$this->key]);
155 }
156
157 /**
158 * Set the error message for the compatibility check.
159 * @param string $message The error message.
160 */
161 function setErrorMessage($message){
162 $this->error_message = $message;
163 }
164
165 /**
166 * Get the error message for the compatibility check.
167 * @return string The error message.
168 */
169 function getErrorMessage(){
170 return $this->error_message;
171 }
172 }
173
174 /**
175 * The PHP_Version class extends Compatibility_Attribute to check the PHP version compatibility.
176 */
177 class PHP_Version extends Compatibility_Attribute {
178
179 function __construct($recommendation, $key, $comparision)
180 {
181 parent::__construct($recommendation, $key, $comparision);
182 $this->error_message = __("Your site is on PHP 8+, and some of your plugins are only compatible with older PHP versions, causing issues during the backup creation. Either disable those plugins or temporarily change to an earlier PHP version.", 'backup-backup');
183
184 }
185 protected function checkValue() {
186 $value = explode(' ', phpversion())[0];
187 return !$this->comparision->compare($value, $this->recommendation);
188 }
189 }
190
191 /**
192 * The WP_Debug_Enabled class extends Compatibility_Attribute to check if WP_DEBUG is enabled.
193 */
194 class WP_Debug_Enabled extends Compatibility_Attribute {
195
196 function __construct($recommendation, $key, $comparision)
197 {
198 parent::__construct($recommendation, $key, $comparision);
199 $this->error_message = __("The WP_DEBUG is not active. It is recommended to enable it.", 'backup-backup');
200 }
201 protected function checkValue() {
202 return $this->comparision->compare(WP_DEBUG, $this->recommendation);
203 }
204
205 }
206
207 /**
208 * The KeepAlive_Timeout class extends Compatibility_Attribute to check the KeepAlive timeout compatibility.
209 * Note: The checkValue method is not implemented yet.
210 */
211 class KeepAlive_Timeout extends Compatibility_Attribute {
212
213 function __construct($recommendation, $key, $comparision)
214 {
215 parent::__construct($recommendation, $key, $comparision);
216 $this->error_message = __("The KeepAlive is not compatible. The recommended value is %s1.", 'backup-backup');
217 $this->error_message= str_replace(
218 ['%s1'],
219 [$this->recommendation],
220 $this->error_message
221 );
222 }
223 protected function checkValue() {
224 //TODO: implement the check
225 }
226
227 }
228
229
230 class CURL_Enabled extends Compatibility_Attribute {
231
232 function __construct($recommendation, $key, $comparision)
233 {
234 parent::__construct($recommendation, $key, $comparision);
235 $this->error_message = __("The CURL is not enabled. It is recommended to enable it.", 'backup-backup');
236 }
237
238 // return false if curl is not enabled and user use alternative backup method
239 protected function checkValue() {
240 return $this->comparision->compare(System_Info::is_curl_work(), $this->recommendation);
241
242 }
243 }
244
245 class PHP_CLI_Enabled extends Compatibility_Attribute {
246
247 function __construct($recommendation, $key, $comparision)
248 {
249 parent::__construct($recommendation, $key, $comparision);
250 $this->error_message = __("The PHP CLI is not active. It is recommended to enable it.", 'backup-backup');
251 }
252
253 // return false if php_cli is not enabled and user use default backup method
254 protected function checkValue() {
255 return $this->comparision->compare(System_Info::is_php_cli_runnable(), $this->recommendation);
256 }
257
258 function getErrorMessage() {
259 return $this->error_message;
260 }
261 }
262
263 class Disk_Space extends Compatibility_Attribute {
264
265 protected $available_space;
266
267 public function __construct($recommendation, $key, $comparision)
268 {
269 parent::__construct($recommendation, $key, $comparision);
270 $this->available_space = $this->getDiskAvaialbleSpace();
271 $this->error_message = __("The minimum required disk space is %s1, while the available space is %s2.", 'backup-backup');
272 $this->error_message = str_replace(
273 ['%s1', '%s2'],
274 [BMP::humanSize(intval($this->recommendation)), BMP::humanSize(intval($this->available_space))],
275 $this->error_message
276 );
277 }
278 protected function checkValue() {
279 return $this->comparision->compare(intval($this->available_space), intval($this->recommendation));
280 }
281
282 public function getDiskAvaialbleSpace(){
283
284 return $this->getDiskAvaialbleSpaceByHARDWay();
285 }
286
287 public function getDiskAvaialbleSpaceByHARDWay() {
288
289 $file = BMI_BACKUPS . '/' . '.space_check';
290 try {
291 $size = $this->recommendation;
292 $fh = fopen($file, 'w');
293 while($size > 0){
294 $chunk = 1024;
295 fputs($fh, str_pad('', min($chunk, $size)));
296 $size -= $chunk;
297 }
298 fclose($fh);
299
300 $fs = filesize($file);
301 @unlink($file);
302
303 return $fs;
304
305
306 } catch (\Exception $e) {
307 if (file_exists($file)){
308 $fileSize = filesize($file);
309 unlink($file);
310 return $fileSize;
311 }
312
313 } catch (\Throwable $e) {
314 if (file_exists($file)){
315 $fileSize = filesize($file);
316 unlink($file);
317 return $fileSize;
318 }
319
320 }
321 return false;
322
323 }
324 }
325
326 class Normal_Attribute extends Compatibility_Attribute{
327
328 function __construct($recommendation, $key, $comparision, $error_message = '')
329 {
330 parent::__construct($recommendation, $key, $comparision);
331 $this->error_message = $error_message != '' ? $error_message : __("The %s1 is not compatible. The recommended value is %s2.", 'backup-backup');
332 $recommendation = $this->recommendation;
333 if (is_array($recommendation)) {
334 $recommendation = implode('/', $recommendation);
335 }
336 $this->error_message= str_replace(
337 ['%s1', '%s2'],
338 [$this->key, $recommendation],
339 $this->error_message
340 );
341
342 }
343 }
344
345
346 /**
347 * The Compatibility class is used to add compatibility strategies and check the compatibility of the system.
348 */
349 class Compatibility {
350 private $attrs = [];
351 protected $errors = [];
352 protected $system_info;
353
354 protected $for;
355
356 /**
357 * Constructor to initialize the system information and add default compatibility strategies.
358 */
359 public function __construct($for = 'backup') {
360 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'check' . DIRECTORY_SEPARATOR . 'system_info.php';
361 $system = new System_Info();
362 $this->system_info = $system->to_array();
363 $this->for = $for;
364 $this->addDefaultStrategies();
365 $this->addMoreRecommendations();
366 }
367
368 /**
369 * Add default compatibility strategies based on the type of operation.
370 */
371 public function addDefaultStrategies() {
372 if ($this->for == 'backup') {
373 $this->addBackupDefaultStrategies();
374 } else if ($this->for == 'migration') {
375 $this->addMigrationDefaultStrategies();
376 }
377 }
378
379 /**
380 * Add default compatibility strategies for migration.
381 */
382 public function addMigrationDefaultStrategies() {
383 $this->addStrategy(new Normal_Attribute(true, 'php_allow_url_fopen', new Bool_Comparision(), __("allow_url_fopen is not enabled.", 'backup-backup')));
384 $this->addStrategy(new Normal_Attribute('5.5', 'mysql_version', new Version_Comparision(), __("MySQL version is not compatible. recommended to use version 5.5+.", 'backup-backup')));
385 $this->addStrategy(new Normal_Attribute(['Apache', 'Nginx'], 'web_server_name', new In_Comparision(), __("We recommend using Apache/Nginx server type.", 'backup-backup')));
386 $this->addStrategy(new PHP_Version('8.0', null, new Version_Comparision()));
387
388 $max_execution_time = new Normal_Attribute('180', 'php_max_execution_time', new Int_Comparision());
389 $max_execution_time_error = __("PHP max execution time is %s1. The recommended value is %s2.", 'backup-backup');
390 $max_execution_time_error = str_replace(
391 ['%s1', '%s2'],
392 [$this->system_info['php_max_execution_time'], '180'],
393 $max_execution_time_error
394 );
395 $max_execution_time->setErrorMessage($max_execution_time_error);
396 $this->addStrategy($max_execution_time);
397 }
398
399 /**
400 * Add default compatibility strategies for backup.
401 */
402 public function addBackupDefaultStrategies() {
403
404 $this->addStrategy(new CURL_Enabled(true, null, new Bool_Comparision()));
405 $this->addStrategy(new Normal_Attribute(true, 'php_allow_url_fopen', new Bool_Comparision(), __("allow_url_fopen is not enabled.", 'backup-backup')));
406 $this->addStrategy(new Normal_Attribute('5.5', 'mysql_version', new Version_Comparision(), __("MySQL version is not compatible. recommended to use version 5.5+.", 'backup-backup')));
407 $this->addStrategy(new Normal_Attribute(['Apache', 'Nginx'], 'web_server_name', new In_Comparision(), __("We recommend using Apache/Nginx server type.", 'backup-backup')));
408 $this->addStrategy(new PHP_Version('8.0', null, new Version_Comparision()));
409
410 $max_execution_time = new Normal_Attribute('180', 'php_max_execution_time', new Int_Comparision());
411 $max_execution_time_error = __("PHP max execution time is %s1. The recommended value is %s2.", 'backup-backup');
412 $max_execution_time_error = str_replace(
413 ['%s1', '%s2'],
414 [$this->system_info['php_max_execution_time'], '180'],
415 $max_execution_time_error
416 );
417 $max_execution_time->setErrorMessage($max_execution_time_error);
418 $this->addStrategy($max_execution_time);
419 }
420
421 /**
422 * Add more recommendations based on verbose in log.
423 * @return void
424 */
425 public function addMoreRecommendations() {
426 // e.g.
427 // $this->addRecommendation('missing space.', __("The disk space is not enough. Please free up some space.", 'backup-backup'));
428 if ($this->for == 'backup') {
429 $this->addBackupRecommendations();
430 } else if ($this->for == 'migration') {
431 $this->addMigrationRecommendations();
432 }
433 }
434
435 public function addMigrationRecommendations() {
436 }
437
438 public function addBackupRecommendations() {
439 $this->addRecommendation('Disk quota exceeded', __("There is not enough space for the backup, please free up some space.", 'backup-backup'));
440 }
441
442 /**
443 * Add recommendation based on vebose in log
444 * @param string $verbose The verbose in log
445 * @param string $the message to show
446 * @return void
447 */
448 public function addRecommendation($verbose, $message) {
449 $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest.log';
450 $content = file_get_contents($file);
451 $pattern = '#^\[VERBOSE\] \[[0-9-]+ [0-9:]+\] ' . $verbose . '#mi'; // e.g. [VERBOSE] [2021-12-31 23:59:59] missing space.
452 if (preg_match($pattern, $content, $matches)) {
453 array_push($this->errors, $message);
454 }
455
456 }
457 /**
458 * Add a compatibility strategy.
459 * @param Compatibility_Attribute $strategy The compatibility strategy to add.
460 */
461 public function addStrategy( Compatibility_Attribute $strategy) {
462 array_push($this->attrs, $strategy);
463 }
464
465 /**
466 * Check the compatibility of the system.
467 * @return array The errors found during the compatibility check.
468 */
469 public function check() {
470 foreach ($this->attrs as $attribute) {
471 if(is_array($this->system_info) && !$attribute->isCompatible($this->system_info)) {
472 array_push($this->errors, $attribute->getErrorMessage());
473 }
474 }
475 return $this->errors;
476 }
477
478 /**
479 * Get backup size from backup log.
480 * @return int $bytes
481 */
482 public function getBackupSize() {
483 if(current_user_can('manage_options') && current_user_can('administrator')) {
484 return BMP::getRecentSize() * 1.4;
485 }
486 }
487
488 }
489
490