PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.4.9
Backup Migration v1.4.9
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 11 months ago compatibility.php 11 months ago system_info.php 11 months ago
compatibility.php
524 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
36 protected $operator;
37
38 public function __construct($operator = '>=') {
39 $this->operator = $operator;
40 }
41
42
43 public function compare($value, $recommendation) {
44 return version_compare($value, $recommendation, $this->operator);
45 }
46 }
47
48 /**
49 * The String_Comparision class implements the Comparision interface for string comparisons.
50 */
51 class String_Comparision implements Comparision {
52 public function compare($value, $recommendation)
53 {
54 return strpos($value, $recommendation) !== false;
55 }
56 }
57
58 /**
59 * The In_Comparision class implements the Comparision interface for checking if a value is in a set of recommendations.
60 */
61 class In_Comparision implements Comparision {
62 public function compare($value, $recommendation)
63 {
64 foreach ($recommendation as $rec) {
65 if (strpos($value, $rec) !== false) {
66 return true;
67 }
68 }
69 return false;
70 }
71 }
72
73 /**
74 * The Int_Comparision class implements the Comparision interface for integer comparisons.
75 */
76 class Int_Comparision implements Comparision {
77 public function compare($value, $recommendation)
78 {
79 if (is_string($value)) {
80 $value = intval($value);
81 $recommendation = intval($recommendation);
82 }
83 return $value >= $recommendation;
84 }
85 }
86
87 /**
88 * The Bool_Comparision class implements the Comparision interface for boolean comparisons.
89 */
90 class Bool_Comparision implements Comparision {
91 public function compare($value, $recommendation)
92 {
93 return boolval($value) === boolval($recommendation);
94 }
95 }
96
97 /**
98 * The Compatibility_Attribute abstract class defines the structure for a compatibility attribute.
99 * It uses a Comparision object to compare a system value with a recommendation.
100 */
101 abstract class Compatibility_Attribute {
102
103 /**
104 * The key to access the system value.
105 * @var string
106 */
107 protected $key;
108
109 /**
110 * The recommendation to compare with.
111 * @var mixed
112 */
113 protected $recommendation;
114
115 /**
116 * The Comparision object to use for the comparison.
117 * @var Comparision
118 */
119 protected $comparision;
120
121 /**
122 * The error message for the compatibility check.
123 * @var string
124 */
125 protected $error_message;
126
127 /**
128 * Constructor
129 * @param mixed $recommendation The recommendation to compare with.
130 * @param string|null $key The key to access the system value. (if null, the compatability check will be determined by the checkValue method)
131 * @param Comparision $comparision The Comparision object to use for the comparison.
132 */
133 public function __construct($recommendation, $key, $comparision)
134 {
135 $this->recommendation = $recommendation;
136 $this->key = $key;
137 $this->comparision = $comparision;
138 }
139
140 /**
141 * Check if the system value is compatible with the recommendation.
142 * @param array $system The system information.
143 * @return bool True if the system value is compatible with the recommendation, false otherwise.
144 */
145 public function isCompatible($system){
146 if ($this->keyExists($system)) {
147 return $this->comparision->compare($system[$this->key], $this->recommendation);
148 }
149 return false;
150 }
151
152 /**
153 * Check if the key exists in the system information.
154 * @param array $system The system information.
155 * @return bool True if the key exists, false otherwise.
156 */
157 protected function keyExists($system) {
158 return isset($system[$this->key]);
159 }
160
161 /**
162 * Set the error message for the compatibility check.
163 * @param string $message The error message.
164 */
165 function setErrorMessage($message){
166 $this->error_message = $message;
167 }
168
169 /**
170 * Get the error message for the compatibility check.
171 * @return string The error message.
172 */
173 function getErrorMessage(){
174 return $this->error_message;
175 }
176 }
177
178 /**
179 * The KeepAlive_Timeout class extends Compatibility_Attribute to check the KeepAlive timeout compatibility.
180 * Note: The checkValue method is not implemented yet.
181 */
182 class KeepAlive_Timeout extends Compatibility_Attribute {
183
184 function __construct($recommendation, $key, $comparision)
185 {
186 parent::__construct($recommendation, $key, $comparision);
187 $this->error_message = __("The KeepAlive is not compatible. The recommended value is %s1.", 'backup-backup');
188 $this->error_message= str_replace(
189 ['%s1'],
190 [$this->recommendation],
191 $this->error_message
192 );
193 }
194 // public function isCompatible($system) {
195 // //TODO: implement the check
196 // }
197
198 }
199
200
201 class CURL_Enabled extends Compatibility_Attribute {
202
203 function __construct($recommendation, $key, $comparision)
204 {
205 parent::__construct($recommendation, $key, $comparision);
206 $this->error_message = __("The CURL is not enabled. It is recommended to enable it.", 'backup-backup');
207 }
208
209 public function isCompatible($system) {
210 return $this->comparision->compare(System_Info::is_curl_work(), $this->recommendation);
211
212 }
213 }
214
215 class PHP_CLI_Enabled extends Compatibility_Attribute {
216
217 function __construct($recommendation, $key, $comparision)
218 {
219 parent::__construct($recommendation, $key, $comparision);
220 $this->error_message = __("The PHP CLI is not active. It is recommended to enable it.", 'backup-backup');
221 }
222
223 // return false if php_cli is not enabled and user use default backup method
224 public function isCompatible($system) {
225 return $this->comparision->compare(System_Info::is_php_cli_runnable(), $this->recommendation);
226 }
227 }
228
229 class Disk_Space extends Compatibility_Attribute {
230
231 protected $available_space;
232
233 public function __construct($recommendation, $key, $comparision)
234 {
235 parent::__construct($recommendation, $key, $comparision);
236 $this->available_space = $this->getDiskAvaialbleSpace();
237 $this->error_message = __("The minimum required disk space is %s1, while the available space is %s2.", 'backup-backup');
238 $this->error_message = str_replace(
239 ['%s1', '%s2'],
240 [BMP::humanSize(intval($this->recommendation)), BMP::humanSize(intval($this->available_space))],
241 $this->error_message
242 );
243 }
244 public function isCompatible($system) {
245 return $this->comparision->compare(intval($this->available_space), intval($this->recommendation));
246 }
247
248 public function getDiskAvaialbleSpace(){
249
250 return $this->getDiskAvaialbleSpaceByHARDWay();
251 }
252
253 public function getDiskAvaialbleSpaceByHARDWay() {
254
255 $file = BMI_BACKUPS . '/' . '.space_check';
256 try {
257 $size = $this->recommendation;
258 $fh = fopen($file, 'w');
259 while($size > 0){
260 $chunk = 1024;
261 fputs($fh, str_pad('', min($chunk, $size)));
262 $size -= $chunk;
263 }
264 fclose($fh);
265
266 $fs = filesize($file);
267 @unlink($file);
268
269 return $fs;
270
271
272 } catch (\Exception $e) {
273 if (file_exists($file)){
274 $fileSize = filesize($file);
275 unlink($file);
276 return $fileSize;
277 }
278
279 } catch (\Throwable $e) {
280 if (file_exists($file)){
281 $fileSize = filesize($file);
282 unlink($file);
283 return $fileSize;
284 }
285
286 }
287 return false;
288
289 }
290 }
291
292 class Normal_Attribute extends Compatibility_Attribute{
293
294 function __construct($recommendation, $key, $comparision, $error_message = '')
295 {
296 parent::__construct($recommendation, $key, $comparision);
297 $this->error_message = $error_message != '' ? $error_message : __("The %s1 is not compatible. The recommended value is %s2.", 'backup-backup');
298 $recommendation = $this->recommendation;
299 if (is_array($recommendation)) {
300 $recommendation = implode('/', $recommendation);
301 }
302 $this->error_message= str_replace(
303 ['%s1', '%s2'],
304 [$this->key, $recommendation],
305 $this->error_message
306 );
307
308 }
309 }
310
311 /**
312 * Check if any incompatible plugins are active and ask the user to temporarily deactivate them.
313 */
314 class Incompatible_Plugins extends Compatibility_Attribute {
315 protected $incompatiblePlugins;
316
317 public function __construct($incompatiblePlugins, $key, $comparison)
318 {
319 parent::__construct($incompatiblePlugins, $key, $comparison);
320 $this->incompatiblePlugins = $incompatiblePlugins;
321 }
322
323 public function isCompatible($system) {
324 // incompatiblePlugins = ['plugin1', 'plugin2', ...]
325 $activePlugins = $system['wp_active_plugins_info']; // [ ['name' => 'Plugin 1', 'version' => '1.0', 'slug' => 'plugin1'], ... ]
326 $incompatibleActivePlugins = array_filter($activePlugins, function($plugin) {
327 return in_array($plugin['slug'], $this->incompatiblePlugins);
328 });
329
330 $isCompatible = empty($incompatibleActivePlugins);
331
332 $this->error_message = $this->generateErrorMessage($incompatibleActivePlugins);
333 return $isCompatible;
334 }
335
336 protected function generateErrorMessage($incompatibleActivePlugins) {
337 $count = count($incompatibleActivePlugins);
338
339 if ($count === 0) {
340 return '';
341 }
342
343 $pluginList = $this->formatPluginList($incompatibleActivePlugins);
344
345 if ($count === 1) {
346 return sprintf(
347 __("We've detected that the plugin %s is currently active and may interfere with our process. Please temporarily deactivate it and try again.", 'backup-backup'),
348 $pluginList
349 );
350 }
351
352 return sprintf(
353 __("We've detected that the following plugins are currently active and may interfere with our process: %s. Please temporarily deactivate them and try again.", 'backup-backup'),
354 $pluginList
355 );
356 }
357
358 protected function formatPluginList($incompatibleActivePlugins) {
359 $plugins = array_map(function($plugin) {
360 return "<strong>". $plugin['name'] ."</strong>";
361 }, $incompatibleActivePlugins);
362
363 if (count($plugins) > 1) {
364 $lastPlugin = array_pop($plugins);
365 return implode(', ', $plugins) . ' ' . __('and', 'backup-backup') . ' ' . $lastPlugin;
366 }
367
368 return reset($plugins);
369 }
370 }
371
372
373 /**
374 * The Compatibility class is used to add compatibility strategies and check the compatibility of the system.
375 */
376 class Compatibility {
377 private $attrs = [];
378 protected $errors = [];
379 protected $mainReasonFound = false;
380 protected $system_info;
381
382 protected $for;
383
384 /**
385 * Constructor to initialize the system information and add default compatibility strategies.
386 */
387 public function __construct($for = 'backup') {
388 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'check' . DIRECTORY_SEPARATOR . 'system_info.php';
389 $system = new System_Info();
390 $this->system_info = $system->to_array();
391 $this->for = $for;
392 $this->addDefaultStrategies();
393 $this->addMoreRecommendations();
394 }
395
396 /**
397 * Add default compatibility strategies based on the type of operation.
398 */
399 public function addDefaultStrategies() {
400 $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')));
401 $this->addStrategy(new Normal_Attribute(['Apache', 'Nginx'], 'web_server_name', new In_Comparision(), __("We recommend using Apache/Nginx server type.", 'backup-backup')));
402 $this->addStrategy(new Normal_Attribute('8.0', 'php_version_full', new Version_Comparision('<'), __("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')));
403 $this->addStrategy(new Incompatible_Plugins(['wordfence', 'security-ninja'], null, null));
404
405 $max_execution_time = new Normal_Attribute('300', 'php_max_execution_time', new Int_Comparision());
406 $max_execution_time_error = __("PHP max execution time is %s1. The recommended value is %s2.", 'backup-backup');
407 $max_execution_time_error = str_replace(
408 ['%s1', '%s2'],
409 [$this->system_info['php_max_execution_time'], '300'],
410 $max_execution_time_error
411 );
412 $max_execution_time->setErrorMessage($max_execution_time_error);
413 $this->addStrategy($max_execution_time);
414
415
416 if ($this->for == 'backup') {
417 $this->addBackupDefaultStrategies();
418 } else if ($this->for == 'migration') {
419 $this->addMigrationDefaultStrategies();
420 }
421 }
422
423 /**
424 * Add default compatibility strategies for migration.
425 */
426 public function addMigrationDefaultStrategies() {
427 }
428
429 /**
430 * Add default compatibility strategies for backup.
431 */
432 public function addBackupDefaultStrategies() {
433
434 $this->addStrategy(new CURL_Enabled(true, null, new Bool_Comparision()));
435 }
436
437 /**
438 * Add more recommendations based on verbose in log.
439 * @return void
440 */
441 public function addMoreRecommendations() {
442 // e.g.
443 // $this->addRecommendation('missing space.', __("The disk space is not enough. Please free up some space.", 'backup-backup'));
444 $requiredSpace = get_option('bmi_required_space', false);
445 if (is_numeric($requiredSpace) && intval($requiredSpace) > 0) {
446 $message = __("There is not enough free space on the server. Please secure more free space (%s1) and then try to run the process again.", 'backup-backup');
447 $message = str_replace(
448 ['%s1'],
449 [BMP::humanSize(intval($requiredSpace))],
450 $message
451 );
452 if ($this->addRecommendation('not_enough_space', $message)) {
453 delete_option('bmi_required_space');
454 $this->mainReasonFound = true;
455 }
456 }
457 if ($this->for == 'backup') {
458 $this->addBackupRecommendations();
459 } else if ($this->for == 'migration') {
460 $this->addMigrationRecommendations();
461 }
462 }
463
464 public function addMigrationRecommendations() {
465 }
466
467 public function addBackupRecommendations() {
468 }
469
470 /**
471 * Add recommendation based on vebose in log
472 * @param string $verbose The verbose in log
473 * @param string $the message to show
474 * @return bool True if the the recommendation is added, false otherwise.
475 */
476 public function addRecommendation($verbose, $message) {
477 $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest' . ($this->for == 'backup' ? '' : '_migration') . '.log';
478 $content = file_get_contents($file);
479 $pattern = '#^\[VERBOSE\] \[[0-9-]+ [0-9:]+\] ' . $verbose . '#mi'; // e.g. [VERBOSE] [2021-12-31 23:59:59] missing space.
480 if (preg_match($pattern, $content, $matches)) {
481 array_push($this->errors, $message);
482 return true;
483 }
484 return false;
485
486 }
487 /**
488 * Add a compatibility strategy.
489 * @param Compatibility_Attribute $strategy The compatibility strategy to add.
490 */
491 public function addStrategy( Compatibility_Attribute $strategy) {
492 array_push($this->attrs, $strategy);
493 }
494
495 /**
496 * Check the compatibility of the system.
497 * @return array The errors found during the compatibility check.
498 */
499 public function check() {
500 if ($this->mainReasonFound) {
501 return $this->errors;
502 }
503
504 foreach ($this->attrs as $attribute) {
505 if(!$attribute->isCompatible($this->system_info)) {
506 array_push($this->errors, $attribute->getErrorMessage());
507 }
508 }
509 return $this->errors;
510 }
511
512 /**
513 * Get backup size from backup log.
514 * @return int $bytes
515 */
516 public function getBackupSize() {
517 if(current_user_can('manage_options') && current_user_can('administrator')) {
518 return BMP::getRecentSize() * 1.4;
519 }
520 }
521
522 }
523
524