├── LICENSE.md ├── check.sh ├── composer.json └── server ├── checkit.php ├── requirements ├── .htaccess ├── RequirementsChecker.php └── requirements.php └── views ├── console └── index.php └── web ├── css.php └── index.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Pixel & Tonic, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Craft CMS Server Check (one-liner) 4 | # 5 | # From the environment you want to check, run: 6 | # `curl -Lsf https://raw.githubusercontent.com/craftcms/server-check/HEAD/check.sh | bash` 7 | 8 | [[ $- = *i* ]] && echo "Don't source this script!" && return 10 9 | 10 | checkTools() { 11 | Tools=("curl" "php" "rm" "tar" "grep" "cut") 12 | 13 | for tool in ${Tools[*]}; do 14 | if ! checkCmd $tool; then 15 | echo "Aborted, missing $tool." 16 | exit 6 17 | fi 18 | done 19 | } 20 | 21 | checkCmd() { 22 | command -v "$1" > /dev/null 2>&1 23 | } 24 | 25 | function serverCheck() { 26 | checkTools 27 | 28 | tmpDir="/tmp/craftcms-server-check" 29 | assetUrl="https://github.com/craftcms/server-check/releases/latest/download/craftcms-server-check.tar.gz" 30 | downloadToFile="${tmpDir}/craftcms-server-check.tar.gz" 31 | phpScript="${tmpDir}/checkit.php" 32 | 33 | echo "Downloading file… ${assetUrl}" 34 | mkdir "${tmpDir}" 35 | curl -fsSL "${assetUrl}" --output "${downloadToFile}" 36 | 37 | echo "Extracting…" 38 | tar -xzf "${downloadToFile}" -C "${tmpDir}" 39 | 40 | echo "Running Craft Server Check…" 41 | php $phpScript 42 | returnCode=$? 43 | 44 | rm -rf "${tmpDir}" 45 | 46 | return $returnCode 47 | } 48 | 49 | serverCheck 50 | exit $? 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "craftcms/server-check", 3 | "description": "Craft CMS Server Check", 4 | "keywords": [ 5 | "cms", 6 | "craftcms", 7 | "requirements", 8 | "yii2" 9 | ], 10 | "homepage": "https://craftcms.com/", 11 | "support": { 12 | "email": "support@craftcms.com", 13 | "issues": "https://github.com/craftcms/server-check/issues?state=open", 14 | "forum": "https://craftcms.stackexchange.com/", 15 | "source": "https://github.com/craftcms/server-check", 16 | "docs": "https://github.com/craftcms/docs", 17 | "rss": "https://github.com/craftcms/server-check/releases.atom" 18 | }, 19 | "license": "MIT", 20 | "autoload": { 21 | "classmap": [ 22 | "server/requirements" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/checkit.php: -------------------------------------------------------------------------------- 1 | checkCraft()->render(); 11 | $strict = (bool) getenv('CRAFT_STRICT_SERVER_CHECK'); 12 | 13 | if ($checker->result['summary']['errors'] || ($strict && $checker->result['summary']['warnings'])) { 14 | exit(1); 15 | } 16 | 17 | exit(0); 18 | -------------------------------------------------------------------------------- /server/requirements/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all 2 | -------------------------------------------------------------------------------- /server/requirements/RequirementsChecker.php: -------------------------------------------------------------------------------- 1 | 'PHP Some Extension', 27 | * 'mandatory' => true, 28 | * 'condition' => extension_loaded('some_extension'), 29 | * 'memo' => 'PHP extension "some_extension" required', 30 | * ), 31 | * ); 32 | * 33 | * $requirementsChecker->checkCraft()->check($requirements)->render(); 34 | * ~~~ 35 | * 36 | * If you wish to render the report with your own representation, use [[getResult()]] instead of [[render()]] 37 | * 38 | * Note: this class definition does not match ordinary Craft style, because it should match PHP 4.3 39 | * and should not use features from newer PHP versions! 40 | * 41 | * @author Pixel & Tonic, Inc. 42 | * @since 3.0 43 | */ 44 | class RequirementsChecker 45 | { 46 | var $dsn; 47 | var $dbDriver; 48 | var $dbUser; 49 | var $dbPassword; 50 | 51 | var $result; 52 | 53 | var $requiredMySqlVersion = '8.0.17'; 54 | var $requiredMariaDbVersion = '10.4.6'; 55 | var $requiredPgSqlVersion = '13.0'; 56 | 57 | /** 58 | * Check the given requirements, collecting results into internal field. 59 | * This method can be invoked several times checking different requirement sets. 60 | * Use [[getResult()]] or [[render()]] to get the results. 61 | * 62 | * @param array|string $requirements The requirements to be checked. If an array, it is treated as the set of 63 | * requirements. If a string, it is treated as the path of the file, which 64 | * contains the requirements; 65 | * 66 | * @return static self reference 67 | */ 68 | function check($requirements) 69 | { 70 | if (is_string($requirements)) { 71 | $requirements = require $requirements; 72 | } 73 | 74 | if (!is_array($requirements)) { 75 | $this->usageError('Requirements must be an array, "'.gettype($requirements).'" has been given!'); 76 | } 77 | 78 | if (!isset($this->result) || !is_array($this->result)) { 79 | $this->result = array( 80 | 'summary' => array( 81 | 'total' => 0, 82 | 'errors' => 0, 83 | 'warnings' => 0, 84 | ), 85 | 'requirements' => array(), 86 | ); 87 | } 88 | 89 | foreach ($requirements as $key => $rawRequirement) { 90 | $requirement = $this->normalizeRequirement($rawRequirement, $key); 91 | $this->result['summary']['total']++; 92 | 93 | if (!$requirement['condition']) { 94 | if ($requirement['mandatory']) { 95 | $requirement['error'] = true; 96 | $requirement['warning'] = true; 97 | $this->result['summary']['errors']++; 98 | } else { 99 | $requirement['error'] = false; 100 | $requirement['warning'] = true; 101 | $this->result['summary']['warnings']++; 102 | } 103 | } else { 104 | $requirement['error'] = false; 105 | $requirement['warning'] = false; 106 | } 107 | 108 | $this->result['requirements'][] = $requirement; 109 | } 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Performs the check for the Craft core requirements. 116 | * 117 | * @return static self reference 118 | */ 119 | function checkCraft() 120 | { 121 | return $this->check(dirname(__FILE__).DIRECTORY_SEPARATOR.'requirements.php'); 122 | } 123 | 124 | /** 125 | * Return the check results. 126 | * 127 | * The results will be returned in this format: 128 | * 129 | * ```php 130 | * array( 131 | * 'summary' => array( 132 | * 'total' => total number of checks, 133 | * 'errors' => number of errors, 134 | * 'warnings' => number of warnings, 135 | * ), 136 | * 'requirements' => array( 137 | * array( 138 | * ... 139 | * 'error' => is there an error, 140 | * 'warning' => is there a warning, 141 | * ), 142 | * // ... 143 | * ), 144 | * ) 145 | * ``` 146 | * 147 | * @return array|null The check results 148 | */ 149 | function getResult() 150 | { 151 | if (isset($this->result)) { 152 | return $this->result; 153 | } 154 | 155 | return null; 156 | } 157 | 158 | /** 159 | * Renders the requirements check result. The output will vary depending is a script running from web or from console. 160 | */ 161 | function render() 162 | { 163 | if (!isset($this->result)) { 164 | $this->usageError('Nothing to render!'); 165 | } 166 | 167 | $baseViewFilePath = dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'views'; 168 | 169 | if (!empty($_SERVER['argv'])) { 170 | $viewFilename = $baseViewFilePath.DIRECTORY_SEPARATOR.'console'.DIRECTORY_SEPARATOR.'index.php'; 171 | } else { 172 | $viewFilename = $baseViewFilePath.DIRECTORY_SEPARATOR.'web'.DIRECTORY_SEPARATOR.'index.php'; 173 | } 174 | 175 | $this->renderViewFile($viewFilename, $this->result); 176 | } 177 | 178 | /** 179 | * Checks if the given PHP extension is available and its version matches the given one. 180 | * 181 | * @param string $extensionName The PHP extension name. 182 | * @param string $version The required PHP extension version. 183 | * @param string $compare The comparison operator, by default '>='. 184 | * 185 | * @return bool Whether the PHP extension version matches 186 | */ 187 | function checkPhpExtensionVersion($extensionName, $version, $compare = '>=') 188 | { 189 | if (!extension_loaded($extensionName)) { 190 | return false; 191 | } 192 | 193 | $extensionVersion = phpversion($extensionName); 194 | 195 | if (empty($extensionVersion)) { 196 | return false; 197 | } 198 | 199 | if (strncasecmp($extensionVersion, 'PECL-', 5) === 0) { 200 | $extensionVersion = substr($extensionVersion, 5); 201 | } 202 | 203 | return version_compare($extensionVersion, $version, $compare); 204 | } 205 | 206 | /** 207 | * Checks if the given PHP configuration option (from php.ini) is on. 208 | * 209 | * @param string $name The configuration option name. 210 | * 211 | * @return bool Whether the option is on 212 | */ 213 | function checkPhpIniOn($name) 214 | { 215 | $value = ini_get($name); 216 | 217 | if (empty($value)) { 218 | return false; 219 | } 220 | 221 | return ((int)$value === 1 || strtolower($value) === 'on'); 222 | } 223 | 224 | /** 225 | * Checks if the given PHP configuration option (from php.ini) is off. 226 | * 227 | * @param string $name The configuration option name. 228 | * 229 | * @return bool Whether the option is off 230 | */ 231 | function checkPhpIniOff($name) 232 | { 233 | $value = ini_get($name); 234 | 235 | if (empty($value)) { 236 | return true; 237 | } 238 | 239 | return (strtolower($value) === 'off'); 240 | } 241 | 242 | /** 243 | * Gets the size in bytes from verbose size representation. For example: '5K' => 5 * 1024 244 | * 245 | * @param string $value The verbose size representation. 246 | * 247 | * @return int|float The actual size in bytes 248 | */ 249 | function getByteSize($value) 250 | { 251 | // Copied from craft\helpers\App::phpConfigValueInBytes() 252 | if (!preg_match('/(\d+)(K|M|G)/i', $value, $matches)) { 253 | return (int)$value; 254 | } 255 | 256 | $value = (int)$matches[1]; 257 | 258 | // Multiply! 259 | switch (strtolower($matches[2])) { 260 | case 'g': 261 | $value *= 1024; 262 | // no break 263 | case 'm': 264 | $value *= 1024; 265 | // no break 266 | case 'k': 267 | $value *= 1024; 268 | // no break 269 | } 270 | 271 | return $value; 272 | } 273 | 274 | /** 275 | * Renders a view file. 276 | * This method includes the view file as a PHP script and captures the display result if required. 277 | * 278 | * @param string $_viewFile_ The view file. 279 | * @param array $_data_ The data to be extracted and made available to the view file. 280 | * @param boolean $_return_ Whether the rendering result should be returned as a string. 281 | * 282 | * @return string|null The rendering result, or `null` if the rendering result is not required 283 | */ 284 | function renderViewFile($_viewFile_, $_data_ = null, $_return_ = false) 285 | { 286 | // we use special variable names here to avoid conflict when extracting data 287 | if (is_array($_data_)) { 288 | extract($_data_, EXTR_PREFIX_SAME, 'data'); 289 | } 290 | 291 | if ($_return_) { 292 | ob_start(); 293 | ob_implicit_flush(false); 294 | 295 | require $_viewFile_; 296 | 297 | return ob_get_clean(); 298 | } 299 | 300 | require $_viewFile_; 301 | 302 | return null; 303 | } 304 | 305 | /** 306 | * Normalizes requirement ensuring it has correct format. 307 | * 308 | * @param array $requirement The raw requirement. 309 | * @param integer $requirementKey The requirement key in the list. 310 | * 311 | * @return array The normalized requirement 312 | */ 313 | function normalizeRequirement($requirement, $requirementKey = 0) 314 | { 315 | if (!is_array($requirement)) { 316 | $this->usageError('Requirement must be an array!'); 317 | } 318 | 319 | if (!array_key_exists('condition', $requirement)) { 320 | $this->usageError("Requirement '{$requirementKey}' has no condition!"); 321 | } 322 | 323 | if (!array_key_exists('name', $requirement)) { 324 | $requirement['name'] = is_numeric($requirementKey) ? 'Requirement #'.$requirementKey : $requirementKey; 325 | } 326 | 327 | if (!array_key_exists('mandatory', $requirement)) { 328 | if (array_key_exists('required', $requirement)) { 329 | $requirement['mandatory'] = $requirement['required']; 330 | } else { 331 | $requirement['mandatory'] = false; 332 | } 333 | } 334 | 335 | if (!array_key_exists('memo', $requirement)) { 336 | $requirement['memo'] = ''; 337 | } 338 | 339 | return $requirement; 340 | } 341 | 342 | /** 343 | * Displays a usage error. This method will then terminate the execution of the current application. 344 | * 345 | * @param string $message the error message 346 | */ 347 | function usageError($message) 348 | { 349 | echo "Error: $message\n\n"; 350 | exit(1); 351 | } 352 | 353 | /** 354 | * Returns the server information. 355 | * 356 | * @return string The server information 357 | */ 358 | function getServerInfo() 359 | { 360 | return isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : ''; 361 | } 362 | 363 | /** 364 | * Returns the current date if possible in string representation. 365 | * 366 | * @return string The current date 367 | */ 368 | function getCurrentDate() 369 | { 370 | return @strftime('%Y-%m-%d %H:%M', time()); 371 | } 372 | 373 | /** 374 | * Error-handler that mutes errors. 375 | */ 376 | function muteErrorHandler() 377 | { 378 | } 379 | 380 | /** 381 | * @return PDO|false 382 | */ 383 | function getDbConnection() 384 | { 385 | static $conn; 386 | 387 | if ($conn === null) { 388 | try { 389 | $conn = new PDO((string)$this->dsn, $this->dbUser, $this->dbPassword); 390 | $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 391 | } catch (PDOException $e) { 392 | $conn = false; 393 | } 394 | } 395 | 396 | return $conn; 397 | } 398 | 399 | /** 400 | * @param PDO $conn 401 | * @param string $requiredVersion 402 | * 403 | * @return bool 404 | * @throws Exception 405 | */ 406 | function checkDatabaseServerVersion($conn, $requiredVersion) 407 | { 408 | return version_compare($conn->getAttribute(PDO::ATTR_SERVER_VERSION), $requiredVersion, '>='); 409 | } 410 | 411 | /** 412 | * Checks to see if the MySQL InnoDB storage engine is installed and enabled. 413 | * 414 | * @param PDO $conn 415 | * @return bool 416 | */ 417 | function isInnoDbSupported($conn) 418 | { 419 | $results = $conn->query('SHOW ENGINES'); 420 | 421 | foreach ($results as $result) { 422 | if (strtolower($result['Engine']) === 'innodb' && strtolower($result['Support']) !== 'no') { 423 | return true; 424 | } 425 | } 426 | 427 | return false; 428 | } 429 | 430 | /** 431 | * This method attempts to see if MySQL timezone data has been populated on 432 | * the MySQL server Craft is configured to use. 433 | * 434 | * https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html 435 | * 436 | * @param PDO $conn 437 | * @return bool 438 | */ 439 | function validateDatabaseTimezoneSupport($conn) 440 | { 441 | $query = $conn->query("SELECT CONVERT_TZ('2007-03-11 02:00:00','America/Los_Angeles','America/New_York') AS time1"); 442 | $result = $query->fetchColumn(); 443 | 444 | if (!$result) { 445 | return false; 446 | } 447 | 448 | return true; 449 | } 450 | 451 | /** 452 | * @return array 453 | */ 454 | function iniSetRequirement() 455 | { 456 | $oldValue = ini_get('memory_limit'); 457 | 458 | $setValue = '442M'; // A random PHP memory limit value. 459 | if ($oldValue !== '-1'){ 460 | // When the old value is not equal to '-1', add 1MB to the limit set at the moment. 461 | $bytes = $this->getByteSize($oldValue) + $this->getByteSize('1M'); 462 | $setValue = sprintf('%sM', $bytes / (1024 * 1024)); 463 | } 464 | 465 | set_error_handler(array($this, 'muteErrorHandler')); 466 | $result = ini_set('memory_limit', $setValue); 467 | $newValue = ini_get('memory_limit'); 468 | ini_set('memory_limit', $oldValue); 469 | restore_error_handler(); 470 | 471 | $mandatory = true; 472 | 473 | // ini_set can return false or an empty string depending on your php version / FastCGI. 474 | // If ini_set has been disabled in php.ini, the value will be null because of our muted error handler 475 | if ($result === null) { 476 | $memo = 'It looks like ini_set has been disabled in your php.ini file. Craft requires that to operate.'; 477 | $condition = false; 478 | } 479 | 480 | // ini_set can return false or an empty string or the current value of memory_limit depending on your php 481 | // version and FastCGI. Regard, calling it didn't work, but there was no error. 482 | else if ($result === false || $result === '' || $result === $newValue) { 483 | $memo = 'It appears calls to ini_set are not working for Craft. You may need to increase some settings in your php.ini file such as memory_limit and max_execution_time for long running operations like updating and asset transformations.'; 484 | 485 | // Set mandatory to false here so it's not a "fatal" error, but will be treated as a warning. 486 | $mandatory = false; 487 | $condition = false; 488 | } else { 489 | $memo = 'Calls to ini_set are working correctly.'; 490 | $condition = true; 491 | } 492 | 493 | return array( 494 | 'name' => 'ini_set calls', 495 | 'mandatory' => $mandatory, 496 | 'condition' => $condition, 497 | 'memo' => $memo, 498 | ); 499 | } 500 | 501 | /** 502 | * @return array 503 | * 504 | * @see https://php.net/manual/en/ini.core.php#ini.memory-limit 505 | */ 506 | function memoryLimitRequirement() 507 | { 508 | $memoryLimit = ini_get('memory_limit'); 509 | $bytes = $this->getByteSize($memoryLimit); 510 | 511 | $humanLimit = $memoryLimit . ($memoryLimit === -1 ? ' (no limit)' : ''); 512 | $memo = "Craft requires a minimum PHP memory limit of 256M. The memory_limit directive in php.ini is currently set to {$humanLimit}."; 513 | 514 | return array( 515 | 'name' => 'Memory Limit', 516 | 'mandatory' => false, 517 | 'condition' => $bytes === -1 || $bytes >= 268435456, 518 | 'memo' => $memo, 519 | ); 520 | } 521 | 522 | /** 523 | * @return array 524 | */ 525 | function webrootRequirement() 526 | { 527 | $pathService = Craft::$app->getPath(); 528 | $folders = array( 529 | 'config' => $pathService->getConfigPath(), 530 | 'storage' => $pathService->getStoragePath(), 531 | 'templates' => $pathService->getSiteTemplatesPath(), 532 | 'translations' => $pathService->getSiteTranslationsPath(), 533 | 'vendor' => $pathService->getVendorPath(), 534 | ); 535 | 536 | // figure out which ones are public 537 | $publicFolders = array(); 538 | foreach ($folders as $key => $path) { 539 | if ( 540 | $path && 541 | ($realPath = realpath($path)) && 542 | $this->isPathInsideWebroot($realPath) 543 | ) { 544 | $publicFolders[] = $key; 545 | } 546 | } 547 | 548 | if ($condition = empty($publicFolders)) { 549 | $memo = 'All of your Craft folders appear to be above your web root.'; 550 | } else { 551 | $total = count($publicFolders); 552 | $folderString = ''; 553 | 554 | foreach ($publicFolders as $i => $folder) { 555 | if ($total >= 3 && $i > 0) { 556 | $folderString .= ', '; 557 | if ($i === $total - 2) { 558 | $folderString .= 'and '; 559 | } 560 | } else if ($total === 2 && $i === 1) { 561 | $folderString .= ' and '; 562 | } 563 | 564 | $folderString .= "{$folder}/"; 565 | } 566 | 567 | if ($total > 1) { 568 | $memo = "Your {$folderString} folders appear to be publicly accessible, which is a security risk. They should be moved above your web root."; 569 | } else { 570 | $memo = "Your {$folderString} folder appears to be publicly accessible, which is a security risk. It should be moved above your web root."; 571 | } 572 | } 573 | 574 | return array( 575 | 'name' => 'Sensitive folders should not be publicly accessible', 576 | 'mandatory' => false, 577 | 'condition' => $condition, 578 | 'memo' => $memo, 579 | ); 580 | } 581 | 582 | /** 583 | * @param string $pathToTest 584 | * 585 | * @return bool 586 | */ 587 | function isPathInsideWebroot($pathToTest) 588 | { 589 | // If the path is empty, the folder doesn't even exist. 590 | if ($pathToTest) { 591 | // Get the base path without the script name. 592 | $subBasePath = \craft\helpers\FileHelper::normalizePath(mb_substr(Craft::$app->getRequest()->getScriptFile(), 0, -mb_strlen(Craft::$app->getRequest()->getScriptUrl()))); 593 | 594 | // If $subBasePath === '', then both the craft folder and index.php are living at the root of the filesystem. 595 | // Note that some web servers (Idea Web Server) can be configured with virtual roots so that PHP's realpath 596 | // returns that instead of the actual root. 597 | if ($subBasePath === '' || mb_strpos($pathToTest, $subBasePath) !== false) { 598 | return true; 599 | } 600 | } 601 | 602 | return false; 603 | } 604 | } 605 | -------------------------------------------------------------------------------- /server/requirements/requirements.php: -------------------------------------------------------------------------------- 1 | 'PHP 8.2+', 10 | 'mandatory' => true, 11 | 'condition' => PHP_VERSION_ID >= 80200, 12 | 'memo' => 'PHP 8.2 or later is required.', 13 | ), 14 | ); 15 | 16 | $conn = $this->getDbConnection(); 17 | $pdoExtensionRequirement = null; 18 | $opCacheLoaded = extension_loaded('opcache') || extension_loaded('Zend OPcache'); 19 | 20 | switch ($this->dbDriver) { 21 | case 'mysql': 22 | $pdoExtensionRequirement = array( 23 | 'name' => 'PDO MySQL extension', 24 | 'mandatory' => true, 25 | 'condition' => extension_loaded('pdo_mysql'), 26 | 'memo' => 'The PDO MySQL extension is required.' 27 | ); 28 | if ($conn !== false) { 29 | $version = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); 30 | if (preg_match('/[\d.]+-([\d.]+)-\bMariaDB\b/', $version, $match)) { 31 | $name = 'MariaDB'; 32 | $version = $match[1]; 33 | $requiredVersion = $this->requiredMariaDbVersion; 34 | $tzUrl = 'https://mariadb.com/kb/en/time-zones/#mysql-time-zone-tables'; 35 | } else { 36 | $name = 'MySQL'; 37 | $requiredVersion = $this->requiredMySqlVersion; 38 | $tzUrl = 'https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html'; 39 | } 40 | $requirements[] = array( 41 | 'name' => "{$name} {$requiredVersion}+", 42 | 'mandatory' => true, 43 | 'condition' => version_compare($version, $requiredVersion, '>='), 44 | 'memo' => "{$name} {$requiredVersion} or higher is required to run Craft CMS.", 45 | ); 46 | $requirements[] = array( 47 | 'name' => "{$name} InnoDB support", 48 | 'mandatory' => true, 49 | 'condition' => $this->isInnoDbSupported($conn), 50 | 'memo' => "Craft CMS requires the {$name} InnoDB storage engine to run.", 51 | ); 52 | $requirements[] = array( 53 | 'name' => "{$name} timezone support", 54 | 'mandatory' => false, 55 | 'condition' => $this->validateDatabaseTimezoneSupport($conn), 56 | 'memo' => "{$name} should be configured with full timezone support.", 57 | ); 58 | } 59 | break; 60 | case 'pgsql': 61 | $pdoExtensionRequirement = array( 62 | 'name' => 'PDO PostgreSQL extension', 63 | 'mandatory' => true, 64 | 'condition' => extension_loaded('pdo_pgsql'), 65 | 'memo' => 'The PDO PostgreSQL extension is required.' 66 | ); 67 | if ($conn !== false) { 68 | $requirements[] = array( 69 | 'name' => "PostgreSQL {$this->requiredPgSqlVersion}+", 70 | 'mandatory' => true, 71 | 'condition' => $this->checkDatabaseServerVersion($conn, $this->requiredPgSqlVersion), 72 | 'memo' => "PostgresSQL {$this->requiredPgSqlVersion} or higher is required to run Craft CMS.", 73 | ); 74 | } 75 | break; 76 | } 77 | 78 | // Only run this requirement check if we're running in the context of Craft. 79 | if (class_exists('Craft')) { 80 | $requirements[] = $this->webrootRequirement(); 81 | } 82 | 83 | $requirements = array_merge($requirements, array_filter(array( 84 | array( 85 | 'name' => 'BCMath extension', 86 | 'mandatory' => true, 87 | 'condition' => extension_loaded('bcmath'), 88 | 'memo' => 'The BCMath extension is required.' 89 | ), 90 | array( 91 | 'name' => 'ctype extension', 92 | 'mandatory' => true, 93 | 'condition' => extension_loaded('ctype'), 94 | 'memo' => 'The ctype extension is required.', 95 | ), 96 | array( 97 | 'name' => 'cURL extension', 98 | 'mandatory' => true, 99 | 'condition' => extension_loaded('curl'), 100 | 'memo' => 'The cURL extension is required.', 101 | ), 102 | array( 103 | 'name' => 'DOM extension', 104 | 'mandatory' => true, 105 | 'condition' => extension_loaded('dom'), 106 | 'memo' => 'The DOM extension is required.', 107 | ), 108 | array( 109 | 'name' => 'Fileinfo extension', 110 | 'mandatory' => true, 111 | 'condition' => extension_loaded('fileinfo'), 112 | 'memo' => 'The Fileinfo extension required.' 113 | ), 114 | array( 115 | 'name' => 'GD extension or ImageMagick extension', 116 | 'mandatory' => false, 117 | 'condition' => extension_loaded('gd') || (extension_loaded('imagick') && !empty(\Imagick::queryFormats())), 118 | 'memo' => 'When using Craft\'s default image transformer, the GD or ImageMagick extension is required. ImageMagick is recommended as it adds animated GIF support, and preserves 8-bit and 24-bit PNGs during image transforms.' 119 | ), 120 | array( 121 | 'name' => 'iconv extension', 122 | 'mandatory' => true, 123 | 'condition' => function_exists('iconv'), 124 | 'memo' => 'iconv is required for more robust character set conversion support.', 125 | ), 126 | array( 127 | 'name' => 'Intl extension', 128 | 'mandatory' => true, 129 | 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='), 130 | 'memo' => 'The Intl extension (version 1.0.2+) is recommended.' 131 | ), 132 | array( 133 | 'name' => 'JSON extension', 134 | 'mandatory' => true, 135 | 'condition' => extension_loaded('json'), 136 | 'memo' => 'The JSON extension is required for JSON encoding and decoding.', 137 | ), 138 | array( 139 | 'name' => 'Multibyte String extension (with Function Overloading disabled)', 140 | 'mandatory' => true, 141 | 'condition' => extension_loaded('mbstring') && ini_get('mbstring.func_overload') == 0, 142 | 'memo' => 'Craft CMS requires the Multibyte String extension with Function Overloading disabled in order to run.' 143 | ), 144 | array( 145 | 'name' => $opCacheLoaded ? 'OPcache extension (with save_comments)' : 'OPcache extension', 146 | 'mandatory' => $opCacheLoaded, 147 | 'condition' => $opCacheLoaded && ini_get('opcache.save_comments') == 1, 148 | 'memo' => $opCacheLoaded 149 | ? 'The opcache.save_comments configuration setting must be enabled.' 150 | : 'The OPcache extension is recommended in production environments.' 151 | ), 152 | array( 153 | 'name' => 'OpenSSL extension', 154 | 'mandatory' => true, 155 | 'condition' => extension_loaded('openssl'), 156 | 'memo' => 'The OpenSSL extension is required.' 157 | ), 158 | array( 159 | 'name' => 'PCRE extension (with UTF-8 support)', 160 | 'mandatory' => true, 161 | 'condition' => extension_loaded('pcre') && preg_match('/./u', 'Ü') === 1, 162 | 'memo' => 'The PCRE extension is required and it must be compiled to support UTF-8.', 163 | ), 164 | array( 165 | 'name' => 'PDO extension', 166 | 'mandatory' => true, 167 | 'condition' => extension_loaded('pdo'), 168 | 'memo' => 'The PDO extension is required.' 169 | ), 170 | $pdoExtensionRequirement, 171 | array( 172 | 'name' => 'Reflection extension', 173 | 'mandatory' => true, 174 | 'condition' => extension_loaded('reflection'), 175 | 'memo' => 'The Reflection extension is required.', 176 | ), 177 | array( 178 | 'name' => 'SPL extension', 179 | 'mandatory' => true, 180 | 'condition' => extension_loaded('SPL'), 181 | 'memo' => 'The SPL extension is required.' 182 | ), 183 | array( 184 | 'name' => 'Zip extension', 185 | 'mandatory' => true, 186 | 'condition' => extension_loaded('zip'), 187 | 'memo' => 'The zip extension is required for zip and unzip operations.', 188 | ), 189 | 190 | array( 191 | 'name' => 'ignore_user_abort()', 192 | 'mandatory' => false, 193 | 'condition' => function_exists('ignore_user_abort'), 194 | 'memo' => 'ignore_user_abort() must be enabled in your PHP configuration for the native web-based queue runner to work.', 195 | ), 196 | array( 197 | 'name' => 'password_hash()', 198 | 'mandatory' => true, 199 | 'condition' => function_exists('password_hash'), 200 | 'memo' => 'The password_hash() function is required so Craft can create secure passwords.', 201 | ), 202 | array( 203 | 'name' => 'proc_close()', 204 | 'mandatory' => false, 205 | 'condition' => function_exists('proc_close'), 206 | 'memo' => 'The proc_close() function is required for Plugin Store operations as well as sending emails.', 207 | ), 208 | array( 209 | 'name' => 'proc_get_status()', 210 | 'mandatory' => false, 211 | 'condition' => function_exists('proc_get_status'), 212 | 'memo' => 'The proc_get_status() function is required for Plugin Store operations as well as sending emails.', 213 | ), 214 | array( 215 | 'name' => 'proc_open()', 216 | 'mandatory' => false, 217 | 'condition' => function_exists('proc_open'), 218 | 'memo' => 'The proc_open() function is required for Plugin Store operations as well as sending emails.', 219 | ), 220 | array( 221 | 'name' => 'proc_terminate()', 222 | 'mandatory' => false, 223 | 'condition' => function_exists('proc_terminate'), 224 | 'memo' => 'The proc_terminate() function is required for Plugin Store operations as well as sending emails.', 225 | ), 226 | 227 | array( 228 | 'name' => 'allow_url_fopen', 229 | 'mandatory' => false, 230 | 'condition' => ini_get('allow_url_fopen'), 231 | 'memo' => 'allow_url_fopen must be enabled in your PHP configuration for Plugin Store and updating operations.', 232 | ), 233 | 234 | $this->iniSetRequirement(), 235 | $this->memoryLimitRequirement(), 236 | ))); 237 | 238 | return $requirements; 239 | -------------------------------------------------------------------------------- /server/views/console/index.php: -------------------------------------------------------------------------------- 1 | $requirement) 18 | { 19 | if ($requirement['condition']) { 20 | echo $requirement['name'].": OK\n"; 21 | echo "\n"; 22 | } else { 23 | echo $requirement['name'].': '.($requirement['mandatory'] ? 'FAILED!!!' : 'WARNING!!!')."\n"; 24 | 25 | $memo = strip_tags($requirement['memo']); 26 | 27 | if (!empty($memo)) { 28 | echo 'Memo: '.strip_tags($requirement['memo'])."\n"; 29 | } 30 | 31 | echo "\n"; 32 | } 33 | } 34 | 35 | $summaryString = 'Errors: '.$summary['errors'].' Warnings: '.$summary['warnings'].' Total checks: '.$summary['total']; 36 | echo str_pad('', strlen($summaryString), '-')."\n"; 37 | echo $summaryString; 38 | 39 | echo "\n\n"; 40 | -------------------------------------------------------------------------------- /server/views/web/css.php: -------------------------------------------------------------------------------- 1 | 6808 | -------------------------------------------------------------------------------- /server/views/web/index.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | Craft CMS Requirement Checker 11 | renderViewFile(__DIR__.'/css.php'); ?> 12 | 13 | 14 |
15 |
16 |

