artabro/wire/modules/System/SystemUpdater/SystemUpdaterChecks.php
2024-08-27 11:35:37 +02:00

493 lines
14 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php namespace ProcessWire;
/**
* Class to check for potential issues in the system that may require updates from the admin
*
* All check* methods in this class return true if the check was a success and there are no problem,
* or they return false if the check failed and there is a potential issue to resolve.
*
*/
class SystemUpdaterChecks extends Wire {
/**
* Show warning notices to user?
*
* @var bool
*
*/
protected $showNotices = false;
/**
* Test all to force warning conditions?
*
* @var bool
*
*/
protected $testAll = false;
/**
* Has execute been called to check all?
*
* @var bool
*
*/
protected $checkAll = false;
/**
* @var SystemUpdater
*
*/
protected $systemUpdater;
/**
* @var array
*
*/
protected $warnings = array();
/**
* Set whether or not to show verbose notices
*
* @param bool $showNotices
*
*/
public function setShowNotices($showNotices = true) {
$this->showNotices = $showNotices;
}
/**
* Set whether or not to test all checks (as if all checks failed)
*
* @param bool $testAll
*
*/
public function setTestAll($testAll = true) {
$this->testAll = $testAll;
}
/**
* Run all system checks and return array of results
*
* @return array
*
*/
public function execute() {
$this->checkAll = true;
$results = array();
$checks = array(
'checkWelcome',
'checkIndexFile',
'checkHtaccessFile',
'checkOtherHtaccessFiles',
'checkInstallerFiles',
'checkFilePermissions',
'checkPublishedField',
'checkLocale',
'checkDebugMode',
'checkMemoryLimit',
);
foreach($checks as $method) {
try {
$results[$method] = $this->$method();
} catch(\Exception $e) {
if($this->showNotices) $this->warning("$method: " . $e->getMessage());
$results[$method] = false;
}
}
$this->checkAll = false;
$numWarnings = count($this->warnings);
if($this->showNotices && $numWarnings) {
if($numWarnings > 1) $this->warning($this->_('Multiple issues detected, please review:'));
foreach($this->warnings as $warning) {
$this->warning($warning[0], $warning[1]);
}
}
$this->warnings = array();
return $results;
}
/**
* Check that index.php file is the correct version
*
* @return bool
*
*/
public function checkIndexFile() {
$requiredVersion = ProcessWire::indexVersion;
$actualVersion = PROCESSWIRE;
if(PROCESSWIRE < $requiredVersion || $this->testAll) {
if($this->showNotices) {
$warning = sprintf(
$this->_('Please note that your root %s file is not up-to-date with this ProcessWire version, please update it when possible.'),
$this->location('index.php')
);
$details = $this->versionsLabel($requiredVersion, $actualVersion);
$this->warning($warning . $this->small($details), Notice::log | Notice::allowMarkup);
}
return false;
}
return true;
}
/**
* Check that main htaccess file is the correct version
*
* @return bool
* @throws WireException
*
*/
public function checkHtaccessFile() {
$requiredVersion = ProcessWire::htaccessVersion;
$htaccessFile = $this->wire()->config->paths->root . '.htaccess';
if(is_readable($htaccessFile)) {
$data = file_get_contents($htaccessFile);
if(!preg_match('/@(?:htaccess|index)Version\s+(\d+)\b/', $data, $matches) || ((int) $matches[1]) < $requiredVersion || $this->testAll) {
if($this->showNotices) {
$foundVersion = isset($matches[1]) ? (int) $matches[1] : '?';
$warning = sprintf(
$this->_('Please note that your root %s file is not up-to-date with this ProcessWire version, please update it when possible.'),
$this->location('.htaccess')
);
$details = $this->small(
$this->versionsLabel($requiredVersion, $foundVersion) . ' ' .
$this->_('To suppress this warning, replace or add the following in the top of your existing .htaccess file:') .
$this->code("# @htaccessVersion $requiredVersion")
);
$this->warning("$warning$details", Notice::log | Notice::allowMarkup);
}
return false;
}
} else {
// if .htaccess not present then this is likely an IIS or other not-offically supported server software
// if($this->showNotices) $this->warning($this->fileNotFoundLabel($htaccessFile));
return false;
}
return true;
}
/**
* Check that other useful htaccess files are present
*
* @return bool
*
*/
public function checkOtherHtaccessFiles() {
/** @var SystemUpdater $systemUpdater */
$systemUpdater = $this->wire()->modules->get('SystemUpdater');
if(!$systemUpdater) return false;
$result = true;
/** @var SystemUpdate17 $update */
// update 17 verifies that fallback .htaccess files are in place for 2nd layer protections
$update = $systemUpdater->getUpdate(17);
if($update) {
if($update->isUseful() || $this->testAll) $result = $update->update();
}
return $result;
}
/**
* Check if this is the first call to checkWelcome and show a welcome message and add an active.php file if so
*
* @return bool Returns false if active.php does not yet exist or true if it does
*
*/
public function checkWelcome() {
$config = $this->wire()->config;
$activeFile = $config->paths->assets . 'active.php';
$exists = is_file($activeFile);
if($this->showNotices && ((!$exists && !$config->debug) || $this->testAll)) {
$this->message(
$this->strong($this->_('Welcome to ProcessWire!')) . ' ' .
$this->_('If this installation is currently being used for development or testing, we recommend enabling debug mode.') . ' ' .
$this->_('Debug mode ensures all errors are visible, which can be helpful during development or troubleshooting.') . ' ' .
$this->_('It also enables additional developer information to appear here in the admin.') . ' ' .
sprintf($this->_('You can enable debug mode by editing your %s file and adding the following:'), $this->location('/site/config.php')) .
$this->code('$config->debug = true;') .
$this->small($this->_('Please note: this notification will not be shown again. Remember to disable debug mode before going live.')),
Notice::allowMarkup | Notice::prepend
);
}
if(!$exists) {
$data =
"<?php // Created by ProcessWire - Do not delete this file. " .
"The existence of this file indicates the site is confirmed active " .
"and first-time use errors may be suppressed. Installed at: " .
"[{$config->paths->root}]";
$this->wire()->files->filePutContents($activeFile, $data);
return false;
}
return true;
}
/**
* Check if unnecessary installer files are present
*
* @return bool
*
*/
public function checkInstallerFiles() {
if(is_file($this->wire()->config->paths->root . "install.php") || $this->testAll) {
if($this->showNotices) {
$warning = $this->_("Security Warning: file '%s' exists and should be deleted as soon as possible.");
$this->warning(sprintf($warning, '/install.php'), Notice::log);
}
return false;
}
return true;
}
/**
* Check for insecure file permissions
*
* @return bool
*
*/
public function checkFilePermissions() {
$config = $this->wire()->config;
// warnings about 0666/0777 file permissions
if($config->chmodDir != '0777' && $config->chmodFile != '0666' && !$this->testAll) return true;
if(!$config->chmodWarn || !$this->showNotices) return false;
$warning = sprintf(
$this->_('Warning, your %s file specifies file permissions that are too loose for many environments:'),
$this->location('/site/config.php')
);
$code =
$this->code("\$config->chmodFile = '{$config->chmodFile}';") .
$this->code("\$config->chmodDir = '{$config->chmodDir}';");
$link = $this->link(
'https://processwire.com/docs/security/file-permissions/',
$this->_('Read "Securing file permissions" for more details')
);
$details = $this->small(
sprintf($this->_('To suppress this warning, add the following to your %s file:'), $this->location('/site/config.php')) . ' ' .
$this->code('$config->chmodWarn = false;')
);
$code = str_replace(array('0666', '0777'), array('<u>0666</u>', '<u>0777</u>'), $code);
$this->warning("$warning$code$link$details", Notice::allowMarkup | Notice::log);
return false;
}
/**
* Check if there is a field named 'published' that should not be present
*
* @return bool
*
*/
public function checkPublishedField() {
if(!$this->wire()->fields->get('published') && !$this->testAll) return true;
if($this->showNotices) $this->warning(
$this->_('Warning: you have a field named “published” that conflicts with the page “published” property.') . ' ' .
$this->_('Please rename your field field to something else and update any templates referencing it.')
);
return false;
}
/**
* Check locale setting
*
* Warning about servers with locales that break UTF-8 strings called by basename
* and other file functions, due to a long running PHP bug
*
* @return bool
*
*/
public function checkLocale() {
if(basename("§") !== "" && !$this->testAll) return true;
if(!$this->showNotices) return false;
$example = stripos(PHP_OS, 'WIN') === 0 ? 'en-US' : 'en_US.UTF-8';
$localeLabel = $this->_('Your current locale setting is “%s”.') . ' ';
$warning = $this->_('Note: your current server locale setting isnt working as expected with the UTF-8 charset and may cause minor issues.');
$msg = '';
if($this->wire()->modules->isInstalled('LanguageSupport')) {
// language support installed
$textdomain = 'wire--modules--languagesupport--languagesupport-module';
$locale = __('C', $textdomain);
if(empty($locale)) $locale = setlocale(LC_CTYPE, 0);
$msg .= ' ' .
sprintf($localeLabel, $locale) . ' ' .
sprintf(
$this->_('Please translate the “C” locale setting for each language to the compatible locale in %s'),
$this->location('/wire/modules/LanguageSupport/LanguageSupport.module') . ':'
);
foreach($this->wire()->languages as $language) {
$url = $this->wire()->config->urls->admin .
"setup/language-translator/edit/?" .
"language_id=$language->id&" .
"textdomain=$textdomain&" .
"filename=wire/modules/LanguageSupport/LanguageSupport.module";
$msg .= "<div>" . $this->link($url, $language->get('title|name')) . "</div>";
}
$msg .= $this->small(
sprintf(
$this->_('For example, the locale setting for US English might be: %s'),
$this->strong($example)
)
);
} else {
// no language support installed
$locale = setlocale(LC_CTYPE, 0);
$msg .=
sprintf($localeLabel, $locale) .
sprintf(
$this->_('Please add this to your %1$s file (adjust “%2$s” as needed):'),
$this->location('/site/config.php'),
$example
) .
$this->code("setlocale(LC_ALL, '$example');");
}
$this->warning("$warning $msg", Notice::allowMarkup);
return false;
}
/**
* Check for debug mode
*
* return bool Always returns true, as there is no way to fail this test
*
*/
public function checkDebugMode() {
if(!$this->wire()->config->debug && !$this->testAll) return true;
if($this->showNotices) $this->message('icon-bug ' .
$this->_('The site is in debug mode, suitable for sites in development') .
$this->small(
sprintf(
$this->_('If this is a live/production site, you should disable debug mode in %1$s with: %2$s'),
$this->location('/site/config.php'),
'<u>$config->debug = false;</u>'
)
),
Notice::allowMarkup | Notice::anonymous
);
return true;
}
/**
* Check PHP memory_limit setting
*
* @return bool Always returns true as memory_limit errors not considered fatal
* @since 3.0.206
*
*/
public function checkMemoryLimit() {
$memoryLimit = $this->getMemoryLimit('M');
if(empty($memoryLimit)) return true;
$label = sprintf($this->_('Your PHP memory_limit is currently set to %s.'), "$memoryLimit MB") . ' ';
$mb = '128 MB'; // recommended memory_limit
if($memoryLimit < 64) {
$this->warning(
$label .
sprintf($this->_('We recommend increasing it to at least %s.'), $mb)
);
} else if($memoryLimit < 128) {
$this->warning(
$label .
sprintf($this->_('As a performance optimization, consider increasing it to at least %s.'), $mb)
);
}
return true;
}
/**
* Get memory limit
*
* @param string $getInUnit Get value in 'K' [kilobytes], 'M' [megabytes], 'G' [gigabytes] (default='M')
* @return int|float
* @since 3.0.206
*
*/
public function getMemoryLimit($getInUnit = 'M') {
// $units = array('M' => 1048576, 'K' => 1024, 'G' => 1073741824);
$units = array('M' => 1000000, 'K' => 1000, 'G' => 1000000000);
$value = (string) ini_get('memory_limit');
$value = trim(strtoupper($value), ' B'); // KB=K, MB=>M, GB=G,
$unit = substr($value, -1); // K, M, G
$value = (int) rtrim($value, 'KMG');
if($unit === $getInUnit) return $value; // already in correct unit
if(isset($units[$unit])) $value = $value * $units[$unit]; // convert value to bytes
if(isset($units[$getInUnit])) $value = round($value / $units[$getInUnit]);
if(strpos("$value", '.') !== false) $value = round($value, 1);
return $value;
}
/*********************************************************************************************/
public function warning($text, $flags = 0) {
if($this->checkAll && $this->showNotices) {
$this->warnings[] = array($text, $flags);
} else {
parent::warning($text, $flags);
}
return $this;
}
protected function versionsLabel($requiredVersion, $foundVersion) {
return sprintf($this->_('Required version: %1$s, Found version: %2$s.'), "$requiredVersion", "$foundVersion");
}
protected function fileNotFoundLabel($file) {
return sprintf($this->_('Unable to locate required file: %s'), $file);
}
protected function code($str, $block = true) {
$style = 'font-family:monospace;font-size:14px;';
if($block) $style .= "background:rgba(255,255,255,0.3);padding:5px 7px;border:1px solid rgba(0,0,0,0.1)";
$out = "<span style='$style'>$str</span>";
if($block) $out = "<div style='margin:5px 0'>$out</div>";
return $out;
}
protected function small($str, $block = true) {
$tag = $block ? 'div' : 'span';
return "<$tag><small class='ui-priority-secondary'>$str</small></$tag>";
}
protected function strong($str) {
return "<strong>$str</strong>";
}
protected function link($href, $label, $newWindow = true) {
$target = $newWindow ? "target='_blank'" : "";
return wireIconMarkup('angle-right') . " <a href='$href' $target>$label</a> ";
}
protected function location($str) {
return "<u>$str</u>";
}
}