From 937100e9bb2a2f5ab035e283e01e6d96e569ee51 Mon Sep 17 00:00:00 2001 From: marvin-borner@live.com Date: Sat, 14 Apr 2018 21:01:44 +0200 Subject: Added login things --- login/app/sprinkles/core/src/Alert/AlertStream.php | 144 +++++ .../sprinkles/core/src/Alert/CacheAlertStream.php | 84 +++ .../core/src/Alert/SessionAlertStream.php | 70 ++ .../core/src/Controller/CoreController.php | 95 +++ .../core/src/Controller/SimpleController.php | 36 ++ login/app/sprinkles/core/src/Core.php | 121 ++++ login/app/sprinkles/core/src/Database/Builder.php | 210 ++++++ .../core/src/Database/DatabaseInvalidException.php | 20 + .../src/Database/Migrations/v400/SessionsTable.php | 48 ++ .../Database/Migrations/v400/ThrottlesTable.php | 52 ++ .../Database/Models/Concerns/HasRelationships.php | 278 ++++++++ .../sprinkles/core/src/Database/Models/Model.php | 140 ++++ .../core/src/Database/Models/Throttle.php | 36 ++ .../Relations/BelongsToManyConstrained.php | 122 ++++ .../Database/Relations/BelongsToManyThrough.php | 232 +++++++ .../src/Database/Relations/BelongsToManyUnique.php | 22 + .../src/Database/Relations/Concerns/Syncable.php | 132 ++++ .../src/Database/Relations/Concerns/Unique.php | 563 ++++++++++++++++ .../src/Database/Relations/HasManySyncable.php | 22 + .../src/Database/Relations/MorphManySyncable.php | 22 + .../src/Database/Relations/MorphToManyUnique.php | 22 + .../core/src/Error/ExceptionHandlerManager.php | 93 +++ .../core/src/Error/Handler/ExceptionHandler.php | 275 ++++++++ .../Error/Handler/ExceptionHandlerInterface.php | 32 + .../src/Error/Handler/HttpExceptionHandler.php | 64 ++ .../src/Error/Handler/NotFoundExceptionHandler.php | 38 ++ .../Error/Handler/PhpMailerExceptionHandler.php | 30 + .../core/src/Error/Renderer/ErrorRenderer.php | 64 ++ .../src/Error/Renderer/ErrorRendererInterface.php | 29 + .../core/src/Error/Renderer/HtmlRenderer.php | 151 +++++ .../core/src/Error/Renderer/JsonRenderer.php | 57 ++ .../core/src/Error/Renderer/PlainTextRenderer.php | 65 ++ .../core/src/Error/Renderer/WhoopsRenderer.php | 712 +++++++++++++++++++++ .../core/src/Error/Renderer/XmlRenderer.php | 48 ++ login/app/sprinkles/core/src/Facades/Debug.php | 28 + .../app/sprinkles/core/src/Facades/Translator.php | 28 + .../src/Http/Concerns/DeterminesContentType.php | 76 +++ .../app/sprinkles/core/src/Log/DatabaseHandler.php | 53 ++ .../app/sprinkles/core/src/Log/MixedFormatter.php | 59 ++ .../app/sprinkles/core/src/Mail/EmailRecipient.php | 136 ++++ login/app/sprinkles/core/src/Mail/MailMessage.php | 186 ++++++ login/app/sprinkles/core/src/Mail/Mailer.php | 204 ++++++ .../sprinkles/core/src/Mail/StaticMailMessage.php | 78 +++ .../sprinkles/core/src/Mail/TwigMailMessage.php | 93 +++ login/app/sprinkles/core/src/Model/UFModel.php | 27 + login/app/sprinkles/core/src/Router.php | 101 +++ .../core/src/ServicesProvider/ServicesProvider.php | 618 ++++++++++++++++++ login/app/sprinkles/core/src/Sprunje/Sprunje.php | 566 ++++++++++++++++ .../sprinkles/core/src/Throttle/ThrottleRule.php | 140 ++++ .../app/sprinkles/core/src/Throttle/Throttler.php | 178 ++++++ .../core/src/Throttle/ThrottlerException.php | 18 + login/app/sprinkles/core/src/Twig/CacheHelper.php | 58 ++ .../app/sprinkles/core/src/Twig/CoreExtension.php | 124 ++++ .../core/src/Util/BadClassNameException.php | 18 + login/app/sprinkles/core/src/Util/Captcha.php | 159 +++++ .../sprinkles/core/src/Util/CheckEnvironment.php | 340 ++++++++++ login/app/sprinkles/core/src/Util/ClassMapper.php | 94 +++ .../sprinkles/core/src/Util/EnvironmentInfo.php | 68 ++ .../sprinkles/core/src/Util/ShutdownHandler.php | 167 +++++ login/app/sprinkles/core/src/Util/Util.php | 173 +++++ 60 files changed, 7919 insertions(+) create mode 100755 login/app/sprinkles/core/src/Alert/AlertStream.php create mode 100755 login/app/sprinkles/core/src/Alert/CacheAlertStream.php create mode 100755 login/app/sprinkles/core/src/Alert/SessionAlertStream.php create mode 100755 login/app/sprinkles/core/src/Controller/CoreController.php create mode 100755 login/app/sprinkles/core/src/Controller/SimpleController.php create mode 100755 login/app/sprinkles/core/src/Core.php create mode 100755 login/app/sprinkles/core/src/Database/Builder.php create mode 100755 login/app/sprinkles/core/src/Database/DatabaseInvalidException.php create mode 100755 login/app/sprinkles/core/src/Database/Migrations/v400/SessionsTable.php create mode 100755 login/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php create mode 100755 login/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php create mode 100755 login/app/sprinkles/core/src/Database/Models/Model.php create mode 100755 login/app/sprinkles/core/src/Database/Models/Throttle.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/BelongsToManyConstrained.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/Concerns/Syncable.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/HasManySyncable.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/MorphManySyncable.php create mode 100755 login/app/sprinkles/core/src/Database/Relations/MorphToManyUnique.php create mode 100755 login/app/sprinkles/core/src/Error/ExceptionHandlerManager.php create mode 100755 login/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php create mode 100755 login/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php create mode 100755 login/app/sprinkles/core/src/Error/Handler/HttpExceptionHandler.php create mode 100755 login/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php create mode 100755 login/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/ErrorRenderer.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/HtmlRenderer.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/JsonRenderer.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/PlainTextRenderer.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/WhoopsRenderer.php create mode 100755 login/app/sprinkles/core/src/Error/Renderer/XmlRenderer.php create mode 100755 login/app/sprinkles/core/src/Facades/Debug.php create mode 100755 login/app/sprinkles/core/src/Facades/Translator.php create mode 100755 login/app/sprinkles/core/src/Http/Concerns/DeterminesContentType.php create mode 100755 login/app/sprinkles/core/src/Log/DatabaseHandler.php create mode 100755 login/app/sprinkles/core/src/Log/MixedFormatter.php create mode 100755 login/app/sprinkles/core/src/Mail/EmailRecipient.php create mode 100755 login/app/sprinkles/core/src/Mail/MailMessage.php create mode 100755 login/app/sprinkles/core/src/Mail/Mailer.php create mode 100755 login/app/sprinkles/core/src/Mail/StaticMailMessage.php create mode 100755 login/app/sprinkles/core/src/Mail/TwigMailMessage.php create mode 100755 login/app/sprinkles/core/src/Model/UFModel.php create mode 100755 login/app/sprinkles/core/src/Router.php create mode 100755 login/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php create mode 100755 login/app/sprinkles/core/src/Sprunje/Sprunje.php create mode 100755 login/app/sprinkles/core/src/Throttle/ThrottleRule.php create mode 100755 login/app/sprinkles/core/src/Throttle/Throttler.php create mode 100755 login/app/sprinkles/core/src/Throttle/ThrottlerException.php create mode 100755 login/app/sprinkles/core/src/Twig/CacheHelper.php create mode 100755 login/app/sprinkles/core/src/Twig/CoreExtension.php create mode 100755 login/app/sprinkles/core/src/Util/BadClassNameException.php create mode 100755 login/app/sprinkles/core/src/Util/Captcha.php create mode 100755 login/app/sprinkles/core/src/Util/CheckEnvironment.php create mode 100755 login/app/sprinkles/core/src/Util/ClassMapper.php create mode 100755 login/app/sprinkles/core/src/Util/EnvironmentInfo.php create mode 100755 login/app/sprinkles/core/src/Util/ShutdownHandler.php create mode 100755 login/app/sprinkles/core/src/Util/Util.php (limited to 'login/app/sprinkles/core/src') diff --git a/login/app/sprinkles/core/src/Alert/AlertStream.php b/login/app/sprinkles/core/src/Alert/AlertStream.php new file mode 100755 index 0000000..3946cbf --- /dev/null +++ b/login/app/sprinkles/core/src/Alert/AlertStream.php @@ -0,0 +1,144 @@ +messagesKey = $messagesKey; + + $this->setTranslator($translator); + } + + /** + * Set the translator to be used for all message streams. Must be done before `addMessageTranslated` can be used. + * + * @param UserFrosting\I18n\MessageTranslator $translator A MessageTranslator to be used to translate messages when added via `addMessageTranslated`. + */ + public function setTranslator($translator) + { + $this->messageTranslator = $translator; + return $this; + } + + /** + * Adds a raw text message to the cache message stream. + * + * @param string $type The type of message, indicating how it will be styled when outputted. Should be set to "success", "danger", "warning", or "info". + * @param string $message The message to be added to the message stream. + * @return MessageStream this MessageStream object. + */ + public function addMessage($type, $message) + { + $messages = $this->messages(); + $messages[] = array( + "type" => $type, + "message" => $message + ); + $this->saveMessages($messages); + return $this; + } + + /** + * Adds a text message to the cache message stream, translated into the currently selected language. + * + * @param string $type The type of message, indicating how it will be styled when outputted. Should be set to "success", "danger", "warning", or "info". + * @param string $messageId The message id for the message to be added to the message stream. + * @param array[string] $placeholders An optional hash of placeholder names => placeholder values to substitute into the translated message. + * @return MessageStream this MessageStream object. + */ + public function addMessageTranslated($type, $messageId, $placeholders = array()) + { + if (!$this->messageTranslator){ + throw new \RuntimeException("No translator has been set! Please call MessageStream::setTranslator first."); + } + + $message = $this->messageTranslator->translate($messageId, $placeholders); + return $this->addMessage($type, $message); + } + + /** + * Get the messages and then clear the message stream. + * This function does the same thing as `messages()`, except that it also clears all messages afterwards. + * This is useful, because typically we don't want to view the same messages more than once. + * + * @return array An array of messages, each of which is itself an array containing "type" and "message" fields. + */ + public function getAndClearMessages() + { + $messages = $this->messages(); + $this->resetMessageStream(); + return $messages; + } + + /** + * Add error messages from a ServerSideValidator object to the message stream. + * + * @param ServerSideValidator $validator + */ + public function addValidationErrors(ServerSideValidator $validator) + { + foreach ($validator->errors() as $idx => $field) { + foreach($field as $eidx => $error) { + $this->addMessage("danger", $error); + } + } + } + + /** + * Return the translator for this message stream. + * + * @return MessageTranslator The translator for this message stream. + */ + public function translator() + { + return $this->messageTranslator; + } + + /** + * Get the messages from this message stream. + * + * @return array An array of messages, each of which is itself an array containing "type" and "message" fields. + */ + abstract public function messages(); + + /** + * Clear all messages from this message stream. + */ + abstract public function resetMessageStream(); + + /** + * Save messages to the stream + */ + abstract protected function saveMessages($message); +} diff --git a/login/app/sprinkles/core/src/Alert/CacheAlertStream.php b/login/app/sprinkles/core/src/Alert/CacheAlertStream.php new file mode 100755 index 0000000..1fd5131 --- /dev/null +++ b/login/app/sprinkles/core/src/Alert/CacheAlertStream.php @@ -0,0 +1,84 @@ +cache = $cache; + $this->config = $config; + parent::__construct($messagesKey, $translator); + } + + /** + * Get the messages from this message stream. + * + * @return array An array of messages, each of which is itself an array containing 'type' and 'message' fields. + */ + public function messages() + { + if ($this->cache->tags('_s'.session_id())->has($this->messagesKey)) { + return $this->cache->tags('_s'.session_id())->get($this->messagesKey) ?: []; + } else { + return []; + } + } + + /** + * Clear all messages from this message stream. + * + * @return void + */ + public function resetMessageStream() + { + $this->cache->tags('_s'.session_id())->forget($this->messagesKey); + } + + /** + * Save messages to the stream + * + * @param string $messages The message + * @return void + */ + protected function saveMessages($messages) + { + $this->cache->tags('_s'.session_id())->forever($this->messagesKey, $messages); + } +} diff --git a/login/app/sprinkles/core/src/Alert/SessionAlertStream.php b/login/app/sprinkles/core/src/Alert/SessionAlertStream.php new file mode 100755 index 0000000..8b4604b --- /dev/null +++ b/login/app/sprinkles/core/src/Alert/SessionAlertStream.php @@ -0,0 +1,70 @@ +session = $session; + parent::__construct($messagesKey, $translator); + } + + /** + * Get the messages from this message stream. + * + * @return array An array of messages, each of which is itself an array containing "type" and "message" fields. + */ + public function messages() + { + return $this->session[$this->messagesKey] ?: []; + } + + /** + * Clear all messages from this message stream. + * + * @return void + */ + public function resetMessageStream() + { + $this->session[$this->messagesKey] = []; + } + + /** + * Save messages to the stream + * + * @param string $messages The message + * @return void + */ + protected function saveMessages($messages) + { + $this->session[$this->messagesKey] = $messages; + } +} diff --git a/login/app/sprinkles/core/src/Controller/CoreController.php b/login/app/sprinkles/core/src/Controller/CoreController.php new file mode 100755 index 0000000..0dd8165 --- /dev/null +++ b/login/app/sprinkles/core/src/Controller/CoreController.php @@ -0,0 +1,95 @@ +ci->view->render($response, 'pages/index.html.twig'); + } + + /** + * Renders a sample "about" page for UserFrosting. + * + * Request type: GET + */ + public function pageAbout($request, $response, $args) + { + return $this->ci->view->render($response, 'pages/about.html.twig'); + } + + /** + * Renders terms of service page. + * + * Request type: GET + */ + public function pageLegal($request, $response, $args) + { + return $this->ci->view->render($response, 'pages/legal.html.twig'); + } + + /** + * Renders privacy page. + * + * Request type: GET + */ + public function pagePrivacy($request, $response, $args) + { + return $this->ci->view->render($response, 'pages/privacy.html.twig'); + } + + /** + * Render the alert stream as a JSON object. + * + * The alert stream contains messages which have been generated by calls to `MessageStream::addMessage` and `MessageStream::addMessageTranslated`. + * Request type: GET + */ + public function jsonAlerts($request, $response, $args) + { + return $response->withJson($this->ci->alerts->getAndClearMessages()); + } + + /** + * Handle all requests for raw assets. + * Request type: GET + */ + public function getAsset($request, $response, $args) + { + // By starting this service, we ensure that the timezone gets set. + $config = $this->ci->config; + + $assetLoader = $this->ci->assetLoader; + + if (!$assetLoader->loadAsset($args['url'])) { + throw new NotFoundException($request, $response); + } + + return $response + ->withHeader('Content-Type', $assetLoader->getType()) + ->withHeader('Content-Length', $assetLoader->getLength()) + ->write($assetLoader->getContent()); + } +} diff --git a/login/app/sprinkles/core/src/Controller/SimpleController.php b/login/app/sprinkles/core/src/Controller/SimpleController.php new file mode 100755 index 0000000..b0fc152 --- /dev/null +++ b/login/app/sprinkles/core/src/Controller/SimpleController.php @@ -0,0 +1,36 @@ +ci = $ci; + } +} diff --git a/login/app/sprinkles/core/src/Core.php b/login/app/sprinkles/core/src/Core.php new file mode 100755 index 0000000..d7e1dcb --- /dev/null +++ b/login/app/sprinkles/core/src/Core.php @@ -0,0 +1,121 @@ + ['onSprinklesInitialized', 0], + 'onSprinklesRegisterServices' => ['onSprinklesRegisterServices', 0], + 'onAddGlobalMiddleware' => ['onAddGlobalMiddleware', 0] + ]; + } + + /** + * Set static references to DI container in necessary classes. + */ + public function onSprinklesInitialized() + { + // Set container for data model + Model::$ci = $this->ci; + + // Set container for environment info class + EnvironmentInfo::$ci = $this->ci; + } + + /** + * Get shutdownHandler set up. This needs to be constructed explicitly because it's invoked natively by PHP. + */ + public function onSprinklesRegisterServices() + { + // Set up any global PHP settings from the config service. + $config = $this->ci->config; + + // Display PHP fatal errors natively. + if (isset($config['php.display_errors_native'])) { + ini_set('display_errors', $config['php.display_errors_native']); + } + + // Log PHP fatal errors + if (isset($config['php.log_errors'])) { + ini_set('log_errors', $config['php.log_errors']); + } + + // Configure error-reporting level + if (isset($config['php.error_reporting'])) { + error_reporting($config['php.error_reporting']); + } + + // Configure time zone + if (isset($config['php.timezone'])) { + date_default_timezone_set($config['php.timezone']); + } + + // Determine if error display is enabled in the shutdown handler. + $displayErrors = false; + if (in_array(strtolower($config['php.display_errors']), [ + '1', + 'on', + 'true', + 'yes' + ])) { + $displayErrors = true; + } + + $sh = new ShutdownHandler($this->ci, $displayErrors); + $sh->register(); + } + + /** + * Add CSRF middleware. + */ + public function onAddGlobalMiddleware(Event $event) + { + $request = $this->ci->request; + $path = $request->getUri()->getPath(); + $method = $request->getMethod(); + + // Normalize path to always have a leading slash + $path = '/' . ltrim($path, '/'); + // Normalize method to uppercase + $method = strtoupper($method); + + $csrfBlacklist = $this->ci->config['csrf.blacklist']; + $isBlacklisted = false; + + // Go through the blacklist and determine if the path and method match any of the blacklist entries. + foreach ($csrfBlacklist as $pattern => $methods) { + $methods = array_map('strtoupper', (array) $methods); + if (in_array($method, $methods) && $pattern != '' && preg_match('~' . $pattern . '~', $path)) { + $isBlacklisted = true; + break; + } + } + + if (!$path || !$isBlacklisted) { + $app = $event->getApp(); + $app->add($this->ci->csrf); + } + } +} diff --git a/login/app/sprinkles/core/src/Database/Builder.php b/login/app/sprinkles/core/src/Database/Builder.php new file mode 100755 index 0000000..8e27b7c --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Builder.php @@ -0,0 +1,210 @@ +where($field, 'LIKE', "$value%"); + } + + /** + * Perform an "ends with" pattern match on a specified column in a query. + * + * @param $query + * @param $field string The column to match + * @param $value string The value to match + */ + public function endsWith($field, $value) + { + return $this->where($field, 'LIKE', "%$value"); + } + + /** + * Add columns to be excluded from the query. + * + * @param $value array|string The column(s) to exclude + * @return $this + */ + public function exclude($column) + { + $column = is_array($column) ? $column : func_get_args(); + + $this->excludedColumns = array_merge((array) $this->excludedColumns, $column); + + return $this; + } + + /** + * Perform a pattern match on a specified column in a query. + * @param $query + * @param $field string The column to match + * @param $value string The value to match + */ + public function like($field, $value) + { + return $this->where($field, 'LIKE', "%$value%"); + } + + /** + * Perform a pattern match on a specified column in a query. + * @param $query + * @param $field string The column to match + * @param $value string The value to match + */ + public function orLike($field, $value) + { + return $this->orWhere($field, 'LIKE', "%$value%"); + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Support\Collection + */ + public function get($columns = ['*']) + { + $original = $this->columns; + + if (is_null($original)) { + $this->columns = $columns; + } + + // Exclude any explicitly excluded columns + if (!is_null($this->excludedColumns)) { + $this->removeExcludedSelectColumns(); + } + + $results = $this->processor->processSelect($this, $this->runSelect()); + + $this->columns = $original; + + return collect($results); + } + + /** + * Remove excluded columns from the select column list. + */ + protected function removeExcludedSelectColumns() + { + // Convert current column list and excluded column list to fully-qualified list + $this->columns = $this->convertColumnsToFullyQualified($this->columns); + $excludedColumns = $this->convertColumnsToFullyQualified($this->excludedColumns); + + // Remove any explicitly referenced excludable columns + $this->columns = array_diff($this->columns, $excludedColumns); + + // Replace any remaining wildcard columns (*, table.*, etc) with a list + // of fully-qualified column names + $this->columns = $this->replaceWildcardColumns($this->columns); + + $this->columns = array_diff($this->columns, $excludedColumns); + } + + /** + * Find any wildcard columns ('*'), remove it from the column list and replace with an explicit list of columns. + * + * @param array $columns + * @return array + */ + protected function replaceWildcardColumns(array $columns) + { + $wildcardTables = $this->findWildcardTables($columns); + + foreach ($wildcardTables as $wildColumn => $table) { + $schemaColumns = $this->getQualifiedColumnNames($table); + + // Remove the `*` or `.*` column and replace with the individual schema columns + $columns = array_diff($columns, [$wildColumn]); + $columns = array_merge($columns, $schemaColumns); + } + + return $columns; + } + + /** + * Return a list of wildcard columns from the list of columns, mapping columns to their corresponding tables. + * + * @param array $columns + * @return array + */ + protected function findWildcardTables(array $columns) + { + $tables = []; + + foreach ($columns as $column) { + if ($column == '*') { + $tables[$column] = $this->from; + continue; + } + + if (substr($column, -1) == '*') { + $tableName = explode('.', $column)[0]; + if ($tableName) { + $tables[$column] = $tableName; + } + } + } + + return $tables; + } + + /** + * Gets the fully qualified column names for a specified table. + * + * @param string $table + * @return array + */ + protected function getQualifiedColumnNames($table = null) + { + $schema = $this->getConnection()->getSchemaBuilder(); + + return $this->convertColumnsToFullyQualified($schema->getColumnListing($table), $table); + } + + /** + * Fully qualify any unqualified columns in a list with this builder's table name. + * + * @param array $columns + * @return array + */ + protected function convertColumnsToFullyQualified($columns, $table = null) + { + if (is_null($table)) { + $table = $this->from; + } + + array_walk($columns, function (&$item, $key) use ($table) { + if (strpos($item, '.') === false) { + $item = "$table.$item"; + } + }); + + return $columns; + } +} diff --git a/login/app/sprinkles/core/src/Database/DatabaseInvalidException.php b/login/app/sprinkles/core/src/Database/DatabaseInvalidException.php new file mode 100755 index 0000000..08f8a31 --- /dev/null +++ b/login/app/sprinkles/core/src/Database/DatabaseInvalidException.php @@ -0,0 +1,20 @@ +schema->hasTable('sessions')) { + $this->schema->create('sessions', function (Blueprint $table) { + $table->string('id')->unique(); + $table->integer('user_id')->nullable(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->text('payload'); + $table->integer('last_activity'); + }); + } + } + + /** + * {@inheritDoc} + */ + public function down() + { + $this->schema->drop('sessions'); + } +} diff --git a/login/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php b/login/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php new file mode 100755 index 0000000..1c742f7 --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php @@ -0,0 +1,52 @@ +schema->hasTable('throttles')) { + $this->schema->create('throttles', function (Blueprint $table) { + $table->increments('id'); + $table->string('type'); + $table->string('ip')->nullable(); + $table->text('request_data')->nullable(); + $table->timestamps(); + + $table->engine = 'InnoDB'; + $table->collation = 'utf8_unicode_ci'; + $table->charset = 'utf8'; + $table->index('type'); + $table->index('ip'); + }); + } + } + + /** + * {@inheritDoc} + */ + public function down() + { + $this->schema->drop('throttles'); + } +} diff --git a/login/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php b/login/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php new file mode 100755 index 0000000..4fe9a30 --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php @@ -0,0 +1,278 @@ +newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $localKey = $localKey ?: $this->getKeyName(); + + return new HasManySyncable( + $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey + ); + } + + /** + * Overrides the default Eloquent morphMany relationship to return a MorphManySyncable. + * + * {@inheritDoc} + * @return \UserFrosting\Sprinkle\Core\Database\Relations\MorphManySyncable + */ + public function morphMany($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = $this->newRelatedInstance($related); + + // Here we will gather up the morph type and ID for the relationship so that we + // can properly query the intermediate table of a relation. Finally, we will + // get the table and create the relationship instances for the developers. + list($type, $id) = $this->getMorphs($name, $type, $id); + $table = $instance->getTable(); + $localKey = $localKey ?: $this->getKeyName(); + + return new MorphManySyncable($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Define a many-to-many 'through' relationship. + * This is basically hasManyThrough for many-to-many relationships. + * + * @param string $related + * @param string $through + * @param string $firstJoiningTable + * @param string $firstForeignKey + * @param string $firstRelatedKey + * @param string $secondJoiningTable + * @param string $secondForeignKey + * @param string $secondRelatedKey + * @param string $throughRelation + * @param string $relation + * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough + */ + public function belongsToManyThrough( + $related, + $through, + $firstJoiningTable = null, + $firstForeignKey = null, + $firstRelatedKey = null, + $secondJoiningTable = null, + $secondForeignKey = null, + $secondRelatedKey = null, + $throughRelation = null, + $relation = null + ) + { + // If no relationship name was passed, we will pull backtraces to get the + // name of the calling function. We will use that function name as the + // title of this relation since that is a great convention to apply. + if (is_null($relation)) { + $relation = $this->guessBelongsToManyRelation(); + } + + // Create models for through and related + $through = new $through; + $related = $this->newRelatedInstance($related); + + if (is_null($throughRelation)) { + $throughRelation = $through->getTable(); + } + + // If no table names were provided, we can guess it by concatenating the parent + // and through table names. The two model names are transformed to snake case + // from their default CamelCase also. + if (is_null($firstJoiningTable)) { + $firstJoiningTable = $this->joiningTable($through); + } + + if (is_null($secondJoiningTable)) { + $secondJoiningTable = $through->joiningTable($related); + } + + $firstForeignKey = $firstForeignKey ?: $this->getForeignKey(); + $firstRelatedKey = $firstRelatedKey ?: $through->getForeignKey(); + $secondForeignKey = $secondForeignKey ?: $through->getForeignKey(); + $secondRelatedKey = $secondRelatedKey ?: $related->getForeignKey(); + + // This relationship maps the top model (this) to the through model. + $intermediateRelationship = $this->belongsToMany($through, $firstJoiningTable, $firstForeignKey, $firstRelatedKey, $throughRelation) + ->withPivot($firstForeignKey); + + // Now we set up the relationship with the related model. + $query = new BelongsToManyThrough( + $related->newQuery(), $this, $intermediateRelationship, $secondJoiningTable, $secondForeignKey, $secondRelatedKey, $relation + ); + + return $query; + } + + /** + * Define a unique many-to-many relationship. Similar to a regular many-to-many relationship, but removes duplicate child objects. + * Can also be used to implement ternary relationships. + * + * {@inheritDoc} + * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyUnique + */ + public function belongsToManyUnique($related, $table = null, $foreignKey = null, $relatedKey = null, $relation = null) + { + // If no relationship name was passed, we will pull backtraces to get the + // name of the calling function. We will use that function name as the + // title of this relation since that is a great convention to apply. + if (is_null($relation)) { + $relation = $this->guessBelongsToManyRelation(); + } + + // First, we'll need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we'll make the query + // instances as well as the relationship instances we need for this. + $instance = $this->newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $relatedKey = $relatedKey ?: $instance->getForeignKey(); + + // If no table name was provided, we can guess it by concatenating the two + // models using underscores in alphabetical order. The two model names + // are transformed to snake case from their default CamelCase also. + if (is_null($table)) { + $table = $this->joiningTable($related); + } + + return new BelongsToManyUnique( + $instance->newQuery(), $this, $table, $foreignKey, $relatedKey, $relation + ); + } + + /** + * Define a unique morphs-to-many relationship. Similar to a regular morphs-to-many relationship, but removes duplicate child objects. + * + * {@inheritDoc} + * @return \UserFrosting\Sprinkle\Core\Database\Relations\MorphToManyUnique + */ + public function morphToManyUnique($related, $name, $table = null, $foreignKey = null, $otherKey = null, $inverse = false) + { + $caller = $this->getBelongsToManyCaller(); + + // First, we will need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we will make the query + // instances, as well as the relationship instances we need for these. + $foreignKey = $foreignKey ?: $name.'_id'; + + $instance = new $related; + + $otherKey = $otherKey ?: $instance->getForeignKey(); + + // Now we're ready to create a new query builder for this related model and + // the relationship instances for this relation. This relations will set + // appropriate query constraints then entirely manages the hydrations. + $query = $instance->newQuery(); + + $table = $table ?: Str::plural($name); + + return new MorphToManyUnique( + $query, $this, $name, $table, $foreignKey, + $otherKey, $caller, $inverse + ); + } + + /** + * Define a constrained many-to-many relationship. + * This is similar to a regular many-to-many, but constrains the child results to match an additional constraint key in the parent object. + * This has been superseded by the belongsToManyUnique relationship's `withTernary` method since 4.1.7. + * + * @deprecated since 4.1.6 + * @param string $related + * @param string $constraintKey + * @param string $table + * @param string $foreignKey + * @param string $relatedKey + * @param string $relation + * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyConstrained + */ + public function belongsToManyConstrained($related, $constraintKey, $table = null, $foreignKey = null, $relatedKey = null, $relation = null) + { + // If no relationship name was passed, we will pull backtraces to get the + // name of the calling function. We will use that function name as the + // title of this relation since that is a great convention to apply. + if (is_null($relation)) { + $relation = $this->guessBelongsToManyRelation(); + } + + // First, we'll need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we'll make the query + // instances as well as the relationship instances we need for this. + $instance = $this->newRelatedInstance($related); + + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $relatedKey = $relatedKey ?: $instance->getForeignKey(); + + // If no table name was provided, we can guess it by concatenating the two + // models using underscores in alphabetical order. The two model names + // are transformed to snake case from their default CamelCase also. + if (is_null($table)) { + $table = $this->joiningTable($related); + } + + return new BelongsToManyConstrained( + $instance->newQuery(), $this, $constraintKey, $table, $foreignKey, $relatedKey, $relation + ); + } + + /** + * Get the relationship name of the belongs to many. + * + * @return string + */ + protected function getBelongsToManyCaller() + { + $self = __FUNCTION__; + + $caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($key, $trace) use ($self) { + $caller = $trace['function']; + return ! in_array($caller, HasRelationships::$manyMethodsExtended) && $caller != $self; + }); + + return ! is_null($caller) ? $caller['function'] : null; + } +} diff --git a/login/app/sprinkles/core/src/Database/Models/Model.php b/login/app/sprinkles/core/src/Database/Models/Model.php new file mode 100755 index 0000000..1c18c2c --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Models/Model.php @@ -0,0 +1,140 @@ +attributes); + } + + /** + * Determines whether a model exists by checking a unique column, including checking soft-deleted records + * + * @param mixed $value + * @param string $identifier + * @param bool $checkDeleted set to true to include soft-deleted records + * @return \UserFrosting\Sprinkle\Core\Database\Models\Model|null + */ + public static function findUnique($value, $identifier, $checkDeleted = true) + { + $query = static::where($identifier, $value); + + if ($checkDeleted) { + $query = $query->withTrashed(); + } + + return $query->first(); + } + + /** + * Determine if an relation exists on the model - even if it is null. + * + * @param string $key + * @return bool + */ + public function relationExists($key) + { + return array_key_exists($key, $this->relations); + } + + /** + * Store the object in the DB, creating a new row if one doesn't already exist. + * + * Calls save(), then returns the id of the new record in the database. + * @return int the id of this object. + */ + public function store() + { + $this->save(); + + // Store function should always return the id of the object + return $this->id; + } + + /** + * Overrides Laravel's base Model to return our custom query builder object. + * + * @return \UserFrosting\Sprinkles\Core\Database\Builder + */ + protected function newBaseQueryBuilder() + { + /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ + $classMapper = static::$ci->classMapper; + + $connection = $this->getConnection(); + + return $classMapper->createInstance( + 'query_builder', + $connection, + $connection->getQueryGrammar(), + $connection->getPostProcessor() + ); + } + + /** + * Get the properties of this object as an associative array. Alias for toArray(). + * + * @deprecated since 4.1.8 There is no point in having this alias. + * @return array + */ + public function export() + { + return $this->toArray(); + } + + /** + * For raw array fetching. Must be static, otherwise PHP gets confused about where to find $table. + * + * @deprecated since 4.1.8 setFetchMode is no longer available as of Laravel 5.4. + * @link https://github.com/laravel/framework/issues/17728 + */ + public static function queryBuilder() + { + // Set query builder to fetch result sets as associative arrays (instead of creating stdClass objects) + DB::connection()->setFetchMode(\PDO::FETCH_ASSOC); + return DB::table(static::$table); + } +} diff --git a/login/app/sprinkles/core/src/Database/Models/Throttle.php b/login/app/sprinkles/core/src/Database/Models/Throttle.php new file mode 100755 index 0000000..d13a7c1 --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Models/Throttle.php @@ -0,0 +1,36 @@ +constraintKey = $constraintKey; + parent::__construct($query, $parent, $table, $foreignKey, $relatedKey, $relationName); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + // To make the query more efficient, we only bother querying related models if their pivot key value + // matches the pivot key value of one of the parent models. + $pivotKeys = $this->getPivotKeys($models, $this->constraintKey); + $this->query->whereIn($this->getQualifiedForeignKeyName(), $this->getKeys($models)) + ->whereIn($this->constraintKey, $pivotKeys); + } + + /** + * Gets a list of unique pivot key values from an array of models. + */ + protected function getPivotKeys(array $models, $pivotKey) + { + $pivotKeys = []; + foreach ($models as $model) { + $pivotKeys[] = $model->getRelation('pivot')->{$pivotKey}; + } + return array_unique($pivotKeys); + } + + /** + * Match the eagerly loaded results to their parents, constraining the results by matching the values of $constraintKey + * in the parent object to the child objects. + * + * @link Called in https://github.com/laravel/framework/blob/2f4135d8db5ded851d1f4f611124c53b768a3c08/src/Illuminate/Database/Eloquent/Builder.php + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have an array dictionary of child objects we can easily match the + // children back to their parent using the dictionary and the keys on the + // the parent models. Then we will return the hydrated models back out. + foreach ($models as $model) { + $pivotValue = $model->getRelation('pivot')->{$this->constraintKey}; + if (isset($dictionary[$key = $model->getKey()])) { + // Only match children if their pivot key value matches that of the parent model + $items = $this->findMatchingPivots($dictionary[$key], $pivotValue); + $model->setRelation( + $relation, $this->related->newCollection($items) + ); + } + } + + return $models; + } + + /** + * Filter an array of models, only taking models whose $constraintKey value matches $pivotValue. + * + * @param mixed $pivotValue + * @return array + */ + protected function findMatchingPivots($items, $pivotValue) + { + $result = []; + foreach ($items as $item) { + if ($item->getRelation('pivot')->{$this->constraintKey} == $pivotValue) { + $result[] = $item; + } + } + return $result; + } +} diff --git a/login/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php b/login/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php new file mode 100755 index 0000000..33be507 --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php @@ -0,0 +1,232 @@ +intermediateRelation = $intermediateRelation; + + parent::__construct($query, $parent, $table, $foreignKey, $relatedKey, $relationName); + } + + /** + * Use the intermediate relationship to determine the "parent" pivot key name + * + * This is a crazy roundabout way to get the name of the intermediate relation's foreign key. + * It would be better if BelongsToMany had a simple accessor for its foreign key. + * @return string + */ + public function getParentKeyName() + { + return $this->intermediateRelation->newExistingPivot()->getForeignKey(); + } + + /** + * Get the key for comparing against the parent key in "has" query. + * + * @see \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return string + */ + public function getExistenceCompareKey() + { + return $this->intermediateRelation->getQualifiedForeignKeyName(); + } + + /** + * Add a "via" query to load the intermediate models through which the child models are related. + * + * @param string $viaRelationName + * @param callable $viaCallback + * @return $this + */ + public function withVia($viaRelationName = null, $viaCallback = null) + { + $this->tertiaryRelated = $this->intermediateRelation->getRelated(); + + // Set tertiary key and related model + $this->tertiaryKey = $this->foreignKey; + + $this->tertiaryRelationName = is_null($viaRelationName) ? $this->intermediateRelation->getRelationName() . '_via' : $viaRelationName; + + $this->tertiaryCallback = is_null($viaCallback) + ? function () { + // + } + : $viaCallback; + + return $this; + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + // Constraint to only load models where the intermediate relation's foreign key matches the parent model + $intermediateForeignKeyName = $this->intermediateRelation->getQualifiedForeignKeyName(); + + return $this->query->whereIn($intermediateForeignKeyName, $this->getKeys($models)); + } + + /** + * Set the where clause for the relation query. + * + * @return $this + */ + protected function addWhereConstraints() + { + $parentKeyName = $this->getParentKeyName(); + + $this->query->where( + $parentKeyName, '=', $this->parent->getKey() + ); + + return $this; + } + + /** + * Match the eagerly loaded results to their parents + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + // Build dictionary of parent (e.g. user) to related (e.g. permission) models + list($dictionary, $nestedViaDictionary) = $this->buildDictionary($results, $this->getParentKeyName()); + + // Once we have an array dictionary of child objects we can easily match the + // children back to their parent using the dictionary and the keys on the + // the parent models. Then we will return the hydrated models back out. + foreach ($models as $model) { + if (isset($dictionary[$key = $model->getKey()])) { + /** @var array */ + $items = $dictionary[$key]; + + // Eliminate any duplicates + $items = $this->related->newCollection($items)->unique(); + + // If set, match up the via models to the models in the related collection + if (!is_null($nestedViaDictionary)) { + $this->matchTertiaryModels($nestedViaDictionary[$key], $items); + } + + // Remove the tertiary pivot key from the condensed models + foreach ($items as $relatedModel) { + unset($relatedModel->pivot->{$this->foreignKey}); + } + + $model->setRelation( + $relation, $items + ); + } + } + + return $models; + } + + /** + * Unset tertiary pivots on a collection or array of models. + * + * @param \Illuminate\Database\Eloquent\Collection $models + * @return void + */ + protected function unsetTertiaryPivots(Collection $models) + { + foreach ($models as $model) { + unset($model->pivot->{$this->foreignKey}); + } + } + + /** + * Set the join clause for the relation query. + * + * @param \Illuminate\Database\Eloquent\Builder|null $query + * @return $this + */ + protected function performJoin($query = null) + { + $query = $query ?: $this->query; + + parent::performJoin($query); + + // We need to join to the intermediate table on the related model's primary + // key column with the intermediate table's foreign key for the related + // model instance. Then we can set the "where" for the parent models. + $intermediateTable = $this->intermediateRelation->getTable(); + + $key = $this->intermediateRelation->getQualifiedRelatedKeyName(); + + $query->join($intermediateTable, $key, '=', $this->getQualifiedForeignKeyName()); + + return $this; + } + + /** + * Get the pivot columns for the relation. + * + * "pivot_" is prefixed to each column for easy removal later. + * + * @return array + */ + protected function aliasedPivotColumns() + { + $defaults = [$this->foreignKey, $this->relatedKey]; + $aliasedPivotColumns = collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) { + return $this->table.'.'.$column.' as pivot_'.$column; + }); + + $parentKeyName = $this->getParentKeyName(); + + // Add pivot column for the intermediate relation + $aliasedPivotColumns[] = "{$this->intermediateRelation->getQualifiedForeignKeyName()} as pivot_$parentKeyName"; + + return $aliasedPivotColumns->unique()->all(); + } +} diff --git a/login/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php b/login/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php new file mode 100755 index 0000000..f256f17 --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php @@ -0,0 +1,22 @@ + [], 'deleted' => [], 'updated' => [], + ]; + + if (is_null($relatedKeyName)) { + $relatedKeyName = $this->related->getKeyName(); + } + + // First we need to attach any of the associated models that are not currently + // in the child entity table. We'll spin through the given IDs, checking to see + // if they exist in the array of current ones, and if not we will insert. + $current = $this->newQuery()->pluck( + $relatedKeyName + )->all(); + + // Separate the submitted data into "update" and "new" + $updateRows = []; + $newRows = []; + foreach ($data as $row) { + // We determine "updateable" rows as those whose $relatedKeyName (usually 'id') is set, not empty, and + // match a related row in the database. + if (isset($row[$relatedKeyName]) && !empty($row[$relatedKeyName]) && in_array($row[$relatedKeyName], $current)) { + $id = $row[$relatedKeyName]; + $updateRows[$id] = $row; + } else { + $newRows[] = $row; + } + } + + // Next, we'll determine the rows in the database that aren't in the "update" list. + // These rows will be scheduled for deletion. Again, we determine based on the relatedKeyName (typically 'id'). + $updateIds = array_keys($updateRows); + $deleteIds = []; + foreach ($current as $currentId) { + if (!in_array($currentId, $updateIds)) { + $deleteIds[] = $currentId; + } + } + + // Delete any non-matching rows + if ($deleting && count($deleteIds) > 0) { + // Remove global scopes to avoid ambiguous keys + $this->getRelated() + ->withoutGlobalScopes() + ->whereIn($relatedKeyName, $deleteIds) + ->delete(); + + $changes['deleted'] = $this->castKeys($deleteIds); + } + + // Update the updatable rows + foreach ($updateRows as $id => $row) { + // Remove global scopes to avoid ambiguous keys + $this->getRelated() + ->withoutGlobalScopes() + ->where($relatedKeyName, $id) + ->update($row); + } + + $changes['updated'] = $this->castKeys($updateIds); + + // Insert the new rows + $newIds = []; + foreach ($newRows as $row) { + if ($forceCreate) { + $newModel = $this->forceCreate($row); + } else { + $newModel = $this->create($row); + } + $newIds[] = $newModel->$relatedKeyName; + } + + $changes['created'] = $this->castKeys($newIds); + + return $changes; + } + + + /** + * Cast the given keys to integers if they are numeric and string otherwise. + * + * @param array $keys + * @return array + */ + protected function castKeys(array $keys) + { + return (array) array_map(function ($v) { + return $this->castKey($v); + }, $keys); + } + + /** + * Cast the given key to an integer if it is numeric. + * + * @param mixed $key + * @return mixed + */ + protected function castKey($key) + { + return is_numeric($key) ? (int) $key : (string) $key; + } +} diff --git a/login/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php b/login/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php new file mode 100755 index 0000000..4b529bb --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php @@ -0,0 +1,563 @@ +offset($value); + } + + /** + * Set the "offset" value of the query. + * + * @todo Implement for 'unionOffset' as well? (By checking the value of $this->query->getQuery()->unions) + * @see \Illuminate\Database\Query\Builder + * @param int $value + * @return $this + */ + public function offset($value) + { + $this->offset = max(0, $value); + + return $this; + } + + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the "limit" value of the query. + * + * @todo Implement for 'unionLimit' as well? (By checking the value of $this->query->getQuery()->unions) + * @see \Illuminate\Database\Query\Builder + * @param int $value + * @return $this + */ + public function limit($value) + { + if ($value >= 0) { + $this->limit = $value; + } + + return $this; + } + + /** + * Set the limit on the number of intermediate models to load. + * + * @deprecated since 4.1.7 + * @param int $value + * @return $this + */ + public function withLimit($value) + { + return $this->limit($value); + } + + /** + * Set the offset when loading the intermediate models. + * + * @deprecated since 4.1.7 + * @param int $value + * @return $this + */ + public function withOffset($value) + { + return $this->offset($value); + } + + /** + * Add a query to load the nested tertiary models for this relationship. + * + * @param \Illuminate\Database\Eloquent\Model $tertiaryRelated + * @param string $tertiaryRelationName + * @param string $tertiaryKey + * @param callable $tertiaryCallback + * @return $this + */ + public function withTertiary($tertiaryRelated, $tertiaryRelationName = null, $tertiaryKey = null, $tertiaryCallback = null) + { + $this->tertiaryRelated = new $tertiaryRelated; + + // Try to guess the tertiary related key from the tertiaryRelated model. + $this->tertiaryKey = $tertiaryKey ?: $this->tertiaryRelated->getForeignKey(); + + // Also add the tertiary key as a pivot + $this->withPivot($this->tertiaryKey); + + $this->tertiaryRelationName = is_null($tertiaryRelationName) ? $this->tertiaryRelated->getTable() : $tertiaryRelationName; + + $this->tertiaryCallback = is_null($tertiaryCallback) + ? function () { + // + } + : $tertiaryCallback; + + return $this; + } + + /** + * Return the count of child models for this relationship. + * + * @see http://stackoverflow.com/a/29728129/2970321 + * @return int + */ + public function count() + { + $constrainedBuilder = clone $this->query; + + $constrainedBuilder = $constrainedBuilder->distinct(); + + return $constrainedBuilder->count($this->relatedKey); + } + + /** + * Add the constraints for a relationship count query. + * + * @see \Illuminate\Database\Eloquent\Relations\Relation + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery) + { + return $this->getRelationExistenceQuery( + $query, $parentQuery, new Expression("count(distinct {$this->relatedKey})") + ); + } + + /** + * Match the eagerly loaded results to their parents + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + // Build dictionary of parent (e.g. user) to related (e.g. permission) models + list($dictionary, $nestedTertiaryDictionary) = $this->buildDictionary($results, $this->foreignKey); + + // Once we have an array dictionary of child objects we can easily match the + // children back to their parent using the dictionary and the keys on the + // the parent models. Then we will return the hydrated models back out. + foreach ($models as $model) { + if (isset($dictionary[$key = $model->getKey()])) { + /** @var array */ + $items = $dictionary[$key]; + + // Eliminate any duplicates + $items = $this->related->newCollection($items)->unique(); + + // If set, match up the tertiary models to the models in the related collection + if (!is_null($nestedTertiaryDictionary)) { + $this->matchTertiaryModels($nestedTertiaryDictionary[$key], $items); + } + + $model->setRelation( + $relation, $items + ); + } + } + + return $models; + } + + /** + * Execute the query as a "select" statement, getting all requested models + * and matching up any tertiary models. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = ['*']) + { + // Get models and condense the result set + $models = $this->getModels($columns, true); + + // Remove the tertiary pivot key from the condensed models + $this->unsetTertiaryPivots($models); + + return $models; + } + + /** + * If we are applying either a limit or offset, we'll first determine a limited/offset list of model ids + * to select from in the final query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $limit + * @param int $offset + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getPaginatedQuery(Builder $query, $limit = null, $offset = null) + { + $constrainedBuilder = clone $query; + + // Since some unique models will be represented by more than one row in the database, + // we cannot apply limit/offset directly to the query. If we did that, we'd miss + // some of the records that are to be coalesced into the final set of models. + // Instead, we perform an additional query with grouping and limit/offset to determine + // the desired set of unique model _ids_, and then constrain our final query + // to these models with a whereIn clause. + $relatedKeyName = $this->related->getQualifiedKeyName(); + + // Apply an additional scope to override any selected columns in other global scopes + $uniqueIdScope = function ($subQuery) use ($relatedKeyName) { + $subQuery->select($relatedKeyName) + ->groupBy($relatedKeyName); + }; + + $identifier = spl_object_hash($uniqueIdScope); + + $constrainedBuilder->withGlobalScope($identifier, $uniqueIdScope); + + if ($limit) { + $constrainedBuilder->limit($limit); + } + + if ($offset) { + $constrainedBuilder->offset($offset); + } + + $primaryKeyName = $this->getParent()->getKeyName(); + $modelIds = $constrainedBuilder->get()->pluck($primaryKeyName)->toArray(); + + // Modify the unconstrained query to limit to these models + return $query->whereIn($relatedKeyName, $modelIds); + } + + /** + * Get the full join results for this query, overriding the default getEager() method. + * The default getEager() method would normally just call get() on this relationship. + * This is not what we want here though, because our get() method removes records before + * `match` has a chance to build out the substructures. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getEager() + { + return $this->getModels(['*'], false); + } + + /** + * Get the hydrated models and eager load their relations, optionally + * condensing the set of models before performing the eager loads. + * + * @param array $columns + * @param bool $condenseModels + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getModels($columns = ['*'], $condenseModels = true) + { + // First we'll add the proper select columns onto the query so it is run with + // the proper columns. Then, we will get the results and hydrate out pivot + // models with the result of those columns as a separate model relation. + $columns = $this->query->getQuery()->columns ? [] : $columns; + + // Add any necessary pagination on the related models + if ($this->limit || $this->offset) { + $this->getPaginatedQuery($this->query, $this->limit, $this->offset); + } + + // Apply scopes to the Eloquent\Builder instance. + $builder = $this->query->applyScopes(); + + $builder = $builder->addSelect( + $this->shouldSelect($columns) + ); + + $models = $builder->getModels(); + + // Hydrate the pivot models so we can load the via models + $this->hydratePivotRelation($models); + + if ($condenseModels) { + $models = $this->condenseModels($models); + } + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) { + $models = $builder->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Condense the raw join query results into a set of unique models. + * + * Before doing this, we may optionally find any tertiary models that should be + * set as sub-relations on these models. + * @param array $models + * @return array + */ + protected function condenseModels(array $models) + { + // Build dictionary of tertiary models, if `withTertiary` was called + $dictionary = null; + if ($this->tertiaryRelated) { + $dictionary = $this->buildTertiaryDictionary($models); + } + + // Remove duplicate models from collection + $models = $this->related->newCollection($models)->unique(); + + // If using withTertiary, use the dictionary to set the tertiary relation on each model. + if (!is_null($dictionary)) { + $this->matchTertiaryModels($dictionary, $models); + } + + return $models->all(); + } + + /** + * Build dictionary of related models keyed by the top-level "parent" id. + * If there is a tertiary query set as well, then also build a two-level dictionary + * that maps parent ids to arrays of related ids, which in turn map to arrays + * of tertiary models corresponding to each relationship. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $parentKey + * @return array + */ + protected function buildDictionary(Collection $results, $parentKey = null) + { + // First we will build a dictionary of child models keyed by the "parent key" (foreign key + // of the intermediate relation) so that we will easily and quickly match them to their + // parents without having a possibly slow inner loops for every models. + $dictionary = []; + + //Example nested dictionary: + //[ + // // User 1 + // '1' => [ + // // Permission 3 + // '3' => [ + // Role1, + // Role2 + // ], + // ... + // ], + // ... + //] + $nestedTertiaryDictionary = null; + $tertiaryModels = null; + + if ($this->tertiaryRelationName) { + // Get all tertiary models from the result set matching any of the parent models. + $tertiaryModels = $this->getTertiaryModels($results->all()); + } + + foreach ($results as $result) { + $parentKeyValue = $result->pivot->$parentKey; + + // Set the related model in the main dictionary. + // Note that this can end up adding duplicate models. It's cheaper to simply + // go back and remove the duplicates when we actually use the dictionary, + // rather than check for duplicates on each insert. + $dictionary[$parentKeyValue][] = $result; + + // If we're loading tertiary models, then set the keys in the nested dictionary as well. + if (!is_null($tertiaryModels)) { + $tertiaryKeyValue = $result->pivot->{$this->tertiaryKey}; + + if (!is_null($tertiaryKeyValue)) { + $tertiaryModel = clone $tertiaryModels[$tertiaryKeyValue]; + + // We also transfer the pivot relation at this point, since we have already coalesced + // any tertiary models into the nested dictionary. + $this->transferPivotsToTertiary($result, $tertiaryModel); + + $nestedTertiaryDictionary[$parentKeyValue][$result->getKey()][] = $tertiaryModel; + } + } + } + + return [$dictionary, $nestedTertiaryDictionary]; + } + + /** + * Build dictionary of tertiary models keyed by the corresponding related model keys. + * + * @param array $models + * @return array + */ + protected function buildTertiaryDictionary(array $models) + { + $dictionary = []; + + // Find the related tertiary entities (e.g. tasks) for all related models (e.g. locations) + $tertiaryModels = $this->getTertiaryModels($models); + + // Now for each related model (e.g. location), we will build out a dictionary of their tertiary models (e.g. tasks) + foreach ($models as $model) { + $tertiaryKeyValue = $model->pivot->{$this->tertiaryKey}; + + $tertiaryModel = clone $tertiaryModels[$tertiaryKeyValue]; + + $this->transferPivotsToTertiary($model, $tertiaryModel); + + $dictionary[$model->getKey()][] = $tertiaryModel; + } + + return $dictionary; + } + + protected function transferPivotsToTertiary($model, $tertiaryModel) + { + $pivotAttributes = []; + foreach ($this->pivotColumns as $column) { + $pivotAttributes[$column] = $model->pivot->$column; + unset($model->pivot->$column); + } + // Copy the related key pivot as well, but don't unset on the related model + $pivotAttributes[$this->relatedKey] = $model->pivot->{$this->relatedKey}; + + // Set the tertiary key pivot as well + $pivotAttributes[$this->tertiaryKey] = $tertiaryModel->getKey(); + + $pivot = $this->newExistingPivot($pivotAttributes); + $tertiaryModel->setRelation('pivot', $pivot); + } + + /** + * Get the tertiary models for the relationship. + * + * @param array $models + * @return \Illuminate\Database\Eloquent\Collection + */ + protected function getTertiaryModels(array $models) + { + $tertiaryClass = $this->tertiaryRelated; + + $keys = []; + foreach ($models as $model) { + $keys[] = $model->getRelation('pivot')->{$this->tertiaryKey}; + } + $keys = array_unique($keys); + + $query = $tertiaryClass->whereIn($tertiaryClass->getQualifiedKeyName(), $keys); + + // Add any additional constraints/eager loads to the tertiary query + $callback = $this->tertiaryCallback; + $callback($query); + + $tertiaryModels = $query + ->get() + ->keyBy($tertiaryClass->getKeyName()); + + return $tertiaryModels; + } + + /** + * Match a collection of child models into a collection of parent models using a dictionary. + * + * @param array $dictionary + * @param \Illuminate\Database\Eloquent\Collection $results + * @return void + */ + protected function matchTertiaryModels(array $dictionary, Collection $results) + { + // Now go through and set the tertiary relation on each child model + foreach ($results as $model) { + if (isset($dictionary[$key = $model->getKey()])) { + $tertiaryModels = $dictionary[$key]; + + $model->setRelation( + $this->tertiaryRelationName, $this->tertiaryRelated->newCollection($tertiaryModels) + ); + } + } + } + + /** + * Unset tertiary pivots on a collection or array of models. + * + * @param \Illuminate\Database\Eloquent\Collection $models + * @return void + */ + protected function unsetTertiaryPivots(Collection $models) + { + foreach ($models as $model) { + foreach ($this->pivotColumns as $column) { + unset($model->pivot->$column); + } + } + } +} diff --git a/login/app/sprinkles/core/src/Database/Relations/HasManySyncable.php b/login/app/sprinkles/core/src/Database/Relations/HasManySyncable.php new file mode 100755 index 0000000..bcf2a9d --- /dev/null +++ b/login/app/sprinkles/core/src/Database/Relations/HasManySyncable.php @@ -0,0 +1,22 @@ +ci = $ci; + $this->displayErrorDetails = (bool)$displayErrorDetails; + } + + /** + * Invoke error handler + * + * @param ServerRequestInterface $request The most recent Request object + * @param ResponseInterface $response The most recent Response object + * @param Throwable $exception The caught Exception object + * + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $exception) + { + // Default exception handler class + $handlerClass = '\UserFrosting\Sprinkle\Core\Error\Handler\ExceptionHandler'; + + // Get the last matching registered handler class, and instantiate it + foreach ($this->exceptionHandlers as $exceptionClass => $matchedHandlerClass) { + if ($exception instanceof $exceptionClass) { + $handlerClass = $matchedHandlerClass; + } + } + + $handler = new $handlerClass($this->ci, $request, $response, $exception, $this->displayErrorDetails); + + return $handler->handle(); + } + + /** + * Register an exception handler for a specified exception class. + * + * The exception handler must implement \UserFrosting\Sprinkle\Core\Handler\ExceptionHandlerInterface. + * + * @param string $exceptionClass The fully qualified class name of the exception to handle. + * @param string $handlerClass The fully qualified class name of the assigned handler. + * @throws InvalidArgumentException If the registered handler fails to implement ExceptionHandlerInterface + */ + public function registerHandler($exceptionClass, $handlerClass) + { + if (!is_a($handlerClass, '\UserFrosting\Sprinkle\Core\Error\Handler\ExceptionHandlerInterface', true)) { + throw new \InvalidArgumentException("Registered exception handler must implement ExceptionHandlerInterface!"); + } + + $this->exceptionHandlers[$exceptionClass] = $handlerClass; + } +} diff --git a/login/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php b/login/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php new file mode 100755 index 0000000..4fdc51d --- /dev/null +++ b/login/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php @@ -0,0 +1,275 @@ +ci = $ci; + $this->request = $request; + $this->response = $response; + $this->exception = $exception; + $this->displayErrorDetails = $displayErrorDetails; + $this->statusCode = $this->determineStatusCode(); + $this->contentType = $this->determineContentType($request, $this->ci->config['site.debug.ajax']); + $this->renderer = $this->determineRenderer(); + } + + /** + * Handle the caught exception. + * The handler may render a detailed debugging error page, a generic error page, write to logs, and/or add messages to the alert stream. + * + * @return ResponseInterface + */ + public function handle() + { + // If displayErrorDetails is set to true, we'll halt and immediately respond with a detailed debugging page. + // We do not log errors in this case. + if ($this->displayErrorDetails) { + $response = $this->renderDebugResponse(); + } else { + // Write exception to log + $this->writeToErrorLog(); + + // Render generic error page + $response = $this->renderGenericResponse(); + } + + // If this is an AJAX request and AJAX debugging is turned off, write messages to the alert stream + if ($this->request->isXhr() && !$this->ci->config['site.debug.ajax']) { + $this->writeAlerts(); + } + + return $response; + } + + /** + * Render a detailed response with debugging information. + * + * @return ResponseInterface + */ + public function renderDebugResponse() + { + $body = $this->renderer->renderWithBody(); + + return $this->response + ->withStatus($this->statusCode) + ->withHeader('Content-type', $this->contentType) + ->withBody($body); + } + + /** + * Render a generic, user-friendly response without sensitive debugging information. + * + * @return ResponseInterface + */ + public function renderGenericResponse() + { + $messages = $this->determineUserMessages(); + $httpCode = $this->statusCode; + + try { + $template = $this->ci->view->getEnvironment()->loadTemplate("pages/error/$httpCode.html.twig"); + } catch (\Twig_Error_Loader $e) { + $template = $this->ci->view->getEnvironment()->loadTemplate("pages/abstract/error.html.twig"); + } + + return $this->response + ->withStatus($httpCode) + ->withHeader('Content-type', $this->contentType) + ->write($template->render([ + 'messages' => $messages + ])); + } + + /** + * Write to the error log + * + * @return void + */ + public function writeToErrorLog() + { + $renderer = new PlainTextRenderer($this->request, $this->response, $this->exception, true); + $error = $renderer->render(); + $error .= PHP_EOL . 'View in rendered output by enabling the "displayErrorDetails" setting.' . PHP_EOL; + $this->logError($error); + } + + /** + * Write user-friendly error messages to the alert message stream. + * + * @return void + */ + public function writeAlerts() + { + $messages = $this->determineUserMessages(); + + foreach ($messages as $message) { + $this->ci->alerts->addMessageTranslated('danger', $message->message, $message->parameters); + } + } + + /** + * Determine which renderer to use based on content type + * Overloaded $renderer from calling class takes precedence over all + * + * @return ErrorRendererInterface + * + * @throws \RuntimeException + */ + protected function determineRenderer() + { + $renderer = $this->renderer; + + if ((!is_null($renderer) && !class_exists($renderer)) + || (!is_null($renderer) && !in_array('UserFrosting\Sprinkle\Core\Error\Renderer\ErrorRendererInterface', class_implements($renderer))) + ) { + throw new \RuntimeException(sprintf( + 'Non compliant error renderer provided (%s). ' . + 'Renderer must implement the ErrorRendererInterface', + $renderer + )); + } + + if (is_null($renderer)) { + switch ($this->contentType) { + case 'application/json': + $renderer = JsonRenderer::class; + break; + + case 'text/xml': + case 'application/xml': + $renderer = XmlRenderer::class; + break; + + case 'text/plain': + $renderer = PlainTextRenderer::class; + break; + + default: + case 'text/html': + $renderer = WhoopsRenderer::class; + break; + } + } + + return new $renderer($this->request, $this->response, $this->exception, $this->displayErrorDetails); + } + + /** + * Resolve the status code to return in the response from this handler. + * + * @return int + */ + protected function determineStatusCode() + { + if ($this->request->getMethod() === 'OPTIONS') { + return 200; + } + return 500; + } + + /** + * Resolve a list of error messages to present to the end user. + * + * @return array + */ + protected function determineUserMessages() + { + return [ + new UserMessage("ERROR.SERVER") + ]; + } + + /** + * Monolog logging for errors + * + * @param $message + * @return void + */ + protected function logError($message) + { + $this->ci->errorLogger->error($message); + } +} diff --git a/login/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php b/login/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php new file mode 100755 index 0000000..a928b69 --- /dev/null +++ b/login/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php @@ -0,0 +1,32 @@ +statusCode != 500) { + return; + } + + parent::writeToErrorLog(); + } + + /** + * Resolve the status code to return in the response from this handler. + * + * @return int + */ + protected function determineStatusCode() + { + if ($this->request->getMethod() === 'OPTIONS') { + return 200; + } elseif ($this->exception instanceof HttpException) { + return $this->exception->getHttpErrorCode(); + } + return 500; + } + + /** + * Resolve a list of error messages to present to the end user. + * + * @return array + */ + protected function determineUserMessages() + { + if ($this->exception instanceof HttpException) { + return $this->exception->getUserMessages(); + } + + // Fallback + return [ + new UserMessage("ERROR.SERVER") + ]; + } +} diff --git a/login/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php b/login/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php new file mode 100755 index 0000000..306feed --- /dev/null +++ b/login/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php @@ -0,0 +1,38 @@ +renderGenericResponse(); + + // If this is an AJAX request and AJAX debugging is turned off, write messages to the alert stream + if ($this->request->isXhr() && !$this->ci->config['site.debug.ajax']) { + $this->writeAlerts(); + } + + return $response; + } +} diff --git a/login/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php b/login/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php new file mode 100755 index 0000000..45f0e8d --- /dev/null +++ b/login/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php @@ -0,0 +1,30 @@ +request = $request; + $this->response = $response; + $this->exception = $exception; + $this->displayErrorDetails = $displayErrorDetails; + } + + abstract public function render(); + + /** + * @return Body + */ + public function renderWithBody() + { + $body = new Body(fopen('php://temp', 'r+')); + $body->write($this->render()); + return $body; + } +} diff --git a/login/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php b/login/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php new file mode 100755 index 0000000..7af269a --- /dev/null +++ b/login/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php @@ -0,0 +1,29 @@ +displayErrorDetails) { + $html = '
The application could not run because of the following error:
'; + $html .= 'A website error has occurred. Sorry for the temporary inconvenience.
'; + } + + $output = sprintf( + "" . + "%s', htmlentities($trace)); + } + + return $html; + } + + /** + * Render HTML representation of original request. + * + * @return string + */ + public function renderRequest() + { + $method = $this->request->getMethod(); + $uri = $this->request->getUri(); + $params = $this->request->getParams(); + $requestHeaders = $this->request->getHeaders(); + + $html = '
Name | Value |
---|---|
$name | $value |
" . $e->getCode() . "
\n";
+ $xml .= " $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/login/app/sprinkles/core/src/Util/ClassMapper.php b/login/app/sprinkles/core/src/Util/ClassMapper.php
new file mode 100755
index 0000000..5fa0881
--- /dev/null
+++ b/login/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/login/app/sprinkles/core/src/Util/EnvironmentInfo.php b/login/app/sprinkles/core/src/Util/EnvironmentInfo.php
new file mode 100755
index 0000000..aba9837
--- /dev/null
+++ b/login/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/login/app/sprinkles/core/src/Util/ShutdownHandler.php b/login/app/sprinkles/core/src/Util/ShutdownHandler.php
new file mode 100755
index 0000000..e7a6903
--- /dev/null
+++ b/login/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( + "" . + "