From cf14306c2b3f82a81f8d56669a71633b4d4b5fce Mon Sep 17 00:00:00 2001 From: marvin-borner@live.com Date: Mon, 16 Apr 2018 21:09:05 +0200 Subject: Main merge to user management system - files are now at /main/public/ --- .../core/src/Util/BadClassNameException.php | 18 ++ main/app/sprinkles/core/src/Util/Captcha.php | 159 ++++++++++ .../sprinkles/core/src/Util/CheckEnvironment.php | 340 +++++++++++++++++++++ main/app/sprinkles/core/src/Util/ClassMapper.php | 94 ++++++ .../sprinkles/core/src/Util/EnvironmentInfo.php | 68 +++++ .../sprinkles/core/src/Util/ShutdownHandler.php | 167 ++++++++++ main/app/sprinkles/core/src/Util/Util.php | 173 +++++++++++ 7 files changed, 1019 insertions(+) create mode 100755 main/app/sprinkles/core/src/Util/BadClassNameException.php create mode 100755 main/app/sprinkles/core/src/Util/Captcha.php create mode 100755 main/app/sprinkles/core/src/Util/CheckEnvironment.php create mode 100755 main/app/sprinkles/core/src/Util/ClassMapper.php create mode 100755 main/app/sprinkles/core/src/Util/EnvironmentInfo.php create mode 100755 main/app/sprinkles/core/src/Util/ShutdownHandler.php create mode 100755 main/app/sprinkles/core/src/Util/Util.php (limited to 'main/app/sprinkles/core/src/Util') diff --git a/main/app/sprinkles/core/src/Util/BadClassNameException.php b/main/app/sprinkles/core/src/Util/BadClassNameException.php new file mode 100755 index 0000000..09c4ea5 --- /dev/null +++ b/main/app/sprinkles/core/src/Util/BadClassNameException.php @@ -0,0 +1,18 @@ +session = $session; + $this->key = $key; + + if (!$this->session->has($key)) { + $this->session[$key] = array(); + } + } + + /** + * Generates a new captcha for the user registration form. + * + * This generates a random 5-character captcha and stores it in the session with an md5 hash. + * Also, generates the corresponding captcha image. + */ + public function generateRandomCode() + { + $md5_hash = md5(rand(0,99999)); + $this->code = substr($md5_hash, 25, 5); + $enc = md5($this->code); + + // Store the generated captcha value to the session + $this->session[$this->key] = $enc; + + $this->generateImage(); + } + + /** + * Returns the captcha code. + */ + public function getCaptcha() + { + return $this->code; + } + + /** + * Returns the captcha image. + */ + public function getImage() + { + return $this->image; + } + + /** + * Check that the specified code, when hashed, matches the code in the session. + * + * Also, stores the specified code in the session with an md5 hash. + * @param string + * @return bool + */ + public function verifyCode($code) + { + return (md5($code) == $this->session[$this->key]); + } + + /** + * Generate the image for the current captcha. + * + * This generates an image as a binary string. + */ + protected function generateImage() + { + $width = 150; + $height = 30; + + $image = imagecreatetruecolor(150, 30); + + //color pallette + $white = imagecolorallocate($image, 255, 255, 255); + $black = imagecolorallocate($image, 0, 0, 0); + $red = imagecolorallocate($image,255,0,0); + $yellow = imagecolorallocate($image, 255, 255, 0); + $dark_grey = imagecolorallocate($image, 64,64,64); + $blue = imagecolorallocate($image, 0,0,255); + + //create white rectangle + imagefilledrectangle($image,0,0,150,30,$white); + + //add some lines + for($i=0;$i<2;$i++) { + imageline($image,0,rand()%10,10,rand()%30,$dark_grey); + imageline($image,0,rand()%30,150,rand()%30,$red); + imageline($image,0,rand()%30,150,rand()%30,$yellow); + } + + // RandTab color pallette + $randc[0] = imagecolorallocate($image, 0, 0, 0); + $randc[1] = imagecolorallocate($image,255,0,0); + $randc[2] = imagecolorallocate($image, 255, 255, 0); + $randc[3] = imagecolorallocate($image, 64,64,64); + $randc[4] = imagecolorallocate($image, 0,0,255); + + //add some dots + for($i=0;$i<1000;$i++) { + imagesetpixel($image,rand()%200,rand()%50,$randc[rand()%5]); + } + + //calculate center of text + $x = ( 150 - 0 - imagefontwidth( 5 ) * strlen( $this->code ) ) / 2 + 0 + 5; + + //write string twice + imagestring($image,5, $x, 7, $this->code, $black); + imagestring($image,5, $x, 7, $this->code, $black); + //start ob + ob_start(); + imagepng($image); + + //get binary image data + $this->image = ob_get_clean(); + + return $this->image; + } +} diff --git a/main/app/sprinkles/core/src/Util/CheckEnvironment.php b/main/app/sprinkles/core/src/Util/CheckEnvironment.php new file mode 100755 index 0000000..26925d9 --- /dev/null +++ b/main/app/sprinkles/core/src/Util/CheckEnvironment.php @@ -0,0 +1,340 @@ +view = $view; + $this->locator = $locator; + $this->cache = $cache; + } + + /** + * Invoke the CheckEnvironment middleware, performing all pre-flight checks and returning an error page if problems were found. + * + * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request + * @param \Psr\Http\Message\ResponseInterface $response PSR7 response + * @param callable $next Next middleware + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function __invoke($request, $response, $next) + { + $problemsFound = false; + + // If production environment and no cached checks, perform environment checks + if ($this->isProduction() && $this->cache->get('checkEnvironment') != 'pass') { + $problemsFound = $this->checkAll(); + + // Cache if checks passed + if (!$problemsFound) { + $this->cache->forever('checkEnvironment', 'pass'); + } + } elseif (!$this->isProduction()) { + $problemsFound = $this->checkAll(); + } + + if ($problemsFound) { + $results = array_merge($this->resultsFailed, $this->resultsSuccess); + + $response = $this->view->render($response, 'pages/error/config-errors.html.twig', [ + "messages" => $results + ]); + } else { + $response = $next($request, $response); + } + + return $response; + } + + /** + * Run through all pre-flight checks. + */ + public function checkAll() + { + $problemsFound = false; + + if ($this->checkApache()) $problemsFound = true; + + if ($this->checkPhp()) $problemsFound = true; + + if ($this->checkPdo()) $problemsFound = true; + + if ($this->checkGd()) $problemsFound = true; + + if ($this->checkImageFunctions()) $problemsFound = true; + + if ($this->checkPermissions()) $problemsFound = true; + + return $problemsFound; + } + + /** + * For Apache environments, check that required Apache modules are installed. + */ + public function checkApache() + { + $problemsFound = false; + + // Perform some Apache checks. We may also need to do this before any routing takes place. + if (strpos(php_sapi_name(), 'apache') !== false) { + + $require_apache_modules = ['mod_rewrite']; + $apache_modules = apache_get_modules(); + + $apache_status = []; + + foreach ($require_apache_modules as $module) { + if (!in_array($module, $apache_modules)) { + $problemsFound = true; + $this->resultsFailed['apache-' . $module] = [ + "title" => " Missing Apache module $module.", + "message" => "Please make sure that the $module Apache module is installed and enabled. If you use shared hosting, you will need to ask your web host to do this for you.", + "success" => false + ]; + } else { + $this->resultsSuccess['apache-' . $module] = [ + "title" => " Apache module $module is installed and enabled.", + "message" => "Great, we found the $module Apache module!", + "success" => true + ]; + } + } + } + + return $problemsFound; + } + + /** + * Check for GD library (required for Captcha). + */ + public function checkGd() + { + $problemsFound = false; + + if (!(extension_loaded('gd') && function_exists('gd_info'))) { + $problemsFound = true; + $this->resultsFailed['gd'] = [ + "title" => " GD library not installed", + "message" => "We could not confirm that the GD library is installed and enabled. GD is an image processing library that UserFrosting uses to generate captcha codes for user account registration.", + "success" => false + ]; + } else { + $this->resultsSuccess['gd'] = [ + "title" => " GD library installed!", + "message" => "Great, you have GD installed and enabled.", + "success" => true + ]; + } + + return $problemsFound; + } + + /** + * Check that all image* functions used by Captcha exist. + * + * Some versions of GD are missing one or more of these functions, thus why we check for them explicitly. + */ + public function checkImageFunctions() + { + $problemsFound = false; + + $funcs = [ + 'imagepng', + 'imagecreatetruecolor', + 'imagecolorallocate', + 'imagefilledrectangle', + 'imageline', + 'imagesetpixel', + 'imagefontwidth', + 'imagestring' + ]; + + foreach ($funcs as $func) { + if (!function_exists($func)) { + $problemsFound = true; + $this->resultsFailed['function-' . $func] = [ + "title" => " Missing image manipulation function.", + "message" => "It appears that function $func is not available. UserFrosting needs this to render captchas.", + "success" => false + ]; + } else { + $this->resultsSuccess['function-' . $func] = [ + "title" => " Function $func is available!", + "message" => "Sweet!", + "success" => true + ]; + } + } + + return $problemsFound; + } + + /** + * Check that PDO is installed and enabled. + */ + public function checkPdo() + { + $problemsFound = false; + + if (!class_exists('PDO')) { + $problemsFound = true; + $this->resultsFailed['pdo'] = [ + "title" => " PDO is not installed.", + "message" => "I'm sorry, you must have PDO installed and enabled in order for UserFrosting to access the database. If you don't know what PDO is, please see http://php.net/manual/en/book.pdo.php.", + "success" => false + ]; + } else { + $this->resultsSuccess['pdo'] = [ + "title" => " PDO is installed!", + "message" => "You've got PDO installed. Good job!", + "success" => true + ]; + } + + return $problemsFound; + } + + /** + * Check that log, cache, and session directories are writable, and that other directories are set appropriately for the environment. + */ + function checkPermissions() + { + $problemsFound = false; + + $shouldBeWriteable = [ + $this->locator->findResource('log://') => true, + $this->locator->findResource('cache://') => true, + $this->locator->findResource('session://') => true + ]; + + if ($this->isProduction()) { + // Should be write-protected in production! + $shouldBeWriteable = array_merge($shouldBeWriteable, [ + \UserFrosting\SPRINKLES_DIR => false, + \UserFrosting\VENDOR_DIR => false + ]); + } + + // Check for essential files & perms + foreach ($shouldBeWriteable as $file => $assertWriteable) { + $is_dir = false; + if (!file_exists($file)) { + $problemsFound = true; + $this->resultsFailed['file-' . $file] = [ + "title" => " File or directory does not exist.", + "message" => "We could not find the file or directory $file.", + "success" => false + ]; + } else { + $writeable = is_writable($file); + if ($assertWriteable !== $writeable) { + $problemsFound = true; + $this->resultsFailed['file-' . $file] = [ + "title" => " Incorrect permissions for file or directory.", + "message" => "$file is " + . ($writeable ? "writeable" : "not writeable") + . ", but it should " + . ($assertWriteable ? "be writeable" : "not be writeable") + . ". Please modify the OS user or group permissions so that user " + . exec('whoami') . " " + . ($assertWriteable ? "has" : "does not have") . " write permissions for this directory.", + "success" => false + ]; + } else { + $this->resultsSuccess['file-' . $file] = [ + "title" => " File/directory check passed!", + "message" => "$file exists and is correctly set as " + . ($writeable ? "writeable" : "not writeable") + . ".", + "success" => true + ]; + } + } + } + return $problemsFound; + } + + /** + * Check that PHP meets the minimum required version. + */ + public function checkPhp() + { + $problemsFound = false; + + // Check PHP version + if (version_compare(phpversion(), \UserFrosting\PHP_MIN_VERSION, '<')) { + $problemsFound = true; + $this->resultsFailed['phpVersion'] = [ + "title" => " You need to upgrade your PHP installation.", + "message" => "I'm sorry, UserFrosting requires version " . \UserFrosting\PHP_MIN_VERSION . " or greater. Please upgrade your version of PHP, or contact your web hosting service and ask them to upgrade it for you.", + "success" => false + ]; + } else { + $this->resultsSuccess['phpVersion'] = [ + "title" => " PHP version checks out!", + "message" => "You're using PHP " . \UserFrosting\PHP_MIN_VERSION . "or higher. Great!", + "success" => true + ]; + } + + return $problemsFound; + } + + /** + * Determine whether or not we are running in production mode. + * + * @return bool + */ + public function isProduction() + { + return (getenv('UF_MODE') == 'production'); + } +} diff --git a/main/app/sprinkles/core/src/Util/ClassMapper.php b/main/app/sprinkles/core/src/Util/ClassMapper.php new file mode 100755 index 0000000..5fa0881 --- /dev/null +++ b/main/app/sprinkles/core/src/Util/ClassMapper.php @@ -0,0 +1,94 @@ +getClassMapping($identifier); + + $params = array_slice(func_get_args(), 1); + + // We must use reflection in PHP < 5.6. See http://stackoverflow.com/questions/8734522/dynamically-call-class-with-variable-number-of-parameters-in-the-constructor + $reflection = new \ReflectionClass($className); + + return $reflection->newInstanceArgs($params); + } + + /** + * Gets the fully qualified class name for a specified class identifier. + * + * @param string $identifier + * @return string + */ + public function getClassMapping($identifier) + { + if (isset($this->classMappings[$identifier])) { + return $this->classMappings[$identifier]; + } else { + throw new \OutOfBoundsException("There is no class mapped to the identifier '$identifier'."); + } + } + + /** + * Assigns a fully qualified class name to a specified class identifier. + * + * @param string $identifier + * @param string $className + * @return ClassMapper + */ + public function setClassMapping($identifier, $className) + { + // Check that class exists + if (!class_exists($className)) { + throw new BadClassNameException("Unable to find the class '$className'." ); + } + + $this->classMappings[$identifier] = $className; + + return $this; + } + + /** + * Call a static method for a specified class. + * + * @param string $identifier The identifier for the class, e.g. 'user' + * @param string $methodName The method to be invoked. + * @param mixed ...$arg Whatever needs to be passed to the method. + */ + public function staticMethod($identifier, $methodName) + { + $className = $this->getClassMapping($identifier); + + $params = array_slice(func_get_args(), 2); + + return call_user_func_array("$className::$methodName", $params); + } +} diff --git a/main/app/sprinkles/core/src/Util/EnvironmentInfo.php b/main/app/sprinkles/core/src/Util/EnvironmentInfo.php new file mode 100755 index 0000000..aba9837 --- /dev/null +++ b/main/app/sprinkles/core/src/Util/EnvironmentInfo.php @@ -0,0 +1,68 @@ +getPdo(); + $results = []; + + try { + $results['type'] = $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } catch (Exception $e) { + $results['type'] = "Unknown"; + } + + try { + $results['version'] = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION); + } catch (Exception $e) { + $results['version'] = ""; + } + + return $results; + } + + /** + * Test whether a DB connection can be established. + * + * @return bool true if the connection can be established, false otherwise. + */ + public static function canConnectToDatabase() + { + try { + Capsule::connection()->getPdo(); + } catch (\PDOException $e) { + return false; + } + + return true; + } +} diff --git a/main/app/sprinkles/core/src/Util/ShutdownHandler.php b/main/app/sprinkles/core/src/Util/ShutdownHandler.php new file mode 100755 index 0000000..e7a6903 --- /dev/null +++ b/main/app/sprinkles/core/src/Util/ShutdownHandler.php @@ -0,0 +1,167 @@ +ci = $ci; + $this->displayErrorInfo = $displayErrorInfo; + } + + /** + * Register this class with the shutdown handler. + * + * @return void + */ + public function register() + { + register_shutdown_function([$this, 'fatalHandler']); + } + + /** + * Set up the fatal error handler, so that we get a clean error message and alert instead of a WSOD. + */ + public function fatalHandler() + { + $error = error_get_last(); + $fatalErrorTypes = [ + E_ERROR, + E_PARSE, + E_CORE_ERROR, + E_COMPILE_ERROR, + E_RECOVERABLE_ERROR + ]; + + // Handle fatal errors and parse errors + if ($error !== NULL && in_array($error['type'], $fatalErrorTypes)) { + + // Build the appropriate error message (debug or client) + if ($this->displayErrorInfo) { + $errorMessage = $this->buildErrorInfoMessage($error); + } else { + $errorMessage = "Oops, looks like our server might have goofed. If you're an admin, please ensure that php.log_errors is enabled, and then check the PHP error log."; + } + + // For CLI, just print the message and exit. + if (php_sapi_name() === 'cli') { + exit($errorMessage . PHP_EOL); + } + + // For all other environments, print a debug response for the requested data type + echo $this->buildErrorPage($errorMessage); + + // If this is an AJAX request and AJAX debugging is turned off, write message to the alert stream + if ($this->ci->request->isXhr() && !$this->ci->config['site.debug.ajax']) { + if ($this->ci->alerts && is_object($this->ci->alerts)) { + $this->ci->alerts->addMessageTranslated('danger', $errorMessage); + } + } + + header('HTTP/1.1 500 Internal Server Error'); + exit(); + } + } + + /** + * Build the error message string. + * + * @param array $error + * @return string + */ + protected function buildErrorInfoMessage(array $error) + { + $errfile = $error['file']; + $errline = (string) $error['line']; + $errstr = $error['message']; + + $errorTypes = [ + E_ERROR => 'Fatal error', + E_PARSE => 'Parse error', + E_CORE_ERROR => 'PHP core error', + E_COMPILE_ERROR => 'Zend compile error', + E_RECOVERABLE_ERROR => 'Catchable fatal error' + ]; + + return "" . $errorTypes[$error['type']] . ": $errstr in $errfile on line $errline"; + } + + /** + * Build an error response of the appropriate type as determined by the request's Accept header. + * + * @param string $message + * @return string + */ + protected function buildErrorPage($message) + { + $contentType = $this->determineContentType($this->ci->request, $this->ci->config['site.debug.ajax']); + + switch ($contentType) { + case 'application/json': + $error = ['message' => $message]; + return json_encode($error, JSON_PRETTY_PRINT); + + case 'text/html': + return $this->buildHtmlErrorPage($message); + + default: + case 'text/plain': + return $message; + } + } + + /** + * Build an HTML error page from an error string. + * + * @param string $errorMessage + * @return string + */ + protected function buildHtmlErrorPage($message) + { + $title = 'UserFrosting Application Error'; + $html = "

$message

"; + + return sprintf( + "" . + "%s

%s

%s", + $title, + $title, + $html + ); + } +} diff --git a/main/app/sprinkles/core/src/Util/Util.php b/main/app/sprinkles/core/src/Util/Util.php new file mode 100755 index 0000000..ae551cf --- /dev/null +++ b/main/app/sprinkles/core/src/Util/Util.php @@ -0,0 +1,173 @@ +'.str_repeat( '  ', $newLineLevel); + } + + $result .= $char.$post; + } + + return $result; + } + + /** + * Generate a random phrase, consisting of a specified number of adjectives, followed by a noun. + * + * @param int $numAdjectives + * @param int $maxLength + * @param int $maxTries + * @param string $separator + * @return string + */ + static public function randomPhrase($numAdjectives, $maxLength = 9999999, $maxTries = 10, $separator = '-') + { + $adjectives = include('extra://adjectives.php'); + $nouns = include('extra://nouns.php'); + + for ($n = 0; $n < $maxTries; $n++) { + $keys = array_rand($adjectives, $numAdjectives); + $matches = array_only($adjectives, $keys); + + $result = implode($separator, $matches); + $result .= $separator . $nouns[array_rand($nouns)]; + $result = str_slug($result, $separator); + if (strlen($result) < $maxLength) { + return $result; + } + } + + return ''; + } +} -- cgit v1.2.3