compatibility.php
444 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 $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 $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 $comparision) |
| 180 | { |
| 181 | parent::__construct($recommendation, $key, $comparision); |
| 182 | $this->error_message = __("It's recommended to use PHP version 7.4.", '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 $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 $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 $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 $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 $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 $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 | /** |
| 355 | * Constructor to initialize the system information and add default compatibility strategies. |
| 356 | */ |
| 357 | public function __construct() { |
| 358 | require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'check' . DIRECTORY_SEPARATOR . 'system_info.php'; |
| 359 | $system = new System_Info(); |
| 360 | $this->system_info = $system->to_array(); |
| 361 | $this->addDefaultStrategies(); |
| 362 | $this->addMoreRecommendations(); |
| 363 | } |
| 364 | |
| 365 | /** |
| 366 | * Add default compatibility strategies. |
| 367 | */ |
| 368 | public function addDefaultStrategies() { |
| 369 | $this->addStrategy(new CURL_Enabled(true, null, new Bool_Comparision())); |
| 370 | // $this->addStrategy(new Disk_Space($this->getBackupSize(), null, new Int_Comparision())); |
| 371 | $this->addStrategy(new Normal_Attribute(true, 'php_allow_url_fopen', new Bool_Comparision(), __("allow_url_fopen is not enabled.", 'backup-backup'))); |
| 372 | $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'))); |
| 373 | $this->addStrategy(new Normal_Attribute(['Apache', 'Nginx'], 'web_server_name', new In_Comparision(), __("We recommend using Apache/Nginx server type.", 'backup-backup'))); |
| 374 | $this->addStrategy(new PHP_Version('8.0', null, new Version_Comparision())); |
| 375 | |
| 376 | $max_execution_time = new Normal_Attribute('180', 'php_max_execution_time', new Int_Comparision()); |
| 377 | $max_execution_time_error = __("PHP max execution time is %s1. The recommended value is %s2.", 'backup-backup'); |
| 378 | $max_execution_time_error = str_replace( |
| 379 | ['%s1', '%s2'], |
| 380 | [$this->system_info['php_max_execution_time'], '180'], |
| 381 | $max_execution_time_error |
| 382 | ); |
| 383 | $max_execution_time->setErrorMessage($max_execution_time_error); |
| 384 | $this->addStrategy($max_execution_time); |
| 385 | } |
| 386 | |
| 387 | /** |
| 388 | * Add more recommendations based on verbose in log. |
| 389 | * @return void |
| 390 | */ |
| 391 | public function addMoreRecommendations() { |
| 392 | // e.g. |
| 393 | // $this->addRecommendation('missing space.', __("The disk space is not enough. Please free up some space.", 'backup-backup')); |
| 394 | } |
| 395 | |
| 396 | /** |
| 397 | * Add recommendation based on vebose in log |
| 398 | * @param string $verbose The verbose in log |
| 399 | * @param string $the message to show |
| 400 | * @return void |
| 401 | */ |
| 402 | public function addRecommendation($verbose, $message) { |
| 403 | $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest.log'; |
| 404 | $content = file_get_contents($file); |
| 405 | $pattern = '#^\[VERBOSE\] \[[0-9-]+ [0-9:]+\] ' . $verbose . '#mi'; // e.g. [VERBOSE] [2021-12-31 23:59:59] missing space. |
| 406 | if (preg_match($pattern, $content, $matches)) { |
| 407 | array_push($this->errors, $message); |
| 408 | } |
| 409 | |
| 410 | } |
| 411 | /** |
| 412 | * Add a compatibility strategy. |
| 413 | * @param Compatibility_Attribute $strategy The compatibility strategy to add. |
| 414 | */ |
| 415 | public function addStrategy( Compatibility_Attribute $strategy) { |
| 416 | array_push($this->attrs, $strategy); |
| 417 | } |
| 418 | |
| 419 | /** |
| 420 | * Check the compatibility of the system. |
| 421 | * @return array The errors found during the compatibility check. |
| 422 | */ |
| 423 | public function check() { |
| 424 | foreach ($this->attrs as $attribute) { |
| 425 | if(is_array($this->system_info) && !$attribute->isCompatible($this->system_info)) { |
| 426 | array_push($this->errors, $attribute->getErrorMessage()); |
| 427 | } |
| 428 | } |
| 429 | return $this->errors; |
| 430 | } |
| 431 | |
| 432 | /** |
| 433 | * Get backup size from backup log. |
| 434 | * @return int $bytes |
| 435 | */ |
| 436 | public function getBackupSize() { |
| 437 | if(current_user_can('manage_options') && current_user_can('administrator')) { |
| 438 | return BMP::getRecentSize() * 1.4; |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | } |
| 443 | |
| 444 |