diff --git a/README.md b/README.md index ee9b524..bc6b0ef 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ Register checks in your own `_config.php` - see the `_config.php` in this module * `HasClassCheck`: Check that the given class exists. This can be used to check that PHP modules or features are installed. * `FileWriteableCheck`: Check that the given file is writeable. + * `FileAgeCheck`: Checks for the maximum age of one or more files or folders. + Useful for files which should be frequently auto-generated, + like static caches, as well as for backup files and folders. + * `ExternalURLCheck`: Checks that one or more URLs are reachable via HTTP. + * `SMTPConnectCheck`: Checks if the SMTP connection configured through PHP.ini works as expected. + ## Adding more checks To add more checks, you should put additional `EnvironmentCheckSuite::register` calls into your `_config.php`. See the `_config.php` file of this mode for examples. diff --git a/code/checks/ExternalURLCheck.php b/code/checks/ExternalURLCheck.php new file mode 100644 index 0000000..a8a09b5 --- /dev/null +++ b/code/checks/ExternalURLCheck.php @@ -0,0 +1,110 @@ +EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?"); + */ +class ExternalURLCheck implements EnvironmentCheck { + + /** + * @var array + */ + protected $urls = array(); + + /** + * @var Int Timeout in seconds. + */ + protected $timeout; + + /** + * @param String Space separated list of absolute URLs + * (can't be an array as we're using Object::create() with strings for the constructor signature) + */ + function __construct($urls, $timeout = 15) { + if($urls) $this->urls = explode(' ', $urls); + $this->timeout = $timeout; + } + + function check() { + $urls = $this->getURLs(); + + $chs = array(); + foreach($urls as $url) { + $ch = curl_init(); + $chs[] = $ch; + curl_setopt_array($ch, $this->getCurlOpts($url)); + } + // Parallel execution for faster performance + $mh = curl_multi_init(); + foreach($chs as $ch) curl_multi_add_handle($mh,$ch); + + $active = null; + // Execute the handles + do { + $mrc = curl_multi_exec($mh, $active); + } while ($mrc == CURLM_CALL_MULTI_PERFORM); + + while ($active && $mrc == CURLM_OK) { + if (curl_multi_select($mh) != -1) { + do { + $mrc = curl_multi_exec($mh, $active); + } while ($mrc == CURLM_CALL_MULTI_PERFORM); + } + } + + $hasError = false; + $msgs = array(); + foreach($chs as $ch) { + $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if(curl_errno($ch) || $code >= 400) { + $hasError = true; + $msgs[] = sprintf( + 'Error retrieving "%s": %s (Code: %s)', + $url, + curl_error($ch), + $code + ); + } else { + $msgs[] = sprintf( + 'Success retrieving "%s" (Code: %s)', + $url, + $code + ); + } + } + + // Close the handles + foreach($chs as $ch) curl_multi_remove_handle($mh, $ch); + curl_multi_close($mh); + + if($hasError) { + return array(EnvironmentCheck::ERROR, implode(', ', $msgs)); + } else { + return array(EnvironmentCheck::OK, implode(', ', $msgs)); + } + } + + /** + * @return Array + */ + protected function getCurlOpts($url) { + return array( + CURLOPT_URL => $url, + CURLOPT_HEADER => 0, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_FAILONERROR => 1, + CURLOPT_TIMEOUT => $this->timeout, + ); + } + + /** + * @return Array + */ + protected function getURLs() { + return $this->urls; + } +} \ No newline at end of file diff --git a/code/checks/FileAgeCheck.php b/code/checks/FileAgeCheck.php new file mode 100644 index 0000000..b531983 --- /dev/null +++ b/code/checks/FileAgeCheck.php @@ -0,0 +1,106 @@ +', " . FileAgeCheck::CHECK_ALL) . "' + * ); + * + * // Checks that at least one backup folder has been created in the last 24h + * EnvironmentCheckSuite::register( + * 'check', + * 'FileAgeCheck("' . BASE_PATH . '/../backups/*' . '", "-1 day", '>', " . FileAgeCheck::CHECK_SINGLE) . "' + * ); + */ +class FileAgeCheck implements EnvironmentCheck { + + const CHECK_SINGLE = 1; + + const CHECK_ALL = 2; + + /** + * @var String Absolute path to a file or folder, compatible with glob(). + */ + protected $path; + + /** + * @var String strtotime() compatible relative date specification. + */ + protected $relativeAge; + + /** + * @var String The function to use for checking file age, + * so filemtime(), filectime() or fileatime(). + */ + protected $checkFn; + + /** + * @var Int Constant, check for a single file to match age criteria, or all of them. + */ + protected $checkType; + + /** + * @var String Either '>' or '<'. + */ + protected $compareOperand; + + function __construct($path, $relativeAge, $compareOperand = '>', $checkType = null, $checkFn = 'filemtime') { + $this->path = $path; + $this->relativeAge = $relativeAge; + $this->checkFn = $checkFn; + $this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE; + $this->compareOperand = $compareOperand; + } + + function check() { + $cutoffTime = strtotime($this->relativeAge, SS_Datetime::now()->Format('U')); + $files = $this->getFiles(); + $invalidFiles = array(); + $validFiles = array(); + $checkFn = $this->checkFn; + $allValid = true; + if($files) foreach($files as $file) { + $fileTime = $checkFn($file); + $valid = ($this->compareOperand == '>') ? ($fileTime >= $cutoffTime) : ($fileTime <= $cutoffTime); + if($valid) { + $validFiles[] = $file; + } else { + $invalidFiles[] = $file; + if($this->checkType == self::CHECK_ALL) { + return array( + EnvironmentCheck::ERROR, + sprintf( + 'File "%s" doesn\'t match age check (compare %s: %s, actual: %s)', + $file, $this->compareOperand, date('c', $cutoffTime), date('c', $fileTime) + ) + ); + } + } + } + + // If at least one file was valid, count as passed + if($this->checkType == self::CHECK_SINGLE && count($invalidFiles) < count($files)) { + return array(EnvironmentCheck::OK, ''); + } else { + return array( + EnvironmentCheck::ERROR, + sprintf('No files matched criteria (%s %s)', $this->compareOperand, date('c', $cutoffTime)) + ); + } + + } + + /** + * @return Array Of absolute file paths + */ + protected function getFiles() { + return glob($this->path); + } + +} \ No newline at end of file diff --git a/code/checks/SMTPConnectCheck.php b/code/checks/SMTPConnectCheck.php new file mode 100644 index 0000000..d3dfb8c --- /dev/null +++ b/code/checks/SMTPConnectCheck.php @@ -0,0 +1,58 @@ +host = ($host) ? $host : ini_get('SMTP'); + if(!$this->host) $this->host = 'localhost'; + + $this->port = ($port) ? $port : ini_get('smtp_port'); + if(!$this->port) $this->port = 25; + + $this->timeout = $timeout; + } + + function check() { + $f = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout); + if(!$f) { + return array( + EnvironmentCheck::ERROR, + sprintf("Couldn't connect to SMTP on %s:%s (Error: %s %s)", $this->host, $this->port, $errno, $errstr) + ); + } + + fwrite($f, "HELO its_me\r\n"); + $response = fread($f, 26); + if(substr($response, 0, 3) != '220') { + return array( + EnvironmentCheck::ERROR, + sprintf("Invalid mail server response: %s", $response) + ); + } + + return array(EnvironmentCheck::OK, ''); + + } +} \ No newline at end of file