Craft CMS Requirement Checker

17 |
18 |
19 | 20 |
21 |

Description

22 |

23 | This script checks if your web server configuration meets the requirements for running a Craft CMS installation. 24 | It checks if the server is running the right version of PHP, if appropriate PHP extensions have been loaded, and if php.ini file settings are correct. 25 |

26 |

27 | There are two kinds of requirements being checked. Mandatory requirements are those that have to be met 28 | to allow Craft to work as expected. There are also some optional requirements being checked which will 29 | show you a warning when they do not meet. You can use Craft without them but some specific 30 | functionality may be not available in this case. 31 |

32 | 33 |

Results

34 | 0): ?> 35 |
36 | Unfortunately your server configuration does not meet the requirements to run Craft.
Please refer to the table below for detailed explanation.
37 |
38 | 0): ?> 39 |
40 | Your server configuration meet the minimum requirements to run Craft.
Please pay attention to the warnings listed below and check if your site will use the corresponding features.
41 |
42 | 43 |
44 | Congratulations! Your server configuration can run Craft! 45 |
46 | 47 | 48 |

Details

49 | 50 | 51 | 52 | 53 | 54 | 57 | 60 | 63 | 64 | 65 |
NameResultMemo
55 | 56 | 58 | 59 | 61 | 62 |
66 | 67 |
68 | 69 |
70 | 71 | 75 |
76 | 77 | 78 | --------------------------------------------------------------------------------