diff --git a/_config.php b/_config.php index 85ef6d6..e2dab15 100644 --- a/_config.php +++ b/_config.php @@ -20,6 +20,6 @@ // EnvironmentCheckSuite::register('check', 'FileWriteableCheck("' . TEMP_FOLDER . '")', "Is the temp folder writeable?"); Director::addRules(100, array( - 'dev/health' => 'DevHealth', - 'dev/check' => 'DevCheck', + 'dev/health' => 'DevHealthController', + 'dev/check' => 'DevCheckController', )); \ No newline at end of file diff --git a/code/DefaultHealthChecks.php b/code/DefaultHealthChecks.php deleted file mode 100644 index 4aee636..0000000 --- a/code/DefaultHealthChecks.php +++ /dev/null @@ -1,166 +0,0 @@ -checkTable = $checkTable; - } - - function check() { - $count = DB::query("SELECT COUNT(*) FROM \"$this->checkTable\"")->value(); - - if($count > 0) { - return array(EnvironmentCheck::OK, ""); - } else { - return array(EnvironmentCheck::WARNING, "$this->checkTable queried ok but has no records"); - } - } -} - -/** - * Check that a given URL is functioning, by default, the homepage. - * - * Note that Director::test() will be used rather than a CURL check. - */ -class URLCheck implements EnvironmentCheck { - protected $url; - protected $testString; - - /* - * @param $url The URL to check, relative to the site. "" is the homepage. - * @param $testString A piece of text to optionally search for in the homepage HTML. If omitted, no such check is made. - */ - function __construct($url = "", $testString = "") { - $this->url = $url; - $this->testString = $testString; - } - - function check() { - $response = Director::test($this->url); - - if($response->getStatusCode() != 200) { - return array( - EnvironmentCheck::ERROR, - sprintf('Error retrieving "%s" (Code: %d)', $this->url, $response->getStatusCode()) - ); - - } else if($this->testString && (strpos($response->getBody(), $this->testString) === false)) { - return array( - EnvironmentCheck::WARNING, - sprintf('Success retrieving "%s", but string "%s" not found', $this->url, $testString) - ); - - } else { - return array( - EnvironmentCheck::OK, - sprintf('Success retrieving "%s"', $this->url) - ); - } - } -} - -/** - * Check that the given function exists. - * This can be used to check that PHP modules or features are installed. - * @param $functionName The name of the function to look for. - */ -class HasFunctionCheck implements EnvironmentCheck { - protected $functionName; - - function __construct($functionName) { - $this->functionName = $functionName; - } - - function check() { - if(function_exists($this->functionName)) return array(EnvironmentCheck::OK, $this->functionName.'() exists'); - else return array(EnvironmentCheck::ERROR, $this->functionName.'() doesn\'t exist'); - } -} - -/** - * Check that the given class exists. - * This can be used to check that PHP modules or features are installed. - * @param $className The name of the class to look for. - */ -class HasClassCheck implements EnvironmentCheck { - protected $className; - - function __construct($className) { - $this->className = $className; - } - - function check() { - if(class_exists($this->className)) return array(EnvironmentCheck::OK, 'Class ' . $this->className.' exists'); - else return array(EnvironmentCheck::ERROR, 'Class ' . $this->className.' doesn\'t exist'); - } -} - -/** - * Check that the given file is writeable. - * This can be used to check that the environment doesn't have permission set-up errors. - * @param $path The full path. If a relative path, it will relative to the BASE_PATH - */ -class FileWriteableCheck implements EnvironmentCheck { - protected $path; - - function __construct($path) { - $this->path = $path; - } - - function check() { - if($this->path[0] == '/') $filename = $this->path; - else $filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $this->path); - - if(file_exists($filename)) $isWriteable = is_writeable($filename); - else $isWriteable = is_writeable(dirname($filename)); - - if(!$isWriteable) { - if(function_exists('posix_getgroups')) { - $userID = posix_geteuid(); - $user = posix_getpwuid($userID); - - $currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename) ); - $currentOwner = posix_getpwuid($currentOwnerID); - - $message = "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is currently owned by '$currentOwner[name]'. "; - - if($user['name'] == $currentOwner['name']) { - $message .= "We recommend that you make the file writeable."; - } else { - - $groups = posix_getgroups(); - $groupList = array(); - foreach($groups as $group) { - $groupInfo = posix_getgrgid($group); - if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name']; - } - if($groupList) { - $message .= " We recommend that you make the file group-writeable and change the group to one of these groups:\n - ". implode("\n - ", $groupList) - . "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename"; - } else { - $message .= " There is no user-group that contains both the web-server user and the owner of this file. Change the ownership of the file, create a new group, or temporarily make the file writeable by everyone during the install process."; - } - } - - } else { - $message .= "The webserver user needs to be able to write to this file:\n$filename"; - } - - return array(EnvironmentCheck::ERROR, $message); - } - - return array(EnvironmentCheck::OK,''); - } -} \ No newline at end of file diff --git a/code/DevCheck.php b/code/DevCheckController.php similarity index 79% rename from code/DevCheck.php rename to code/DevCheckController.php index 8df6086..6c9884b 100644 --- a/code/DevCheck.php +++ b/code/DevCheckController.php @@ -1,6 +1,6 @@ setErrorCode(404); diff --git a/code/EnvironmentCheck.php b/code/EnvironmentCheck.php index 2583048..0b3af58 100644 --- a/code/EnvironmentCheck.php +++ b/code/EnvironmentCheck.php @@ -27,152 +27,4 @@ interface EnvironmentCheck { */ function check(); -} - -/** - * Represents a suite of environment checks. - * Also has a register for assigning environment checks to named instances of EnvironmentCheckSuite - * - * Usage: - * EnvironmentCheckSuite::register('health', 'MyHealthCheck'); - * - * $result = EnvironmentCheckSuite::inst('health')->run(); - */ -class EnvironmentCheckSuite { - protected $checks = array(); - - /** - * Run this test suite - * @return The result code of the worst result. - */ - public function run() { - $worstResult = 0; - - $result = new EnvironmentCheckSuiteResult; - foreach($this->checkInstances() as $check) { - list($checkClass, $checkTitle) = $check; - try { - list($status, $message) = $checkClass->check(); - // If the check fails, register that as an error - } catch(Exception $e) { - $status = EnvironmentCheck::ERROR; - $message = $e->getMessage(); - } - $result->addResult($status, $message, $checkTitle); - } - - return $result; - } - - /** - * Get instances of all the environment checks - */ - protected function checkInstances() { - $output = array(); - foreach($this->checks as $check) { - list($checkClass, $checkTitle) = $check; - if(is_string($checkClass)) { - $checkInst = Object::create_from_string($checkClass); - if($checkInst instanceof EnvironmentCheck) { - $output[] = array($checkInst, $checkTitle); - } else { - throw new InvalidArgumentException("Bad EnvironmentCheck: '$checkClass' - the named class doesn't implement EnvironmentCheck"); - } - } else if($checkClass instanceof EnvironmentCheck) { - $output[] = array($checkClass, $checkTitle); - } else { - throw new InvalidArgumentException("Bad EnvironmentCheck: " . var_export($check, true)); - } - } - return $output; - } - - /** - * Add a check to this suite. - * - */ - public function push($check, $title = null) { - if(!$title) { - $title = is_string($check) ? $check : get_class($check); - } - $this->checks[] = array($check, $title); - } - - ///////////////////////////////////////////////////////////////////////////////////////////// - - protected static $instances = array(); - - /** - * Return a named instance of EnvironmentCheckSuite. - */ - static function inst($name) { - if(!isset(self::$instances[$name])) self::$instances[$name] = new EnvironmentCheckSuite(); - return self::$instances[$name]; - } - - /** - * Register a check against the named check suite. - * - * @param String|Array - */ - static function register($names, $check, $title = null) { - if(!is_array($names)) $names = array($names); - foreach($names as $name) self::inst($name)->push($check, $title); - } -} - -/** - * A single set of results from running an EnvironmentCheckSuite - */ -class EnvironmentCheckSuiteResult extends ViewableData { - protected $details, $worst = 0; - - function __construct() { - parent::__construct(); - $this->details = new DataObjectSet(); - } - - function addResult($status, $message, $checkIdentifier) { - $this->details->push(new ArrayData(array( - 'Check' => $checkIdentifier, - 'Status' => $this->statusText($status), - 'Message' => $message, - ))); - - $this->worst = max($this->worst, $status); - } - - /** - * Returns true if there are no ERRORs, only WARNINGs or OK - */ - function ShouldPass() { - return $this->worst <= EnvironmentCheck::WARNING; - } - - /** - * Returns overall (i.e. worst) status as a string. - */ - function Status() { - return $this->statusText($this->worst); - } - - /** - * Returns detailed status information about each check - */ - function Details() { - return $this->details; - } - - /** - * Return a text version of a status code - */ - protected function statusText($status) { - switch($status) { - case EnvironmentCheck::ERROR: return "ERROR"; - case EnvironmentCheck::WARNING: return "WARNING"; - case EnvironmentCheck::OK: return "OK"; - case 0: return "NO CHECKS"; - default: throw new InvalidArgumentException("Bad environment check status '$status'"); - } - } } \ No newline at end of file diff --git a/code/EnvironmentCheckSuite.php b/code/EnvironmentCheckSuite.php new file mode 100644 index 0000000..6041d3f --- /dev/null +++ b/code/EnvironmentCheckSuite.php @@ -0,0 +1,148 @@ +run(); + */ +class EnvironmentCheckSuite { + protected $checks = array(); + + /** + * Run this test suite + * @return The result code of the worst result. + */ + public function run() { + $worstResult = 0; + + $result = new EnvironmentCheckSuiteResult; + foreach($this->checkInstances() as $check) { + list($checkClass, $checkTitle) = $check; + try { + list($status, $message) = $checkClass->check(); + // If the check fails, register that as an error + } catch(Exception $e) { + $status = EnvironmentCheck::ERROR; + $message = $e->getMessage(); + } + $result->addResult($status, $message, $checkTitle); + } + + return $result; + } + + /** + * Get instances of all the environment checks + */ + protected function checkInstances() { + $output = array(); + foreach($this->checks as $check) { + list($checkClass, $checkTitle) = $check; + if(is_string($checkClass)) { + $checkInst = Object::create_from_string($checkClass); + if($checkInst instanceof EnvironmentCheck) { + $output[] = array($checkInst, $checkTitle); + } else { + throw new InvalidArgumentException("Bad EnvironmentCheck: '$checkClass' - the named class doesn't implement EnvironmentCheck"); + } + } else if($checkClass instanceof EnvironmentCheck) { + $output[] = array($checkClass, $checkTitle); + } else { + throw new InvalidArgumentException("Bad EnvironmentCheck: " . var_export($check, true)); + } + } + return $output; + } + + /** + * Add a check to this suite. + * + */ + public function push($check, $title = null) { + if(!$title) { + $title = is_string($check) ? $check : get_class($check); + } + $this->checks[] = array($check, $title); + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + + protected static $instances = array(); + + /** + * Return a named instance of EnvironmentCheckSuite. + */ + static function inst($name) { + if(!isset(self::$instances[$name])) self::$instances[$name] = new EnvironmentCheckSuite(); + return self::$instances[$name]; + } + + /** + * Register a check against the named check suite. + * + * @param String|Array + */ + static function register($names, $check, $title = null) { + if(!is_array($names)) $names = array($names); + foreach($names as $name) self::inst($name)->push($check, $title); + } +} + +/** + * A single set of results from running an EnvironmentCheckSuite + */ +class EnvironmentCheckSuiteResult extends ViewableData { + protected $details, $worst = 0; + + function __construct() { + parent::__construct(); + $this->details = new DataObjectSet(); + } + + function addResult($status, $message, $checkIdentifier) { + $this->details->push(new ArrayData(array( + 'Check' => $checkIdentifier, + 'Status' => $this->statusText($status), + 'Message' => $message, + ))); + + $this->worst = max($this->worst, $status); + } + + /** + * Returns true if there are no ERRORs, only WARNINGs or OK + */ + function ShouldPass() { + return $this->worst <= EnvironmentCheck::WARNING; + } + + /** + * Returns overall (i.e. worst) status as a string. + */ + function Status() { + return $this->statusText($this->worst); + } + + /** + * Returns detailed status information about each check + */ + function Details() { + return $this->details; + } + + /** + * Return a text version of a status code + */ + protected function statusText($status) { + switch($status) { + case EnvironmentCheck::ERROR: return "ERROR"; + case EnvironmentCheck::WARNING: return "WARNING"; + case EnvironmentCheck::OK: return "OK"; + case 0: return "NO CHECKS"; + default: throw new InvalidArgumentException("Bad environment check status '$status'"); + } + } +} \ No newline at end of file diff --git a/code/checks/DatabaseCheck.php b/code/checks/DatabaseCheck.php new file mode 100644 index 0000000..9696c0f --- /dev/null +++ b/code/checks/DatabaseCheck.php @@ -0,0 +1,29 @@ +checkTable = $checkTable; + } + + function check() { + $count = DB::query("SELECT COUNT(*) FROM \"$this->checkTable\"")->value(); + + if($count > 0) { + return array(EnvironmentCheck::OK, ""); + } else { + return array(EnvironmentCheck::WARNING, "$this->checkTable queried ok but has no records"); + } + } +} \ No newline at end of file diff --git a/code/checks/FileWriteableCheck.php b/code/checks/FileWriteableCheck.php new file mode 100644 index 0000000..3d3ae98 --- /dev/null +++ b/code/checks/FileWriteableCheck.php @@ -0,0 +1,58 @@ +path = $path; + } + + function check() { + if($this->path[0] == '/') $filename = $this->path; + else $filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $this->path); + + if(file_exists($filename)) $isWriteable = is_writeable($filename); + else $isWriteable = is_writeable(dirname($filename)); + + if(!$isWriteable) { + if(function_exists('posix_getgroups')) { + $userID = posix_geteuid(); + $user = posix_getpwuid($userID); + + $currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename) ); + $currentOwner = posix_getpwuid($currentOwnerID); + + $message = "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is currently owned by '$currentOwner[name]'. "; + + if($user['name'] == $currentOwner['name']) { + $message .= "We recommend that you make the file writeable."; + } else { + + $groups = posix_getgroups(); + $groupList = array(); + foreach($groups as $group) { + $groupInfo = posix_getgrgid($group); + if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name']; + } + if($groupList) { + $message .= " We recommend that you make the file group-writeable and change the group to one of these groups:\n - ". implode("\n - ", $groupList) + . "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename"; + } else { + $message .= " There is no user-group that contains both the web-server user and the owner of this file. Change the ownership of the file, create a new group, or temporarily make the file writeable by everyone during the install process."; + } + } + + } else { + $message .= "The webserver user needs to be able to write to this file:\n$filename"; + } + + return array(EnvironmentCheck::ERROR, $message); + } + + return array(EnvironmentCheck::OK,''); + } +} \ No newline at end of file diff --git a/code/checks/HasClassCheck.php b/code/checks/HasClassCheck.php new file mode 100644 index 0000000..db372fa --- /dev/null +++ b/code/checks/HasClassCheck.php @@ -0,0 +1,18 @@ +className = $className; + } + + function check() { + if(class_exists($this->className)) return array(EnvironmentCheck::OK, 'Class ' . $this->className.' exists'); + else return array(EnvironmentCheck::ERROR, 'Class ' . $this->className.' doesn\'t exist'); + } +} \ No newline at end of file diff --git a/code/checks/HasFunctionCheck.php b/code/checks/HasFunctionCheck.php new file mode 100644 index 0000000..2f3d78b --- /dev/null +++ b/code/checks/HasFunctionCheck.php @@ -0,0 +1,18 @@ +functionName = $functionName; + } + + function check() { + if(function_exists($this->functionName)) return array(EnvironmentCheck::OK, $this->functionName.'() exists'); + else return array(EnvironmentCheck::ERROR, $this->functionName.'() doesn\'t exist'); + } +} \ No newline at end of file diff --git a/code/checks/URLCheck.php b/code/checks/URLCheck.php new file mode 100644 index 0000000..6db586b --- /dev/null +++ b/code/checks/URLCheck.php @@ -0,0 +1,42 @@ +url = $url; + $this->testString = $testString; + } + + function check() { + $response = Director::test($this->url); + + if($response->getStatusCode() != 200) { + return array( + EnvironmentCheck::ERROR, + sprintf('Error retrieving "%s" (Code: %d)', $this->url, $response->getStatusCode()) + ); + + } else if($this->testString && (strpos($response->getBody(), $this->testString) === false)) { + return array( + EnvironmentCheck::WARNING, + sprintf('Success retrieving "%s", but string "%s" not found', $this->url, $testString) + ); + + } else { + return array( + EnvironmentCheck::OK, + sprintf('Success retrieving "%s"', $this->url) + ); + } + } +} \ No newline at end of file