aboutsummaryrefslogtreecommitdiffhomepage
path: root/main/app/sprinkles/core/src
diff options
context:
space:
mode:
authorMarvin Borner2018-06-08 20:03:25 +0200
committerMarvin Borner2018-06-08 20:03:25 +0200
commit92b7dd3335a6572debeacfb5faa82c63a5e67888 (patch)
tree7ebbca22595d542ec5e2912a24a0400ac8f6b113 /main/app/sprinkles/core/src
parent22a1bb27f94ea33042b0bdd35bef1a5cfa96cc0d (diff)
Some minor fixes
Diffstat (limited to 'main/app/sprinkles/core/src')
-rw-r--r--main/app/sprinkles/core/src/Alert/AlertStream.php276
-rw-r--r--main/app/sprinkles/core/src/Alert/CacheAlertStream.php162
-rw-r--r--main/app/sprinkles/core/src/Alert/SessionAlertStream.php134
-rw-r--r--main/app/sprinkles/core/src/Controller/SimpleController.php72
-rw-r--r--main/app/sprinkles/core/src/Core.php236
-rw-r--r--main/app/sprinkles/core/src/Database/Builder.php400
-rw-r--r--main/app/sprinkles/core/src/Database/DatabaseInvalidException.php42
-rw-r--r--main/app/sprinkles/core/src/Database/Migrations/v400/SessionsTable.php94
-rw-r--r--main/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php102
-rw-r--r--main/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php544
-rw-r--r--main/app/sprinkles/core/src/Database/Models/Model.php266
-rw-r--r--main/app/sprinkles/core/src/Database/Models/Throttle.php74
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/BelongsToManyConstrained.php236
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php446
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php46
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/Concerns/Syncable.php260
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php1086
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/HasManySyncable.php46
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/MorphManySyncable.php46
-rw-r--r--main/app/sprinkles/core/src/Database/Relations/MorphToManyUnique.php46
-rw-r--r--main/app/sprinkles/core/src/Error/ExceptionHandlerManager.php182
-rw-r--r--main/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php534
-rw-r--r--main/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php66
-rw-r--r--main/app/sprinkles/core/src/Error/Handler/HttpExceptionHandler.php124
-rw-r--r--main/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php76
-rw-r--r--main/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php60
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/ErrorRenderer.php126
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php60
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/HtmlRenderer.php294
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/JsonRenderer.php110
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/PlainTextRenderer.php126
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/WhoopsRenderer.php1376
-rw-r--r--main/app/sprinkles/core/src/Error/Renderer/XmlRenderer.php94
-rw-r--r--main/app/sprinkles/core/src/Facades/Debug.php56
-rw-r--r--main/app/sprinkles/core/src/Facades/Translator.php56
-rw-r--r--main/app/sprinkles/core/src/Http/Concerns/DeterminesContentType.php152
-rw-r--r--main/app/sprinkles/core/src/Log/DatabaseHandler.php104
-rw-r--r--main/app/sprinkles/core/src/Log/MixedFormatter.php116
-rw-r--r--main/app/sprinkles/core/src/Mail/EmailRecipient.php258
-rw-r--r--main/app/sprinkles/core/src/Mail/MailMessage.php350
-rw-r--r--main/app/sprinkles/core/src/Mail/Mailer.php400
-rw-r--r--main/app/sprinkles/core/src/Mail/StaticMailMessage.php148
-rw-r--r--main/app/sprinkles/core/src/Mail/TwigMailMessage.php178
-rw-r--r--main/app/sprinkles/core/src/Model/UFModel.php54
-rw-r--r--main/app/sprinkles/core/src/Router.php200
-rw-r--r--main/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php1242
-rw-r--r--main/app/sprinkles/core/src/Sprunje/Sprunje.php1094
-rw-r--r--main/app/sprinkles/core/src/Throttle/ThrottleRule.php266
-rw-r--r--main/app/sprinkles/core/src/Throttle/Throttler.php344
-rw-r--r--main/app/sprinkles/core/src/Throttle/ThrottlerException.php38
-rw-r--r--main/app/sprinkles/core/src/Twig/CacheHelper.php114
-rw-r--r--main/app/sprinkles/core/src/Twig/CoreExtension.php240
-rw-r--r--main/app/sprinkles/core/src/Util/BadClassNameException.php38
-rw-r--r--main/app/sprinkles/core/src/Util/Captcha.php308
-rw-r--r--main/app/sprinkles/core/src/Util/CheckEnvironment.php662
-rw-r--r--main/app/sprinkles/core/src/Util/ClassMapper.php180
-rw-r--r--main/app/sprinkles/core/src/Util/EnvironmentInfo.php134
-rw-r--r--main/app/sprinkles/core/src/Util/ShutdownHandler.php324
-rw-r--r--main/app/sprinkles/core/src/Util/Util.php348
59 files changed, 7623 insertions, 7623 deletions
diff --git a/main/app/sprinkles/core/src/Alert/AlertStream.php b/main/app/sprinkles/core/src/Alert/AlertStream.php
index adb9b5b..2db441f 100644
--- a/main/app/sprinkles/core/src/Alert/AlertStream.php
+++ b/main/app/sprinkles/core/src/Alert/AlertStream.php
@@ -1,138 +1,138 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Alert;
-
-use UserFrosting\Fortress\ServerSideValidator;
-
-/**
- * AlertStream Class
- *
- * Implements an alert stream for use between HTTP requests, with i18n support via the MessageTranslator class
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @see http://www.userfrosting.com/components/#messages
- */
-abstract class AlertStream
-{
-
- /**
- * @var string
- */
- protected $messagesKey;
-
- /**
- * @var UserFrosting\I18n\MessageTranslator|null
- */
- protected $messageTranslator = NULL;
-
- /**
- * Create a new message stream.
- */
- public function __construct($messagesKey, $translator = NULL) {
- $this->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);
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Alert;
+
+use UserFrosting\Fortress\ServerSideValidator;
+
+/**
+ * AlertStream Class
+ *
+ * Implements an alert stream for use between HTTP requests, with i18n support via the MessageTranslator class
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/components/#messages
+ */
+abstract class AlertStream
+{
+
+ /**
+ * @var string
+ */
+ protected $messagesKey;
+
+ /**
+ * @var UserFrosting\I18n\MessageTranslator|null
+ */
+ protected $messageTranslator = NULL;
+
+ /**
+ * Create a new message stream.
+ */
+ public function __construct($messagesKey, $translator = NULL) {
+ $this->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/main/app/sprinkles/core/src/Alert/CacheAlertStream.php b/main/app/sprinkles/core/src/Alert/CacheAlertStream.php
index f3f6489..8d31462 100644
--- a/main/app/sprinkles/core/src/Alert/CacheAlertStream.php
+++ b/main/app/sprinkles/core/src/Alert/CacheAlertStream.php
@@ -1,81 +1,81 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Alert;
-
-use Illuminate\Cache\Repository as Cache;
-use UserFrosting\I18n\MessageTranslator;
-use UserFrosting\Support\Repository\Repository;
-
-/**
- * CacheAlertStream Class
- * Implements a message stream for use between HTTP requests, with i18n
- * support via the MessageTranslator class using the cache system to store
- * the alerts. Note that the tags are added each time instead of the
- * constructor since the session_id can change when the user logs in or out
- *
- * @author Louis Charette
- */
-class CacheAlertStream extends AlertStream
-{
- /**
- * @var Cache Object We use the cache object so that added messages will automatically appear in the cache.
- */
- protected $cache;
-
- /**
- * @var Repository Object We use the cache object so that added messages will automatically appear in the cache.
- */
- protected $config;
-
- /**
- * Create a new message stream.
- *
- * @param string $messagesKey Store the messages under this key
- * @param MessageTranslator|null $translator
- * @param Cache $cache
- * @param Repository $config
- */
- public function __construct($messagesKey, MessageTranslator $translator = NULL, Cache $cache, Repository $config) {
- $this->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);
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Alert;
+
+use Illuminate\Cache\Repository as Cache;
+use UserFrosting\I18n\MessageTranslator;
+use UserFrosting\Support\Repository\Repository;
+
+/**
+ * CacheAlertStream Class
+ * Implements a message stream for use between HTTP requests, with i18n
+ * support via the MessageTranslator class using the cache system to store
+ * the alerts. Note that the tags are added each time instead of the
+ * constructor since the session_id can change when the user logs in or out
+ *
+ * @author Louis Charette
+ */
+class CacheAlertStream extends AlertStream
+{
+ /**
+ * @var Cache Object We use the cache object so that added messages will automatically appear in the cache.
+ */
+ protected $cache;
+
+ /**
+ * @var Repository Object We use the cache object so that added messages will automatically appear in the cache.
+ */
+ protected $config;
+
+ /**
+ * Create a new message stream.
+ *
+ * @param string $messagesKey Store the messages under this key
+ * @param MessageTranslator|null $translator
+ * @param Cache $cache
+ * @param Repository $config
+ */
+ public function __construct($messagesKey, MessageTranslator $translator = NULL, Cache $cache, Repository $config) {
+ $this->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/main/app/sprinkles/core/src/Alert/SessionAlertStream.php b/main/app/sprinkles/core/src/Alert/SessionAlertStream.php
index fec0973..47a7ff7 100644
--- a/main/app/sprinkles/core/src/Alert/SessionAlertStream.php
+++ b/main/app/sprinkles/core/src/Alert/SessionAlertStream.php
@@ -1,67 +1,67 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Alert;
-
-use UserFrosting\I18n\MessageTranslator;
-use UserFrosting\Session\Session;
-
-/**
- * SessionAlertStream Class
- * Implements a message stream for use between HTTP requests, with i18n support via the MessageTranslator class
- * Using the session storage to store the alerts
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class SessionAlertStream extends AlertStream
-{
- /**
- * @var Session We use the session object so that added messages will automatically appear in the session.
- */
- protected $session;
-
- /**
- * Create a new message stream.
- *
- * @param string $messagesKey Store the messages under this key
- * @param MessageTranslator|null $translator
- * @param Session $session
- */
- public function __construct($messagesKey, MessageTranslator $translator = NULL, Session $session) {
- $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Alert;
+
+use UserFrosting\I18n\MessageTranslator;
+use UserFrosting\Session\Session;
+
+/**
+ * SessionAlertStream Class
+ * Implements a message stream for use between HTTP requests, with i18n support via the MessageTranslator class
+ * Using the session storage to store the alerts
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class SessionAlertStream extends AlertStream
+{
+ /**
+ * @var Session We use the session object so that added messages will automatically appear in the session.
+ */
+ protected $session;
+
+ /**
+ * Create a new message stream.
+ *
+ * @param string $messagesKey Store the messages under this key
+ * @param MessageTranslator|null $translator
+ * @param Session $session
+ */
+ public function __construct($messagesKey, MessageTranslator $translator = NULL, Session $session) {
+ $this->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/main/app/sprinkles/core/src/Controller/SimpleController.php b/main/app/sprinkles/core/src/Controller/SimpleController.php
index 1e3303a..1e5c45a 100644
--- a/main/app/sprinkles/core/src/Controller/SimpleController.php
+++ b/main/app/sprinkles/core/src/Controller/SimpleController.php
@@ -1,36 +1,36 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Controller;
-
-use Interop\Container\ContainerInterface;
-
-/**
- * SimpleController Class
- *
- * Basic controller class, that imports the entire DI container for easy access to services.
- * Your controller classes may extend this controller class.
- * @author Alex Weissman (https://alexanderweissman.com)
- * @see http://www.userfrosting.com/navigating/#structure
- */
-class SimpleController
-{
- /**
- * @var ContainerInterface The global container object, which holds all your services.
- */
- protected $ci;
-
- /**
- * Constructor.
- *
- * @param ContainerInterface $ci The global container object, which holds all your services.
- */
- public function __construct(ContainerInterface $ci) {
- $this->ci = $ci;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Controller;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * SimpleController Class
+ *
+ * Basic controller class, that imports the entire DI container for easy access to services.
+ * Your controller classes may extend this controller class.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/navigating/#structure
+ */
+class SimpleController
+{
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * Constructor.
+ *
+ * @param ContainerInterface $ci The global container object, which holds all your services.
+ */
+ public function __construct(ContainerInterface $ci) {
+ $this->ci = $ci;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Core.php b/main/app/sprinkles/core/src/Core.php
index 9518fe2..6bf1e36 100644
--- a/main/app/sprinkles/core/src/Core.php
+++ b/main/app/sprinkles/core/src/Core.php
@@ -1,118 +1,118 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core;
-
-use RocketTheme\Toolbox\Event\Event;
-use UserFrosting\Sprinkle\Core\Database\Models\Model;
-use UserFrosting\Sprinkle\Core\Util\EnvironmentInfo;
-use UserFrosting\Sprinkle\Core\Util\ShutdownHandler;
-use UserFrosting\System\Sprinkle\Sprinkle;
-
-/**
- * Bootstrapper class for the core sprinkle.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Core extends Sprinkle
-{
- /**
- * Defines which events in the UF lifecycle our Sprinkle should hook into.
- */
- public static function getSubscribedEvents() {
- return [
- 'onSprinklesInitialized' => ['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);
- }
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core;
+
+use RocketTheme\Toolbox\Event\Event;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+use UserFrosting\Sprinkle\Core\Util\EnvironmentInfo;
+use UserFrosting\Sprinkle\Core\Util\ShutdownHandler;
+use UserFrosting\System\Sprinkle\Sprinkle;
+
+/**
+ * Bootstrapper class for the core sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Core extends Sprinkle
+{
+ /**
+ * Defines which events in the UF lifecycle our Sprinkle should hook into.
+ */
+ public static function getSubscribedEvents() {
+ return [
+ 'onSprinklesInitialized' => ['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/main/app/sprinkles/core/src/Database/Builder.php b/main/app/sprinkles/core/src/Database/Builder.php
index cebc318..1ed26ff 100644
--- a/main/app/sprinkles/core/src/Database/Builder.php
+++ b/main/app/sprinkles/core/src/Database/Builder.php
@@ -1,200 +1,200 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database;
-
-use Illuminate\Database\Capsule\Manager as DB;
-use Illuminate\Database\Query\Builder as LaravelBuilder;
-
-/**
- * UFBuilder Class
- *
- * The base Eloquent data model, from which all UserFrosting data classes extend.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Builder extends LaravelBuilder
-{
- protected $excludedColumns = NULL;
-
- /**
- * Perform a "begins 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 beginsWith($field, $value) {
- return $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use Illuminate\Database\Query\Builder as LaravelBuilder;
+
+/**
+ * UFBuilder Class
+ *
+ * The base Eloquent data model, from which all UserFrosting data classes extend.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Builder extends LaravelBuilder
+{
+ protected $excludedColumns = NULL;
+
+ /**
+ * Perform a "begins 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 beginsWith($field, $value) {
+ return $this->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/main/app/sprinkles/core/src/Database/DatabaseInvalidException.php b/main/app/sprinkles/core/src/Database/DatabaseInvalidException.php
index 0eba67b..ec30aa6 100644
--- a/main/app/sprinkles/core/src/Database/DatabaseInvalidException.php
+++ b/main/app/sprinkles/core/src/Database/DatabaseInvalidException.php
@@ -1,21 +1,21 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database;
-
-use UserFrosting\Support\Exception\ForbiddenException;
-
-/**
- * Invalid database exception. Used when the database cannot be accessed.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class DatabaseInvalidException extends ForbiddenException
-{
- protected $defaultMessage = 'DB_INVALID';
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database;
+
+use UserFrosting\Support\Exception\ForbiddenException;
+
+/**
+ * Invalid database exception. Used when the database cannot be accessed.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class DatabaseInvalidException extends ForbiddenException
+{
+ protected $defaultMessage = 'DB_INVALID';
+}
diff --git a/main/app/sprinkles/core/src/Database/Migrations/v400/SessionsTable.php b/main/app/sprinkles/core/src/Database/Migrations/v400/SessionsTable.php
index 82d6534..b5fe437 100644
--- a/main/app/sprinkles/core/src/Database/Migrations/v400/SessionsTable.php
+++ b/main/app/sprinkles/core/src/Database/Migrations/v400/SessionsTable.php
@@ -1,47 +1,47 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Migrations\v400;
-
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Database\Schema\Builder;
-use UserFrosting\System\Bakery\Migration;
-
-/**
- * Sessions table migration
- * Version 4.0.0
- *
- * See https://laravel.com/docs/5.4/migrations#tables
- * @extends Migration
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class SessionsTable extends Migration
-{
- /**
- * {@inheritDoc}
- */
- public function up() {
- if (!$this->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');
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Sessions table migration
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class SessionsTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->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/main/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php b/main/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php
index f74fee8..676b44e 100644
--- a/main/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php
+++ b/main/app/sprinkles/core/src/Database/Migrations/v400/ThrottlesTable.php
@@ -1,51 +1,51 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Migrations\v400;
-
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Database\Schema\Builder;
-use UserFrosting\System\Bakery\Migration;
-
-/**
- * Throttles table migration
- * Version 4.0.0
- *
- * @extends Migration
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ThrottlesTable extends Migration
-{
- /**
- * {@inheritDoc}
- */
- public function up() {
- if (!$this->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');
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Throttles table migration
+ * Version 4.0.0
+ *
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ThrottlesTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->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/main/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php b/main/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php
index 919c108..ccccf32 100644
--- a/main/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php
+++ b/main/app/sprinkles/core/src/Database/Models/Concerns/HasRelationships.php
@@ -1,272 +1,272 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Models\Concerns;
-
-use Illuminate\Support\Arr;
-use Illuminate\Support\Str;
-
-use Illuminate\Database\Eloquent\Relations\MorphMany;
-use Illuminate\Database\Eloquent\Relations\MorphToMany;
-
-use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyConstrained;
-use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough;
-use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyUnique;
-use UserFrosting\Sprinkle\Core\Database\Relations\HasManySyncable;
-use UserFrosting\Sprinkle\Core\Database\Relations\MorphManySyncable;
-use UserFrosting\Sprinkle\Core\Database\Relations\MorphToManyUnique;
-
-/**
- * HasRelationships trait
- *
- * Extends Laravel's Model class to add some additional relationships.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-trait HasRelationships
-{
- /**
- * The many to many relationship methods.
- *
- * @var array
- */
- public static $manyMethodsExtended = ['belongsToMany', 'morphToMany', 'morphedByMany', 'morphToManyUnique'];
-
- /**
- * Overrides the default Eloquent hasMany relationship to return a HasManySyncable.
- *
- * {@inheritDoc}
- * @return \UserFrosting\Sprinkle\Core\Database\Relations\HasManySyncable
- */
- public function hasMany($related, $foreignKey = NULL, $localKey = NULL) {
- $instance = $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Models\Concerns;
+
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+
+use Illuminate\Database\Eloquent\Relations\MorphMany;
+use Illuminate\Database\Eloquent\Relations\MorphToMany;
+
+use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyConstrained;
+use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough;
+use UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyUnique;
+use UserFrosting\Sprinkle\Core\Database\Relations\HasManySyncable;
+use UserFrosting\Sprinkle\Core\Database\Relations\MorphManySyncable;
+use UserFrosting\Sprinkle\Core\Database\Relations\MorphToManyUnique;
+
+/**
+ * HasRelationships trait
+ *
+ * Extends Laravel's Model class to add some additional relationships.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+trait HasRelationships
+{
+ /**
+ * The many to many relationship methods.
+ *
+ * @var array
+ */
+ public static $manyMethodsExtended = ['belongsToMany', 'morphToMany', 'morphedByMany', 'morphToManyUnique'];
+
+ /**
+ * Overrides the default Eloquent hasMany relationship to return a HasManySyncable.
+ *
+ * {@inheritDoc}
+ * @return \UserFrosting\Sprinkle\Core\Database\Relations\HasManySyncable
+ */
+ public function hasMany($related, $foreignKey = NULL, $localKey = NULL) {
+ $instance = $this->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/main/app/sprinkles/core/src/Database/Models/Model.php b/main/app/sprinkles/core/src/Database/Models/Model.php
index 28b6be0..38e49fd 100644
--- a/main/app/sprinkles/core/src/Database/Models/Model.php
+++ b/main/app/sprinkles/core/src/Database/Models/Model.php
@@ -1,133 +1,133 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Models;
-
-use Illuminate\Database\Capsule\Manager as DB;
-use Illuminate\Database\Eloquent\Model as LaravelModel;
-use UserFrosting\Sprinkle\Core\Database\Models\Concerns\HasRelationships;
-
-/**
- * Model Class
- *
- * UserFrosting's base data model, from which all UserFrosting data classes extend.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-abstract class Model extends LaravelModel
-{
- use HasRelationships;
-
- /**
- * @var ContainerInterface The DI container for your application.
- */
- public static $ci;
-
- /**
- * @var bool Disable timestamps for now.
- */
- public $timestamps = FALSE;
-
- public function __construct(array $attributes = []) {
- // Hacky way to force the DB service to load before attempting to use the model
- static::$ci['db'];
-
- parent::__construct($attributes);
- }
-
- /**
- * Determine if an attribute exists on the model - even if it is null.
- *
- * @param string $key
- * @return bool
- */
- public function attributeExists($key) {
- return array_key_exists($key, $this->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);
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use Illuminate\Database\Eloquent\Model as LaravelModel;
+use UserFrosting\Sprinkle\Core\Database\Models\Concerns\HasRelationships;
+
+/**
+ * Model Class
+ *
+ * UserFrosting's base data model, from which all UserFrosting data classes extend.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+abstract class Model extends LaravelModel
+{
+ use HasRelationships;
+
+ /**
+ * @var ContainerInterface The DI container for your application.
+ */
+ public static $ci;
+
+ /**
+ * @var bool Disable timestamps for now.
+ */
+ public $timestamps = FALSE;
+
+ public function __construct(array $attributes = []) {
+ // Hacky way to force the DB service to load before attempting to use the model
+ static::$ci['db'];
+
+ parent::__construct($attributes);
+ }
+
+ /**
+ * Determine if an attribute exists on the model - even if it is null.
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function attributeExists($key) {
+ return array_key_exists($key, $this->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/main/app/sprinkles/core/src/Database/Models/Throttle.php b/main/app/sprinkles/core/src/Database/Models/Throttle.php
index 82d2c87..5810e0d 100644
--- a/main/app/sprinkles/core/src/Database/Models/Throttle.php
+++ b/main/app/sprinkles/core/src/Database/Models/Throttle.php
@@ -1,37 +1,37 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Models;
-
-/**
- * Throttle Class
- *
- * Represents a throttleable request from a user agent.
- * @author Alex Weissman (https://alexanderweissman.com)
- * @property string type
- * @property string ip
- * @property string request_data
- */
-class Throttle extends Model
-{
- /**
- * @var string The name of the table for the current model.
- */
- protected $table = "throttles";
-
- protected $fillable = [
- "type",
- "ip",
- "request_data"
- ];
-
- /**
- * @var bool Enable timestamps for Throttles.
- */
- public $timestamps = TRUE;
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Models;
+
+/**
+ * Throttle Class
+ *
+ * Represents a throttleable request from a user agent.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property string type
+ * @property string ip
+ * @property string request_data
+ */
+class Throttle extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "throttles";
+
+ protected $fillable = [
+ "type",
+ "ip",
+ "request_data"
+ ];
+
+ /**
+ * @var bool Enable timestamps for Throttles.
+ */
+ public $timestamps = TRUE;
+}
diff --git a/main/app/sprinkles/core/src/Database/Relations/BelongsToManyConstrained.php b/main/app/sprinkles/core/src/Database/Relations/BelongsToManyConstrained.php
index cf79223..a207b75 100644
--- a/main/app/sprinkles/core/src/Database/Relations/BelongsToManyConstrained.php
+++ b/main/app/sprinkles/core/src/Database/Relations/BelongsToManyConstrained.php
@@ -1,118 +1,118 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations;
-
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Database\Eloquent\Relations\BelongsToMany;
-
-/**
- * A BelongsToMany relationship that constrains on the value of an additional foreign key in the pivot table.
- * This has been superseded by the BelongsToTernary relationship since 4.1.6.
- *
- * @deprecated since 4.1.6
- * @author Alex Weissman (https://alexanderweissman.com)
- * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
- */
-class BelongsToManyConstrained extends BelongsToMany
-{
- /**
- * @var The pivot foreign key on which to constrain the result sets for this relation.
- */
- protected $constraintKey;
-
- /**
- * Create a new belongs to many constrained relationship instance.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $constraintKey
- * @param string $table
- * @param string $foreignKey
- * @param string $relatedKey
- * @param string $relationName
- * @return void
- */
- public function __construct(Builder $query, Model $parent, $constraintKey, $table, $foreignKey, $relatedKey, $relationName = NULL) {
- $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+
+/**
+ * A BelongsToMany relationship that constrains on the value of an additional foreign key in the pivot table.
+ * This has been superseded by the BelongsToTernary relationship since 4.1.6.
+ *
+ * @deprecated since 4.1.6
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
+ */
+class BelongsToManyConstrained extends BelongsToMany
+{
+ /**
+ * @var The pivot foreign key on which to constrain the result sets for this relation.
+ */
+ protected $constraintKey;
+
+ /**
+ * Create a new belongs to many constrained relationship instance.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Model $parent
+ * @param string $constraintKey
+ * @param string $table
+ * @param string $foreignKey
+ * @param string $relatedKey
+ * @param string $relationName
+ * @return void
+ */
+ public function __construct(Builder $query, Model $parent, $constraintKey, $table, $foreignKey, $relatedKey, $relationName = NULL) {
+ $this->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/main/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php b/main/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php
index 67304be..24aa4cf 100644
--- a/main/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php
+++ b/main/app/sprinkles/core/src/Database/Relations/BelongsToManyThrough.php
@@ -1,223 +1,223 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations;
-
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Database\Eloquent\Relations\BelongsToMany;
-use Illuminate\Database\Eloquent\Relations\Relation;
-use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Unique;
-
-/**
- * A BelongsToMany relationship that queries through an additional intermediate model.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
- */
-class BelongsToManyThrough extends BelongsToMany
-{
- use Unique;
-
- /**
- * The relation through which we are joining.
- *
- * @var Relation
- */
- protected $intermediateRelation;
-
- /**
- * Create a new belongs to many relationship instance.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param \Illuminate\Database\Eloquent\Relations\Relation $intermediateRelation
- * @param string $table
- * @param string $foreignKey
- * @param string $relatedKey
- * @param string $relationName
- * @return void
- */
- public function __construct(Builder $query, Model $parent, Relation $intermediateRelation, $table, $foreignKey, $relatedKey, $relationName = NULL) {
- $this->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();
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\Relation;
+use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Unique;
+
+/**
+ * A BelongsToMany relationship that queries through an additional intermediate model.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
+ */
+class BelongsToManyThrough extends BelongsToMany
+{
+ use Unique;
+
+ /**
+ * The relation through which we are joining.
+ *
+ * @var Relation
+ */
+ protected $intermediateRelation;
+
+ /**
+ * Create a new belongs to many relationship instance.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Model $parent
+ * @param \Illuminate\Database\Eloquent\Relations\Relation $intermediateRelation
+ * @param string $table
+ * @param string $foreignKey
+ * @param string $relatedKey
+ * @param string $relationName
+ * @return void
+ */
+ public function __construct(Builder $query, Model $parent, Relation $intermediateRelation, $table, $foreignKey, $relatedKey, $relationName = NULL) {
+ $this->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/main/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php b/main/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php
index d5d473d..1bde954 100644
--- a/main/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php
+++ b/main/app/sprinkles/core/src/Database/Relations/BelongsToManyUnique.php
@@ -1,23 +1,23 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations;
-
-use Illuminate\Database\Eloquent\Relations\BelongsToMany;
-use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Unique;
-
-/**
- * A BelongsToMany relationship that reduces the related members to a unique (by primary key) set.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
- */
-class BelongsToManyUnique extends BelongsToMany
-{
- use Unique;
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations;
+
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Unique;
+
+/**
+ * A BelongsToMany relationship that reduces the related members to a unique (by primary key) set.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
+ */
+class BelongsToManyUnique extends BelongsToMany
+{
+ use Unique;
+}
diff --git a/main/app/sprinkles/core/src/Database/Relations/Concerns/Syncable.php b/main/app/sprinkles/core/src/Database/Relations/Concerns/Syncable.php
index cb32d52..b52ab99 100644
--- a/main/app/sprinkles/core/src/Database/Relations/Concerns/Syncable.php
+++ b/main/app/sprinkles/core/src/Database/Relations/Concerns/Syncable.php
@@ -1,130 +1,130 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations\Concerns;
-
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Database\Eloquent\Collection;
-
-/**
- * Implements the `sync` method for HasMany relationships.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-trait Syncable
-{
- /**
- * Synchronizes an array of data for related models with a parent model.
- *
- * @param mixed[] $data
- * @param bool $deleting Delete models from the database that are not represented in the input data.
- * @param bool $forceCreate Ignore mass assignment restrictions on child models.
- * @param string $relatedKeyName The primary key used to determine which child models are new, updated, or deleted.
- */
- public function sync($data, $deleting = TRUE, $forceCreate = FALSE, $relatedKeyName = NULL) {
- $changes = [
- 'created' => [], '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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations\Concerns;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
+
+/**
+ * Implements the `sync` method for HasMany relationships.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+trait Syncable
+{
+ /**
+ * Synchronizes an array of data for related models with a parent model.
+ *
+ * @param mixed[] $data
+ * @param bool $deleting Delete models from the database that are not represented in the input data.
+ * @param bool $forceCreate Ignore mass assignment restrictions on child models.
+ * @param string $relatedKeyName The primary key used to determine which child models are new, updated, or deleted.
+ */
+ public function sync($data, $deleting = TRUE, $forceCreate = FALSE, $relatedKeyName = NULL) {
+ $changes = [
+ 'created' => [], '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/main/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php b/main/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php
index 3a321e4..deb2673 100644
--- a/main/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php
+++ b/main/app/sprinkles/core/src/Database/Relations/Concerns/Unique.php
@@ -1,543 +1,543 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations\Concerns;
-
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Database\Query\Expression;
-
-/**
- * Enforce uniqueness for BelongsToManyUnique, MorphToManyUnique, and BelongsToManyThrough.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-trait Unique
-{
- /**
- * The related tertiary model instance.
- *
- * @var \Illuminate\Database\Eloquent\Model
- */
- protected $tertiaryRelated = NULL;
-
- /**
- * The name to use for the tertiary relation (e.g. 'roles_via', etc)
- *
- * @var string
- */
- protected $tertiaryRelationName = NULL;
-
- /**
- * The foreign key to the related tertiary model instance.
- *
- * @var string
- */
- protected $tertiaryKey;
-
- /**
- * A callback to apply to the tertiary query.
- *
- * @var callable|null
- */
- protected $tertiaryCallback = NULL;
-
- /**
- * The limit to apply on the number of related models retrieved.
- *
- * @var int|null
- */
- protected $limit = NULL;
-
- /**
- * The offset to apply on the related models retrieved.
- *
- * @var int|null
- */
- protected $offset = NULL;
-
- /**
- * Alias to set the "offset" value of the query.
- *
- * @param int $value
- * @return $this
- */
- public function skip($value) {
- return $this->offset($value);
- }
-
- /**
- * Set the "offset" value of the query.
- *
- * 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.
- *
- * 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);
- }
- }
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations\Concerns;
+
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Query\Expression;
+
+/**
+ * Enforce uniqueness for BelongsToManyUnique, MorphToManyUnique, and BelongsToManyThrough.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+trait Unique
+{
+ /**
+ * The related tertiary model instance.
+ *
+ * @var \Illuminate\Database\Eloquent\Model
+ */
+ protected $tertiaryRelated = NULL;
+
+ /**
+ * The name to use for the tertiary relation (e.g. 'roles_via', etc)
+ *
+ * @var string
+ */
+ protected $tertiaryRelationName = NULL;
+
+ /**
+ * The foreign key to the related tertiary model instance.
+ *
+ * @var string
+ */
+ protected $tertiaryKey;
+
+ /**
+ * A callback to apply to the tertiary query.
+ *
+ * @var callable|null
+ */
+ protected $tertiaryCallback = NULL;
+
+ /**
+ * The limit to apply on the number of related models retrieved.
+ *
+ * @var int|null
+ */
+ protected $limit = NULL;
+
+ /**
+ * The offset to apply on the related models retrieved.
+ *
+ * @var int|null
+ */
+ protected $offset = NULL;
+
+ /**
+ * Alias to set the "offset" value of the query.
+ *
+ * @param int $value
+ * @return $this
+ */
+ public function skip($value) {
+ return $this->offset($value);
+ }
+
+ /**
+ * Set the "offset" value of the query.
+ *
+ * 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.
+ *
+ * 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/main/app/sprinkles/core/src/Database/Relations/HasManySyncable.php b/main/app/sprinkles/core/src/Database/Relations/HasManySyncable.php
index 9d85b1f..d09e25d 100644
--- a/main/app/sprinkles/core/src/Database/Relations/HasManySyncable.php
+++ b/main/app/sprinkles/core/src/Database/Relations/HasManySyncable.php
@@ -1,23 +1,23 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations;
-
-use Illuminate\Database\Eloquent\Relations\HasMany;
-use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Syncable;
-
-/**
- * A HasMany relationship that supports a `sync` method.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/HasMany.php
- */
-class HasManySyncable extends HasMany
-{
- use Syncable;
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations;
+
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Syncable;
+
+/**
+ * A HasMany relationship that supports a `sync` method.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/HasMany.php
+ */
+class HasManySyncable extends HasMany
+{
+ use Syncable;
+}
diff --git a/main/app/sprinkles/core/src/Database/Relations/MorphManySyncable.php b/main/app/sprinkles/core/src/Database/Relations/MorphManySyncable.php
index bb90257..26938f6 100644
--- a/main/app/sprinkles/core/src/Database/Relations/MorphManySyncable.php
+++ b/main/app/sprinkles/core/src/Database/Relations/MorphManySyncable.php
@@ -1,23 +1,23 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations;
-
-use Illuminate\Database\Eloquent\Relations\MorphMany;
-use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Syncable;
-
-/**
- * A MorphMany relationship that constrains on the value of an additional foreign key in the pivot table.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/MorphMany.php
- */
-class MorphManySyncable extends MorphMany
-{
- use Syncable;
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations;
+
+use Illuminate\Database\Eloquent\Relations\MorphMany;
+use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Syncable;
+
+/**
+ * A MorphMany relationship that constrains on the value of an additional foreign key in the pivot table.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/MorphMany.php
+ */
+class MorphManySyncable extends MorphMany
+{
+ use Syncable;
+}
diff --git a/main/app/sprinkles/core/src/Database/Relations/MorphToManyUnique.php b/main/app/sprinkles/core/src/Database/Relations/MorphToManyUnique.php
index 0920d1c..7739711 100644
--- a/main/app/sprinkles/core/src/Database/Relations/MorphToManyUnique.php
+++ b/main/app/sprinkles/core/src/Database/Relations/MorphToManyUnique.php
@@ -1,23 +1,23 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Database\Relations;
-
-use Illuminate\Database\Eloquent\Relations\MorphToMany;
-use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Unique;
-
-/**
- * A MorphToMany relationship that reduces the related members to a unique (by primary key) set.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php
- */
-class MorphToManyUnique extends MorphToMany
-{
- use Unique;
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Database\Relations;
+
+use Illuminate\Database\Eloquent\Relations\MorphToMany;
+use UserFrosting\Sprinkle\Core\Database\Relations\Concerns\Unique;
+
+/**
+ * A MorphToMany relationship that reduces the related members to a unique (by primary key) set.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php
+ */
+class MorphToManyUnique extends MorphToMany
+{
+ use Unique;
+}
diff --git a/main/app/sprinkles/core/src/Error/ExceptionHandlerManager.php b/main/app/sprinkles/core/src/Error/ExceptionHandlerManager.php
index 716ccdd..f144597 100644
--- a/main/app/sprinkles/core/src/Error/ExceptionHandlerManager.php
+++ b/main/app/sprinkles/core/src/Error/ExceptionHandlerManager.php
@@ -1,91 +1,91 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error;
-
-use Interop\Container\ContainerInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use UserFrosting\Sprinkle\Core\Handler\ExceptionHandlerInterface;
-
-/**
- * Default UserFrosting application error handler
- *
- * It outputs the error message and diagnostic information in either JSON, XML, or HTML based on the Accept header.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ExceptionHandlerManager
-{
- /**
- * @var ContainerInterface The global container object, which holds all your services.
- */
- protected $ci;
-
- /**
- * @var array[string] An array that maps Exception types to callbacks, for special processing of certain types of errors.
- */
- protected $exceptionHandlers = [];
-
- /**
- * @var bool
- */
- protected $displayErrorDetails;
-
- /**
- * Constructor
- *
- * @param ContainerInterface $ci The global container object, which holds all your services.
- * @param boolean $displayErrorDetails Set to true to display full details
- */
- public function __construct(ContainerInterface $ci, $displayErrorDetails = FALSE) {
- $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error;
+
+use Interop\Container\ContainerInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use UserFrosting\Sprinkle\Core\Handler\ExceptionHandlerInterface;
+
+/**
+ * Default UserFrosting application error handler
+ *
+ * It outputs the error message and diagnostic information in either JSON, XML, or HTML based on the Accept header.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ExceptionHandlerManager
+{
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * @var array[string] An array that maps Exception types to callbacks, for special processing of certain types of errors.
+ */
+ protected $exceptionHandlers = [];
+
+ /**
+ * @var bool
+ */
+ protected $displayErrorDetails;
+
+ /**
+ * Constructor
+ *
+ * @param ContainerInterface $ci The global container object, which holds all your services.
+ * @param boolean $displayErrorDetails Set to true to display full details
+ */
+ public function __construct(ContainerInterface $ci, $displayErrorDetails = FALSE) {
+ $this->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/main/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php b/main/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php
index 89e890e..1ce5954 100644
--- a/main/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php
+++ b/main/app/sprinkles/core/src/Error/Handler/ExceptionHandler.php
@@ -1,267 +1,267 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Handler;
-
-use Interop\Container\ContainerInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use UserFrosting\Sprinkle\Core\Error\Renderer\HtmlRenderer;
-use UserFrosting\Sprinkle\Core\Error\Renderer\JsonRenderer;
-use UserFrosting\Sprinkle\Core\Error\Renderer\PlainTextRenderer;
-use UserFrosting\Sprinkle\Core\Error\Renderer\WhoopsRenderer;
-use UserFrosting\Sprinkle\Core\Error\Renderer\XmlRenderer;
-use UserFrosting\Sprinkle\Core\Http\Concerns\DeterminesContentType;
-use UserFrosting\Support\Message\UserMessage;
-
-/**
- * Generic handler for exceptions.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ExceptionHandler implements ExceptionHandlerInterface
-{
- use DeterminesContentType;
-
- /**
- * @var ContainerInterface The global container object, which holds all your services.
- */
- protected $ci;
-
- /**
- * @var ServerRequestInterface
- */
- protected $request;
-
- /**
- * @var ResponseInterface
- */
- protected $response;
-
- /**
- * @var Throwable
- */
- protected $exception;
-
- /**
- * @var ErrorRendererInterface
- */
- protected $renderer = NULL;
-
- /**
- * @var string
- */
- protected $contentType;
-
- /**
- * @var int
- */
- protected $statusCode;
-
- /**
- * Tells the handler whether or not to output detailed error information to the client.
- * Each handler may choose if and how to implement this.
- *
- * @var bool
- */
- protected $displayErrorDetails;
-
- /**
- * Create a new ExceptionHandler object.
- *
- * @param ContainerInterface $ci
- * @param ServerRequestInterface $request The most recent Request object
- * @param ResponseInterface $response The most recent Response object
- * @param Throwable $exception The caught Exception object
- * @param bool $displayErrorDetails
- */
- public function __construct(
- ContainerInterface $ci,
- ServerRequestInterface $request,
- ResponseInterface $response,
- $exception,
- $displayErrorDetails = FALSE
- ) {
- $this->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);
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Handler;
+
+use Interop\Container\ContainerInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use UserFrosting\Sprinkle\Core\Error\Renderer\HtmlRenderer;
+use UserFrosting\Sprinkle\Core\Error\Renderer\JsonRenderer;
+use UserFrosting\Sprinkle\Core\Error\Renderer\PlainTextRenderer;
+use UserFrosting\Sprinkle\Core\Error\Renderer\WhoopsRenderer;
+use UserFrosting\Sprinkle\Core\Error\Renderer\XmlRenderer;
+use UserFrosting\Sprinkle\Core\Http\Concerns\DeterminesContentType;
+use UserFrosting\Support\Message\UserMessage;
+
+/**
+ * Generic handler for exceptions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ExceptionHandler implements ExceptionHandlerInterface
+{
+ use DeterminesContentType;
+
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * @var ServerRequestInterface
+ */
+ protected $request;
+
+ /**
+ * @var ResponseInterface
+ */
+ protected $response;
+
+ /**
+ * @var Throwable
+ */
+ protected $exception;
+
+ /**
+ * @var ErrorRendererInterface
+ */
+ protected $renderer = NULL;
+
+ /**
+ * @var string
+ */
+ protected $contentType;
+
+ /**
+ * @var int
+ */
+ protected $statusCode;
+
+ /**
+ * Tells the handler whether or not to output detailed error information to the client.
+ * Each handler may choose if and how to implement this.
+ *
+ * @var bool
+ */
+ protected $displayErrorDetails;
+
+ /**
+ * Create a new ExceptionHandler object.
+ *
+ * @param ContainerInterface $ci
+ * @param ServerRequestInterface $request The most recent Request object
+ * @param ResponseInterface $response The most recent Response object
+ * @param Throwable $exception The caught Exception object
+ * @param bool $displayErrorDetails
+ */
+ public function __construct(
+ ContainerInterface $ci,
+ ServerRequestInterface $request,
+ ResponseInterface $response,
+ $exception,
+ $displayErrorDetails = FALSE
+ ) {
+ $this->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/main/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php b/main/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php
index 2ac9ada..2905382 100644
--- a/main/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php
+++ b/main/app/sprinkles/core/src/Error/Handler/ExceptionHandlerInterface.php
@@ -1,33 +1,33 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Handler;
-
-use Interop\Container\ContainerInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-
-/**
- * All exception handlers must implement this interface.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-interface ExceptionHandlerInterface
-{
- public function __construct(ContainerInterface $ci, ServerRequestInterface $request, ResponseInterface $response, $exception, $displayErrorDetails = FALSE);
-
- public function handle();
-
- public function renderDebugResponse();
-
- public function renderGenericResponse();
-
- public function writeToErrorLog();
-
- public function writeAlerts();
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Handler;
+
+use Interop\Container\ContainerInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * All exception handlers must implement this interface.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+interface ExceptionHandlerInterface
+{
+ public function __construct(ContainerInterface $ci, ServerRequestInterface $request, ResponseInterface $response, $exception, $displayErrorDetails = FALSE);
+
+ public function handle();
+
+ public function renderDebugResponse();
+
+ public function renderGenericResponse();
+
+ public function writeToErrorLog();
+
+ public function writeAlerts();
+}
diff --git a/main/app/sprinkles/core/src/Error/Handler/HttpExceptionHandler.php b/main/app/sprinkles/core/src/Error/Handler/HttpExceptionHandler.php
index cb66919..e902711 100644
--- a/main/app/sprinkles/core/src/Error/Handler/HttpExceptionHandler.php
+++ b/main/app/sprinkles/core/src/Error/Handler/HttpExceptionHandler.php
@@ -1,62 +1,62 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Handler;
-
-use UserFrosting\Support\Exception\HttpException;
-
-/**
- * Handler for HttpExceptions.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class HttpExceptionHandler extends ExceptionHandler
-{
- /**
- * For HttpExceptions, only write to the error log if the status code is 500
- *
- * @return void
- */
- public function writeToErrorLog() {
- if ($this->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;
- } else if ($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")
- ];
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Handler;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Handler for HttpExceptions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class HttpExceptionHandler extends ExceptionHandler
+{
+ /**
+ * For HttpExceptions, only write to the error log if the status code is 500
+ *
+ * @return void
+ */
+ public function writeToErrorLog() {
+ if ($this->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;
+ } else if ($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/main/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php b/main/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php
index a866cc3..e243a02 100644
--- a/main/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php
+++ b/main/app/sprinkles/core/src/Error/Handler/NotFoundExceptionHandler.php
@@ -1,38 +1,38 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Handler;
-
-use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler;
-use UserFrosting\Support\Exception\HttpException;
-use UserFrosting\Support\Message\UserMessage;
-
-/**
- * Handler for NotFoundExceptions.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class NotFoundExceptionHandler extends HttpExceptionHandler
-{
- /**
- * Custom handling for NotFoundExceptions. Always render a generic response!
- *
- * @return Response
- */
- public function handle() {
- // 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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Handler;
+
+use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler;
+use UserFrosting\Support\Exception\HttpException;
+use UserFrosting\Support\Message\UserMessage;
+
+/**
+ * Handler for NotFoundExceptions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class NotFoundExceptionHandler extends HttpExceptionHandler
+{
+ /**
+ * Custom handling for NotFoundExceptions. Always render a generic response!
+ *
+ * @return Response
+ */
+ public function handle() {
+ // 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;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php b/main/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php
index e7117e9..15e16f8 100644
--- a/main/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php
+++ b/main/app/sprinkles/core/src/Error/Handler/PhpMailerExceptionHandler.php
@@ -1,30 +1,30 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Handler;
-
-use UserFrosting\Support\Message\UserMessage;
-
-/**
- * Handler for phpMailer exceptions.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class PhpMailerExceptionHandler extends ExceptionHandler
-{
- /**
- * Resolve a list of error messages to present to the end user.
- *
- * @return array
- */
- protected function determineUserMessages() {
- return [
- new UserMessage("ERROR.MAIL")
- ];
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Handler;
+
+use UserFrosting\Support\Message\UserMessage;
+
+/**
+ * Handler for phpMailer exceptions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PhpMailerExceptionHandler extends ExceptionHandler
+{
+ /**
+ * Resolve a list of error messages to present to the end user.
+ *
+ * @return array
+ */
+ protected function determineUserMessages() {
+ return [
+ new UserMessage("ERROR.MAIL")
+ ];
+ }
+}
diff --git a/main/app/sprinkles/core/src/Error/Renderer/ErrorRenderer.php b/main/app/sprinkles/core/src/Error/Renderer/ErrorRenderer.php
index 0cf12f3..2d464a5 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/ErrorRenderer.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/ErrorRenderer.php
@@ -1,63 +1,63 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-use Slim\Http\Body;
-
-abstract class ErrorRenderer implements ErrorRendererInterface
-{
- /**
- * @var ServerRequestInterface
- */
- protected $request;
-
- /**
- * @var ResponseInterface
- */
- protected $response;
-
- /**
- * @var Exception
- */
- protected $exception;
-
- /**
- * Tells the renderer whether or not to output detailed error information to the client.
- * Each renderer may choose if and how to implement this.
- *
- * @var bool
- */
- protected $displayErrorDetails;
-
- /**
- * Create a new ErrorRenderer object.
- *
- * @param ServerRequestInterface $request The most recent Request object
- * @param ResponseInterface $response The most recent Response object
- * @param Exception $exception The caught Exception object
- * @param bool $displayErrorDetails
- */
- public function __construct($request, $response, $exception, $displayErrorDetails = FALSE) {
- $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+use Slim\Http\Body;
+
+abstract class ErrorRenderer implements ErrorRendererInterface
+{
+ /**
+ * @var ServerRequestInterface
+ */
+ protected $request;
+
+ /**
+ * @var ResponseInterface
+ */
+ protected $response;
+
+ /**
+ * @var Exception
+ */
+ protected $exception;
+
+ /**
+ * Tells the renderer whether or not to output detailed error information to the client.
+ * Each renderer may choose if and how to implement this.
+ *
+ * @var bool
+ */
+ protected $displayErrorDetails;
+
+ /**
+ * Create a new ErrorRenderer object.
+ *
+ * @param ServerRequestInterface $request The most recent Request object
+ * @param ResponseInterface $response The most recent Response object
+ * @param Exception $exception The caught Exception object
+ * @param bool $displayErrorDetails
+ */
+ public function __construct($request, $response, $exception, $displayErrorDetails = FALSE) {
+ $this->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/main/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php b/main/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php
index efb0bd6..1633a8b 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/ErrorRendererInterface.php
@@ -1,30 +1,30 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-interface ErrorRendererInterface
-{
- /**
- * @param ServerRequestInterface $request The most recent Request object
- * @param ResponseInterface $response The most recent Response object
- * @param Exception $exception The caught Exception object
- * @param bool $displayErrorDetails
- */
- public function __construct($request, $response, $exception, $displayErrorDetails = FALSE);
-
- /**
- * @return string
- */
- public function render();
-
- /**
- * @return \Slim\Http\Body
- */
- public function renderWithBody();
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+interface ErrorRendererInterface
+{
+ /**
+ * @param ServerRequestInterface $request The most recent Request object
+ * @param ResponseInterface $response The most recent Response object
+ * @param Exception $exception The caught Exception object
+ * @param bool $displayErrorDetails
+ */
+ public function __construct($request, $response, $exception, $displayErrorDetails = FALSE);
+
+ /**
+ * @return string
+ */
+ public function render();
+
+ /**
+ * @return \Slim\Http\Body
+ */
+ public function renderWithBody();
+}
diff --git a/main/app/sprinkles/core/src/Error/Renderer/HtmlRenderer.php b/main/app/sprinkles/core/src/Error/Renderer/HtmlRenderer.php
index e97a5de..f7a6ecb 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/HtmlRenderer.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/HtmlRenderer.php
@@ -1,147 +1,147 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-class HtmlRenderer extends ErrorRenderer
-{
- /**
- * Render HTML error report.
- *
- * @return string
- */
- public function render() {
- $title = 'UserFrosting Application Error';
-
- if ($this->displayErrorDetails) {
- $html = '<p>The application could not run because of the following error:</p>';
- $html .= '<h2>Details</h2>';
- $html .= $this->renderException($this->exception);
-
- $html .= '<h2>Your request</h2>';
- $html .= $this->renderRequest();
-
- $html .= '<h2>Response headers</h2>';
- $html .= $this->renderResponseHeaders();
-
- $exception = $this->exception;
- while ($exception = $exception->getPrevious()) {
- $html .= '<h2>Previous exception</h2>';
- $html .= $this->renderException($exception);
- }
- } else {
- $html = '<p>A website error has occurred. Sorry for the temporary inconvenience.</p>';
- }
-
- $output = sprintf(
- "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'>" .
- "<title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana," .
- "sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{" .
- "display:inline-block;width:65px;}table,th,td{font:12px Helvetica,Arial,Verdana," .
- "sans-serif;border:1px solid black;border-collapse:collapse;padding:5px;text-align: left;}" .
- "th{font-weight:600;}" .
- "</style></head><body><h1>%s</h1>%s</body></html>",
- $title,
- $title,
- $html
- );
-
- return $output;
- }
-
- /**
- * Render a summary of the exception.
- *
- * @param Exception $exception
- * @return string
- */
- public function renderException($exception) {
- $html = sprintf('<div><strong>Type:</strong> %s</div>', get_class($exception));
-
- if (($code = $exception->getCode())) {
- $html .= sprintf('<div><strong>Code:</strong> %s</div>', $code);
- }
-
- if (($message = $exception->getMessage())) {
- $html .= sprintf('<div><strong>Message:</strong> %s</div>', htmlentities($message));
- }
-
- if (($file = $exception->getFile())) {
- $html .= sprintf('<div><strong>File:</strong> %s</div>', $file);
- }
-
- if (($line = $exception->getLine())) {
- $html .= sprintf('<div><strong>Line:</strong> %s</div>', $line);
- }
-
- if (($trace = $exception->getTraceAsString())) {
- $html .= '<h2>Trace</h2>';
- $html .= sprintf('<pre>%s</pre>', 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 = '<h3>Request URI:</h3>';
-
- $html .= sprintf('<div><strong>%s</strong> %s</div>', $method, $uri);
-
- $html .= '<h3>Request parameters:</h3>';
-
- $html .= $this->renderTable($params);
-
- $html .= '<h3>Request headers:</h3>';
-
- $html .= $this->renderTable($requestHeaders);
-
- return $html;
- }
-
- /**
- * Render HTML representation of response headers.
- *
- * @return string
- */
- public function renderResponseHeaders() {
- $html = '<h3>Response headers:</h3>';
- $html .= '<em>Additional response headers may have been set by Slim after the error handling routine. Please check your browser console for a complete list.</em><br>';
-
- $html .= $this->renderTable($this->response->getHeaders());
-
- return $html;
- }
-
- /**
- * Render HTML representation of a table of data.
- *
- * @param mixed[] $data the array of data to render.
- *
- * @return string
- */
- protected function renderTable($data) {
- $html = '<table><tr><th>Name</th><th>Value</th></tr>';
- foreach ($data as $name => $value) {
- $value = print_r($value, TRUE);
- $html .= "<tr><td>$name</td><td>$value</td></tr>";
- }
- $html .= '</table>';
-
- return $html;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+class HtmlRenderer extends ErrorRenderer
+{
+ /**
+ * Render HTML error report.
+ *
+ * @return string
+ */
+ public function render() {
+ $title = 'UserFrosting Application Error';
+
+ if ($this->displayErrorDetails) {
+ $html = '<p>The application could not run because of the following error:</p>';
+ $html .= '<h2>Details</h2>';
+ $html .= $this->renderException($this->exception);
+
+ $html .= '<h2>Your request</h2>';
+ $html .= $this->renderRequest();
+
+ $html .= '<h2>Response headers</h2>';
+ $html .= $this->renderResponseHeaders();
+
+ $exception = $this->exception;
+ while ($exception = $exception->getPrevious()) {
+ $html .= '<h2>Previous exception</h2>';
+ $html .= $this->renderException($exception);
+ }
+ } else {
+ $html = '<p>A website error has occurred. Sorry for the temporary inconvenience.</p>';
+ }
+
+ $output = sprintf(
+ "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'>" .
+ "<title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana," .
+ "sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{" .
+ "display:inline-block;width:65px;}table,th,td{font:12px Helvetica,Arial,Verdana," .
+ "sans-serif;border:1px solid black;border-collapse:collapse;padding:5px;text-align: left;}" .
+ "th{font-weight:600;}" .
+ "</style></head><body><h1>%s</h1>%s</body></html>",
+ $title,
+ $title,
+ $html
+ );
+
+ return $output;
+ }
+
+ /**
+ * Render a summary of the exception.
+ *
+ * @param Exception $exception
+ * @return string
+ */
+ public function renderException($exception) {
+ $html = sprintf('<div><strong>Type:</strong> %s</div>', get_class($exception));
+
+ if (($code = $exception->getCode())) {
+ $html .= sprintf('<div><strong>Code:</strong> %s</div>', $code);
+ }
+
+ if (($message = $exception->getMessage())) {
+ $html .= sprintf('<div><strong>Message:</strong> %s</div>', htmlentities($message));
+ }
+
+ if (($file = $exception->getFile())) {
+ $html .= sprintf('<div><strong>File:</strong> %s</div>', $file);
+ }
+
+ if (($line = $exception->getLine())) {
+ $html .= sprintf('<div><strong>Line:</strong> %s</div>', $line);
+ }
+
+ if (($trace = $exception->getTraceAsString())) {
+ $html .= '<h2>Trace</h2>';
+ $html .= sprintf('<pre>%s</pre>', 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 = '<h3>Request URI:</h3>';
+
+ $html .= sprintf('<div><strong>%s</strong> %s</div>', $method, $uri);
+
+ $html .= '<h3>Request parameters:</h3>';
+
+ $html .= $this->renderTable($params);
+
+ $html .= '<h3>Request headers:</h3>';
+
+ $html .= $this->renderTable($requestHeaders);
+
+ return $html;
+ }
+
+ /**
+ * Render HTML representation of response headers.
+ *
+ * @return string
+ */
+ public function renderResponseHeaders() {
+ $html = '<h3>Response headers:</h3>';
+ $html .= '<em>Additional response headers may have been set by Slim after the error handling routine. Please check your browser console for a complete list.</em><br>';
+
+ $html .= $this->renderTable($this->response->getHeaders());
+
+ return $html;
+ }
+
+ /**
+ * Render HTML representation of a table of data.
+ *
+ * @param mixed[] $data the array of data to render.
+ *
+ * @return string
+ */
+ protected function renderTable($data) {
+ $html = '<table><tr><th>Name</th><th>Value</th></tr>';
+ foreach ($data as $name => $value) {
+ $value = print_r($value, TRUE);
+ $html .= "<tr><td>$name</td><td>$value</td></tr>";
+ }
+ $html .= '</table>';
+
+ return $html;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Error/Renderer/JsonRenderer.php b/main/app/sprinkles/core/src/Error/Renderer/JsonRenderer.php
index 6a96780..eb1ddd2 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/JsonRenderer.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/JsonRenderer.php
@@ -1,55 +1,55 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-/**
- * Default JSON Error Renderer
- */
-class JsonRenderer extends ErrorRenderer
-{
- /**
- * @return string
- */
- public function render() {
- $message = $this->exception->getMessage();
- return $this->formatExceptionPayload($message);
- }
-
- /**
- * @param $message
- * @return string
- */
- public function formatExceptionPayload($message) {
- $e = $this->exception;
- $error = ['message' => $message];
-
- if ($this->displayErrorDetails) {
- $error['exception'] = [];
- do {
- $error['exception'][] = $this->formatExceptionFragment($e);
- } while ($e = $e->getPrevious());
- }
-
- return json_encode($error, JSON_PRETTY_PRINT);
- }
-
- /**
- * @param \Exception|\Throwable $e
- * @return array
- */
- public function formatExceptionFragment($e) {
- return [
- 'type' => get_class($e),
- 'code' => $e->getCode(),
- 'message' => $e->getMessage(),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
- ];
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+/**
+ * Default JSON Error Renderer
+ */
+class JsonRenderer extends ErrorRenderer
+{
+ /**
+ * @return string
+ */
+ public function render() {
+ $message = $this->exception->getMessage();
+ return $this->formatExceptionPayload($message);
+ }
+
+ /**
+ * @param $message
+ * @return string
+ */
+ public function formatExceptionPayload($message) {
+ $e = $this->exception;
+ $error = ['message' => $message];
+
+ if ($this->displayErrorDetails) {
+ $error['exception'] = [];
+ do {
+ $error['exception'][] = $this->formatExceptionFragment($e);
+ } while ($e = $e->getPrevious());
+ }
+
+ return json_encode($error, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * @param \Exception|\Throwable $e
+ * @return array
+ */
+ public function formatExceptionFragment($e) {
+ return [
+ 'type' => get_class($e),
+ 'code' => $e->getCode(),
+ 'message' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ ];
+ }
+}
diff --git a/main/app/sprinkles/core/src/Error/Renderer/PlainTextRenderer.php b/main/app/sprinkles/core/src/Error/Renderer/PlainTextRenderer.php
index 9922f22..847681e 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/PlainTextRenderer.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/PlainTextRenderer.php
@@ -1,63 +1,63 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-/**
- * Plain Text Error Renderer
- */
-class PlainTextRenderer extends ErrorRenderer
-{
- public function render() {
- if ($this->displayErrorDetails) {
- return $this->formatExceptionBody();
- }
-
- return $this->exception->getMessage();
- }
-
- public function formatExceptionBody() {
- $e = $this->exception;
-
- $text = 'UserFrosting Application Error:' . PHP_EOL;
- $text .= $this->formatExceptionFragment($e);
-
- while ($e = $e->getPrevious()) {
- $text .= PHP_EOL . 'Previous Error:' . PHP_EOL;
- $text .= $this->formatExceptionFragment($e);
- }
-
- return $text;
- }
-
- /**
- * @param \Exception|\Throwable $e
- * @return string
- */
- public function formatExceptionFragment($e) {
- $text = sprintf('Type: %s' . PHP_EOL, get_class($e));
-
- if ($code = $e->getCode()) {
- $text .= sprintf('Code: %s' . PHP_EOL, $code);
- }
- if ($message = $e->getMessage()) {
- $text .= sprintf('Message: %s' . PHP_EOL, htmlentities($message));
- }
- if ($file = $e->getFile()) {
- $text .= sprintf('File: %s' . PHP_EOL, $file);
- }
- if ($line = $e->getLine()) {
- $text .= sprintf('Line: %s' . PHP_EOL, $line);
- }
- if ($trace = $e->getTraceAsString()) {
- $text .= sprintf('Trace: %s', $trace);
- }
-
- return $text;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+/**
+ * Plain Text Error Renderer
+ */
+class PlainTextRenderer extends ErrorRenderer
+{
+ public function render() {
+ if ($this->displayErrorDetails) {
+ return $this->formatExceptionBody();
+ }
+
+ return $this->exception->getMessage();
+ }
+
+ public function formatExceptionBody() {
+ $e = $this->exception;
+
+ $text = 'UserFrosting Application Error:' . PHP_EOL;
+ $text .= $this->formatExceptionFragment($e);
+
+ while ($e = $e->getPrevious()) {
+ $text .= PHP_EOL . 'Previous Error:' . PHP_EOL;
+ $text .= $this->formatExceptionFragment($e);
+ }
+
+ return $text;
+ }
+
+ /**
+ * @param \Exception|\Throwable $e
+ * @return string
+ */
+ public function formatExceptionFragment($e) {
+ $text = sprintf('Type: %s' . PHP_EOL, get_class($e));
+
+ if ($code = $e->getCode()) {
+ $text .= sprintf('Code: %s' . PHP_EOL, $code);
+ }
+ if ($message = $e->getMessage()) {
+ $text .= sprintf('Message: %s' . PHP_EOL, htmlentities($message));
+ }
+ if ($file = $e->getFile()) {
+ $text .= sprintf('File: %s' . PHP_EOL, $file);
+ }
+ if ($line = $e->getLine()) {
+ $text .= sprintf('Line: %s' . PHP_EOL, $line);
+ }
+ if ($trace = $e->getTraceAsString()) {
+ $text .= sprintf('Trace: %s', $trace);
+ }
+
+ return $text;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Error/Renderer/WhoopsRenderer.php b/main/app/sprinkles/core/src/Error/Renderer/WhoopsRenderer.php
index 4113470..d16d048 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/WhoopsRenderer.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/WhoopsRenderer.php
@@ -1,688 +1,688 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-use InvalidArgumentException;
-use RuntimeException;
-use Symfony\Component\VarDumper\Cloner\AbstractCloner;
-use Symfony\Component\VarDumper\Cloner\VarCloner;
-use UnexpectedValueException;
-use UserFrosting\Sprinkle\Core\Util\Util;
-use Whoops\Exception\Formatter;
-use Whoops\Exception\Inspector;
-use Whoops\Handler\PlainTextHandler;
-use Whoops\Util\Misc;
-use Whoops\Util\TemplateHelper;
-
-class WhoopsRenderer extends ErrorRenderer
-{
- /**
- * Search paths to be scanned for resources, in the reverse
- * order they're declared.
- *
- * @var array
- */
- private $searchPaths = [];
-
- /**
- * Fast lookup cache for known resource locations.
- *
- * @var array
- */
- private $resourceCache = [];
-
- /**
- * The name of the custom css file.
- *
- * @var string
- */
- private $customCss = NULL;
-
- /**
- * @var array[]
- */
- private $extraTables = [];
-
- /**
- * @var bool
- */
- private $handleUnconditionally = FALSE;
-
- /**
- * @var string
- */
- private $pageTitle = 'Whoops! There was an error.';
-
- /**
- * @var array[]
- */
- private $applicationPaths;
-
- /**
- * @var array[]
- */
- private $blacklist = [
- '_GET' => [],
- '_POST' => [],
- '_FILES' => [],
- '_COOKIE' => [],
- '_SESSION' => [],
- '_SERVER' => ['DB_PASSWORD', 'SMTP_PASSWORD'],
- '_ENV' => ['DB_PASSWORD', 'SMTP_PASSWORD'],
- ];
-
- /**
- * A string identifier for a known IDE/text editor, or a closure
- * that resolves a string that can be used to open a given file
- * in an editor. If the string contains the special substrings
- * %file or %line, they will be replaced with the correct data.
- *
- * @example
- * "txmt://open?url=%file&line=%line"
- * @var mixed $editor
- */
- protected $editor;
-
- /**
- * A list of known editor strings
- * @var array
- */
- protected $editors = [
- "sublime" => "subl://open?url=file://%file&line=%line",
- "textmate" => "txmt://open?url=file://%file&line=%line",
- "emacs" => "emacs://open?url=file://%file&line=%line",
- "macvim" => "mvim://open/?url=file://%file&line=%line",
- "phpstorm" => "phpstorm://open?file=%file&line=%line",
- "idea" => "idea://open?file=%file&line=%line",
- ];
-
- /**
- * @var Inspector
- */
- protected $inspector;
-
- /**
- * @var TemplateHelper
- */
- private $templateHelper;
-
- /**
- * {@inheritDoc}
- */
- public function __construct($request, $response, $exception, $displayErrorDetails = FALSE) {
- $this->request = $request;
- $this->response = $response;
- $this->exception = $exception;
- $this->displayErrorDetails = $displayErrorDetails;
-
- if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
- // Register editor using xdebug's file_link_format option.
- $this->editors['xdebug'] = function ($file, $line) {
- return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
- };
- }
-
- // Add the default, local resource search path:
- $this->searchPaths[] = \UserFrosting\VENDOR_DIR . '/filp/whoops/src/Whoops/Resources';
-
- // blacklist php provided auth based values
- $this->blacklist('_SERVER', 'PHP_AUTH_PW');
-
- $this->templateHelper = new TemplateHelper();
-
- // Set up dummy inspector
- $this->inspector = new Inspector($exception);
-
- if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
- $cloner = new VarCloner();
- // Only dump object internals if a custom caster exists.
- $cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
- $class = $stub->class;
- $classes = [$class => $class] + class_parents($class) + class_implements($class);
-
- foreach ($classes as $class) {
- if (isset(AbstractCloner::$defaultCasters[$class])) {
- return $a;
- }
- }
-
- // Remove all internals
- return [];
- }]);
- $this->templateHelper->setCloner($cloner);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public function render() {
- if (!$this->handleUnconditionally()) {
- // Check conditions for outputting HTML:
- // : Make this more robust
- if (php_sapi_name() === 'cli') {
- // Help users who have been relying on an internal test value
- // fix their code to the proper method
- if (isset($_ENV['whoops-test'])) {
- throw new \Exception(
- 'Use handleUnconditionally instead of whoops-test'
- . ' environment variable'
- );
- }
-
- return Handler::DONE;
- }
- }
-
- $templateFile = $this->getResource("views/layout.html.php");
- $cssFile = $this->getResource("css/whoops.base.css");
- $zeptoFile = $this->getResource("js/zepto.min.js");
- $clipboard = $this->getResource("js/clipboard.min.js");
- $jsFile = $this->getResource("js/whoops.base.js");
-
- if ($this->customCss) {
- $customCssFile = $this->getResource($this->customCss);
- }
-
- $inspector = $this->getInspector();
- $frames = $inspector->getFrames();
-
- $code = $inspector->getException()->getCode();
-
- if ($inspector->getException() instanceof \ErrorException) {
- // ErrorExceptions wrap the php-error types within the "severity" property
- $code = Misc::translateErrorCode($inspector->getException()->getSeverity());
- }
-
- // Detect frames that belong to the application.
- if ($this->applicationPaths) {
- /* @var \Whoops\Exception\Frame $frame */
- foreach ($frames as $frame) {
- foreach ($this->applicationPaths as $path) {
- if (substr($frame->getFile(), 0, strlen($path)) === $path) {
- $frame->setApplication(TRUE);
- break;
- }
- }
- }
- }
-
- // Nicely format the session object
- $session = isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [];
- $session = ['session' => Util::prettyPrintArray($session)];
-
- // List of variables that will be passed to the layout template.
- $vars = [
- "page_title" => $this->getPageTitle(),
-
- // : Asset compiler
- "stylesheet" => file_get_contents($cssFile),
- "zepto" => file_get_contents($zeptoFile),
- "clipboard" => file_get_contents($clipboard),
- "javascript" => file_get_contents($jsFile),
-
- // Template paths:
- "header" => $this->getResource("views/header.html.php"),
- "header_outer" => $this->getResource("views/header_outer.html.php"),
- "frame_list" => $this->getResource("views/frame_list.html.php"),
- "frames_description" => $this->getResource("views/frames_description.html.php"),
- "frames_container" => $this->getResource("views/frames_container.html.php"),
- "panel_details" => $this->getResource("views/panel_details.html.php"),
- "panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
- "panel_left" => $this->getResource("views/panel_left.html.php"),
- "panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"),
- "frame_code" => $this->getResource("views/frame_code.html.php"),
- "env_details" => $this->getResource("views/env_details.html.php"),
-
- "title" => $this->getPageTitle(),
- "name" => explode("\\", $inspector->getExceptionName()),
- "message" => $inspector->getException()->getMessage(),
- "code" => $code,
- "plain_exception" => Formatter::formatExceptionPlain($inspector),
- "frames" => $frames,
- "has_frames" => !!count($frames),
- "handler" => $this,
- "handlers" => [$this],
-
- "active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
- "has_frames_tabs" => $this->getApplicationPaths(),
-
- "tables" => [
- "GET Data" => $this->masked($_GET, '_GET'),
- "POST Data" => $this->masked($_POST, '_POST'),
- "Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
- "Cookies" => $this->masked($_COOKIE, '_COOKIE'),
- "Session" => $session,
- "Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
- "Environment Variables" => $this->masked($_ENV, '_ENV'),
- ],
- ];
-
- if (isset($customCssFile)) {
- $vars["stylesheet"] .= file_get_contents($customCssFile);
- }
-
- // Add extra entries list of data tables:
- // : Consolidate addDataTable and addDataTableCallback
- $extraTables = array_map(function ($table) use ($inspector) {
- return $table instanceof \Closure ? $table($inspector) : $table;
- }, $this->getDataTables());
- $vars["tables"] = array_merge($extraTables, $vars["tables"]);
-
- $plainTextHandler = new PlainTextHandler();
- $plainTextHandler->setException($this->getException());
- $plainTextHandler->setInspector($this->getInspector());
- $vars["preface"] = "<!--\n\n\n" . $plainTextHandler->generateResponse() . "\n\n\n\n\n\n\n\n\n\n\n-->";
-
- $this->templateHelper->setVariables($vars);
-
- ob_start();
- $this->templateHelper->render($templateFile);
-
- $result = ob_get_clean();
- return $result;
- }
-
- /**
- * Adds an entry to the list of tables displayed in the template.
- * The expected data is a simple associative array. Any nested arrays
- * will be flattened with print_r
- * @param string $label
- * @param array $data
- */
- public function addDataTable($label, array $data) {
- $this->extraTables[$label] = $data;
- }
-
- /**
- * Lazily adds an entry to the list of tables displayed in the table.
- * The supplied callback argument will be called when the error is rendered,
- * it should produce a simple associative array. Any nested arrays will
- * be flattened with print_r.
- *
- * @throws InvalidArgumentException If $callback is not callable
- * @param string $label
- * @param callable $callback Callable returning an associative array
- */
- public function addDataTableCallback($label, /* callable */
- $callback) {
- if (!is_callable($callback)) {
- throw new InvalidArgumentException('Expecting callback argument to be callable');
- }
-
- $this->extraTables[$label] = function (\Whoops\Exception\Inspector $inspector = NULL) use ($callback) {
- try {
- $result = call_user_func($callback, $inspector);
-
- // Only return the result if it can be iterated over by foreach().
- return is_array($result) || $result instanceof \Traversable ? $result : [];
- } catch (\Exception $e) {
- // Don't allow failure to break the rendering of the original exception.
- return [];
- }
- };
- }
-
- /**
- * blacklist a sensitive value within one of the superglobal arrays.
- *
- * @param $superGlobalName string the name of the superglobal array, e.g. '_GET'
- * @param $key string the key within the superglobal
- */
- public function blacklist($superGlobalName, $key) {
- $this->blacklist[$superGlobalName][] = $key;
- }
-
- /**
- * Returns all the extra data tables registered with this handler.
- * Optionally accepts a 'label' parameter, to only return the data
- * table under that label.
- * @param string|null $label
- * @return array[]|callable
- */
- public function getDataTables($label = NULL) {
- if ($label !== NULL) {
- return isset($this->extraTables[$label]) ?
- $this->extraTables[$label] : [];
- }
-
- return $this->extraTables;
- }
-
- /**
- * Allows to disable all attempts to dynamically decide whether to
- * handle or return prematurely.
- * Set this to ensure that the handler will perform no matter what.
- * @param bool|null $value
- * @return bool|null
- */
- public function handleUnconditionally($value = NULL) {
- if (func_num_args() == 0) {
- return $this->handleUnconditionally;
- }
-
- $this->handleUnconditionally = (bool)$value;
- }
-
- /**
- * Adds an editor resolver, identified by a string
- * name, and that may be a string path, or a callable
- * resolver. If the callable returns a string, it will
- * be set as the file reference's href attribute.
- *
- * @example
- * $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
- * @example
- * $run->addEditor('remove-it', function($file, $line) {
- * unlink($file);
- * return "http://stackoverflow.com";
- * });
- * @param string $identifier
- * @param string $resolver
- */
- public function addEditor($identifier, $resolver) {
- $this->editors[$identifier] = $resolver;
- }
-
- /**
- * Set the editor to use to open referenced files, by a string
- * identifier, or a callable that will be executed for every
- * file reference, with a $file and $line argument, and should
- * return a string.
- *
- * @example
- * $run->setEditor(function($file, $line) { return "file:///{$file}"; });
- * @example
- * $run->setEditor('sublime');
- *
- * @throws InvalidArgumentException If invalid argument identifier provided
- * @param string|callable $editor
- */
- public function setEditor($editor) {
- if (!is_callable($editor) && !isset($this->editors[$editor])) {
- throw new InvalidArgumentException(
- "Unknown editor identifier: $editor. Known editors:" .
- implode(",", array_keys($this->editors))
- );
- }
-
- $this->editor = $editor;
- }
-
- /**
- * Given a string file path, and an integer file line,
- * executes the editor resolver and returns, if available,
- * a string that may be used as the href property for that
- * file reference.
- *
- * @throws InvalidArgumentException If editor resolver does not return a string
- * @param string $filePath
- * @param int $line
- * @return string|bool
- */
- public function getEditorHref($filePath, $line) {
- $editor = $this->getEditor($filePath, $line);
-
- if (empty($editor)) {
- return FALSE;
- }
-
- // Check that the editor is a string, and replace the
- // %line and %file placeholders:
- if (!isset($editor['url']) || !is_string($editor['url'])) {
- throw new UnexpectedValueException(
- __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
- );
- }
-
- $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
- $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
-
- return $editor['url'];
- }
-
- /**
- * Given a boolean if the editor link should
- * act as an Ajax request. The editor must be a
- * valid callable function/closure
- *
- * @throws UnexpectedValueException If editor resolver does not return a boolean
- * @param string $filePath
- * @param int $line
- * @return bool
- */
- public function getEditorAjax($filePath, $line) {
- $editor = $this->getEditor($filePath, $line);
-
- // Check that the ajax is a bool
- if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
- throw new UnexpectedValueException(
- __METHOD__ . " should always resolve to a bool; got something else instead."
- );
- }
- return $editor['ajax'];
- }
-
- /**
- * @param string $title
- * @return void
- */
- public function setPageTitle($title) {
- $this->pageTitle = (string)$title;
- }
-
- /**
- * @return string
- */
- public function getPageTitle() {
- return $this->pageTitle;
- }
-
- /**
- * Adds a path to the list of paths to be searched for
- * resources.
- *
- * @throws InvalidArgumentException If $path is not a valid directory
- *
- * @param string $path
- * @return void
- */
- public function addResourcePath($path) {
- if (!is_dir($path)) {
- throw new InvalidArgumentException(
- "'$path' is not a valid directory"
- );
- }
-
- array_unshift($this->searchPaths, $path);
- }
-
- /**
- * Adds a custom css file to be loaded.
- *
- * @param string $name
- * @return void
- */
- public function addCustomCss($name) {
- $this->customCss = $name;
- }
-
- /**
- * @return array
- */
- public function getResourcePaths() {
- return $this->searchPaths;
- }
-
- /**
- * @deprecated
- *
- * @return string
- */
- public function getResourcesPath() {
- $allPaths = $this->getResourcePaths();
-
- // Compat: return only the first path added
- return end($allPaths) ?: NULL;
- }
-
- /**
- * @deprecated
- *
- * @param string $resourcesPath
- * @return void
- */
- public function setResourcesPath($resourcesPath) {
- $this->addResourcePath($resourcesPath);
- }
-
- /**
- * Return the application paths.
- *
- * @return array
- */
- public function getApplicationPaths() {
- return $this->applicationPaths;
- }
-
- /**
- * Set the application paths.
- *
- * @param array $applicationPaths
- */
- public function setApplicationPaths($applicationPaths) {
- $this->applicationPaths = $applicationPaths;
- }
-
- /**
- * Set the application root path.
- *
- * @param string $applicationRootPath
- */
- public function setApplicationRootPath($applicationRootPath) {
- $this->templateHelper->setApplicationRootPath($applicationRootPath);
- }
-
- /**
- * Given a boolean if the editor link should
- * act as an Ajax request. The editor must be a
- * valid callable function/closure
- *
- * @param string $filePath
- * @param int $line
- * @return array
- */
- protected function getEditor($filePath, $line) {
- if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
- return [];
- }
-
- if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
- return [
- 'ajax' => FALSE,
- 'url' => $this->editors[$this->editor],
- ];
- }
-
- if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
- if (is_callable($this->editor)) {
- $callback = call_user_func($this->editor, $filePath, $line);
- } else {
- $callback = call_user_func($this->editors[$this->editor], $filePath, $line);
- }
-
- if (is_string($callback)) {
- return [
- 'ajax' => FALSE,
- 'url' => $callback,
- ];
- }
-
- return [
- 'ajax' => isset($callback['ajax']) ? $callback['ajax'] : FALSE,
- 'url' => isset($callback['url']) ? $callback['url'] : $callback,
- ];
- }
-
- return [];
- }
-
- /**
- * @return \Throwable
- */
- protected function getException() {
- return $this->exception;
- }
-
- /**
- * @return Inspector
- */
- protected function getInspector() {
- return $this->inspector;
- }
-
- /**
- * Finds a resource, by its relative path, in all available search paths.
- * The search is performed starting at the last search path, and all the
- * way back to the first, enabling a cascading-type system of overrides
- * for all resources.
- *
- * @throws RuntimeException If resource cannot be found in any of the available paths
- *
- * @param string $resource
- * @return string
- */
- protected function getResource($resource) {
- // If the resource was found before, we can speed things up
- // by caching its absolute, resolved path:
- if (isset($this->resourceCache[$resource])) {
- return $this->resourceCache[$resource];
- }
-
- // Search through available search paths, until we find the
- // resource we're after:
- foreach ($this->searchPaths as $path) {
- $fullPath = $path . "/$resource";
-
- if (is_file($fullPath)) {
- // Cache the result:
- $this->resourceCache[$resource] = $fullPath;
- return $fullPath;
- }
- }
-
- // If we got this far, nothing was found.
- throw new RuntimeException(
- "Could not find resource '$resource' in any resource paths."
- . "(searched: " . join(", ", $this->searchPaths) . ")"
- );
- }
-
- /**
- * Checks all values within the given superGlobal array.
- * Blacklisted values will be replaced by a equal length string cointaining only '*' characters.
- *
- * We intentionally dont rely on $GLOBALS as it depends on 'auto_globals_jit' php.ini setting.
- *
- * @param array $superGlobal One of the superglobal arrays
- * @param string $superGlobalName the name of the superglobal array, e.g. '_GET'
- * @return array $values without sensitive data
- */
- private function masked(array $superGlobal, $superGlobalName) {
- $blacklisted = $this->blacklist[$superGlobalName];
-
- $values = $superGlobal;
- foreach ($blacklisted as $key) {
- if (isset($superGlobal[$key])) {
- $values[$key] = str_repeat('*', strlen($superGlobal[$key]));
- }
- }
- return $values;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+use InvalidArgumentException;
+use RuntimeException;
+use Symfony\Component\VarDumper\Cloner\AbstractCloner;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use UnexpectedValueException;
+use UserFrosting\Sprinkle\Core\Util\Util;
+use Whoops\Exception\Formatter;
+use Whoops\Exception\Inspector;
+use Whoops\Handler\PlainTextHandler;
+use Whoops\Util\Misc;
+use Whoops\Util\TemplateHelper;
+
+class WhoopsRenderer extends ErrorRenderer
+{
+ /**
+ * Search paths to be scanned for resources, in the reverse
+ * order they're declared.
+ *
+ * @var array
+ */
+ private $searchPaths = [];
+
+ /**
+ * Fast lookup cache for known resource locations.
+ *
+ * @var array
+ */
+ private $resourceCache = [];
+
+ /**
+ * The name of the custom css file.
+ *
+ * @var string
+ */
+ private $customCss = NULL;
+
+ /**
+ * @var array[]
+ */
+ private $extraTables = [];
+
+ /**
+ * @var bool
+ */
+ private $handleUnconditionally = FALSE;
+
+ /**
+ * @var string
+ */
+ private $pageTitle = 'Whoops! There was an error.';
+
+ /**
+ * @var array[]
+ */
+ private $applicationPaths;
+
+ /**
+ * @var array[]
+ */
+ private $blacklist = [
+ '_GET' => [],
+ '_POST' => [],
+ '_FILES' => [],
+ '_COOKIE' => [],
+ '_SESSION' => [],
+ '_SERVER' => ['DB_PASSWORD', 'SMTP_PASSWORD'],
+ '_ENV' => ['DB_PASSWORD', 'SMTP_PASSWORD'],
+ ];
+
+ /**
+ * A string identifier for a known IDE/text editor, or a closure
+ * that resolves a string that can be used to open a given file
+ * in an editor. If the string contains the special substrings
+ * %file or %line, they will be replaced with the correct data.
+ *
+ * @example
+ * "txmt://open?url=%file&line=%line"
+ * @var mixed $editor
+ */
+ protected $editor;
+
+ /**
+ * A list of known editor strings
+ * @var array
+ */
+ protected $editors = [
+ "sublime" => "subl://open?url=file://%file&line=%line",
+ "textmate" => "txmt://open?url=file://%file&line=%line",
+ "emacs" => "emacs://open?url=file://%file&line=%line",
+ "macvim" => "mvim://open/?url=file://%file&line=%line",
+ "phpstorm" => "phpstorm://open?file=%file&line=%line",
+ "idea" => "idea://open?file=%file&line=%line",
+ ];
+
+ /**
+ * @var Inspector
+ */
+ protected $inspector;
+
+ /**
+ * @var TemplateHelper
+ */
+ private $templateHelper;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function __construct($request, $response, $exception, $displayErrorDetails = FALSE) {
+ $this->request = $request;
+ $this->response = $response;
+ $this->exception = $exception;
+ $this->displayErrorDetails = $displayErrorDetails;
+
+ if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
+ // Register editor using xdebug's file_link_format option.
+ $this->editors['xdebug'] = function ($file, $line) {
+ return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
+ };
+ }
+
+ // Add the default, local resource search path:
+ $this->searchPaths[] = \UserFrosting\VENDOR_DIR . '/filp/whoops/src/Whoops/Resources';
+
+ // blacklist php provided auth based values
+ $this->blacklist('_SERVER', 'PHP_AUTH_PW');
+
+ $this->templateHelper = new TemplateHelper();
+
+ // Set up dummy inspector
+ $this->inspector = new Inspector($exception);
+
+ if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
+ $cloner = new VarCloner();
+ // Only dump object internals if a custom caster exists.
+ $cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
+ $class = $stub->class;
+ $classes = [$class => $class] + class_parents($class) + class_implements($class);
+
+ foreach ($classes as $class) {
+ if (isset(AbstractCloner::$defaultCasters[$class])) {
+ return $a;
+ }
+ }
+
+ // Remove all internals
+ return [];
+ }]);
+ $this->templateHelper->setCloner($cloner);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render() {
+ if (!$this->handleUnconditionally()) {
+ // Check conditions for outputting HTML:
+ // : Make this more robust
+ if (php_sapi_name() === 'cli') {
+ // Help users who have been relying on an internal test value
+ // fix their code to the proper method
+ if (isset($_ENV['whoops-test'])) {
+ throw new \Exception(
+ 'Use handleUnconditionally instead of whoops-test'
+ . ' environment variable'
+ );
+ }
+
+ return Handler::DONE;
+ }
+ }
+
+ $templateFile = $this->getResource("views/layout.html.php");
+ $cssFile = $this->getResource("css/whoops.base.css");
+ $zeptoFile = $this->getResource("js/zepto.min.js");
+ $clipboard = $this->getResource("js/clipboard.min.js");
+ $jsFile = $this->getResource("js/whoops.base.js");
+
+ if ($this->customCss) {
+ $customCssFile = $this->getResource($this->customCss);
+ }
+
+ $inspector = $this->getInspector();
+ $frames = $inspector->getFrames();
+
+ $code = $inspector->getException()->getCode();
+
+ if ($inspector->getException() instanceof \ErrorException) {
+ // ErrorExceptions wrap the php-error types within the "severity" property
+ $code = Misc::translateErrorCode($inspector->getException()->getSeverity());
+ }
+
+ // Detect frames that belong to the application.
+ if ($this->applicationPaths) {
+ /* @var \Whoops\Exception\Frame $frame */
+ foreach ($frames as $frame) {
+ foreach ($this->applicationPaths as $path) {
+ if (substr($frame->getFile(), 0, strlen($path)) === $path) {
+ $frame->setApplication(TRUE);
+ break;
+ }
+ }
+ }
+ }
+
+ // Nicely format the session object
+ $session = isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [];
+ $session = ['session' => Util::prettyPrintArray($session)];
+
+ // List of variables that will be passed to the layout template.
+ $vars = [
+ "page_title" => $this->getPageTitle(),
+
+ // : Asset compiler
+ "stylesheet" => file_get_contents($cssFile),
+ "zepto" => file_get_contents($zeptoFile),
+ "clipboard" => file_get_contents($clipboard),
+ "javascript" => file_get_contents($jsFile),
+
+ // Template paths:
+ "header" => $this->getResource("views/header.html.php"),
+ "header_outer" => $this->getResource("views/header_outer.html.php"),
+ "frame_list" => $this->getResource("views/frame_list.html.php"),
+ "frames_description" => $this->getResource("views/frames_description.html.php"),
+ "frames_container" => $this->getResource("views/frames_container.html.php"),
+ "panel_details" => $this->getResource("views/panel_details.html.php"),
+ "panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
+ "panel_left" => $this->getResource("views/panel_left.html.php"),
+ "panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"),
+ "frame_code" => $this->getResource("views/frame_code.html.php"),
+ "env_details" => $this->getResource("views/env_details.html.php"),
+
+ "title" => $this->getPageTitle(),
+ "name" => explode("\\", $inspector->getExceptionName()),
+ "message" => $inspector->getException()->getMessage(),
+ "code" => $code,
+ "plain_exception" => Formatter::formatExceptionPlain($inspector),
+ "frames" => $frames,
+ "has_frames" => !!count($frames),
+ "handler" => $this,
+ "handlers" => [$this],
+
+ "active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
+ "has_frames_tabs" => $this->getApplicationPaths(),
+
+ "tables" => [
+ "GET Data" => $this->masked($_GET, '_GET'),
+ "POST Data" => $this->masked($_POST, '_POST'),
+ "Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
+ "Cookies" => $this->masked($_COOKIE, '_COOKIE'),
+ "Session" => $session,
+ "Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
+ "Environment Variables" => $this->masked($_ENV, '_ENV'),
+ ],
+ ];
+
+ if (isset($customCssFile)) {
+ $vars["stylesheet"] .= file_get_contents($customCssFile);
+ }
+
+ // Add extra entries list of data tables:
+ // : Consolidate addDataTable and addDataTableCallback
+ $extraTables = array_map(function ($table) use ($inspector) {
+ return $table instanceof \Closure ? $table($inspector) : $table;
+ }, $this->getDataTables());
+ $vars["tables"] = array_merge($extraTables, $vars["tables"]);
+
+ $plainTextHandler = new PlainTextHandler();
+ $plainTextHandler->setException($this->getException());
+ $plainTextHandler->setInspector($this->getInspector());
+ $vars["preface"] = "<!--\n\n\n" . $plainTextHandler->generateResponse() . "\n\n\n\n\n\n\n\n\n\n\n-->";
+
+ $this->templateHelper->setVariables($vars);
+
+ ob_start();
+ $this->templateHelper->render($templateFile);
+
+ $result = ob_get_clean();
+ return $result;
+ }
+
+ /**
+ * Adds an entry to the list of tables displayed in the template.
+ * The expected data is a simple associative array. Any nested arrays
+ * will be flattened with print_r
+ * @param string $label
+ * @param array $data
+ */
+ public function addDataTable($label, array $data) {
+ $this->extraTables[$label] = $data;
+ }
+
+ /**
+ * Lazily adds an entry to the list of tables displayed in the table.
+ * The supplied callback argument will be called when the error is rendered,
+ * it should produce a simple associative array. Any nested arrays will
+ * be flattened with print_r.
+ *
+ * @throws InvalidArgumentException If $callback is not callable
+ * @param string $label
+ * @param callable $callback Callable returning an associative array
+ */
+ public function addDataTableCallback($label, /* callable */
+ $callback) {
+ if (!is_callable($callback)) {
+ throw new InvalidArgumentException('Expecting callback argument to be callable');
+ }
+
+ $this->extraTables[$label] = function (\Whoops\Exception\Inspector $inspector = NULL) use ($callback) {
+ try {
+ $result = call_user_func($callback, $inspector);
+
+ // Only return the result if it can be iterated over by foreach().
+ return is_array($result) || $result instanceof \Traversable ? $result : [];
+ } catch (\Exception $e) {
+ // Don't allow failure to break the rendering of the original exception.
+ return [];
+ }
+ };
+ }
+
+ /**
+ * blacklist a sensitive value within one of the superglobal arrays.
+ *
+ * @param $superGlobalName string the name of the superglobal array, e.g. '_GET'
+ * @param $key string the key within the superglobal
+ */
+ public function blacklist($superGlobalName, $key) {
+ $this->blacklist[$superGlobalName][] = $key;
+ }
+
+ /**
+ * Returns all the extra data tables registered with this handler.
+ * Optionally accepts a 'label' parameter, to only return the data
+ * table under that label.
+ * @param string|null $label
+ * @return array[]|callable
+ */
+ public function getDataTables($label = NULL) {
+ if ($label !== NULL) {
+ return isset($this->extraTables[$label]) ?
+ $this->extraTables[$label] : [];
+ }
+
+ return $this->extraTables;
+ }
+
+ /**
+ * Allows to disable all attempts to dynamically decide whether to
+ * handle or return prematurely.
+ * Set this to ensure that the handler will perform no matter what.
+ * @param bool|null $value
+ * @return bool|null
+ */
+ public function handleUnconditionally($value = NULL) {
+ if (func_num_args() == 0) {
+ return $this->handleUnconditionally;
+ }
+
+ $this->handleUnconditionally = (bool)$value;
+ }
+
+ /**
+ * Adds an editor resolver, identified by a string
+ * name, and that may be a string path, or a callable
+ * resolver. If the callable returns a string, it will
+ * be set as the file reference's href attribute.
+ *
+ * @example
+ * $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
+ * @example
+ * $run->addEditor('remove-it', function($file, $line) {
+ * unlink($file);
+ * return "http://stackoverflow.com";
+ * });
+ * @param string $identifier
+ * @param string $resolver
+ */
+ public function addEditor($identifier, $resolver) {
+ $this->editors[$identifier] = $resolver;
+ }
+
+ /**
+ * Set the editor to use to open referenced files, by a string
+ * identifier, or a callable that will be executed for every
+ * file reference, with a $file and $line argument, and should
+ * return a string.
+ *
+ * @example
+ * $run->setEditor(function($file, $line) { return "file:///{$file}"; });
+ * @example
+ * $run->setEditor('sublime');
+ *
+ * @throws InvalidArgumentException If invalid argument identifier provided
+ * @param string|callable $editor
+ */
+ public function setEditor($editor) {
+ if (!is_callable($editor) && !isset($this->editors[$editor])) {
+ throw new InvalidArgumentException(
+ "Unknown editor identifier: $editor. Known editors:" .
+ implode(",", array_keys($this->editors))
+ );
+ }
+
+ $this->editor = $editor;
+ }
+
+ /**
+ * Given a string file path, and an integer file line,
+ * executes the editor resolver and returns, if available,
+ * a string that may be used as the href property for that
+ * file reference.
+ *
+ * @throws InvalidArgumentException If editor resolver does not return a string
+ * @param string $filePath
+ * @param int $line
+ * @return string|bool
+ */
+ public function getEditorHref($filePath, $line) {
+ $editor = $this->getEditor($filePath, $line);
+
+ if (empty($editor)) {
+ return FALSE;
+ }
+
+ // Check that the editor is a string, and replace the
+ // %line and %file placeholders:
+ if (!isset($editor['url']) || !is_string($editor['url'])) {
+ throw new UnexpectedValueException(
+ __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
+ );
+ }
+
+ $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
+ $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
+
+ return $editor['url'];
+ }
+
+ /**
+ * Given a boolean if the editor link should
+ * act as an Ajax request. The editor must be a
+ * valid callable function/closure
+ *
+ * @throws UnexpectedValueException If editor resolver does not return a boolean
+ * @param string $filePath
+ * @param int $line
+ * @return bool
+ */
+ public function getEditorAjax($filePath, $line) {
+ $editor = $this->getEditor($filePath, $line);
+
+ // Check that the ajax is a bool
+ if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
+ throw new UnexpectedValueException(
+ __METHOD__ . " should always resolve to a bool; got something else instead."
+ );
+ }
+ return $editor['ajax'];
+ }
+
+ /**
+ * @param string $title
+ * @return void
+ */
+ public function setPageTitle($title) {
+ $this->pageTitle = (string)$title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPageTitle() {
+ return $this->pageTitle;
+ }
+
+ /**
+ * Adds a path to the list of paths to be searched for
+ * resources.
+ *
+ * @throws InvalidArgumentException If $path is not a valid directory
+ *
+ * @param string $path
+ * @return void
+ */
+ public function addResourcePath($path) {
+ if (!is_dir($path)) {
+ throw new InvalidArgumentException(
+ "'$path' is not a valid directory"
+ );
+ }
+
+ array_unshift($this->searchPaths, $path);
+ }
+
+ /**
+ * Adds a custom css file to be loaded.
+ *
+ * @param string $name
+ * @return void
+ */
+ public function addCustomCss($name) {
+ $this->customCss = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getResourcePaths() {
+ return $this->searchPaths;
+ }
+
+ /**
+ * @deprecated
+ *
+ * @return string
+ */
+ public function getResourcesPath() {
+ $allPaths = $this->getResourcePaths();
+
+ // Compat: return only the first path added
+ return end($allPaths) ?: NULL;
+ }
+
+ /**
+ * @deprecated
+ *
+ * @param string $resourcesPath
+ * @return void
+ */
+ public function setResourcesPath($resourcesPath) {
+ $this->addResourcePath($resourcesPath);
+ }
+
+ /**
+ * Return the application paths.
+ *
+ * @return array
+ */
+ public function getApplicationPaths() {
+ return $this->applicationPaths;
+ }
+
+ /**
+ * Set the application paths.
+ *
+ * @param array $applicationPaths
+ */
+ public function setApplicationPaths($applicationPaths) {
+ $this->applicationPaths = $applicationPaths;
+ }
+
+ /**
+ * Set the application root path.
+ *
+ * @param string $applicationRootPath
+ */
+ public function setApplicationRootPath($applicationRootPath) {
+ $this->templateHelper->setApplicationRootPath($applicationRootPath);
+ }
+
+ /**
+ * Given a boolean if the editor link should
+ * act as an Ajax request. The editor must be a
+ * valid callable function/closure
+ *
+ * @param string $filePath
+ * @param int $line
+ * @return array
+ */
+ protected function getEditor($filePath, $line) {
+ if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
+ return [];
+ }
+
+ if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
+ return [
+ 'ajax' => FALSE,
+ 'url' => $this->editors[$this->editor],
+ ];
+ }
+
+ if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
+ if (is_callable($this->editor)) {
+ $callback = call_user_func($this->editor, $filePath, $line);
+ } else {
+ $callback = call_user_func($this->editors[$this->editor], $filePath, $line);
+ }
+
+ if (is_string($callback)) {
+ return [
+ 'ajax' => FALSE,
+ 'url' => $callback,
+ ];
+ }
+
+ return [
+ 'ajax' => isset($callback['ajax']) ? $callback['ajax'] : FALSE,
+ 'url' => isset($callback['url']) ? $callback['url'] : $callback,
+ ];
+ }
+
+ return [];
+ }
+
+ /**
+ * @return \Throwable
+ */
+ protected function getException() {
+ return $this->exception;
+ }
+
+ /**
+ * @return Inspector
+ */
+ protected function getInspector() {
+ return $this->inspector;
+ }
+
+ /**
+ * Finds a resource, by its relative path, in all available search paths.
+ * The search is performed starting at the last search path, and all the
+ * way back to the first, enabling a cascading-type system of overrides
+ * for all resources.
+ *
+ * @throws RuntimeException If resource cannot be found in any of the available paths
+ *
+ * @param string $resource
+ * @return string
+ */
+ protected function getResource($resource) {
+ // If the resource was found before, we can speed things up
+ // by caching its absolute, resolved path:
+ if (isset($this->resourceCache[$resource])) {
+ return $this->resourceCache[$resource];
+ }
+
+ // Search through available search paths, until we find the
+ // resource we're after:
+ foreach ($this->searchPaths as $path) {
+ $fullPath = $path . "/$resource";
+
+ if (is_file($fullPath)) {
+ // Cache the result:
+ $this->resourceCache[$resource] = $fullPath;
+ return $fullPath;
+ }
+ }
+
+ // If we got this far, nothing was found.
+ throw new RuntimeException(
+ "Could not find resource '$resource' in any resource paths."
+ . "(searched: " . join(", ", $this->searchPaths) . ")"
+ );
+ }
+
+ /**
+ * Checks all values within the given superGlobal array.
+ * Blacklisted values will be replaced by a equal length string cointaining only '*' characters.
+ *
+ * We intentionally dont rely on $GLOBALS as it depends on 'auto_globals_jit' php.ini setting.
+ *
+ * @param array $superGlobal One of the superglobal arrays
+ * @param string $superGlobalName the name of the superglobal array, e.g. '_GET'
+ * @return array $values without sensitive data
+ */
+ private function masked(array $superGlobal, $superGlobalName) {
+ $blacklisted = $this->blacklist[$superGlobalName];
+
+ $values = $superGlobal;
+ foreach ($blacklisted as $key) {
+ if (isset($superGlobal[$key])) {
+ $values[$key] = str_repeat('*', strlen($superGlobal[$key]));
+ }
+ }
+ return $values;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Error/Renderer/XmlRenderer.php b/main/app/sprinkles/core/src/Error/Renderer/XmlRenderer.php
index 5c51d8d..8e17554 100644
--- a/main/app/sprinkles/core/src/Error/Renderer/XmlRenderer.php
+++ b/main/app/sprinkles/core/src/Error/Renderer/XmlRenderer.php
@@ -1,47 +1,47 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Error\Renderer;
-
-/**
- * Default XML Error Renderer
- */
-class XmlRenderer extends ErrorRenderer
-{
- /**
- * @return string
- */
- public function render() {
- $e = $this->exception;
- $xml = "<error>\n <message>UserFrosting Application Error</message>\n";
- if ($this->displayErrorDetails) {
- do {
- $xml .= " <exception>\n";
- $xml .= " <type>" . get_class($e) . "</type>\n";
- $xml .= " <code>" . $e->getCode() . "</code>\n";
- $xml .= " <message>" . $this->createCdataSection($e->getMessage()) . "</message>\n";
- $xml .= " <file>" . $e->getFile() . "</file>\n";
- $xml .= " <line>" . $e->getLine() . "</line>\n";
- $xml .= " </exception>\n";
- } while ($e = $e->getPrevious());
- }
- $xml .= "</error>";
-
- return $xml;
- }
-
- /**
- * Returns a CDATA section with the given content.
- *
- * @param string $content
- * @return string
- */
- private function createCdataSection($content) {
- return sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $content));
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Error\Renderer;
+
+/**
+ * Default XML Error Renderer
+ */
+class XmlRenderer extends ErrorRenderer
+{
+ /**
+ * @return string
+ */
+ public function render() {
+ $e = $this->exception;
+ $xml = "<error>\n <message>UserFrosting Application Error</message>\n";
+ if ($this->displayErrorDetails) {
+ do {
+ $xml .= " <exception>\n";
+ $xml .= " <type>" . get_class($e) . "</type>\n";
+ $xml .= " <code>" . $e->getCode() . "</code>\n";
+ $xml .= " <message>" . $this->createCdataSection($e->getMessage()) . "</message>\n";
+ $xml .= " <file>" . $e->getFile() . "</file>\n";
+ $xml .= " <line>" . $e->getLine() . "</line>\n";
+ $xml .= " </exception>\n";
+ } while ($e = $e->getPrevious());
+ }
+ $xml .= "</error>";
+
+ return $xml;
+ }
+
+ /**
+ * Returns a CDATA section with the given content.
+ *
+ * @param string $content
+ * @return string
+ */
+ private function createCdataSection($content) {
+ return sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $content));
+ }
+}
diff --git a/main/app/sprinkles/core/src/Facades/Debug.php b/main/app/sprinkles/core/src/Facades/Debug.php
index 40cc263..58e4302 100644
--- a/main/app/sprinkles/core/src/Facades/Debug.php
+++ b/main/app/sprinkles/core/src/Facades/Debug.php
@@ -1,28 +1,28 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Facades;
-
-use UserFrosting\System\Facade;
-
-/**
- * Implements facade for the "debugLogger" service
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Debug extends Facade
-{
- /**
- * Get the registered name of the component.
- *
- * @return string
- */
- protected static function getFacadeAccessor() {
- return 'debugLogger';
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Facades;
+
+use UserFrosting\System\Facade;
+
+/**
+ * Implements facade for the "debugLogger" service
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Debug extends Facade
+{
+ /**
+ * Get the registered name of the component.
+ *
+ * @return string
+ */
+ protected static function getFacadeAccessor() {
+ return 'debugLogger';
+ }
+}
diff --git a/main/app/sprinkles/core/src/Facades/Translator.php b/main/app/sprinkles/core/src/Facades/Translator.php
index d1d365b..b1d2e32 100644
--- a/main/app/sprinkles/core/src/Facades/Translator.php
+++ b/main/app/sprinkles/core/src/Facades/Translator.php
@@ -1,28 +1,28 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Facades;
-
-use UserFrosting\System\Facade;
-
-/**
- * Implements facade for the "translator" service
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Translator extends Facade
-{
- /**
- * Get the registered name of the component.
- *
- * @return string
- */
- protected static function getFacadeAccessor() {
- return 'translator';
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Facades;
+
+use UserFrosting\System\Facade;
+
+/**
+ * Implements facade for the "translator" service
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Translator extends Facade
+{
+ /**
+ * Get the registered name of the component.
+ *
+ * @return string
+ */
+ protected static function getFacadeAccessor() {
+ return 'translator';
+ }
+}
diff --git a/main/app/sprinkles/core/src/Http/Concerns/DeterminesContentType.php b/main/app/sprinkles/core/src/Http/Concerns/DeterminesContentType.php
index 06cf0b3..2a22331 100644
--- a/main/app/sprinkles/core/src/Http/Concerns/DeterminesContentType.php
+++ b/main/app/sprinkles/core/src/Http/Concerns/DeterminesContentType.php
@@ -1,76 +1,76 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Http\Concerns;
-
-use Psr\Http\Message\ServerRequestInterface;
-
-/**
- * Trait for classes that need to determine a request's accepted content type(s).
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-trait DeterminesContentType
-{
- /**
- * Known handled content types
- *
- * @var array
- */
- protected $knownContentTypes = [
- 'application/json',
- 'application/xml',
- 'text/xml',
- 'text/html',
- 'text/plain'
- ];
-
- /**
- * Determine which content type we know about is wanted using Accept header
- *
- * Note: This method is a bare-bones implementation designed specifically for
- * Slim's error handling requirements. Consider a fully-feature solution such
- * as willdurand/negotiation for any other situation.
- *
- * @param ServerRequestInterface $request
- * @return string
- */
- protected function determineContentType(ServerRequestInterface $request, $ajaxDebug = FALSE) {
- // For AJAX requests, if AJAX debugging is turned on, always return html
- if ($ajaxDebug && $request->isXhr()) {
- return 'text/html';
- }
-
- $acceptHeader = $request->getHeaderLine('Accept');
- $selectedContentTypes = array_intersect(explode(',', $acceptHeader), $this->knownContentTypes);
- $count = count($selectedContentTypes);
-
- if ($count) {
- $current = current($selectedContentTypes);
-
- /**
- * Ensure other supported content types take precedence over text/plain
- * when multiple content types are provided via Accept header.
- */
- if ($current === 'text/plain' && $count > 1) {
- return next($selectedContentTypes);
- }
-
- return $current;
- }
-
- if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) {
- $mediaType = 'application/' . $matches[1];
- if (in_array($mediaType, $this->knownContentTypes)) {
- return $mediaType;
- }
- }
-
- return 'text/html';
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Http\Concerns;
+
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * Trait for classes that need to determine a request's accepted content type(s).
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+trait DeterminesContentType
+{
+ /**
+ * Known handled content types
+ *
+ * @var array
+ */
+ protected $knownContentTypes = [
+ 'application/json',
+ 'application/xml',
+ 'text/xml',
+ 'text/html',
+ 'text/plain'
+ ];
+
+ /**
+ * Determine which content type we know about is wanted using Accept header
+ *
+ * Note: This method is a bare-bones implementation designed specifically for
+ * Slim's error handling requirements. Consider a fully-feature solution such
+ * as willdurand/negotiation for any other situation.
+ *
+ * @param ServerRequestInterface $request
+ * @return string
+ */
+ protected function determineContentType(ServerRequestInterface $request, $ajaxDebug = FALSE) {
+ // For AJAX requests, if AJAX debugging is turned on, always return html
+ if ($ajaxDebug && $request->isXhr()) {
+ return 'text/html';
+ }
+
+ $acceptHeader = $request->getHeaderLine('Accept');
+ $selectedContentTypes = array_intersect(explode(',', $acceptHeader), $this->knownContentTypes);
+ $count = count($selectedContentTypes);
+
+ if ($count) {
+ $current = current($selectedContentTypes);
+
+ /**
+ * Ensure other supported content types take precedence over text/plain
+ * when multiple content types are provided via Accept header.
+ */
+ if ($current === 'text/plain' && $count > 1) {
+ return next($selectedContentTypes);
+ }
+
+ return $current;
+ }
+
+ if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) {
+ $mediaType = 'application/' . $matches[1];
+ if (in_array($mediaType, $this->knownContentTypes)) {
+ return $mediaType;
+ }
+ }
+
+ return 'text/html';
+ }
+}
diff --git a/main/app/sprinkles/core/src/Log/DatabaseHandler.php b/main/app/sprinkles/core/src/Log/DatabaseHandler.php
index d4c9fce..49eb5c2 100644
--- a/main/app/sprinkles/core/src/Log/DatabaseHandler.php
+++ b/main/app/sprinkles/core/src/Log/DatabaseHandler.php
@@ -1,52 +1,52 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Log;
-
-use Monolog\Logger;
-use Monolog\Handler\AbstractProcessingHandler;
-
-/**
- * Monolog handler for storing the record to a database.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class DatabaseHandler extends AbstractProcessingHandler
-{
- /**
- * @var UserFrosting\Sprinkle\Core\Util\ClassMapper
- */
- protected $classMapper;
-
- /**
- * @var string
- */
- protected $modelIdentifier;
-
- /**
- * Create a new DatabaseHandler object.
- *
- * @param ClassMapper $classMapper Maps the modelIdentifier to the specific Eloquent model.
- * @param string $modelIdentifier
- * @param int $level The minimum logging level at which this handler will be triggered
- * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
- */
- public function __construct($classMapper, $modelIdentifier, $level = Logger::DEBUG, $bubble = TRUE) {
- $this->classMapper = $classMapper;
- $this->modelName = $modelIdentifier;
- parent::__construct($level, $bubble);
- }
-
- /**
- * {@inheritDoc}
- */
- protected function write(array $record) {
- $log = $this->classMapper->createInstance($this->modelName, $record['extra']);
- $log->save();
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Log;
+
+use Monolog\Logger;
+use Monolog\Handler\AbstractProcessingHandler;
+
+/**
+ * Monolog handler for storing the record to a database.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class DatabaseHandler extends AbstractProcessingHandler
+{
+ /**
+ * @var UserFrosting\Sprinkle\Core\Util\ClassMapper
+ */
+ protected $classMapper;
+
+ /**
+ * @var string
+ */
+ protected $modelIdentifier;
+
+ /**
+ * Create a new DatabaseHandler object.
+ *
+ * @param ClassMapper $classMapper Maps the modelIdentifier to the specific Eloquent model.
+ * @param string $modelIdentifier
+ * @param int $level The minimum logging level at which this handler will be triggered
+ * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
+ */
+ public function __construct($classMapper, $modelIdentifier, $level = Logger::DEBUG, $bubble = TRUE) {
+ $this->classMapper = $classMapper;
+ $this->modelName = $modelIdentifier;
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function write(array $record) {
+ $log = $this->classMapper->createInstance($this->modelName, $record['extra']);
+ $log->save();
+ }
+}
diff --git a/main/app/sprinkles/core/src/Log/MixedFormatter.php b/main/app/sprinkles/core/src/Log/MixedFormatter.php
index ce21879..ef70268 100644
--- a/main/app/sprinkles/core/src/Log/MixedFormatter.php
+++ b/main/app/sprinkles/core/src/Log/MixedFormatter.php
@@ -1,58 +1,58 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Log;
-
-use Monolog\Formatter\LineFormatter;
-
-/**
- * Monolog formatter for pretty-printing arrays and objects.
- *
- * This class extends the basic Monolog LineFormatter class, and provides basically the same functionality but with one exception:
- * if the second parameter of any logging method (debug, error, info, etc) is an array, it will print it as a nicely formatted,
- * multi-line JSON object instead of all on a single line.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class MixedFormatter extends LineFormatter
-{
-
- /**
- * Return the JSON representation of a value
- *
- * @param mixed $data
- * @param bool $ignoreErrors
- * @throws \RuntimeException if encoding fails and errors are not ignored
- * @return string
- */
- protected function toJson($data, $ignoreErrors = FALSE) {
- // suppress json_encode errors since it's twitchy with some inputs
- if ($ignoreErrors) {
- return @$this->jsonEncodePretty($data);
- }
-
- $json = $this->jsonEncodePretty($data);
-
- if ($json === FALSE) {
- $json = $this->handleJsonError(json_last_error(), $data);
- }
-
- return $json;
- }
-
- /**
- * @param mixed $data
- * @return string JSON encoded data or null on failure
- */
- private function jsonEncodePretty($data) {
- if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
- return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
- }
-
- return json_encode($data);
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Log;
+
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * Monolog formatter for pretty-printing arrays and objects.
+ *
+ * This class extends the basic Monolog LineFormatter class, and provides basically the same functionality but with one exception:
+ * if the second parameter of any logging method (debug, error, info, etc) is an array, it will print it as a nicely formatted,
+ * multi-line JSON object instead of all on a single line.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class MixedFormatter extends LineFormatter
+{
+
+ /**
+ * Return the JSON representation of a value
+ *
+ * @param mixed $data
+ * @param bool $ignoreErrors
+ * @throws \RuntimeException if encoding fails and errors are not ignored
+ * @return string
+ */
+ protected function toJson($data, $ignoreErrors = FALSE) {
+ // suppress json_encode errors since it's twitchy with some inputs
+ if ($ignoreErrors) {
+ return @$this->jsonEncodePretty($data);
+ }
+
+ $json = $this->jsonEncodePretty($data);
+
+ if ($json === FALSE) {
+ $json = $this->handleJsonError(json_last_error(), $data);
+ }
+
+ return $json;
+ }
+
+ /**
+ * @param mixed $data
+ * @return string JSON encoded data or null on failure
+ */
+ private function jsonEncodePretty($data) {
+ if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
+ return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+ }
+
+ return json_encode($data);
+ }
+}
diff --git a/main/app/sprinkles/core/src/Mail/EmailRecipient.php b/main/app/sprinkles/core/src/Mail/EmailRecipient.php
index 33b7db7..29b5de8 100644
--- a/main/app/sprinkles/core/src/Mail/EmailRecipient.php
+++ b/main/app/sprinkles/core/src/Mail/EmailRecipient.php
@@ -1,129 +1,129 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Mail;
-
-/**
- * EmailRecipient Class
- *
- * A class representing a recipient for a MailMessage, with associated parameters.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class EmailRecipient
-{
-
- /**
- * @var string The email address for this recipient.
- */
- protected $email;
-
- /**
- * @var string The name for this recipient.
- */
- protected $name;
-
- /**
- * @var array Any additional parameters (name => value) to use when rendering an email template for this recipient.
- */
- protected $params = [];
-
- /**
- * @var array A list of CCs for this recipient. Each CC is an associative array with `email` and `name` properties.
- */
- protected $cc = [];
-
- /**
- * @var array A list of BCCs for this recipient. Each BCC is an associative array with `email` and `name` properties.
- */
- protected $bcc = [];
-
- /**
- * Create a new EmailRecipient instance.
- *
- * @param string $email The primary recipient email address.
- * @param string $name The primary recipient name.
- * @param array $params An array of template parameters to render the email message with for this particular recipient.
- */
- public function __construct($email, $name = "", $params = []) {
- $this->email = $email;
- $this->name = $name;
- $this->params = $params;
- }
-
- /**
- * Add a CC for this primary recipient.
- *
- * @param string $email The CC recipient email address.
- * @param string $name The CC recipient name.
- */
- public function cc($email, $name = "") {
- $this->cc[] = [
- "email" => $email,
- "name" => $name
- ];
- }
-
- /**
- * Add a BCC for this primary recipient.
- *
- * @param string $email The BCC recipient email address.
- * @param string $name The BCC recipient name.
- */
- public function bcc($email, $name = "") {
- $this->bcc[] = [
- "email" => $email,
- "name" => $name
- ];
- }
-
- /**
- * Get the primary recipient email address.
- *
- * @return string the primary recipient email address.
- */
- public function getEmail() {
- return $this->email;
- }
-
- /**
- * Get the primary recipient name.
- *
- * @return string the primary recipient name.
- */
- public function getName() {
- return $this->name;
- }
-
- /**
- * Get the parameters to use when rendering the template this recipient.
- *
- * @return array The parameters (name => value) to use when rendering an email template for this recipient.
- */
- public function getParams() {
- return $this->params;
- }
-
- /**
- * Get the list of CCs for this recipient.
- *
- * @return array A list of CCs for this recipient. Each CC is an associative array with `email` and `name` properties.
- */
- public function getCCs() {
- return $this->cc;
- }
-
- /**
- * Get the list of BCCs for this recipient.
- *
- * @return array A list of BCCs for this recipient. Each BCC is an associative array with `email` and `name` properties.
- */
- public function getBCCs() {
- return $this->bcc;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Mail;
+
+/**
+ * EmailRecipient Class
+ *
+ * A class representing a recipient for a MailMessage, with associated parameters.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class EmailRecipient
+{
+
+ /**
+ * @var string The email address for this recipient.
+ */
+ protected $email;
+
+ /**
+ * @var string The name for this recipient.
+ */
+ protected $name;
+
+ /**
+ * @var array Any additional parameters (name => value) to use when rendering an email template for this recipient.
+ */
+ protected $params = [];
+
+ /**
+ * @var array A list of CCs for this recipient. Each CC is an associative array with `email` and `name` properties.
+ */
+ protected $cc = [];
+
+ /**
+ * @var array A list of BCCs for this recipient. Each BCC is an associative array with `email` and `name` properties.
+ */
+ protected $bcc = [];
+
+ /**
+ * Create a new EmailRecipient instance.
+ *
+ * @param string $email The primary recipient email address.
+ * @param string $name The primary recipient name.
+ * @param array $params An array of template parameters to render the email message with for this particular recipient.
+ */
+ public function __construct($email, $name = "", $params = []) {
+ $this->email = $email;
+ $this->name = $name;
+ $this->params = $params;
+ }
+
+ /**
+ * Add a CC for this primary recipient.
+ *
+ * @param string $email The CC recipient email address.
+ * @param string $name The CC recipient name.
+ */
+ public function cc($email, $name = "") {
+ $this->cc[] = [
+ "email" => $email,
+ "name" => $name
+ ];
+ }
+
+ /**
+ * Add a BCC for this primary recipient.
+ *
+ * @param string $email The BCC recipient email address.
+ * @param string $name The BCC recipient name.
+ */
+ public function bcc($email, $name = "") {
+ $this->bcc[] = [
+ "email" => $email,
+ "name" => $name
+ ];
+ }
+
+ /**
+ * Get the primary recipient email address.
+ *
+ * @return string the primary recipient email address.
+ */
+ public function getEmail() {
+ return $this->email;
+ }
+
+ /**
+ * Get the primary recipient name.
+ *
+ * @return string the primary recipient name.
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Get the parameters to use when rendering the template this recipient.
+ *
+ * @return array The parameters (name => value) to use when rendering an email template for this recipient.
+ */
+ public function getParams() {
+ return $this->params;
+ }
+
+ /**
+ * Get the list of CCs for this recipient.
+ *
+ * @return array A list of CCs for this recipient. Each CC is an associative array with `email` and `name` properties.
+ */
+ public function getCCs() {
+ return $this->cc;
+ }
+
+ /**
+ * Get the list of BCCs for this recipient.
+ *
+ * @return array A list of BCCs for this recipient. Each BCC is an associative array with `email` and `name` properties.
+ */
+ public function getBCCs() {
+ return $this->bcc;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Mail/MailMessage.php b/main/app/sprinkles/core/src/Mail/MailMessage.php
index 6aea56d..c16d1ad 100644
--- a/main/app/sprinkles/core/src/Mail/MailMessage.php
+++ b/main/app/sprinkles/core/src/Mail/MailMessage.php
@@ -1,175 +1,175 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Mail;
-
-/**
- * MailMessage Class
- *
- * Represents a basic mail message, containing a static subject and body.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-abstract class MailMessage
-{
- /**
- * @var string The current sender email address.
- */
- protected $fromEmail = "";
-
- /**
- * @var string The current sender name.
- */
- protected $fromName = NULL;
-
- /**
- * @var EmailRecipient[] A list of recipients for this message.
- */
- protected $recipients = [];
-
- /**
- * @var string The current reply-to email.
- */
- protected $replyEmail = NULL;
-
- /**
- * @var string The current reply-to name.
- */
- protected $replyName = NULL;
-
- /**
- * Gets the fully rendered text of the message body.
- *
- * @return string
- */
- abstract public function renderBody($params = []);
-
- /**
- * Gets the fully rendered text of the message subject.
- *
- * @return string
- */
- abstract public function renderSubject($params = []);
-
- /**
- * Add an email recipient.
- *
- * @param EmailRecipient $recipient
- */
- public function addEmailRecipient(EmailRecipient $recipient) {
- $this->recipients[] = $recipient;
- return $this;
- }
-
- /**
- * Clears out all recipients for this message.
- */
- public function clearRecipients() {
- $this->recipients = array();
- }
-
- /**
- * Set sender information for this message.
- *
- * This is a shortcut for calling setFromEmail, setFromName, setReplyEmail, and setReplyName.
- * @param string $fromInfo An array containing 'email', 'name', 'reply_email', and 'reply_name'.
- */
- public function from($fromInfo = []) {
- $this->setFromEmail(isset($fromInfo['email']) ? $fromInfo['email'] : "");
- $this->setFromName(isset($fromInfo['name']) ? $fromInfo['name'] : NULL);
- $this->setReplyEmail(isset($fromInfo['reply_email']) ? $fromInfo['reply_email'] : NULL);
- $this->setReplyName(isset($fromInfo['reply_name']) ? $fromInfo['reply_name'] : NULL);
-
- return $this;
- }
-
- /**
- * Get the sender email address.
- *
- * @return string
- */
- public function getFromEmail() {
- return $this->fromEmail;
- }
-
- /**
- * Get the sender name. Defaults to the email address if name is not set.
- *
- * @return string
- */
- public function getFromName() {
- return isset($this->fromName) ? $this->fromName : $this->getFromEmail();
- }
-
- /**
- * Get the list of recipients for this message.
- *
- * @return EmailRecipient[]
- */
- public function getRecipients() {
- return $this->recipients;
- }
-
- /**
- * Get the 'reply-to' address for this message. Defaults to the sender email.
- *
- * @return string
- */
- public function getReplyEmail() {
- return isset($this->replyEmail) ? $this->replyEmail : $this->getFromEmail();
- }
-
- /**
- * Get the 'reply-to' name for this message. Defaults to the sender name.
- *
- * @return string
- */
- public function getReplyName() {
- return isset($this->replyName) ? $this->replyName : $this->getFromName();
- }
-
- /**
- * Set the sender email address.
- *
- * @param string $fromEmail
- */
- public function setFromEmail($fromEmail) {
- $this->fromEmail = $fromEmail;
- return $this;
- }
-
- /**
- * Set the sender name.
- *
- * @param string $fromName
- */
- public function setFromName($fromName) {
- $this->fromName = $fromName;
- return $this;
- }
-
- /**
- * Set the sender 'reply-to' address.
- *
- * @param string $replyEmail
- */
- public function setReplyEmail($replyEmail) {
- $this->replyEmail = $replyEmail;
- return $this;
- }
-
- /**
- * Set the sender 'reply-to' name.
- *
- * @param string $replyName
- */
- public function setReplyName($replyName) {
- $this->replyName = $replyName;
- return $this;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Mail;
+
+/**
+ * MailMessage Class
+ *
+ * Represents a basic mail message, containing a static subject and body.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+abstract class MailMessage
+{
+ /**
+ * @var string The current sender email address.
+ */
+ protected $fromEmail = "";
+
+ /**
+ * @var string The current sender name.
+ */
+ protected $fromName = NULL;
+
+ /**
+ * @var EmailRecipient[] A list of recipients for this message.
+ */
+ protected $recipients = [];
+
+ /**
+ * @var string The current reply-to email.
+ */
+ protected $replyEmail = NULL;
+
+ /**
+ * @var string The current reply-to name.
+ */
+ protected $replyName = NULL;
+
+ /**
+ * Gets the fully rendered text of the message body.
+ *
+ * @return string
+ */
+ abstract public function renderBody($params = []);
+
+ /**
+ * Gets the fully rendered text of the message subject.
+ *
+ * @return string
+ */
+ abstract public function renderSubject($params = []);
+
+ /**
+ * Add an email recipient.
+ *
+ * @param EmailRecipient $recipient
+ */
+ public function addEmailRecipient(EmailRecipient $recipient) {
+ $this->recipients[] = $recipient;
+ return $this;
+ }
+
+ /**
+ * Clears out all recipients for this message.
+ */
+ public function clearRecipients() {
+ $this->recipients = array();
+ }
+
+ /**
+ * Set sender information for this message.
+ *
+ * This is a shortcut for calling setFromEmail, setFromName, setReplyEmail, and setReplyName.
+ * @param string $fromInfo An array containing 'email', 'name', 'reply_email', and 'reply_name'.
+ */
+ public function from($fromInfo = []) {
+ $this->setFromEmail(isset($fromInfo['email']) ? $fromInfo['email'] : "");
+ $this->setFromName(isset($fromInfo['name']) ? $fromInfo['name'] : NULL);
+ $this->setReplyEmail(isset($fromInfo['reply_email']) ? $fromInfo['reply_email'] : NULL);
+ $this->setReplyName(isset($fromInfo['reply_name']) ? $fromInfo['reply_name'] : NULL);
+
+ return $this;
+ }
+
+ /**
+ * Get the sender email address.
+ *
+ * @return string
+ */
+ public function getFromEmail() {
+ return $this->fromEmail;
+ }
+
+ /**
+ * Get the sender name. Defaults to the email address if name is not set.
+ *
+ * @return string
+ */
+ public function getFromName() {
+ return isset($this->fromName) ? $this->fromName : $this->getFromEmail();
+ }
+
+ /**
+ * Get the list of recipients for this message.
+ *
+ * @return EmailRecipient[]
+ */
+ public function getRecipients() {
+ return $this->recipients;
+ }
+
+ /**
+ * Get the 'reply-to' address for this message. Defaults to the sender email.
+ *
+ * @return string
+ */
+ public function getReplyEmail() {
+ return isset($this->replyEmail) ? $this->replyEmail : $this->getFromEmail();
+ }
+
+ /**
+ * Get the 'reply-to' name for this message. Defaults to the sender name.
+ *
+ * @return string
+ */
+ public function getReplyName() {
+ return isset($this->replyName) ? $this->replyName : $this->getFromName();
+ }
+
+ /**
+ * Set the sender email address.
+ *
+ * @param string $fromEmail
+ */
+ public function setFromEmail($fromEmail) {
+ $this->fromEmail = $fromEmail;
+ return $this;
+ }
+
+ /**
+ * Set the sender name.
+ *
+ * @param string $fromName
+ */
+ public function setFromName($fromName) {
+ $this->fromName = $fromName;
+ return $this;
+ }
+
+ /**
+ * Set the sender 'reply-to' address.
+ *
+ * @param string $replyEmail
+ */
+ public function setReplyEmail($replyEmail) {
+ $this->replyEmail = $replyEmail;
+ return $this;
+ }
+
+ /**
+ * Set the sender 'reply-to' name.
+ *
+ * @param string $replyName
+ */
+ public function setReplyName($replyName) {
+ $this->replyName = $replyName;
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Mail/Mailer.php b/main/app/sprinkles/core/src/Mail/Mailer.php
index 761d15a..5331107 100644
--- a/main/app/sprinkles/core/src/Mail/Mailer.php
+++ b/main/app/sprinkles/core/src/Mail/Mailer.php
@@ -1,200 +1,200 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Mail;
-
-use Monolog\Logger;
-
-/**
- * Mailer Class
- *
- * A basic wrapper for sending template-based emails.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Mailer
-{
- /**
- * @var Logger
- */
- protected $logger;
-
- /**
- * @var \PHPMailer
- */
- protected $phpMailer;
-
- /**
- * Create a new Mailer instance.
- *
- * @param Logger $logger A Monolog logger, used to dump debugging info for SMTP server transactions.
- * @param mixed[] $config An array of configuration parameters for phpMailer.
- * @throws \phpmailerException Wrong mailer config value given.
- */
- public function __construct($logger, $config = []) {
- $this->logger = $logger;
-
- // 'true' tells PHPMailer to use exceptions instead of error codes
- $this->phpMailer = new \PHPMailer(TRUE);
-
- // Configuration options
- if (isset($config['mailer'])) {
- if (!in_array($config['mailer'], ['smtp', 'mail', 'qmail', 'sendmail'])) {
- throw new \phpmailerException("'mailer' must be one of 'smtp', 'mail', 'qmail', or 'sendmail'.");
- }
-
- if ($config['mailer'] == 'smtp') {
- $this->phpMailer->isSMTP(TRUE);
- $this->phpMailer->Host = $config['host'];
- $this->phpMailer->Port = $config['port'];
- $this->phpMailer->SMTPAuth = $config['auth'];
- $this->phpMailer->SMTPSecure = $config['secure'];
- $this->phpMailer->Username = $config['username'];
- $this->phpMailer->Password = $config['password'];
- $this->phpMailer->SMTPDebug = $config['smtp_debug'];
-
- if (isset($config['smtp_options'])) {
- $this->phpMailer->SMTPOptions = $config['smtp_options'];
- }
- }
-
- // Set any additional message-specific options
- // enforce which options can be set through this subarray
- if (isset($config['message_options'])) {
- $this->setOptions($config['message_options']);
- }
- }
-
- // Pass logger into phpMailer object
- $this->phpMailer->Debugoutput = function ($message, $level) {
- $this->logger->debug($message);
- };
- }
-
- /**
- * Get the underlying PHPMailer object.
- *
- * @return \PHPMailer
- */
- public function getPhpMailer() {
- return $this->phpMailer;
- }
-
- /**
- * Send a MailMessage message.
- *
- * Sends a single email to all recipients, as well as their CCs and BCCs.
- * Since it is a single-header message, recipient-specific template data will not be included.
- * @param MailMessage $message
- * @param bool $clearRecipients Set to true to clear the list of recipients in the message after calling send(). This helps avoid accidentally sending a message multiple times.
- * @throws \phpmailerException The message could not be sent.
- */
- public function send(MailMessage $message, $clearRecipients = TRUE) {
- $this->phpMailer->From = $message->getFromEmail();
- $this->phpMailer->FromName = $message->getFromName();
- $this->phpMailer->addReplyTo($message->getReplyEmail(), $message->getReplyName());
-
- // Add all email recipients, as well as their CCs and BCCs
- foreach ($message->getRecipients() as $recipient) {
- $this->phpMailer->addAddress($recipient->getEmail(), $recipient->getName());
-
- // Add any CCs and BCCs
- if ($recipient->getCCs()) {
- foreach ($recipient->getCCs() as $cc) {
- $this->phpMailer->addCC($cc['email'], $cc['name']);
- }
- }
-
- if ($recipient->getBCCs()) {
- foreach ($recipient->getBCCs() as $bcc) {
- $this->phpMailer->addBCC($bcc['email'], $bcc['name']);
- }
- }
- }
-
- $this->phpMailer->Subject = $message->renderSubject();
- $this->phpMailer->Body = $message->renderBody();
-
- // Try to send the mail. Will throw an exception on failure.
- $this->phpMailer->send();
-
- // Clear recipients from the PHPMailer object for this iteration,
- // so that we can use the same object for other emails.
- $this->phpMailer->clearAllRecipients();
-
- // Clear out the MailMessage's internal recipient list
- if ($clearRecipients) {
- $message->clearRecipients();
- }
- }
-
- /**
- * Send a MailMessage message, sending a separate email to each recipient.
- *
- * If the message object supports message templates, this will render the template with the corresponding placeholder values for each recipient.
- * @param MailMessage $message
- * @param bool $clearRecipients Set to true to clear the list of recipients in the message after calling send(). This helps avoid accidentally sending a message multiple times.
- * @throws \phpmailerException The message could not be sent.
- */
- public function sendDistinct(MailMessage $message, $clearRecipients = TRUE) {
- $this->phpMailer->From = $message->getFromEmail();
- $this->phpMailer->FromName = $message->getFromName();
- $this->phpMailer->addReplyTo($message->getReplyEmail(), $message->getReplyName());
-
- // Loop through email recipients, sending customized content to each one
- foreach ($message->getRecipients() as $recipient) {
- $this->phpMailer->addAddress($recipient->getEmail(), $recipient->getName());
-
- // Add any CCs and BCCs
- if ($recipient->getCCs()) {
- foreach ($recipient->getCCs() as $cc) {
- $this->phpMailer->addCC($cc['email'], $cc['name']);
- }
- }
-
- if ($recipient->getBCCs()) {
- foreach ($recipient->getBCCs() as $bcc) {
- $this->phpMailer->addBCC($bcc['email'], $bcc['name']);
- }
- }
-
- $this->phpMailer->Subject = $message->renderSubject($recipient->getParams());
- $this->phpMailer->Body = $message->renderBody($recipient->getParams());
-
- // Try to send the mail. Will throw an exception on failure.
- $this->phpMailer->send();
-
- // Clear recipients from the PHPMailer object for this iteration,
- // so that we can send a separate email to the next recipient.
- $this->phpMailer->clearAllRecipients();
- }
-
- // Clear out the MailMessage's internal recipient list
- if ($clearRecipients) {
- $message->clearRecipients();
- }
- }
-
- /**
- * Set option(s) on the underlying phpMailer object.
- *
- * @param mixed[] $options
- * @return Mailer
- */
- public function setOptions($options) {
- if (isset($options['isHtml'])) {
- $this->phpMailer->isHTML($options['isHtml']);
- }
-
- foreach ($options as $name => $value) {
- $this->phpMailer->set($name, $value);
- }
-
- return $this;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Mail;
+
+use Monolog\Logger;
+
+/**
+ * Mailer Class
+ *
+ * A basic wrapper for sending template-based emails.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Mailer
+{
+ /**
+ * @var Logger
+ */
+ protected $logger;
+
+ /**
+ * @var \PHPMailer
+ */
+ protected $phpMailer;
+
+ /**
+ * Create a new Mailer instance.
+ *
+ * @param Logger $logger A Monolog logger, used to dump debugging info for SMTP server transactions.
+ * @param mixed[] $config An array of configuration parameters for phpMailer.
+ * @throws \phpmailerException Wrong mailer config value given.
+ */
+ public function __construct($logger, $config = []) {
+ $this->logger = $logger;
+
+ // 'true' tells PHPMailer to use exceptions instead of error codes
+ $this->phpMailer = new \PHPMailer(TRUE);
+
+ // Configuration options
+ if (isset($config['mailer'])) {
+ if (!in_array($config['mailer'], ['smtp', 'mail', 'qmail', 'sendmail'])) {
+ throw new \phpmailerException("'mailer' must be one of 'smtp', 'mail', 'qmail', or 'sendmail'.");
+ }
+
+ if ($config['mailer'] == 'smtp') {
+ $this->phpMailer->isSMTP(TRUE);
+ $this->phpMailer->Host = $config['host'];
+ $this->phpMailer->Port = $config['port'];
+ $this->phpMailer->SMTPAuth = $config['auth'];
+ $this->phpMailer->SMTPSecure = $config['secure'];
+ $this->phpMailer->Username = $config['username'];
+ $this->phpMailer->Password = $config['password'];
+ $this->phpMailer->SMTPDebug = $config['smtp_debug'];
+
+ if (isset($config['smtp_options'])) {
+ $this->phpMailer->SMTPOptions = $config['smtp_options'];
+ }
+ }
+
+ // Set any additional message-specific options
+ // enforce which options can be set through this subarray
+ if (isset($config['message_options'])) {
+ $this->setOptions($config['message_options']);
+ }
+ }
+
+ // Pass logger into phpMailer object
+ $this->phpMailer->Debugoutput = function ($message, $level) {
+ $this->logger->debug($message);
+ };
+ }
+
+ /**
+ * Get the underlying PHPMailer object.
+ *
+ * @return \PHPMailer
+ */
+ public function getPhpMailer() {
+ return $this->phpMailer;
+ }
+
+ /**
+ * Send a MailMessage message.
+ *
+ * Sends a single email to all recipients, as well as their CCs and BCCs.
+ * Since it is a single-header message, recipient-specific template data will not be included.
+ * @param MailMessage $message
+ * @param bool $clearRecipients Set to true to clear the list of recipients in the message after calling send(). This helps avoid accidentally sending a message multiple times.
+ * @throws \phpmailerException The message could not be sent.
+ */
+ public function send(MailMessage $message, $clearRecipients = TRUE) {
+ $this->phpMailer->From = $message->getFromEmail();
+ $this->phpMailer->FromName = $message->getFromName();
+ $this->phpMailer->addReplyTo($message->getReplyEmail(), $message->getReplyName());
+
+ // Add all email recipients, as well as their CCs and BCCs
+ foreach ($message->getRecipients() as $recipient) {
+ $this->phpMailer->addAddress($recipient->getEmail(), $recipient->getName());
+
+ // Add any CCs and BCCs
+ if ($recipient->getCCs()) {
+ foreach ($recipient->getCCs() as $cc) {
+ $this->phpMailer->addCC($cc['email'], $cc['name']);
+ }
+ }
+
+ if ($recipient->getBCCs()) {
+ foreach ($recipient->getBCCs() as $bcc) {
+ $this->phpMailer->addBCC($bcc['email'], $bcc['name']);
+ }
+ }
+ }
+
+ $this->phpMailer->Subject = $message->renderSubject();
+ $this->phpMailer->Body = $message->renderBody();
+
+ // Try to send the mail. Will throw an exception on failure.
+ $this->phpMailer->send();
+
+ // Clear recipients from the PHPMailer object for this iteration,
+ // so that we can use the same object for other emails.
+ $this->phpMailer->clearAllRecipients();
+
+ // Clear out the MailMessage's internal recipient list
+ if ($clearRecipients) {
+ $message->clearRecipients();
+ }
+ }
+
+ /**
+ * Send a MailMessage message, sending a separate email to each recipient.
+ *
+ * If the message object supports message templates, this will render the template with the corresponding placeholder values for each recipient.
+ * @param MailMessage $message
+ * @param bool $clearRecipients Set to true to clear the list of recipients in the message after calling send(). This helps avoid accidentally sending a message multiple times.
+ * @throws \phpmailerException The message could not be sent.
+ */
+ public function sendDistinct(MailMessage $message, $clearRecipients = TRUE) {
+ $this->phpMailer->From = $message->getFromEmail();
+ $this->phpMailer->FromName = $message->getFromName();
+ $this->phpMailer->addReplyTo($message->getReplyEmail(), $message->getReplyName());
+
+ // Loop through email recipients, sending customized content to each one
+ foreach ($message->getRecipients() as $recipient) {
+ $this->phpMailer->addAddress($recipient->getEmail(), $recipient->getName());
+
+ // Add any CCs and BCCs
+ if ($recipient->getCCs()) {
+ foreach ($recipient->getCCs() as $cc) {
+ $this->phpMailer->addCC($cc['email'], $cc['name']);
+ }
+ }
+
+ if ($recipient->getBCCs()) {
+ foreach ($recipient->getBCCs() as $bcc) {
+ $this->phpMailer->addBCC($bcc['email'], $bcc['name']);
+ }
+ }
+
+ $this->phpMailer->Subject = $message->renderSubject($recipient->getParams());
+ $this->phpMailer->Body = $message->renderBody($recipient->getParams());
+
+ // Try to send the mail. Will throw an exception on failure.
+ $this->phpMailer->send();
+
+ // Clear recipients from the PHPMailer object for this iteration,
+ // so that we can send a separate email to the next recipient.
+ $this->phpMailer->clearAllRecipients();
+ }
+
+ // Clear out the MailMessage's internal recipient list
+ if ($clearRecipients) {
+ $message->clearRecipients();
+ }
+ }
+
+ /**
+ * Set option(s) on the underlying phpMailer object.
+ *
+ * @param mixed[] $options
+ * @return Mailer
+ */
+ public function setOptions($options) {
+ if (isset($options['isHtml'])) {
+ $this->phpMailer->isHTML($options['isHtml']);
+ }
+
+ foreach ($options as $name => $value) {
+ $this->phpMailer->set($name, $value);
+ }
+
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Mail/StaticMailMessage.php b/main/app/sprinkles/core/src/Mail/StaticMailMessage.php
index 482226c..17758db 100644
--- a/main/app/sprinkles/core/src/Mail/StaticMailMessage.php
+++ b/main/app/sprinkles/core/src/Mail/StaticMailMessage.php
@@ -1,74 +1,74 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Mail;
-
-/**
- * StaticMailMessage Class
- *
- * Represents a basic mail message, containing a static subject and body.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class StaticMailMessage extends MailMessage
-{
- /**
- * @var string The default body for this message.
- */
- protected $body;
-
- /**
- * @var string The default subject for this message.
- */
- protected $subject;
-
- /**
- * Create a new MailMessage instance.
- *
- * @param string $subject
- * @param string $body
- */
- public function __construct($subject = "", $body = "") {
- $this->subject = $subject;
- $this->body = $body;
- }
-
- /**
- * {@inheritDoc}
- */
- public function renderBody($params = []) {
- return $this->body;
- }
-
- /**
- * {@inheritDoc}
- */
- public function renderSubject($params = []) {
- return $this->subject;
- }
-
- /**
- * Set the text of the message subject.
- *
- * @param string $subject
- */
- public function setSubject($subject) {
- $this->subject = $subject;
- return $this;
- }
-
- /**
- * Set the text of the message body.
- *
- * @param string $body
- */
- public function setBody($body) {
- $this->body = $body;
- return $this;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Mail;
+
+/**
+ * StaticMailMessage Class
+ *
+ * Represents a basic mail message, containing a static subject and body.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class StaticMailMessage extends MailMessage
+{
+ /**
+ * @var string The default body for this message.
+ */
+ protected $body;
+
+ /**
+ * @var string The default subject for this message.
+ */
+ protected $subject;
+
+ /**
+ * Create a new MailMessage instance.
+ *
+ * @param string $subject
+ * @param string $body
+ */
+ public function __construct($subject = "", $body = "") {
+ $this->subject = $subject;
+ $this->body = $body;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function renderBody($params = []) {
+ return $this->body;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function renderSubject($params = []) {
+ return $this->subject;
+ }
+
+ /**
+ * Set the text of the message subject.
+ *
+ * @param string $subject
+ */
+ public function setSubject($subject) {
+ $this->subject = $subject;
+ return $this;
+ }
+
+ /**
+ * Set the text of the message body.
+ *
+ * @param string $body
+ */
+ public function setBody($body) {
+ $this->body = $body;
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Mail/TwigMailMessage.php b/main/app/sprinkles/core/src/Mail/TwigMailMessage.php
index aa4daea..7197f75 100644
--- a/main/app/sprinkles/core/src/Mail/TwigMailMessage.php
+++ b/main/app/sprinkles/core/src/Mail/TwigMailMessage.php
@@ -1,89 +1,89 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Mail;
-
-/**
- * MailMessage Class
- *
- * Represents a basic mail message, containing a static subject and body.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class TwigMailMessage extends MailMessage
-{
- /**
- * @var mixed[] A list of Twig placeholder values to use when rendering this message.
- */
- protected $params;
-
- /**
- * @var Twig_Template The Twig template object, to source the content for this message.
- */
- protected $template;
-
- /**
- * @var \Slim\Views\Twig The view object, used to render mail templates.
- */
- protected $view;
-
- /**
- * Create a new TwigMailMessage instance.
- *
- * @param Slim\Views\Twig $view The Twig view object used to render mail templates.
- * @param string $filename optional Set the Twig template to use for this message.
- */
- public function __construct($view, $filename = NULL) {
- $this->view = $view;
-
- $twig = $this->view->getEnvironment();
- // Must manually merge in global variables for block rendering
- // should we keep this separate from the local parameters?
- $this->params = $twig->getGlobals();
-
- if ($filename !== NULL) {
- $this->template = $twig->loadTemplate($filename);
- }
- }
-
- /**
- * Merge in any additional global Twig variables to use when rendering this message.
- *
- * @param mixed[] $params
- */
- public function addParams($params = []) {
- $this->params = array_replace_recursive($this->params, $params);
- return $this;
- }
-
- /**
- * {@inheritDoc}
- */
- public function renderSubject($params = []) {
- $params = array_replace_recursive($this->params, $params);
- return $this->template->renderBlock('subject', $params);
- }
-
- /**
- * {@inheritDoc}
- */
- public function renderBody($params = []) {
- $params = array_replace_recursive($this->params, $params);
- return $this->template->renderBlock('body', $params);
- }
-
- /**
- * Sets the Twig template object for this message.
- *
- * @param Twig_Template $template The Twig template object, to source the content for this message.
- */
- public function setTemplate($template) {
- $this->template = $template;
- return $this;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Mail;
+
+/**
+ * MailMessage Class
+ *
+ * Represents a basic mail message, containing a static subject and body.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class TwigMailMessage extends MailMessage
+{
+ /**
+ * @var mixed[] A list of Twig placeholder values to use when rendering this message.
+ */
+ protected $params;
+
+ /**
+ * @var Twig_Template The Twig template object, to source the content for this message.
+ */
+ protected $template;
+
+ /**
+ * @var \Slim\Views\Twig The view object, used to render mail templates.
+ */
+ protected $view;
+
+ /**
+ * Create a new TwigMailMessage instance.
+ *
+ * @param Slim\Views\Twig $view The Twig view object used to render mail templates.
+ * @param string $filename optional Set the Twig template to use for this message.
+ */
+ public function __construct($view, $filename = NULL) {
+ $this->view = $view;
+
+ $twig = $this->view->getEnvironment();
+ // Must manually merge in global variables for block rendering
+ // should we keep this separate from the local parameters?
+ $this->params = $twig->getGlobals();
+
+ if ($filename !== NULL) {
+ $this->template = $twig->loadTemplate($filename);
+ }
+ }
+
+ /**
+ * Merge in any additional global Twig variables to use when rendering this message.
+ *
+ * @param mixed[] $params
+ */
+ public function addParams($params = []) {
+ $this->params = array_replace_recursive($this->params, $params);
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function renderSubject($params = []) {
+ $params = array_replace_recursive($this->params, $params);
+ return $this->template->renderBlock('subject', $params);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function renderBody($params = []) {
+ $params = array_replace_recursive($this->params, $params);
+ return $this->template->renderBlock('body', $params);
+ }
+
+ /**
+ * Sets the Twig template object for this message.
+ *
+ * @param Twig_Template $template The Twig template object, to source the content for this message.
+ */
+ public function setTemplate($template) {
+ $this->template = $template;
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Model/UFModel.php b/main/app/sprinkles/core/src/Model/UFModel.php
index d852606..fb01357 100644
--- a/main/app/sprinkles/core/src/Model/UFModel.php
+++ b/main/app/sprinkles/core/src/Model/UFModel.php
@@ -1,27 +1,27 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Model;
-
-use UserFrosting\Sprinkle\Core\Database\Models\Model;
-use UserFrosting\Sprinkle\Core\Facades\Debug;
-
-/**
- * UFModel Class
- *
- * @deprecated since 4.1
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-abstract class UFModel extends Model
-{
- public function __construct(array $attributes = []) {
- Debug::debug("UFModel has been deprecated and will be removed in future versions. Please move your model " . static::class . " to Database/Models/ and have it extend the base Database/Models/Model class.");
-
- parent::__construct($attributes);
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Model;
+
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+
+/**
+ * UFModel Class
+ *
+ * @deprecated since 4.1
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+abstract class UFModel extends Model
+{
+ public function __construct(array $attributes = []) {
+ Debug::debug("UFModel has been deprecated and will be removed in future versions. Please move your model " . static::class . " to Database/Models/ and have it extend the base Database/Models/Model class.");
+
+ parent::__construct($attributes);
+ }
+}
diff --git a/main/app/sprinkles/core/src/Router.php b/main/app/sprinkles/core/src/Router.php
index 09a358a..1554fa9 100644
--- a/main/app/sprinkles/core/src/Router.php
+++ b/main/app/sprinkles/core/src/Router.php
@@ -1,100 +1,100 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core;
-
-use FastRoute\Dispatcher;
-use Illuminate\Filesystem\Filesystem;
-use InvalidArgumentException;
-use RuntimeException;
-use Psr\Http\Message\ServerRequestInterface;
-use FastRoute\RouteCollector;
-use FastRoute\RouteParser;
-use FastRoute\RouteParser\Std as StdParser;
-use FastRoute\DataGenerator;
-use Slim\Interfaces\RouteGroupInterface;
-use Slim\Interfaces\RouterInterface;
-use Slim\Interfaces\RouteInterface;
-
-/**
- * Router
- *
- * This class extends Slim's router, to permit overriding of routes with the same signature.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Router extends \Slim\Router implements RouterInterface
-{
-
- /*
- * @var string[] a reverse lookup of route identifiers, indexed by route signature
- */
- protected $identifiers;
-
- /**
- * Add route
- *
- * @param string[] $methods Array of HTTP methods
- * @param string $pattern The route pattern
- * @param callable $handler The route callable
- *
- * @return RouteInterface
- *
- * @throws InvalidArgumentException if the route pattern isn't a string
- */
- public function map($methods, $pattern, $handler) {
- if (!is_string($pattern)) {
- throw new InvalidArgumentException('Route pattern must be a string');
- }
-
- // Prepend parent group pattern(s)
- if ($this->routeGroups) {
- $pattern = $this->processGroups() . $pattern;
- }
-
- // According to RFC methods are defined in uppercase (See RFC 7231)
- $methods = array_map("strtoupper", $methods);
-
- // Determine route signature
- $signature = implode('-', $methods) . '-' . $pattern;
-
- // If a route with the same signature already exists, then we must replace it
- if (isset($this->identifiers[$signature])) {
- $route = new \Slim\Route($methods, $pattern, $handler, $this->routeGroups, str_replace('route', '', $this->identifiers[$signature]));
- } else {
- $route = new \Slim\Route($methods, $pattern, $handler, $this->routeGroups, $this->routeCounter);
- }
-
- $this->routes[$route->getIdentifier()] = $route;
-
- // Record identifier in reverse lookup array
- $this->identifiers[$signature] = $route->getIdentifier();
-
- $this->routeCounter++;
-
- return $route;
- }
-
- /**
- * Delete the cache file
- *
- * @access public
- * @return bool true/false if operation is successfull
- */
- public function clearCache() {
- // Get Filesystem instance
- $fs = new FileSystem;
-
- // Make sure file exist and delete it
- if ($fs->exists($this->cacheFile)) {
- return $fs->delete($this->cacheFile);
- }
-
- // It's still considered a success if file doesn't exist
- return TRUE;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core;
+
+use FastRoute\Dispatcher;
+use Illuminate\Filesystem\Filesystem;
+use InvalidArgumentException;
+use RuntimeException;
+use Psr\Http\Message\ServerRequestInterface;
+use FastRoute\RouteCollector;
+use FastRoute\RouteParser;
+use FastRoute\RouteParser\Std as StdParser;
+use FastRoute\DataGenerator;
+use Slim\Interfaces\RouteGroupInterface;
+use Slim\Interfaces\RouterInterface;
+use Slim\Interfaces\RouteInterface;
+
+/**
+ * Router
+ *
+ * This class extends Slim's router, to permit overriding of routes with the same signature.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Router extends \Slim\Router implements RouterInterface
+{
+
+ /*
+ * @var string[] a reverse lookup of route identifiers, indexed by route signature
+ */
+ protected $identifiers;
+
+ /**
+ * Add route
+ *
+ * @param string[] $methods Array of HTTP methods
+ * @param string $pattern The route pattern
+ * @param callable $handler The route callable
+ *
+ * @return RouteInterface
+ *
+ * @throws InvalidArgumentException if the route pattern isn't a string
+ */
+ public function map($methods, $pattern, $handler) {
+ if (!is_string($pattern)) {
+ throw new InvalidArgumentException('Route pattern must be a string');
+ }
+
+ // Prepend parent group pattern(s)
+ if ($this->routeGroups) {
+ $pattern = $this->processGroups() . $pattern;
+ }
+
+ // According to RFC methods are defined in uppercase (See RFC 7231)
+ $methods = array_map("strtoupper", $methods);
+
+ // Determine route signature
+ $signature = implode('-', $methods) . '-' . $pattern;
+
+ // If a route with the same signature already exists, then we must replace it
+ if (isset($this->identifiers[$signature])) {
+ $route = new \Slim\Route($methods, $pattern, $handler, $this->routeGroups, str_replace('route', '', $this->identifiers[$signature]));
+ } else {
+ $route = new \Slim\Route($methods, $pattern, $handler, $this->routeGroups, $this->routeCounter);
+ }
+
+ $this->routes[$route->getIdentifier()] = $route;
+
+ // Record identifier in reverse lookup array
+ $this->identifiers[$signature] = $route->getIdentifier();
+
+ $this->routeCounter++;
+
+ return $route;
+ }
+
+ /**
+ * Delete the cache file
+ *
+ * @access public
+ * @return bool true/false if operation is successfull
+ */
+ public function clearCache() {
+ // Get Filesystem instance
+ $fs = new FileSystem;
+
+ // Make sure file exist and delete it
+ if ($fs->exists($this->cacheFile)) {
+ return $fs->delete($this->cacheFile);
+ }
+
+ // It's still considered a success if file doesn't exist
+ return TRUE;
+ }
+}
diff --git a/main/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php b/main/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php
index 6ac8c41..0adc75a 100644
--- a/main/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php
+++ b/main/app/sprinkles/core/src/ServicesProvider/ServicesProvider.php
@@ -1,621 +1,621 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\ServicesProvider;
-
-use Dotenv\Dotenv;
-use Dotenv\Exception\InvalidPathException;
-use Illuminate\Container\Container;
-use Illuminate\Database\Capsule\Manager as Capsule;
-use Illuminate\Database\Events\QueryExecuted;
-use Illuminate\Events\Dispatcher;
-use Illuminate\Filesystem\Filesystem;
-use Illuminate\Session\DatabaseSessionHandler;
-use Illuminate\Session\FileSessionHandler;
-use Interop\Container\ContainerInterface;
-use League\FactoryMuffin\FactoryMuffin;
-use League\FactoryMuffin\Faker\Facade as Faker;
-use Monolog\Formatter\LineFormatter;
-use Monolog\Handler\ErrorLogHandler;
-use Monolog\Handler\StreamHandler;
-use Monolog\Logger;
-use Slim\Csrf\Guard;
-use Slim\Http\Uri;
-use Slim\Views\Twig;
-use Slim\Views\TwigExtension;
-use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
-use UserFrosting\Assets\AssetBundleSchema;
-use UserFrosting\Assets\AssetLoader;
-use UserFrosting\Assets\AssetManager;
-use UserFrosting\Assets\UrlBuilder\AssetUrlBuilder;
-use UserFrosting\Assets\UrlBuilder\CompiledAssetUrlBuilder;
-use UserFrosting\Cache\TaggableFileStore;
-use UserFrosting\Cache\MemcachedStore;
-use UserFrosting\Cache\RedisStore;
-use UserFrosting\Config\ConfigPathBuilder;
-use UserFrosting\I18n\LocalePathBuilder;
-use UserFrosting\I18n\MessageTranslator;
-use UserFrosting\Session\Session;
-use UserFrosting\Sprinkle\Core\Error\ExceptionHandlerManager;
-use UserFrosting\Sprinkle\Core\Error\Handler\NotFoundExceptionHandler;
-use UserFrosting\Sprinkle\Core\Log\MixedFormatter;
-use UserFrosting\Sprinkle\Core\Mail\Mailer;
-use UserFrosting\Sprinkle\Core\Alert\CacheAlertStream;
-use UserFrosting\Sprinkle\Core\Alert\SessionAlertStream;
-use UserFrosting\Sprinkle\Core\Router;
-use UserFrosting\Sprinkle\Core\Throttle\Throttler;
-use UserFrosting\Sprinkle\Core\Throttle\ThrottleRule;
-use UserFrosting\Sprinkle\Core\Twig\CoreExtension;
-use UserFrosting\Sprinkle\Core\Util\CheckEnvironment;
-use UserFrosting\Sprinkle\Core\Util\ClassMapper;
-use UserFrosting\Support\Exception\BadRequestException;
-use UserFrosting\Support\Exception\NotFoundException;
-use UserFrosting\Support\Repository\Loader\ArrayFileLoader;
-use UserFrosting\Support\Repository\Repository;
-
-/**
- * UserFrosting core services provider.
- *
- * Registers core services for UserFrosting, such as config, database, asset manager, translator, etc.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ServicesProvider
-{
- /**
- * Register UserFrosting's core services.
- *
- * @param ContainerInterface $container A DI container implementing ArrayAccess and container-interop.
- */
- public function register(ContainerInterface $container) {
- /**
- * Flash messaging service.
- *
- * Persists error/success messages between requests in the session.
- */
- $container['alerts'] = function ($c) {
- $config = $c->config;
-
- if ($config['alert.storage'] == 'cache') {
- return new CacheAlertStream($config['alert.key'], $c->translator, $c->cache, $c->config);
- } else if ($config['alert.storage'] == 'session') {
- return new SessionAlertStream($config['alert.key'], $c->translator, $c->session);
- } else {
- throw new \Exception("Bad alert storage handler type '{$config['alert.storage']}' specified in configuration file.");
- }
- };
-
- /**
- * Asset loader service.
- *
- * Loads assets from a specified relative location.
- * Assets are Javascript, CSS, image, and other files used by your site.
- */
- $container['assetLoader'] = function ($c) {
- $basePath = \UserFrosting\SPRINKLES_DIR;
- $pattern = "/^[A-Za-z0-9_\-]+\/assets\//";
-
- $al = new AssetLoader($basePath, $pattern);
- return $al;
- };
-
- /**
- * Asset manager service.
- *
- * Loads raw or compiled asset information from your bundle.config.json schema file.
- * Assets are Javascript, CSS, image, and other files used by your site.
- */
- $container['assets'] = function ($c) {
- $config = $c->config;
- $locator = $c->locator;
-
- // Load asset schema
- if ($config['assets.use_raw']) {
- $baseUrl = $config['site.uri.public'] . '/' . $config['assets.raw.path'];
- $removePrefix = \UserFrosting\APP_DIR_NAME . \UserFrosting\DS . \UserFrosting\SPRINKLES_DIR_NAME;
- $aub = new AssetUrlBuilder($locator, $baseUrl, $removePrefix, 'assets');
-
- $as = new AssetBundleSchema($aub);
-
- // Load Sprinkle assets
- $sprinkles = $c->sprinkleManager->getSprinkleNames();
-
- // move this out into PathBuilder and Loader classes in userfrosting/assets
- // This would also allow us to define and load bundles in themes
- $bundleSchemas = array_reverse($locator->findResources('sprinkles://' . $config['assets.raw.schema'], TRUE, TRUE));
-
- foreach ($bundleSchemas as $schema) {
- if (file_exists($schema)) {
- $as->loadRawSchemaFile($schema);
- }
- }
- } else {
- $baseUrl = $config['site.uri.public'] . '/' . $config['assets.compiled.path'];
- $aub = new CompiledAssetUrlBuilder($baseUrl);
-
- $as = new AssetBundleSchema($aub);
- $as->loadCompiledSchemaFile($locator->findResource("build://" . $config['assets.compiled.schema'], TRUE, TRUE));
- }
-
- $am = new AssetManager($aub, $as);
-
- return $am;
- };
-
- /**
- * Cache service.
- *
- * @return \Illuminate\Cache\Repository
- */
- $container['cache'] = function ($c) {
-
- $config = $c->config;
-
- if ($config['cache.driver'] == 'file') {
- $path = $c->locator->findResource('cache://', TRUE, TRUE);
- $cacheStore = new TaggableFileStore($path);
- } else if ($config['cache.driver'] == 'memcached') {
- // We need to inject the prefix in the memcached config
- $config = array_merge($config['cache.memcached'], ['prefix' => $config['cache.prefix']]);
- $cacheStore = new MemcachedStore($config);
- } else if ($config['cache.driver'] == 'redis') {
- // We need to inject the prefix in the redis config
- $config = array_merge($config['cache.redis'], ['prefix' => $config['cache.prefix']]);
- $cacheStore = new RedisStore($config);
- } else {
- throw new \Exception("Bad cache store type '{$config['cache.driver']}' specified in configuration file.");
- }
-
- return $cacheStore->instance();
- };
-
- /**
- * Middleware to check environment.
- *
- * We should cache the results of this, the first time that it succeeds.
- */
- $container['checkEnvironment'] = function ($c) {
- $checkEnvironment = new CheckEnvironment($c->view, $c->locator, $c->cache);
- return $checkEnvironment;
- };
-
- /**
- * Class mapper.
- *
- * Creates an abstraction on top of class names to allow extending them in sprinkles.
- */
- $container['classMapper'] = function ($c) {
- $classMapper = new ClassMapper();
- $classMapper->setClassMapping('query_builder', 'UserFrosting\Sprinkle\Core\Database\Builder');
- $classMapper->setClassMapping('throttle', 'UserFrosting\Sprinkle\Core\Database\Models\Throttle');
- return $classMapper;
- };
-
- /**
- * Site config service (separate from Slim settings).
- *
- * Will attempt to automatically determine which config file(s) to use based on the value of the UF_MODE environment variable.
- */
- $container['config'] = function ($c) {
- // Grab any relevant dotenv variables from the .env file
- try {
- $dotenv = new Dotenv(\UserFrosting\APP_DIR);
- $dotenv->load();
- } catch (InvalidPathException $e) {
- // Skip loading the environment config file if it doesn't exist.
- }
-
- // Get configuration mode from environment
- $mode = getenv('UF_MODE') ?: '';
-
- // Construct and load config repository
- $builder = new ConfigPathBuilder($c->locator, 'config://');
- $loader = new ArrayFileLoader($builder->buildPaths($mode));
- $config = new Repository($loader->load());
-
- // Construct base url from components, if not explicitly specified
- if (!isset($config['site.uri.public'])) {
- $base_uri = $config['site.uri.base'];
-
- $public = new Uri(
- $base_uri['scheme'],
- $base_uri['host'],
- $base_uri['port'],
- $base_uri['path']
- );
-
- // Slim\Http\Uri likes to add trailing slashes when the path is empty, so this fixes that.
- $config['site.uri.public'] = trim($public, '/');
- }
-
- // Hacky fix to prevent sessions from being hit too much: ignore CSRF middleware for requests for raw assets ;-)
- // See https://github.com/laravel/framework/issues/8172#issuecomment-99112012 for more information on why it's bad to hit Laravel sessions multiple times in rapid succession.
- $csrfBlacklist = $config['csrf.blacklist'];
- $csrfBlacklist['^/' . $config['assets.raw.path']] = [
- 'GET'
- ];
- $csrfBlacklist['^/wormhole'] = [
- 'POST'
- ];
-
- $config->set('csrf.blacklist', $csrfBlacklist);
-
- return $config;
- };
-
- /**
- * Initialize CSRF guard middleware.
- *
- * @see https://github.com/slimphp/Slim-Csrf
- */
- $container['csrf'] = function ($c) {
- $csrfKey = $c->config['session.keys.csrf'];
-
- // Workaround so that we can pass storage into CSRF guard.
- // If we tried to directly pass the indexed portion of `session` (for example, $c->session['site.csrf']),
- // we would get an 'Indirect modification of overloaded element of UserFrosting\Session\Session' error.
- // If we tried to assign an array and use that, PHP would only modify the local variable, and not the session.
- // Since ArrayObject is an object, PHP will modify the object itself, allowing it to persist in the session.
- if (!$c->session->has($csrfKey)) {
- $c->session[$csrfKey] = new \ArrayObject();
- }
- $csrfStorage = $c->session[$csrfKey];
-
- $onFailure = function ($request, $response, $next) {
- $e = new BadRequestException("The CSRF code was invalid or not provided.");
- $e->addUserMessage('CSRF_MISSING');
- throw $e;
-
- return $next($request, $response);
- };
-
- return new Guard($c->config['csrf.name'], $csrfStorage, $onFailure, $c->config['csrf.storage_limit'], $c->config['csrf.strength'], $c->config['csrf.persistent_token']);
- };
-
- /**
- * Initialize Eloquent Capsule, which provides the database layer for UF.
- *
- * construct the individual objects rather than using the facade
- */
- $container['db'] = function ($c) {
- $config = $c->config;
-
- $capsule = new Capsule;
-
- foreach ($config['db'] as $name => $dbConfig) {
- $capsule->addConnection($dbConfig, $name);
- }
-
- $queryEventDispatcher = new Dispatcher(new Container);
-
- $capsule->setEventDispatcher($queryEventDispatcher);
-
- // Register as global connection
- $capsule->setAsGlobal();
-
- // Start Eloquent
- $capsule->bootEloquent();
-
- if ($config['debug.queries']) {
- $logger = $c->queryLogger;
-
- foreach ($config['db'] as $name => $dbConfig) {
- $capsule->connection($name)->enableQueryLog();
- }
-
- // Register listener
- $queryEventDispatcher->listen(QueryExecuted::class, function ($query) use ($logger) {
- $logger->debug("Query executed on database [{$query->connectionName}]:", [
- 'query' => $query->sql,
- 'bindings' => $query->bindings,
- 'time' => $query->time . ' ms'
- ]);
- });
- }
-
- return $capsule;
- };
-
- /**
- * Debug logging with Monolog.
- *
- * Extend this service to push additional handlers onto the 'debug' log stack.
- */
- $container['debugLogger'] = function ($c) {
- $logger = new Logger('debug');
-
- $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
-
- $handler = new StreamHandler($logFile);
-
- $formatter = new MixedFormatter(NULL, NULL, TRUE);
-
- $handler->setFormatter($formatter);
- $logger->pushHandler($handler);
-
- return $logger;
- };
-
- /**
- * Custom error-handler for recoverable errors.
- */
- $container['errorHandler'] = function ($c) {
- $settings = $c->settings;
-
- $handler = new ExceptionHandlerManager($c, $settings['displayErrorDetails']);
-
- // Register the base HttpExceptionHandler.
- $handler->registerHandler('\UserFrosting\Support\Exception\HttpException', '\UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler');
-
- // Register the NotFoundExceptionHandler.
- $handler->registerHandler('\UserFrosting\Support\Exception\NotFoundException', '\UserFrosting\Sprinkle\Core\Error\Handler\NotFoundExceptionHandler');
-
- // Register the PhpMailerExceptionHandler.
- $handler->registerHandler('\phpmailerException', '\UserFrosting\Sprinkle\Core\Error\Handler\PhpMailerExceptionHandler');
-
- return $handler;
- };
-
- /**
- * Error logging with Monolog.
- *
- * Extend this service to push additional handlers onto the 'error' log stack.
- */
- $container['errorLogger'] = function ($c) {
- $log = new Logger('errors');
-
- $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
-
- $handler = new StreamHandler($logFile, Logger::WARNING);
-
- $formatter = new LineFormatter(NULL, NULL, TRUE);
-
- $handler->setFormatter($formatter);
- $log->pushHandler($handler);
-
- return $log;
- };
-
- /**
- * Factory service with FactoryMuffin.
- *
- * Provide access to factories for the rapid creation of objects for the purpose of testing
- */
- $container['factory'] = function ($c) {
-
- // Get the path of all of the sprinkle's factories
- $factoriesPath = $c->locator->findResources('factories://', TRUE, TRUE);
-
- // Create a new Factory Muffin instance
- $fm = new FactoryMuffin();
-
- // Load all of the model definitions
- $fm->loadFactories($factoriesPath);
-
- // Set the locale. Could be the config one, but for testing English should do
- Faker::setLocale('en_EN');
-
- return $fm;
- };
-
- /**
- * Builds search paths for locales in all Sprinkles.
- */
- $container['localePathBuilder'] = function ($c) {
- $config = $c->config;
-
- // Make sure the locale config is a valid string
- if (!is_string($config['site.locales.default']) || $config['site.locales.default'] == '') {
- throw new \UnexpectedValueException('The locale config is not a valid string.');
- }
-
- // Load the base locale file(s) as specified in the configuration
- $locales = explode(',', $config['site.locales.default']);
-
- return new LocalePathBuilder($c->locator, 'locale://', $locales);
- };
-
- /**
- * Mail service.
- */
- $container['mailer'] = function ($c) {
- $mailer = new Mailer($c->mailLogger, $c->config['mail']);
-
- // Use UF debug settings to override any service-specific log settings.
- if (!$c->config['debug.smtp']) {
- $mailer->getPhpMailer()->SMTPDebug = 0;
- }
-
- return $mailer;
- };
-
- /**
- * Mail logging service.
- *
- * PHPMailer will use this to log SMTP activity.
- * Extend this service to push additional handlers onto the 'mail' log stack.
- */
- $container['mailLogger'] = function ($c) {
- $log = new Logger('mail');
-
- $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
-
- $handler = new StreamHandler($logFile);
- $formatter = new LineFormatter(NULL, NULL, TRUE);
-
- $handler->setFormatter($formatter);
- $log->pushHandler($handler);
-
- return $log;
- };
-
- /**
- * Error-handler for 404 errors. Notice that we manually create a UserFrosting NotFoundException,
- * and a NotFoundExceptionHandler. This lets us pass through to the UF error handling system.
- */
- $container['notFoundHandler'] = function ($c) {
- return function ($request, $response) use ($c) {
- $exception = new NotFoundException;
- $handler = new NotFoundExceptionHandler($c, $request, $response, $exception, $c->settings['displayErrorDetails']);
- return $handler->handle();
- };
- };
-
- /**
- * Error-handler for PHP runtime errors. Notice that we just pass this through to our general-purpose
- * error-handling service.
- */
- $container['phpErrorHandler'] = function ($c) {
- return $c->errorHandler;
- };
-
- /**
- * Laravel query logging with Monolog.
- *
- * Extend this service to push additional handlers onto the 'query' log stack.
- */
- $container['queryLogger'] = function ($c) {
- $logger = new Logger('query');
-
- $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
-
- $handler = new StreamHandler($logFile);
-
- $formatter = new MixedFormatter(NULL, NULL, TRUE);
-
- $handler->setFormatter($formatter);
- $logger->pushHandler($handler);
-
- return $logger;
- };
-
- /**
- * Override Slim's default router with the UF router.
- */
- $container['router'] = function ($c) {
- $routerCacheFile = FALSE;
- if (isset($c->config['settings.routerCacheFile'])) {
- $routerCacheFile = $c->config['settings.routerCacheFile'];
- }
-
- return (new Router)->setCacheFile($routerCacheFile);
- };
-
- /**
- * Start the PHP session, with the name and parameters specified in the configuration file.
- */
- $container['session'] = function ($c) {
- $config = $c->config;
-
- // Create appropriate handler based on config
- if ($config['session.handler'] == 'file') {
- $fs = new FileSystem;
- $handler = new FileSessionHandler($fs, $c->locator->findResource('session://'), $config['session.minutes']);
- } else if ($config['session.handler'] == 'database') {
- $connection = $c->db->connection();
- // Table must exist, otherwise an exception will be thrown
- $handler = new DatabaseSessionHandler($connection, $config['session.database.table'], $config['session.minutes']);
- } else {
- throw new \Exception("Bad session handler type '{$config['session.handler']}' specified in configuration file.");
- }
-
- // Create, start and return a new wrapper for $_SESSION
- $session = new Session($handler, $config['session']);
- $session->start();
-
- return $session;
- };
-
- /**
- * Request throttler.
- *
- * Throttles (rate-limits) requests of a predefined type, with rules defined in site config.
- */
- $container['throttler'] = function ($c) {
- $throttler = new Throttler($c->classMapper);
-
- $config = $c->config;
-
- if ($config->has('throttles') && ($config['throttles'] !== NULL)) {
- foreach ($config['throttles'] as $type => $rule) {
- if ($rule) {
- $throttleRule = new ThrottleRule($rule['method'], $rule['interval'], $rule['delays']);
- $throttler->addThrottleRule($type, $throttleRule);
- } else {
- $throttler->addThrottleRule($type, NULL);
- }
- }
- }
-
- return $throttler;
- };
-
- /**
- * Translation service, for translating message tokens.
- */
- $container['translator'] = function ($c) {
- // Load the translations
- $paths = $c->localePathBuilder->buildPaths();
- $loader = new ArrayFileLoader($paths);
-
- // Create the $translator object
- $translator = new MessageTranslator($loader->load());
-
- return $translator;
- };
-
- /**
- * Set up Twig as the view, adding template paths for all sprinkles and the Slim Twig extension.
- *
- * Also adds the UserFrosting core Twig extension, which provides additional functions, filters, global variables, etc.
- */
- $container['view'] = function ($c) {
- $templatePaths = $c->locator->findResources('templates://', TRUE, TRUE);
-
- $view = new Twig($templatePaths);
-
- $loader = $view->getLoader();
-
- $sprinkles = $c->sprinkleManager->getSprinkleNames();
-
- // Add Sprinkles' templates namespaces
- foreach ($sprinkles as $sprinkle) {
- $path = \UserFrosting\SPRINKLES_DIR . \UserFrosting\DS .
- $sprinkle . \UserFrosting\DS .
- \UserFrosting\TEMPLATE_DIR_NAME . \UserFrosting\DS;
-
- if (is_dir($path)) {
- $loader->addPath($path, $sprinkle);
- }
- }
-
- $twig = $view->getEnvironment();
-
- if ($c->config['cache.twig']) {
- $twig->setCache($c->locator->findResource('cache://twig', TRUE, TRUE));
- }
-
- if ($c->config['debug.twig']) {
- $twig->enableDebug();
- $view->addExtension(new \Twig_Extension_Debug());
- }
-
- // Register the Slim extension with Twig
- $slimExtension = new TwigExtension(
- $c->router,
- $c->request->getUri()
- );
- $view->addExtension($slimExtension);
-
- // Register the core UF extension with Twig
- $coreExtension = new CoreExtension($c);
- $view->addExtension($coreExtension);
-
- return $view;
- };
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\ServicesProvider;
+
+use Dotenv\Dotenv;
+use Dotenv\Exception\InvalidPathException;
+use Illuminate\Container\Container;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Illuminate\Database\Events\QueryExecuted;
+use Illuminate\Events\Dispatcher;
+use Illuminate\Filesystem\Filesystem;
+use Illuminate\Session\DatabaseSessionHandler;
+use Illuminate\Session\FileSessionHandler;
+use Interop\Container\ContainerInterface;
+use League\FactoryMuffin\FactoryMuffin;
+use League\FactoryMuffin\Faker\Facade as Faker;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\ErrorLogHandler;
+use Monolog\Handler\StreamHandler;
+use Monolog\Logger;
+use Slim\Csrf\Guard;
+use Slim\Http\Uri;
+use Slim\Views\Twig;
+use Slim\Views\TwigExtension;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use UserFrosting\Assets\AssetBundleSchema;
+use UserFrosting\Assets\AssetLoader;
+use UserFrosting\Assets\AssetManager;
+use UserFrosting\Assets\UrlBuilder\AssetUrlBuilder;
+use UserFrosting\Assets\UrlBuilder\CompiledAssetUrlBuilder;
+use UserFrosting\Cache\TaggableFileStore;
+use UserFrosting\Cache\MemcachedStore;
+use UserFrosting\Cache\RedisStore;
+use UserFrosting\Config\ConfigPathBuilder;
+use UserFrosting\I18n\LocalePathBuilder;
+use UserFrosting\I18n\MessageTranslator;
+use UserFrosting\Session\Session;
+use UserFrosting\Sprinkle\Core\Error\ExceptionHandlerManager;
+use UserFrosting\Sprinkle\Core\Error\Handler\NotFoundExceptionHandler;
+use UserFrosting\Sprinkle\Core\Log\MixedFormatter;
+use UserFrosting\Sprinkle\Core\Mail\Mailer;
+use UserFrosting\Sprinkle\Core\Alert\CacheAlertStream;
+use UserFrosting\Sprinkle\Core\Alert\SessionAlertStream;
+use UserFrosting\Sprinkle\Core\Router;
+use UserFrosting\Sprinkle\Core\Throttle\Throttler;
+use UserFrosting\Sprinkle\Core\Throttle\ThrottleRule;
+use UserFrosting\Sprinkle\Core\Twig\CoreExtension;
+use UserFrosting\Sprinkle\Core\Util\CheckEnvironment;
+use UserFrosting\Sprinkle\Core\Util\ClassMapper;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\NotFoundException;
+use UserFrosting\Support\Repository\Loader\ArrayFileLoader;
+use UserFrosting\Support\Repository\Repository;
+
+/**
+ * UserFrosting core services provider.
+ *
+ * Registers core services for UserFrosting, such as config, database, asset manager, translator, etc.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ServicesProvider
+{
+ /**
+ * Register UserFrosting's core services.
+ *
+ * @param ContainerInterface $container A DI container implementing ArrayAccess and container-interop.
+ */
+ public function register(ContainerInterface $container) {
+ /**
+ * Flash messaging service.
+ *
+ * Persists error/success messages between requests in the session.
+ */
+ $container['alerts'] = function ($c) {
+ $config = $c->config;
+
+ if ($config['alert.storage'] == 'cache') {
+ return new CacheAlertStream($config['alert.key'], $c->translator, $c->cache, $c->config);
+ } else if ($config['alert.storage'] == 'session') {
+ return new SessionAlertStream($config['alert.key'], $c->translator, $c->session);
+ } else {
+ throw new \Exception("Bad alert storage handler type '{$config['alert.storage']}' specified in configuration file.");
+ }
+ };
+
+ /**
+ * Asset loader service.
+ *
+ * Loads assets from a specified relative location.
+ * Assets are Javascript, CSS, image, and other files used by your site.
+ */
+ $container['assetLoader'] = function ($c) {
+ $basePath = \UserFrosting\SPRINKLES_DIR;
+ $pattern = "/^[A-Za-z0-9_\-]+\/assets\//";
+
+ $al = new AssetLoader($basePath, $pattern);
+ return $al;
+ };
+
+ /**
+ * Asset manager service.
+ *
+ * Loads raw or compiled asset information from your bundle.config.json schema file.
+ * Assets are Javascript, CSS, image, and other files used by your site.
+ */
+ $container['assets'] = function ($c) {
+ $config = $c->config;
+ $locator = $c->locator;
+
+ // Load asset schema
+ if ($config['assets.use_raw']) {
+ $baseUrl = $config['site.uri.public'] . '/' . $config['assets.raw.path'];
+ $removePrefix = \UserFrosting\APP_DIR_NAME . \UserFrosting\DS . \UserFrosting\SPRINKLES_DIR_NAME;
+ $aub = new AssetUrlBuilder($locator, $baseUrl, $removePrefix, 'assets');
+
+ $as = new AssetBundleSchema($aub);
+
+ // Load Sprinkle assets
+ $sprinkles = $c->sprinkleManager->getSprinkleNames();
+
+ // move this out into PathBuilder and Loader classes in userfrosting/assets
+ // This would also allow us to define and load bundles in themes
+ $bundleSchemas = array_reverse($locator->findResources('sprinkles://' . $config['assets.raw.schema'], TRUE, TRUE));
+
+ foreach ($bundleSchemas as $schema) {
+ if (file_exists($schema)) {
+ $as->loadRawSchemaFile($schema);
+ }
+ }
+ } else {
+ $baseUrl = $config['site.uri.public'] . '/' . $config['assets.compiled.path'];
+ $aub = new CompiledAssetUrlBuilder($baseUrl);
+
+ $as = new AssetBundleSchema($aub);
+ $as->loadCompiledSchemaFile($locator->findResource("build://" . $config['assets.compiled.schema'], TRUE, TRUE));
+ }
+
+ $am = new AssetManager($aub, $as);
+
+ return $am;
+ };
+
+ /**
+ * Cache service.
+ *
+ * @return \Illuminate\Cache\Repository
+ */
+ $container['cache'] = function ($c) {
+
+ $config = $c->config;
+
+ if ($config['cache.driver'] == 'file') {
+ $path = $c->locator->findResource('cache://', TRUE, TRUE);
+ $cacheStore = new TaggableFileStore($path);
+ } else if ($config['cache.driver'] == 'memcached') {
+ // We need to inject the prefix in the memcached config
+ $config = array_merge($config['cache.memcached'], ['prefix' => $config['cache.prefix']]);
+ $cacheStore = new MemcachedStore($config);
+ } else if ($config['cache.driver'] == 'redis') {
+ // We need to inject the prefix in the redis config
+ $config = array_merge($config['cache.redis'], ['prefix' => $config['cache.prefix']]);
+ $cacheStore = new RedisStore($config);
+ } else {
+ throw new \Exception("Bad cache store type '{$config['cache.driver']}' specified in configuration file.");
+ }
+
+ return $cacheStore->instance();
+ };
+
+ /**
+ * Middleware to check environment.
+ *
+ * We should cache the results of this, the first time that it succeeds.
+ */
+ $container['checkEnvironment'] = function ($c) {
+ $checkEnvironment = new CheckEnvironment($c->view, $c->locator, $c->cache);
+ return $checkEnvironment;
+ };
+
+ /**
+ * Class mapper.
+ *
+ * Creates an abstraction on top of class names to allow extending them in sprinkles.
+ */
+ $container['classMapper'] = function ($c) {
+ $classMapper = new ClassMapper();
+ $classMapper->setClassMapping('query_builder', 'UserFrosting\Sprinkle\Core\Database\Builder');
+ $classMapper->setClassMapping('throttle', 'UserFrosting\Sprinkle\Core\Database\Models\Throttle');
+ return $classMapper;
+ };
+
+ /**
+ * Site config service (separate from Slim settings).
+ *
+ * Will attempt to automatically determine which config file(s) to use based on the value of the UF_MODE environment variable.
+ */
+ $container['config'] = function ($c) {
+ // Grab any relevant dotenv variables from the .env file
+ try {
+ $dotenv = new Dotenv(\UserFrosting\APP_DIR);
+ $dotenv->load();
+ } catch (InvalidPathException $e) {
+ // Skip loading the environment config file if it doesn't exist.
+ }
+
+ // Get configuration mode from environment
+ $mode = getenv('UF_MODE') ?: '';
+
+ // Construct and load config repository
+ $builder = new ConfigPathBuilder($c->locator, 'config://');
+ $loader = new ArrayFileLoader($builder->buildPaths($mode));
+ $config = new Repository($loader->load());
+
+ // Construct base url from components, if not explicitly specified
+ if (!isset($config['site.uri.public'])) {
+ $base_uri = $config['site.uri.base'];
+
+ $public = new Uri(
+ $base_uri['scheme'],
+ $base_uri['host'],
+ $base_uri['port'],
+ $base_uri['path']
+ );
+
+ // Slim\Http\Uri likes to add trailing slashes when the path is empty, so this fixes that.
+ $config['site.uri.public'] = trim($public, '/');
+ }
+
+ // Hacky fix to prevent sessions from being hit too much: ignore CSRF middleware for requests for raw assets ;-)
+ // See https://github.com/laravel/framework/issues/8172#issuecomment-99112012 for more information on why it's bad to hit Laravel sessions multiple times in rapid succession.
+ $csrfBlacklist = $config['csrf.blacklist'];
+ $csrfBlacklist['^/' . $config['assets.raw.path']] = [
+ 'GET'
+ ];
+ $csrfBlacklist['^/wormhole'] = [
+ 'POST'
+ ];
+
+ $config->set('csrf.blacklist', $csrfBlacklist);
+
+ return $config;
+ };
+
+ /**
+ * Initialize CSRF guard middleware.
+ *
+ * @see https://github.com/slimphp/Slim-Csrf
+ */
+ $container['csrf'] = function ($c) {
+ $csrfKey = $c->config['session.keys.csrf'];
+
+ // Workaround so that we can pass storage into CSRF guard.
+ // If we tried to directly pass the indexed portion of `session` (for example, $c->session['site.csrf']),
+ // we would get an 'Indirect modification of overloaded element of UserFrosting\Session\Session' error.
+ // If we tried to assign an array and use that, PHP would only modify the local variable, and not the session.
+ // Since ArrayObject is an object, PHP will modify the object itself, allowing it to persist in the session.
+ if (!$c->session->has($csrfKey)) {
+ $c->session[$csrfKey] = new \ArrayObject();
+ }
+ $csrfStorage = $c->session[$csrfKey];
+
+ $onFailure = function ($request, $response, $next) {
+ $e = new BadRequestException("The CSRF code was invalid or not provided.");
+ $e->addUserMessage('CSRF_MISSING');
+ throw $e;
+
+ return $next($request, $response);
+ };
+
+ return new Guard($c->config['csrf.name'], $csrfStorage, $onFailure, $c->config['csrf.storage_limit'], $c->config['csrf.strength'], $c->config['csrf.persistent_token']);
+ };
+
+ /**
+ * Initialize Eloquent Capsule, which provides the database layer for UF.
+ *
+ * construct the individual objects rather than using the facade
+ */
+ $container['db'] = function ($c) {
+ $config = $c->config;
+
+ $capsule = new Capsule;
+
+ foreach ($config['db'] as $name => $dbConfig) {
+ $capsule->addConnection($dbConfig, $name);
+ }
+
+ $queryEventDispatcher = new Dispatcher(new Container);
+
+ $capsule->setEventDispatcher($queryEventDispatcher);
+
+ // Register as global connection
+ $capsule->setAsGlobal();
+
+ // Start Eloquent
+ $capsule->bootEloquent();
+
+ if ($config['debug.queries']) {
+ $logger = $c->queryLogger;
+
+ foreach ($config['db'] as $name => $dbConfig) {
+ $capsule->connection($name)->enableQueryLog();
+ }
+
+ // Register listener
+ $queryEventDispatcher->listen(QueryExecuted::class, function ($query) use ($logger) {
+ $logger->debug("Query executed on database [{$query->connectionName}]:", [
+ 'query' => $query->sql,
+ 'bindings' => $query->bindings,
+ 'time' => $query->time . ' ms'
+ ]);
+ });
+ }
+
+ return $capsule;
+ };
+
+ /**
+ * Debug logging with Monolog.
+ *
+ * Extend this service to push additional handlers onto the 'debug' log stack.
+ */
+ $container['debugLogger'] = function ($c) {
+ $logger = new Logger('debug');
+
+ $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
+
+ $handler = new StreamHandler($logFile);
+
+ $formatter = new MixedFormatter(NULL, NULL, TRUE);
+
+ $handler->setFormatter($formatter);
+ $logger->pushHandler($handler);
+
+ return $logger;
+ };
+
+ /**
+ * Custom error-handler for recoverable errors.
+ */
+ $container['errorHandler'] = function ($c) {
+ $settings = $c->settings;
+
+ $handler = new ExceptionHandlerManager($c, $settings['displayErrorDetails']);
+
+ // Register the base HttpExceptionHandler.
+ $handler->registerHandler('\UserFrosting\Support\Exception\HttpException', '\UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler');
+
+ // Register the NotFoundExceptionHandler.
+ $handler->registerHandler('\UserFrosting\Support\Exception\NotFoundException', '\UserFrosting\Sprinkle\Core\Error\Handler\NotFoundExceptionHandler');
+
+ // Register the PhpMailerExceptionHandler.
+ $handler->registerHandler('\phpmailerException', '\UserFrosting\Sprinkle\Core\Error\Handler\PhpMailerExceptionHandler');
+
+ return $handler;
+ };
+
+ /**
+ * Error logging with Monolog.
+ *
+ * Extend this service to push additional handlers onto the 'error' log stack.
+ */
+ $container['errorLogger'] = function ($c) {
+ $log = new Logger('errors');
+
+ $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
+
+ $handler = new StreamHandler($logFile, Logger::WARNING);
+
+ $formatter = new LineFormatter(NULL, NULL, TRUE);
+
+ $handler->setFormatter($formatter);
+ $log->pushHandler($handler);
+
+ return $log;
+ };
+
+ /**
+ * Factory service with FactoryMuffin.
+ *
+ * Provide access to factories for the rapid creation of objects for the purpose of testing
+ */
+ $container['factory'] = function ($c) {
+
+ // Get the path of all of the sprinkle's factories
+ $factoriesPath = $c->locator->findResources('factories://', TRUE, TRUE);
+
+ // Create a new Factory Muffin instance
+ $fm = new FactoryMuffin();
+
+ // Load all of the model definitions
+ $fm->loadFactories($factoriesPath);
+
+ // Set the locale. Could be the config one, but for testing English should do
+ Faker::setLocale('en_EN');
+
+ return $fm;
+ };
+
+ /**
+ * Builds search paths for locales in all Sprinkles.
+ */
+ $container['localePathBuilder'] = function ($c) {
+ $config = $c->config;
+
+ // Make sure the locale config is a valid string
+ if (!is_string($config['site.locales.default']) || $config['site.locales.default'] == '') {
+ throw new \UnexpectedValueException('The locale config is not a valid string.');
+ }
+
+ // Load the base locale file(s) as specified in the configuration
+ $locales = explode(',', $config['site.locales.default']);
+
+ return new LocalePathBuilder($c->locator, 'locale://', $locales);
+ };
+
+ /**
+ * Mail service.
+ */
+ $container['mailer'] = function ($c) {
+ $mailer = new Mailer($c->mailLogger, $c->config['mail']);
+
+ // Use UF debug settings to override any service-specific log settings.
+ if (!$c->config['debug.smtp']) {
+ $mailer->getPhpMailer()->SMTPDebug = 0;
+ }
+
+ return $mailer;
+ };
+
+ /**
+ * Mail logging service.
+ *
+ * PHPMailer will use this to log SMTP activity.
+ * Extend this service to push additional handlers onto the 'mail' log stack.
+ */
+ $container['mailLogger'] = function ($c) {
+ $log = new Logger('mail');
+
+ $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
+
+ $handler = new StreamHandler($logFile);
+ $formatter = new LineFormatter(NULL, NULL, TRUE);
+
+ $handler->setFormatter($formatter);
+ $log->pushHandler($handler);
+
+ return $log;
+ };
+
+ /**
+ * Error-handler for 404 errors. Notice that we manually create a UserFrosting NotFoundException,
+ * and a NotFoundExceptionHandler. This lets us pass through to the UF error handling system.
+ */
+ $container['notFoundHandler'] = function ($c) {
+ return function ($request, $response) use ($c) {
+ $exception = new NotFoundException;
+ $handler = new NotFoundExceptionHandler($c, $request, $response, $exception, $c->settings['displayErrorDetails']);
+ return $handler->handle();
+ };
+ };
+
+ /**
+ * Error-handler for PHP runtime errors. Notice that we just pass this through to our general-purpose
+ * error-handling service.
+ */
+ $container['phpErrorHandler'] = function ($c) {
+ return $c->errorHandler;
+ };
+
+ /**
+ * Laravel query logging with Monolog.
+ *
+ * Extend this service to push additional handlers onto the 'query' log stack.
+ */
+ $container['queryLogger'] = function ($c) {
+ $logger = new Logger('query');
+
+ $logFile = $c->locator->findResource('log://userfrosting.log', TRUE, TRUE);
+
+ $handler = new StreamHandler($logFile);
+
+ $formatter = new MixedFormatter(NULL, NULL, TRUE);
+
+ $handler->setFormatter($formatter);
+ $logger->pushHandler($handler);
+
+ return $logger;
+ };
+
+ /**
+ * Override Slim's default router with the UF router.
+ */
+ $container['router'] = function ($c) {
+ $routerCacheFile = FALSE;
+ if (isset($c->config['settings.routerCacheFile'])) {
+ $routerCacheFile = $c->config['settings.routerCacheFile'];
+ }
+
+ return (new Router)->setCacheFile($routerCacheFile);
+ };
+
+ /**
+ * Start the PHP session, with the name and parameters specified in the configuration file.
+ */
+ $container['session'] = function ($c) {
+ $config = $c->config;
+
+ // Create appropriate handler based on config
+ if ($config['session.handler'] == 'file') {
+ $fs = new FileSystem;
+ $handler = new FileSessionHandler($fs, $c->locator->findResource('session://'), $config['session.minutes']);
+ } else if ($config['session.handler'] == 'database') {
+ $connection = $c->db->connection();
+ // Table must exist, otherwise an exception will be thrown
+ $handler = new DatabaseSessionHandler($connection, $config['session.database.table'], $config['session.minutes']);
+ } else {
+ throw new \Exception("Bad session handler type '{$config['session.handler']}' specified in configuration file.");
+ }
+
+ // Create, start and return a new wrapper for $_SESSION
+ $session = new Session($handler, $config['session']);
+ $session->start();
+
+ return $session;
+ };
+
+ /**
+ * Request throttler.
+ *
+ * Throttles (rate-limits) requests of a predefined type, with rules defined in site config.
+ */
+ $container['throttler'] = function ($c) {
+ $throttler = new Throttler($c->classMapper);
+
+ $config = $c->config;
+
+ if ($config->has('throttles') && ($config['throttles'] !== NULL)) {
+ foreach ($config['throttles'] as $type => $rule) {
+ if ($rule) {
+ $throttleRule = new ThrottleRule($rule['method'], $rule['interval'], $rule['delays']);
+ $throttler->addThrottleRule($type, $throttleRule);
+ } else {
+ $throttler->addThrottleRule($type, NULL);
+ }
+ }
+ }
+
+ return $throttler;
+ };
+
+ /**
+ * Translation service, for translating message tokens.
+ */
+ $container['translator'] = function ($c) {
+ // Load the translations
+ $paths = $c->localePathBuilder->buildPaths();
+ $loader = new ArrayFileLoader($paths);
+
+ // Create the $translator object
+ $translator = new MessageTranslator($loader->load());
+
+ return $translator;
+ };
+
+ /**
+ * Set up Twig as the view, adding template paths for all sprinkles and the Slim Twig extension.
+ *
+ * Also adds the UserFrosting core Twig extension, which provides additional functions, filters, global variables, etc.
+ */
+ $container['view'] = function ($c) {
+ $templatePaths = $c->locator->findResources('templates://', TRUE, TRUE);
+
+ $view = new Twig($templatePaths);
+
+ $loader = $view->getLoader();
+
+ $sprinkles = $c->sprinkleManager->getSprinkleNames();
+
+ // Add Sprinkles' templates namespaces
+ foreach ($sprinkles as $sprinkle) {
+ $path = \UserFrosting\SPRINKLES_DIR . \UserFrosting\DS .
+ $sprinkle . \UserFrosting\DS .
+ \UserFrosting\TEMPLATE_DIR_NAME . \UserFrosting\DS;
+
+ if (is_dir($path)) {
+ $loader->addPath($path, $sprinkle);
+ }
+ }
+
+ $twig = $view->getEnvironment();
+
+ if ($c->config['cache.twig']) {
+ $twig->setCache($c->locator->findResource('cache://twig', TRUE, TRUE));
+ }
+
+ if ($c->config['debug.twig']) {
+ $twig->enableDebug();
+ $view->addExtension(new \Twig_Extension_Debug());
+ }
+
+ // Register the Slim extension with Twig
+ $slimExtension = new TwigExtension(
+ $c->router,
+ $c->request->getUri()
+ );
+ $view->addExtension($slimExtension);
+
+ // Register the core UF extension with Twig
+ $coreExtension = new CoreExtension($c);
+ $view->addExtension($coreExtension);
+
+ return $view;
+ };
+ }
+}
diff --git a/main/app/sprinkles/core/src/Sprunje/Sprunje.php b/main/app/sprinkles/core/src/Sprunje/Sprunje.php
index ea066a3..b81d266 100644
--- a/main/app/sprinkles/core/src/Sprunje/Sprunje.php
+++ b/main/app/sprinkles/core/src/Sprunje/Sprunje.php
@@ -1,547 +1,547 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Sprunje;
-
-use Carbon\Carbon;
-use Illuminate\Database\Eloquent\Builder;
-use League\Csv\Writer;
-use Psr\Http\Message\ResponseInterface as Response;
-use UserFrosting\Sprinkle\Core\Facades\Debug;
-use UserFrosting\Sprinkle\Core\Util\ClassMapper;
-use UserFrosting\Support\Exception\BadRequestException;
-use Valitron\Validator;
-
-/**
- * Sprunje
- *
- * Implements a versatile API for sorting, filtering, and paginating an Eloquent query builder.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-abstract class Sprunje
-{
- /**
- * @var UserFrosting\Sprinkle\Core\Util\ClassMapper
- */
- protected $classMapper;
-
- /**
- * Name of this Sprunje, used when generating output files.
- *
- * @var string
- */
- protected $name = '';
-
- /**
- * The base (unfiltered) query.
- *
- * @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation
- */
- protected $query;
-
- /**
- * Default HTTP request parameters
- *
- * @var array[string]
- */
- protected $options = [
- 'sorts' => [],
- 'filters' => [],
- 'lists' => [],
- 'size' => 'all',
- 'page' => NULL,
- 'format' => 'json'
- ];
-
- /**
- * Fields to allow filtering upon.
- *
- * @var array[string]
- */
- protected $filterable = [];
-
- /**
- * Fields to allow listing (enumeration) upon.
- *
- * @var array[string]
- */
- protected $listable = [];
-
- /**
- * Fields to allow sorting upon.
- *
- * @var array[string]
- */
- protected $sortable = [];
-
- /**
- * List of fields to exclude when processing an "_all" filter.
- *
- * @var array[string]
- */
- protected $excludeForAll = [];
-
- /**
- * Separator to use when splitting filter values to treat them as ORs.
- *
- * @var string
- */
- protected $orSeparator = '||';
-
- /**
- * Array key for the total unfiltered object count.
- *
- * @var string
- */
- protected $countKey = 'count';
-
- /**
- * Array key for the filtered object count.
- *
- * @var string
- */
- protected $countFilteredKey = 'count_filtered';
-
- /**
- * Array key for the actual result set.
- *
- * @var string
- */
- protected $rowsKey = 'rows';
-
- /**
- * Array key for the list of enumerated columns and their enumerations.
- *
- * @var string
- */
- protected $listableKey = 'listable';
-
- /**
- * Constructor.
- *
- * @param ClassMapper $classMapper
- * @param mixed[] $options
- */
- public function __construct(ClassMapper $classMapper, array $options) {
- $this->classMapper = $classMapper;
-
- // Validation on input data
- $v = new Validator($options);
- $v->rule('array', ['sorts', 'filters', 'lists']);
- $v->rule('regex', 'sorts.*', '/asc|desc/i');
- $v->rule('regex', 'size', '/all|[0-9]+/i');
- $v->rule('integer', 'page');
- $v->rule('regex', 'format', '/json|csv/i');
-
- // translated rules
- if (!$v->validate()) {
- $e = new BadRequestException();
- foreach ($v->errors() as $idx => $field) {
- foreach ($field as $eidx => $error) {
- $e->addUserMessage($error);
- }
- }
- throw $e;
- }
-
- $this->options = array_replace_recursive($this->options, $options);
-
- $this->query = $this->baseQuery();
-
- // Start a new query on any Model instances
- if (is_a($this->baseQuery(), '\Illuminate\Database\Eloquent\Model')) {
- $this->query = $this->baseQuery()->newQuery();
- }
- }
-
- /**
- * Extend the query by providing a callback.
- *
- * @param callable $callback A callback which accepts and returns a Builder instance.
- * @return $this
- */
- public function extendQuery(callable $callback) {
- $this->query = $callback($this->query);
- return $this;
- }
-
- /**
- * Execute the query and build the results, and append them in the appropriate format to the response.
- *
- * @param ResponseInterface $response
- * @return ResponseInterface
- */
- public function toResponse(Response $response) {
- $format = $this->options['format'];
-
- if ($format == 'csv') {
- $result = $this->getCsv();
-
- // Prepare response
- $settings = http_build_query($this->options);
- $date = Carbon::now()->format('Ymd');
- $response = $response->withAddedHeader('Content-Disposition', "attachment;filename=$date-{$this->name}-$settings.csv");
- $response = $response->withAddedHeader('Content-Type', 'text/csv; charset=utf-8');
- return $response->write($result);
- // Default to JSON
- } else {
- $result = $this->getArray();
- return $response->withJson($result, 200, JSON_PRETTY_PRINT);
- }
- }
-
- /**
- * Executes the sprunje query, applying all sorts, filters, and pagination.
- *
- * Returns an array containing `count` (the total number of rows, before filtering), `count_filtered` (the total number of rows after filtering),
- * and `rows` (the filtered result set).
- * @return mixed[]
- */
- public function getArray() {
- list($count, $countFiltered, $rows) = $this->getModels();
-
- // Return sprunjed results
- return [
- $this->countKey => $count,
- $this->countFilteredKey => $countFiltered,
- $this->rowsKey => $rows->values()->toArray(),
- $this->listableKey => $this->getListable()
- ];
- }
-
- /**
- * Run the query and build a CSV object by flattening the resulting collection. Ignores any pagination.
- *
- * @return SplTempFileObject
- */
- public function getCsv() {
- $filteredQuery = clone $this->query;
-
- // Apply filters
- $this->applyFilters($filteredQuery);
-
- // Apply sorts
- $this->applySorts($filteredQuery);
-
- $collection = collect($filteredQuery->get());
-
- // Perform any additional transformations on the dataset
- $this->applyTransformations($collection);
-
- $csv = Writer::createFromFileObject(new \SplTempFileObject());
-
- $columnNames = [];
-
- // Flatten collection while simultaneously building the column names from the union of each element's keys
- $collection->transform(function ($item, $key) use (&$columnNames) {
- $item = array_dot($item->toArray());
- foreach ($item as $itemKey => $itemValue) {
- if (!in_array($itemKey, $columnNames)) {
- $columnNames[] = $itemKey;
- }
- }
- return $item;
- });
-
- $csv->insertOne($columnNames);
-
- // Insert the data as rows in the CSV document
- $collection->each(function ($item) use ($csv, $columnNames) {
- $row = [];
- foreach ($columnNames as $itemKey) {
- // Only add the value if it is set and not an array. Laravel's array_dot sometimes creates empty child arrays :(
- // See https://github.com/laravel/framework/pull/13009
- if (isset($item[$itemKey]) && !is_array($item[$itemKey])) {
- $row[] = $item[$itemKey];
- } else {
- $row[] = '';
- }
- }
-
- $csv->insertOne($row);
- });
-
- return $csv;
- }
-
- /**
- * Executes the sprunje query, applying all sorts, filters, and pagination.
- *
- * Returns the filtered, paginated result set and the counts.
- * @return mixed[]
- */
- public function getModels() {
- // Count unfiltered total
- $count = $this->count($this->query);
-
- // Clone the Query\Builder, Eloquent\Builder, or Relation
- $filteredQuery = clone $this->query;
-
- // Apply filters
- $this->applyFilters($filteredQuery);
-
- // Count filtered total
- $countFiltered = $this->countFiltered($filteredQuery);
-
- // Apply sorts
- $this->applySorts($filteredQuery);
-
- // Paginate
- $this->applyPagination($filteredQuery);
-
- $collection = collect($filteredQuery->get());
-
- // Perform any additional transformations on the dataset
- $this->applyTransformations($collection);
-
- return [$count, $countFiltered, $collection];
- }
-
- /**
- * Get lists of values for specified fields in 'lists' option, calling a custom lister callback when appropriate.
- *
- * @return array
- */
- public function getListable() {
- $result = [];
- foreach ($this->listable as $name) {
-
- // Determine if a custom filter method has been defined
- $methodName = 'list' . studly_case($name);
-
- if (method_exists($this, $methodName)) {
- $result[$name] = $this->$methodName();
- } else {
- $result[$name] = $this->getColumnValues($name);
- }
- }
-
- return $result;
- }
-
- /**
- * Get the underlying queriable object in its current state.
- *
- * @return Builder
- */
- public function getQuery() {
- return $this->query;
- }
-
- /**
- * Set the underlying QueryBuilder object.
- *
- * @param Builder $query
- * @return $this
- */
- public function setQuery($query) {
- $this->query = $query;
- return $this;
- }
-
- /**
- * Apply any filters from the options, calling a custom filter callback when appropriate.
- *
- * @param Builder $query
- * @return $this
- */
- public function applyFilters($query) {
- foreach ($this->options['filters'] as $name => $value) {
- // Check that this filter is allowed
- if (($name != '_all') && !in_array($name, $this->filterable)) {
- $e = new BadRequestException();
- $e->addUserMessage('VALIDATE.SPRUNJE.BAD_FILTER', ['name' => $name]);
- throw $e;
- }
- // Since we want to match _all_ of the fields, we wrap the field callback in a 'where' callback
- $query->where(function ($fieldQuery) use ($name, $value) {
- $this->buildFilterQuery($fieldQuery, $name, $value);
- });
- }
-
- return $this;
- }
-
- /**
- * Apply any sorts from the options, calling a custom sorter callback when appropriate.
- *
- * @param Builder $query
- * @return $this
- */
- public function applySorts($query) {
- foreach ($this->options['sorts'] as $name => $direction) {
- // Check that this sort is allowed
- if (!in_array($name, $this->sortable)) {
- $e = new BadRequestException();
- $e->addUserMessage('VALIDATE.SPRUNJE.BAD_SORT', ['name' => $name]);
- throw $e;
- }
-
- // Determine if a custom sort method has been defined
- $methodName = 'sort' . studly_case($name);
-
- if (method_exists($this, $methodName)) {
- $this->$methodName($query, $direction);
- } else {
- $query->orderBy($name, $direction);
- }
- }
-
- return $this;
- }
-
- /**
- * Apply pagination based on the `page` and `size` options.
- *
- * @param Builder $query
- * @return $this
- */
- public function applyPagination($query) {
- if (
- ($this->options['page'] !== NULL) &&
- ($this->options['size'] !== NULL) &&
- ($this->options['size'] != 'all')
- ) {
- $offset = $this->options['size'] * $this->options['page'];
- $query->skip($offset)
- ->take($this->options['size']);
- }
-
- return $this;
- }
-
- /**
- * Match any filter in `filterable`.
- *
- * @param Builder $query
- * @param mixed $value
- * @return $this
- */
- protected function filterAll($query, $value) {
- foreach ($this->filterable as $name) {
- if (studly_case($name) != 'all' && !in_array($name, $this->excludeForAll)) {
- // Since we want to match _any_ of the fields, we wrap the field callback in a 'orWhere' callback
- $query->orWhere(function ($fieldQuery) use ($name, $value) {
- $this->buildFilterQuery($fieldQuery, $name, $value);
- });
- }
- }
-
- return $this;
- }
-
- /**
- * Build the filter query for a single field.
- *
- * @param Builder $query
- * @param string $name
- * @param mixed $value
- * @return $this
- */
- protected function buildFilterQuery($query, $name, $value) {
- $methodName = 'filter' . studly_case($name);
-
- // Determine if a custom filter method has been defined
- if (method_exists($this, $methodName)) {
- $this->$methodName($query, $value);
- } else {
- $this->buildFilterDefaultFieldQuery($query, $name, $value);
- }
-
- return $this;
- }
-
- /**
- * Perform a 'like' query on a single field, separating the value string on the or separator and
- * matching any of the supplied values.
- *
- * @param Builder $query
- * @param string $name
- * @param mixed $value
- * @return $this
- */
- protected function buildFilterDefaultFieldQuery($query, $name, $value) {
- // Default filter - split value on separator for OR queries
- // and search by column name
- $values = explode($this->orSeparator, $value);
- foreach ($values as $value) {
- $query->orLike($name, $value);
- }
-
- return $this;
- }
-
- /**
- * Set any transformations you wish to apply to the collection, after the query is executed.
- *
- * @param \Illuminate\Database\Eloquent\Collection $collection
- * @return \Illuminate\Database\Eloquent\Collection
- */
- protected function applyTransformations($collection) {
- return $collection;
- }
-
- /**
- * Set the initial query used by your Sprunje.
- *
- * @return Builder|Relation|Model
- */
- abstract protected function baseQuery();
-
- /**
- * Returns a list of distinct values for a specified column.
- * Formats results to have a "value" and "text" attribute.
- *
- * @param string $column
- * @return array
- */
- protected function getColumnValues($column) {
- $rawValues = $this->query->select($column)->distinct()->orderBy($column, 'asc')->get();
- $values = [];
- foreach ($rawValues as $raw) {
- $values[] = [
- 'value' => $raw[$column],
- 'text' => $raw[$column]
- ];
- }
- return $values;
- }
-
- /**
- * Get the unpaginated count of items (before filtering) in this query.
- *
- * @param Builder $query
- * @return int
- */
- protected function count($query) {
- return $query->count();
- }
-
- /**
- * Get the unpaginated count of items (after filtering) in this query.
- *
- * @param Builder $query
- * @return int
- */
- protected function countFiltered($query) {
- return $query->count();
- }
-
- /**
- * Executes the sprunje query, applying all sorts, filters, and pagination.
- *
- * Returns an array containing `count` (the total number of rows, before filtering), `count_filtered` (the total number of rows after filtering),
- * and `rows` (the filtered result set).
- * @deprecated since 4.1.7 Use getArray() instead.
- * @return mixed[]
- */
- public function getResults() {
- return $this->getArray();
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Sprunje;
+
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Builder;
+use League\Csv\Writer;
+use Psr\Http\Message\ResponseInterface as Response;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Util\ClassMapper;
+use UserFrosting\Support\Exception\BadRequestException;
+use Valitron\Validator;
+
+/**
+ * Sprunje
+ *
+ * Implements a versatile API for sorting, filtering, and paginating an Eloquent query builder.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+abstract class Sprunje
+{
+ /**
+ * @var UserFrosting\Sprinkle\Core\Util\ClassMapper
+ */
+ protected $classMapper;
+
+ /**
+ * Name of this Sprunje, used when generating output files.
+ *
+ * @var string
+ */
+ protected $name = '';
+
+ /**
+ * The base (unfiltered) query.
+ *
+ * @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation
+ */
+ protected $query;
+
+ /**
+ * Default HTTP request parameters
+ *
+ * @var array[string]
+ */
+ protected $options = [
+ 'sorts' => [],
+ 'filters' => [],
+ 'lists' => [],
+ 'size' => 'all',
+ 'page' => NULL,
+ 'format' => 'json'
+ ];
+
+ /**
+ * Fields to allow filtering upon.
+ *
+ * @var array[string]
+ */
+ protected $filterable = [];
+
+ /**
+ * Fields to allow listing (enumeration) upon.
+ *
+ * @var array[string]
+ */
+ protected $listable = [];
+
+ /**
+ * Fields to allow sorting upon.
+ *
+ * @var array[string]
+ */
+ protected $sortable = [];
+
+ /**
+ * List of fields to exclude when processing an "_all" filter.
+ *
+ * @var array[string]
+ */
+ protected $excludeForAll = [];
+
+ /**
+ * Separator to use when splitting filter values to treat them as ORs.
+ *
+ * @var string
+ */
+ protected $orSeparator = '||';
+
+ /**
+ * Array key for the total unfiltered object count.
+ *
+ * @var string
+ */
+ protected $countKey = 'count';
+
+ /**
+ * Array key for the filtered object count.
+ *
+ * @var string
+ */
+ protected $countFilteredKey = 'count_filtered';
+
+ /**
+ * Array key for the actual result set.
+ *
+ * @var string
+ */
+ protected $rowsKey = 'rows';
+
+ /**
+ * Array key for the list of enumerated columns and their enumerations.
+ *
+ * @var string
+ */
+ protected $listableKey = 'listable';
+
+ /**
+ * Constructor.
+ *
+ * @param ClassMapper $classMapper
+ * @param mixed[] $options
+ */
+ public function __construct(ClassMapper $classMapper, array $options) {
+ $this->classMapper = $classMapper;
+
+ // Validation on input data
+ $v = new Validator($options);
+ $v->rule('array', ['sorts', 'filters', 'lists']);
+ $v->rule('regex', 'sorts.*', '/asc|desc/i');
+ $v->rule('regex', 'size', '/all|[0-9]+/i');
+ $v->rule('integer', 'page');
+ $v->rule('regex', 'format', '/json|csv/i');
+
+ // translated rules
+ if (!$v->validate()) {
+ $e = new BadRequestException();
+ foreach ($v->errors() as $idx => $field) {
+ foreach ($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ $this->options = array_replace_recursive($this->options, $options);
+
+ $this->query = $this->baseQuery();
+
+ // Start a new query on any Model instances
+ if (is_a($this->baseQuery(), '\Illuminate\Database\Eloquent\Model')) {
+ $this->query = $this->baseQuery()->newQuery();
+ }
+ }
+
+ /**
+ * Extend the query by providing a callback.
+ *
+ * @param callable $callback A callback which accepts and returns a Builder instance.
+ * @return $this
+ */
+ public function extendQuery(callable $callback) {
+ $this->query = $callback($this->query);
+ return $this;
+ }
+
+ /**
+ * Execute the query and build the results, and append them in the appropriate format to the response.
+ *
+ * @param ResponseInterface $response
+ * @return ResponseInterface
+ */
+ public function toResponse(Response $response) {
+ $format = $this->options['format'];
+
+ if ($format == 'csv') {
+ $result = $this->getCsv();
+
+ // Prepare response
+ $settings = http_build_query($this->options);
+ $date = Carbon::now()->format('Ymd');
+ $response = $response->withAddedHeader('Content-Disposition', "attachment;filename=$date-{$this->name}-$settings.csv");
+ $response = $response->withAddedHeader('Content-Type', 'text/csv; charset=utf-8');
+ return $response->write($result);
+ // Default to JSON
+ } else {
+ $result = $this->getArray();
+ return $response->withJson($result, 200, JSON_PRETTY_PRINT);
+ }
+ }
+
+ /**
+ * Executes the sprunje query, applying all sorts, filters, and pagination.
+ *
+ * Returns an array containing `count` (the total number of rows, before filtering), `count_filtered` (the total number of rows after filtering),
+ * and `rows` (the filtered result set).
+ * @return mixed[]
+ */
+ public function getArray() {
+ list($count, $countFiltered, $rows) = $this->getModels();
+
+ // Return sprunjed results
+ return [
+ $this->countKey => $count,
+ $this->countFilteredKey => $countFiltered,
+ $this->rowsKey => $rows->values()->toArray(),
+ $this->listableKey => $this->getListable()
+ ];
+ }
+
+ /**
+ * Run the query and build a CSV object by flattening the resulting collection. Ignores any pagination.
+ *
+ * @return SplTempFileObject
+ */
+ public function getCsv() {
+ $filteredQuery = clone $this->query;
+
+ // Apply filters
+ $this->applyFilters($filteredQuery);
+
+ // Apply sorts
+ $this->applySorts($filteredQuery);
+
+ $collection = collect($filteredQuery->get());
+
+ // Perform any additional transformations on the dataset
+ $this->applyTransformations($collection);
+
+ $csv = Writer::createFromFileObject(new \SplTempFileObject());
+
+ $columnNames = [];
+
+ // Flatten collection while simultaneously building the column names from the union of each element's keys
+ $collection->transform(function ($item, $key) use (&$columnNames) {
+ $item = array_dot($item->toArray());
+ foreach ($item as $itemKey => $itemValue) {
+ if (!in_array($itemKey, $columnNames)) {
+ $columnNames[] = $itemKey;
+ }
+ }
+ return $item;
+ });
+
+ $csv->insertOne($columnNames);
+
+ // Insert the data as rows in the CSV document
+ $collection->each(function ($item) use ($csv, $columnNames) {
+ $row = [];
+ foreach ($columnNames as $itemKey) {
+ // Only add the value if it is set and not an array. Laravel's array_dot sometimes creates empty child arrays :(
+ // See https://github.com/laravel/framework/pull/13009
+ if (isset($item[$itemKey]) && !is_array($item[$itemKey])) {
+ $row[] = $item[$itemKey];
+ } else {
+ $row[] = '';
+ }
+ }
+
+ $csv->insertOne($row);
+ });
+
+ return $csv;
+ }
+
+ /**
+ * Executes the sprunje query, applying all sorts, filters, and pagination.
+ *
+ * Returns the filtered, paginated result set and the counts.
+ * @return mixed[]
+ */
+ public function getModels() {
+ // Count unfiltered total
+ $count = $this->count($this->query);
+
+ // Clone the Query\Builder, Eloquent\Builder, or Relation
+ $filteredQuery = clone $this->query;
+
+ // Apply filters
+ $this->applyFilters($filteredQuery);
+
+ // Count filtered total
+ $countFiltered = $this->countFiltered($filteredQuery);
+
+ // Apply sorts
+ $this->applySorts($filteredQuery);
+
+ // Paginate
+ $this->applyPagination($filteredQuery);
+
+ $collection = collect($filteredQuery->get());
+
+ // Perform any additional transformations on the dataset
+ $this->applyTransformations($collection);
+
+ return [$count, $countFiltered, $collection];
+ }
+
+ /**
+ * Get lists of values for specified fields in 'lists' option, calling a custom lister callback when appropriate.
+ *
+ * @return array
+ */
+ public function getListable() {
+ $result = [];
+ foreach ($this->listable as $name) {
+
+ // Determine if a custom filter method has been defined
+ $methodName = 'list' . studly_case($name);
+
+ if (method_exists($this, $methodName)) {
+ $result[$name] = $this->$methodName();
+ } else {
+ $result[$name] = $this->getColumnValues($name);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the underlying queriable object in its current state.
+ *
+ * @return Builder
+ */
+ public function getQuery() {
+ return $this->query;
+ }
+
+ /**
+ * Set the underlying QueryBuilder object.
+ *
+ * @param Builder $query
+ * @return $this
+ */
+ public function setQuery($query) {
+ $this->query = $query;
+ return $this;
+ }
+
+ /**
+ * Apply any filters from the options, calling a custom filter callback when appropriate.
+ *
+ * @param Builder $query
+ * @return $this
+ */
+ public function applyFilters($query) {
+ foreach ($this->options['filters'] as $name => $value) {
+ // Check that this filter is allowed
+ if (($name != '_all') && !in_array($name, $this->filterable)) {
+ $e = new BadRequestException();
+ $e->addUserMessage('VALIDATE.SPRUNJE.BAD_FILTER', ['name' => $name]);
+ throw $e;
+ }
+ // Since we want to match _all_ of the fields, we wrap the field callback in a 'where' callback
+ $query->where(function ($fieldQuery) use ($name, $value) {
+ $this->buildFilterQuery($fieldQuery, $name, $value);
+ });
+ }
+
+ return $this;
+ }
+
+ /**
+ * Apply any sorts from the options, calling a custom sorter callback when appropriate.
+ *
+ * @param Builder $query
+ * @return $this
+ */
+ public function applySorts($query) {
+ foreach ($this->options['sorts'] as $name => $direction) {
+ // Check that this sort is allowed
+ if (!in_array($name, $this->sortable)) {
+ $e = new BadRequestException();
+ $e->addUserMessage('VALIDATE.SPRUNJE.BAD_SORT', ['name' => $name]);
+ throw $e;
+ }
+
+ // Determine if a custom sort method has been defined
+ $methodName = 'sort' . studly_case($name);
+
+ if (method_exists($this, $methodName)) {
+ $this->$methodName($query, $direction);
+ } else {
+ $query->orderBy($name, $direction);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Apply pagination based on the `page` and `size` options.
+ *
+ * @param Builder $query
+ * @return $this
+ */
+ public function applyPagination($query) {
+ if (
+ ($this->options['page'] !== NULL) &&
+ ($this->options['size'] !== NULL) &&
+ ($this->options['size'] != 'all')
+ ) {
+ $offset = $this->options['size'] * $this->options['page'];
+ $query->skip($offset)
+ ->take($this->options['size']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Match any filter in `filterable`.
+ *
+ * @param Builder $query
+ * @param mixed $value
+ * @return $this
+ */
+ protected function filterAll($query, $value) {
+ foreach ($this->filterable as $name) {
+ if (studly_case($name) != 'all' && !in_array($name, $this->excludeForAll)) {
+ // Since we want to match _any_ of the fields, we wrap the field callback in a 'orWhere' callback
+ $query->orWhere(function ($fieldQuery) use ($name, $value) {
+ $this->buildFilterQuery($fieldQuery, $name, $value);
+ });
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Build the filter query for a single field.
+ *
+ * @param Builder $query
+ * @param string $name
+ * @param mixed $value
+ * @return $this
+ */
+ protected function buildFilterQuery($query, $name, $value) {
+ $methodName = 'filter' . studly_case($name);
+
+ // Determine if a custom filter method has been defined
+ if (method_exists($this, $methodName)) {
+ $this->$methodName($query, $value);
+ } else {
+ $this->buildFilterDefaultFieldQuery($query, $name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Perform a 'like' query on a single field, separating the value string on the or separator and
+ * matching any of the supplied values.
+ *
+ * @param Builder $query
+ * @param string $name
+ * @param mixed $value
+ * @return $this
+ */
+ protected function buildFilterDefaultFieldQuery($query, $name, $value) {
+ // Default filter - split value on separator for OR queries
+ // and search by column name
+ $values = explode($this->orSeparator, $value);
+ foreach ($values as $value) {
+ $query->orLike($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set any transformations you wish to apply to the collection, after the query is executed.
+ *
+ * @param \Illuminate\Database\Eloquent\Collection $collection
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ protected function applyTransformations($collection) {
+ return $collection;
+ }
+
+ /**
+ * Set the initial query used by your Sprunje.
+ *
+ * @return Builder|Relation|Model
+ */
+ abstract protected function baseQuery();
+
+ /**
+ * Returns a list of distinct values for a specified column.
+ * Formats results to have a "value" and "text" attribute.
+ *
+ * @param string $column
+ * @return array
+ */
+ protected function getColumnValues($column) {
+ $rawValues = $this->query->select($column)->distinct()->orderBy($column, 'asc')->get();
+ $values = [];
+ foreach ($rawValues as $raw) {
+ $values[] = [
+ 'value' => $raw[$column],
+ 'text' => $raw[$column]
+ ];
+ }
+ return $values;
+ }
+
+ /**
+ * Get the unpaginated count of items (before filtering) in this query.
+ *
+ * @param Builder $query
+ * @return int
+ */
+ protected function count($query) {
+ return $query->count();
+ }
+
+ /**
+ * Get the unpaginated count of items (after filtering) in this query.
+ *
+ * @param Builder $query
+ * @return int
+ */
+ protected function countFiltered($query) {
+ return $query->count();
+ }
+
+ /**
+ * Executes the sprunje query, applying all sorts, filters, and pagination.
+ *
+ * Returns an array containing `count` (the total number of rows, before filtering), `count_filtered` (the total number of rows after filtering),
+ * and `rows` (the filtered result set).
+ * @deprecated since 4.1.7 Use getArray() instead.
+ * @return mixed[]
+ */
+ public function getResults() {
+ return $this->getArray();
+ }
+}
diff --git a/main/app/sprinkles/core/src/Throttle/ThrottleRule.php b/main/app/sprinkles/core/src/Throttle/ThrottleRule.php
index c5e0c82..5840027 100644
--- a/main/app/sprinkles/core/src/Throttle/ThrottleRule.php
+++ b/main/app/sprinkles/core/src/Throttle/ThrottleRule.php
@@ -1,133 +1,133 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Throttle;
-
-/**
- * ThrottleRule Class
- *
- * Represents a request throttling rule.
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ThrottleRule
-{
- /** @var string Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling. */
- protected $method;
-
- /** @var int The amount of time, in seconds, to look back in determining attempts to consider. */
- protected $interval;
-
- /**
- * @var int[] A mapping of minimum observation counts (x) to delays (y), in seconds.
- * Any throttleable event that has occurred more than x times in this rule's interval,
- * must wait y seconds after the last occurrence before another attempt is permitted.
- */
- protected $delays;
-
- /**
- * Create a new ThrottleRule object.
- *
- * @param string $method Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling.
- * @param int $interval The amount of time, in seconds, to look back in determining attempts to consider.
- * @param int[] $delays A mapping of minimum observation counts (x) to delays (y), in seconds.
- */
- public function __construct($method, $interval, $delays) {
- $this->setMethod($method);
- $this->setInterval($interval);
- $this->setDelays($delays);
- }
-
- /**
- * Get the current delay on this rule for a particular number of event counts.
- *
- * @param Carbon\Carbon $lastEventTime The timestamp for the last countable event.
- * @param int $count The total number of events which have occurred in an interval.
- */
- public function getDelay($lastEventTime, $count) {
- // Zero occurrences always maps to a delay of 0 seconds.
- if ($count == 0) {
- return 0;
- }
-
- foreach ($this->delays as $observations => $delay) {
- // Skip any delay rules for which we haven't met the requisite number of observations
- if ($count < $observations) {
- continue;
- }
-
- // If this rule meets the observed number of events, and violates the required delay, then return the remaining time left
- if ($lastEventTime->diffInSeconds() < $delay) {
- return $lastEventTime->addSeconds($delay)->diffInSeconds();
- }
- }
-
- return 0;
- }
-
- /**
- * Gets the current mapping of attempts (int) to delays (seconds).
- *
- * @return int[]
- */
- public function getDelays() {
- return $this->delays;
- }
-
- /**
- * Gets the current throttling interval (seconds).
- *
- * @return int
- */
- public function getInterval() {
- return $this->interval;
- }
-
- /**
- * Gets the current throttling method ('ip' or 'data').
- *
- * @return string
- */
- public function getMethod() {
- return $this->method;
- }
-
- /**
- * Sets the current mapping of attempts (int) to delays (seconds).
- *
- * @param int[] A mapping of minimum observation counts (x) to delays (y), in seconds.
- */
- public function setDelays($delays) {
- // Sort the array by key, from highest to lowest value
- $this->delays = $delays;
- krsort($this->delays);
-
- return $this;
- }
-
- /**
- * Sets the current throttling interval (seconds).
- *
- * @param int The amount of time, in seconds, to look back in determining attempts to consider.
- */
- public function setInterval($interval) {
- $this->interval = $interval;
-
- return $this;
- }
-
- /**
- * Sets the current throttling method ('ip' or 'data').
- *
- * @param string Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling.
- */
- public function setMethod($method) {
- $this->method = $method;
-
- return $this;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Throttle;
+
+/**
+ * ThrottleRule Class
+ *
+ * Represents a request throttling rule.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ThrottleRule
+{
+ /** @var string Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling. */
+ protected $method;
+
+ /** @var int The amount of time, in seconds, to look back in determining attempts to consider. */
+ protected $interval;
+
+ /**
+ * @var int[] A mapping of minimum observation counts (x) to delays (y), in seconds.
+ * Any throttleable event that has occurred more than x times in this rule's interval,
+ * must wait y seconds after the last occurrence before another attempt is permitted.
+ */
+ protected $delays;
+
+ /**
+ * Create a new ThrottleRule object.
+ *
+ * @param string $method Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling.
+ * @param int $interval The amount of time, in seconds, to look back in determining attempts to consider.
+ * @param int[] $delays A mapping of minimum observation counts (x) to delays (y), in seconds.
+ */
+ public function __construct($method, $interval, $delays) {
+ $this->setMethod($method);
+ $this->setInterval($interval);
+ $this->setDelays($delays);
+ }
+
+ /**
+ * Get the current delay on this rule for a particular number of event counts.
+ *
+ * @param Carbon\Carbon $lastEventTime The timestamp for the last countable event.
+ * @param int $count The total number of events which have occurred in an interval.
+ */
+ public function getDelay($lastEventTime, $count) {
+ // Zero occurrences always maps to a delay of 0 seconds.
+ if ($count == 0) {
+ return 0;
+ }
+
+ foreach ($this->delays as $observations => $delay) {
+ // Skip any delay rules for which we haven't met the requisite number of observations
+ if ($count < $observations) {
+ continue;
+ }
+
+ // If this rule meets the observed number of events, and violates the required delay, then return the remaining time left
+ if ($lastEventTime->diffInSeconds() < $delay) {
+ return $lastEventTime->addSeconds($delay)->diffInSeconds();
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Gets the current mapping of attempts (int) to delays (seconds).
+ *
+ * @return int[]
+ */
+ public function getDelays() {
+ return $this->delays;
+ }
+
+ /**
+ * Gets the current throttling interval (seconds).
+ *
+ * @return int
+ */
+ public function getInterval() {
+ return $this->interval;
+ }
+
+ /**
+ * Gets the current throttling method ('ip' or 'data').
+ *
+ * @return string
+ */
+ public function getMethod() {
+ return $this->method;
+ }
+
+ /**
+ * Sets the current mapping of attempts (int) to delays (seconds).
+ *
+ * @param int[] A mapping of minimum observation counts (x) to delays (y), in seconds.
+ */
+ public function setDelays($delays) {
+ // Sort the array by key, from highest to lowest value
+ $this->delays = $delays;
+ krsort($this->delays);
+
+ return $this;
+ }
+
+ /**
+ * Sets the current throttling interval (seconds).
+ *
+ * @param int The amount of time, in seconds, to look back in determining attempts to consider.
+ */
+ public function setInterval($interval) {
+ $this->interval = $interval;
+
+ return $this;
+ }
+
+ /**
+ * Sets the current throttling method ('ip' or 'data').
+ *
+ * @param string Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling.
+ */
+ public function setMethod($method) {
+ $this->method = $method;
+
+ return $this;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Throttle/Throttler.php b/main/app/sprinkles/core/src/Throttle/Throttler.php
index 4ab9dd6..f7d1cc7 100644
--- a/main/app/sprinkles/core/src/Throttle/Throttler.php
+++ b/main/app/sprinkles/core/src/Throttle/Throttler.php
@@ -1,172 +1,172 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Throttle;
-
-use Carbon\Carbon;
-use UserFrosting\Sprinkle\Core\Util\ClassMapper;
-
-/**
- * Handles throttling (rate limiting) of specific types of requests.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Throttler
-{
- /**
- * @var UserFrosting\Sprinkle\Core\Util\ClassMapper
- */
- protected $classMapper;
-
- /**
- * @var ThrottleRule[] An array mapping throttle names to throttle rules.
- */
- protected $throttleRules;
-
- /**
- * Create a new Throttler object.
- *
- * @param ClassMapper $classMapper Maps generic class identifiers to specific class names.
- */
- public function __construct(ClassMapper $classMapper) {
- $this->classMapper = $classMapper;
- $this->throttleRules = [];
- }
-
- /**
- * Add a throttling rule for a particular throttle event type.
- *
- * @param string $type The type of throttle event to check against.
- * @param ThrottleRule $rule The rule to use when throttling this type of event.
- */
- public function addThrottleRule($type, $rule) {
- if (!($rule instanceof ThrottleRule || ($rule === NULL))) {
- throw new ThrottlerException('$rule must be of type ThrottleRule (or null).');
- }
-
- $this->throttleRules[$type] = $rule;
-
- return $this;
- }
-
- /**
- * Check the current request against a specified throttle rule.
- *
- * @param string $type The type of throttle event to check against.
- * @param mixed[] $requestData Any additional request parameters to use in checking the throttle.
- * @return bool
- */
- public function getDelay($type, $requestData = []) {
- $throttleRule = $this->getRule($type);
-
- if (is_null($throttleRule)) {
- return 0;
- }
-
- // Get earliest time to start looking for throttleable events
- $startTime = Carbon::now()
- ->subSeconds($throttleRule->getInterval());
-
- // Fetch all throttle events of the specified type, that match the specified rule
- if ($throttleRule->getMethod() == 'ip') {
- $events = $this->classMapper->staticMethod('throttle', 'where', 'type', $type)
- ->where('created_at', '>', $startTime)
- ->where('ip', $_SERVER['REMOTE_ADDR'])
- ->get();
- } else {
- $events = $this->classMapper->staticMethod('throttle', 'where', 'type', $type)
- ->where('created_at', '>', $startTime)
- ->get();
-
- // Filter out only events that match the required JSON data
- $events = $events->filter(function ($item, $key) use ($requestData) {
- $data = json_decode($item->request_data);
-
- // If a field is not specified in the logged data, or it doesn't match the value we're searching for,
- // then filter out this event from the collection.
- foreach ($requestData as $name => $value) {
- if (!isset($data->$name) || ($data->$name != $value)) {
- return FALSE;
- }
- }
-
- return TRUE;
- });
- }
-
- // Check the collection of events against the specified throttle rule.
- return $this->computeDelay($events, $throttleRule);
- }
-
- /**
- * Get a registered rule of a particular type.
- *
- * @param string $type
- * @throws ThrottlerException
- * @return ThrottleRule[]
- */
- public function getRule($type) {
- if (!array_key_exists($type, $this->throttleRules)) {
- throw new ThrottlerException("The throttling rule for '$type' could not be found.");
- }
-
- return $this->throttleRules[$type];
- }
-
- /**
- * Get the current throttling rules.
- *
- * @return ThrottleRule[]
- */
- public function getThrottleRules() {
- return $this->throttleRules;
- }
-
- /**
- * Log a throttleable event to the database.
- *
- * @param string $type the type of event
- * @param string[] $requestData an array of field names => values that are relevant to throttling for this event (e.g. username, email, etc).
- */
- public function logEvent($type, $requestData = []) {
- // Just a check to make sure the rule exists
- $throttleRule = $this->getRule($type);
-
- if (is_null($throttleRule)) {
- return $this;
- }
-
- $event = $this->classMapper->createInstance('throttle', [
- 'type' => $type,
- 'ip' => $_SERVER['REMOTE_ADDR'],
- 'request_data' => json_encode($requestData)
- ]);
-
- $event->save();
-
- return $this;
- }
-
- /**
- * Returns the current delay for a specified throttle rule.
- *
- * @param Throttle[] $events a Collection of throttle events.
- * @param ThrottleRule $throttleRule a rule representing the strategy to use for throttling a particular type of event.
- * @return int seconds remaining until a particular event is permitted to be attempted again.
- */
- protected function computeDelay($events, $throttleRule) {
- // If no matching events found, then there is no delay
- if (!$events->count()) {
- return 0;
- }
-
- // Great, now we compare our delay against the most recent attempt
- $lastEvent = $events->last();
- return $throttleRule->getDelay($lastEvent->created_at, $events->count());
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Throttle;
+
+use Carbon\Carbon;
+use UserFrosting\Sprinkle\Core\Util\ClassMapper;
+
+/**
+ * Handles throttling (rate limiting) of specific types of requests.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Throttler
+{
+ /**
+ * @var UserFrosting\Sprinkle\Core\Util\ClassMapper
+ */
+ protected $classMapper;
+
+ /**
+ * @var ThrottleRule[] An array mapping throttle names to throttle rules.
+ */
+ protected $throttleRules;
+
+ /**
+ * Create a new Throttler object.
+ *
+ * @param ClassMapper $classMapper Maps generic class identifiers to specific class names.
+ */
+ public function __construct(ClassMapper $classMapper) {
+ $this->classMapper = $classMapper;
+ $this->throttleRules = [];
+ }
+
+ /**
+ * Add a throttling rule for a particular throttle event type.
+ *
+ * @param string $type The type of throttle event to check against.
+ * @param ThrottleRule $rule The rule to use when throttling this type of event.
+ */
+ public function addThrottleRule($type, $rule) {
+ if (!($rule instanceof ThrottleRule || ($rule === NULL))) {
+ throw new ThrottlerException('$rule must be of type ThrottleRule (or null).');
+ }
+
+ $this->throttleRules[$type] = $rule;
+
+ return $this;
+ }
+
+ /**
+ * Check the current request against a specified throttle rule.
+ *
+ * @param string $type The type of throttle event to check against.
+ * @param mixed[] $requestData Any additional request parameters to use in checking the throttle.
+ * @return bool
+ */
+ public function getDelay($type, $requestData = []) {
+ $throttleRule = $this->getRule($type);
+
+ if (is_null($throttleRule)) {
+ return 0;
+ }
+
+ // Get earliest time to start looking for throttleable events
+ $startTime = Carbon::now()
+ ->subSeconds($throttleRule->getInterval());
+
+ // Fetch all throttle events of the specified type, that match the specified rule
+ if ($throttleRule->getMethod() == 'ip') {
+ $events = $this->classMapper->staticMethod('throttle', 'where', 'type', $type)
+ ->where('created_at', '>', $startTime)
+ ->where('ip', $_SERVER['REMOTE_ADDR'])
+ ->get();
+ } else {
+ $events = $this->classMapper->staticMethod('throttle', 'where', 'type', $type)
+ ->where('created_at', '>', $startTime)
+ ->get();
+
+ // Filter out only events that match the required JSON data
+ $events = $events->filter(function ($item, $key) use ($requestData) {
+ $data = json_decode($item->request_data);
+
+ // If a field is not specified in the logged data, or it doesn't match the value we're searching for,
+ // then filter out this event from the collection.
+ foreach ($requestData as $name => $value) {
+ if (!isset($data->$name) || ($data->$name != $value)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ });
+ }
+
+ // Check the collection of events against the specified throttle rule.
+ return $this->computeDelay($events, $throttleRule);
+ }
+
+ /**
+ * Get a registered rule of a particular type.
+ *
+ * @param string $type
+ * @throws ThrottlerException
+ * @return ThrottleRule[]
+ */
+ public function getRule($type) {
+ if (!array_key_exists($type, $this->throttleRules)) {
+ throw new ThrottlerException("The throttling rule for '$type' could not be found.");
+ }
+
+ return $this->throttleRules[$type];
+ }
+
+ /**
+ * Get the current throttling rules.
+ *
+ * @return ThrottleRule[]
+ */
+ public function getThrottleRules() {
+ return $this->throttleRules;
+ }
+
+ /**
+ * Log a throttleable event to the database.
+ *
+ * @param string $type the type of event
+ * @param string[] $requestData an array of field names => values that are relevant to throttling for this event (e.g. username, email, etc).
+ */
+ public function logEvent($type, $requestData = []) {
+ // Just a check to make sure the rule exists
+ $throttleRule = $this->getRule($type);
+
+ if (is_null($throttleRule)) {
+ return $this;
+ }
+
+ $event = $this->classMapper->createInstance('throttle', [
+ 'type' => $type,
+ 'ip' => $_SERVER['REMOTE_ADDR'],
+ 'request_data' => json_encode($requestData)
+ ]);
+
+ $event->save();
+
+ return $this;
+ }
+
+ /**
+ * Returns the current delay for a specified throttle rule.
+ *
+ * @param Throttle[] $events a Collection of throttle events.
+ * @param ThrottleRule $throttleRule a rule representing the strategy to use for throttling a particular type of event.
+ * @return int seconds remaining until a particular event is permitted to be attempted again.
+ */
+ protected function computeDelay($events, $throttleRule) {
+ // If no matching events found, then there is no delay
+ if (!$events->count()) {
+ return 0;
+ }
+
+ // Great, now we compare our delay against the most recent attempt
+ $lastEvent = $events->last();
+ return $throttleRule->getDelay($lastEvent->created_at, $events->count());
+ }
+}
diff --git a/main/app/sprinkles/core/src/Throttle/ThrottlerException.php b/main/app/sprinkles/core/src/Throttle/ThrottlerException.php
index 08f2919..af29bc8 100644
--- a/main/app/sprinkles/core/src/Throttle/ThrottlerException.php
+++ b/main/app/sprinkles/core/src/Throttle/ThrottlerException.php
@@ -1,19 +1,19 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Throttle;
-
-/**
- * Throttler exception. Used when there is a problem with the request throttler.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ThrottlerException extends \RuntimeException
-{
-
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Throttle;
+
+/**
+ * Throttler exception. Used when there is a problem with the request throttler.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ThrottlerException extends \RuntimeException
+{
+
+}
diff --git a/main/app/sprinkles/core/src/Twig/CacheHelper.php b/main/app/sprinkles/core/src/Twig/CacheHelper.php
index a3c11c6..64799ca 100644
--- a/main/app/sprinkles/core/src/Twig/CacheHelper.php
+++ b/main/app/sprinkles/core/src/Twig/CacheHelper.php
@@ -1,57 +1,57 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Twig;
-
-use Interop\Container\ContainerInterface;
-use Illuminate\Filesystem\Filesystem;
-
-/**
- * Provides helper function to delete the Twig cache directory
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class CacheHelper
-{
-
- /**
- * @var ContainerInterface The global container object, which holds all your services.
- */
- protected $ci;
-
- /**
- * Constructor.
- *
- * @param ContainerInterface $ci The global container object, which holds all your services.
- */
- public function __construct(ContainerInterface $ci) {
- $this->ci = $ci;
- }
-
- /**
- * Function that delete the Twig cache directory content
- *
- * @access public
- * @return bool true/false if operation is successfull
- */
- public function clearCache() {
- // Get location
- $path = $this->ci->locator->findResource('cache://twig', TRUE, TRUE);
-
- // Get Filesystem instance
- $fs = new FileSystem;
-
- // Make sure directory exist and delete it
- if ($fs->exists($path)) {
- return $fs->deleteDirectory($path, TRUE);
- }
-
- // It's still considered a success if directory doesn't exist yet
- return TRUE;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Twig;
+
+use Interop\Container\ContainerInterface;
+use Illuminate\Filesystem\Filesystem;
+
+/**
+ * Provides helper function to delete the Twig cache directory
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class CacheHelper
+{
+
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * Constructor.
+ *
+ * @param ContainerInterface $ci The global container object, which holds all your services.
+ */
+ public function __construct(ContainerInterface $ci) {
+ $this->ci = $ci;
+ }
+
+ /**
+ * Function that delete the Twig cache directory content
+ *
+ * @access public
+ * @return bool true/false if operation is successfull
+ */
+ public function clearCache() {
+ // Get location
+ $path = $this->ci->locator->findResource('cache://twig', TRUE, TRUE);
+
+ // Get Filesystem instance
+ $fs = new FileSystem;
+
+ // Make sure directory exist and delete it
+ if ($fs->exists($path)) {
+ return $fs->deleteDirectory($path, TRUE);
+ }
+
+ // It's still considered a success if directory doesn't exist yet
+ return TRUE;
+ }
+}
diff --git a/main/app/sprinkles/core/src/Twig/CoreExtension.php b/main/app/sprinkles/core/src/Twig/CoreExtension.php
index 2837e84..fccd542 100644
--- a/main/app/sprinkles/core/src/Twig/CoreExtension.php
+++ b/main/app/sprinkles/core/src/Twig/CoreExtension.php
@@ -1,120 +1,120 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Twig;
-
-use Interop\Container\ContainerInterface;
-use UserFrosting\Sprinkle\Core\Util\Util;
-
-/**
- * Extends Twig functionality for the Core sprinkle.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class CoreExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
-{
-
- /**
- * @var ContainerInterface The global container object, which holds all your services.
- */
- protected $services;
-
- /**
- * Constructor.
- *
- * @param ContainerInterface $services The global container object, which holds all your services.
- */
- public function __construct(ContainerInterface $services) {
- $this->services = $services;
- }
-
- /**
- * Get the name of this extension.
- *
- * @return string
- */
- public function getName() {
- return 'userfrosting/core';
- }
-
- /**
- * Adds Twig functions `getAlerts` and `translate`.
- *
- * @return array[\Twig_SimpleFunction]
- */
- public function getFunctions() {
- return array(
- // Add Twig function for fetching alerts
- new \Twig_SimpleFunction('getAlerts', function ($clear = TRUE) {
- if ($clear) {
- return $this->services['alerts']->getAndClearMessages();
- } else {
- return $this->services['alerts']->messages();
- }
- }),
- new \Twig_SimpleFunction('translate', function ($hook, $params = array()) {
- return $this->services['translator']->translate($hook, $params);
- }, [
- 'is_safe' => ['html']
- ])
- );
- }
-
- /**
- * Adds Twig filters `unescape`.
- *
- * @return array[\Twig_SimpleFilter]
- */
- public function getFilters() {
- return array(
- /**
- * Converts phone numbers to a standard format.
- *
- * @param String $num A unformatted phone number
- * @return String Returns the formatted phone number
- */
- new \Twig_SimpleFilter('phone', function ($num) {
- return Util::formatPhoneNumber($num);
- }),
- new \Twig_SimpleFilter('unescape', function ($string) {
- return html_entity_decode($string);
- })
- );
- }
-
- /**
- * Adds Twig global variables `site` and `assets`.
- *
- * @return array[mixed]
- */
- public function getGlobals() {
- // CSRF token name and value
- $csrfNameKey = $this->services->csrf->getTokenNameKey();
- $csrfValueKey = $this->services->csrf->getTokenValueKey();
- $csrfName = $this->services->csrf->getTokenName();
- $csrfValue = $this->services->csrf->getTokenValue();
-
- $csrf = [
- 'csrf' => [
- 'keys' => [
- 'name' => $csrfNameKey,
- 'value' => $csrfValueKey
- ],
- 'name' => $csrfName,
- 'value' => $csrfValue
- ]
- ];
-
- $site = array_replace_recursive($this->services->config['site'], $csrf);
-
- return [
- 'site' => $site,
- 'assets' => $this->services->assets
- ];
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Twig;
+
+use Interop\Container\ContainerInterface;
+use UserFrosting\Sprinkle\Core\Util\Util;
+
+/**
+ * Extends Twig functionality for the Core sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class CoreExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
+{
+
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $services;
+
+ /**
+ * Constructor.
+ *
+ * @param ContainerInterface $services The global container object, which holds all your services.
+ */
+ public function __construct(ContainerInterface $services) {
+ $this->services = $services;
+ }
+
+ /**
+ * Get the name of this extension.
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'userfrosting/core';
+ }
+
+ /**
+ * Adds Twig functions `getAlerts` and `translate`.
+ *
+ * @return array[\Twig_SimpleFunction]
+ */
+ public function getFunctions() {
+ return array(
+ // Add Twig function for fetching alerts
+ new \Twig_SimpleFunction('getAlerts', function ($clear = TRUE) {
+ if ($clear) {
+ return $this->services['alerts']->getAndClearMessages();
+ } else {
+ return $this->services['alerts']->messages();
+ }
+ }),
+ new \Twig_SimpleFunction('translate', function ($hook, $params = array()) {
+ return $this->services['translator']->translate($hook, $params);
+ }, [
+ 'is_safe' => ['html']
+ ])
+ );
+ }
+
+ /**
+ * Adds Twig filters `unescape`.
+ *
+ * @return array[\Twig_SimpleFilter]
+ */
+ public function getFilters() {
+ return array(
+ /**
+ * Converts phone numbers to a standard format.
+ *
+ * @param String $num A unformatted phone number
+ * @return String Returns the formatted phone number
+ */
+ new \Twig_SimpleFilter('phone', function ($num) {
+ return Util::formatPhoneNumber($num);
+ }),
+ new \Twig_SimpleFilter('unescape', function ($string) {
+ return html_entity_decode($string);
+ })
+ );
+ }
+
+ /**
+ * Adds Twig global variables `site` and `assets`.
+ *
+ * @return array[mixed]
+ */
+ public function getGlobals() {
+ // CSRF token name and value
+ $csrfNameKey = $this->services->csrf->getTokenNameKey();
+ $csrfValueKey = $this->services->csrf->getTokenValueKey();
+ $csrfName = $this->services->csrf->getTokenName();
+ $csrfValue = $this->services->csrf->getTokenValue();
+
+ $csrf = [
+ 'csrf' => [
+ 'keys' => [
+ 'name' => $csrfNameKey,
+ 'value' => $csrfValueKey
+ ],
+ 'name' => $csrfName,
+ 'value' => $csrfValue
+ ]
+ ];
+
+ $site = array_replace_recursive($this->services->config['site'], $csrf);
+
+ return [
+ 'site' => $site,
+ 'assets' => $this->services->assets
+ ];
+ }
+}
diff --git a/main/app/sprinkles/core/src/Util/BadClassNameException.php b/main/app/sprinkles/core/src/Util/BadClassNameException.php
index 1cd6f4e..1271c44 100644
--- a/main/app/sprinkles/core/src/Util/BadClassNameException.php
+++ b/main/app/sprinkles/core/src/Util/BadClassNameException.php
@@ -1,19 +1,19 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-/**
- * Bad class name exception. Used when a class name is dynamically invoked, but the class does not exist.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class BadClassNameException extends \LogicException
-{
- //
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+/**
+ * Bad class name exception. Used when a class name is dynamically invoked, but the class does not exist.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class BadClassNameException extends \LogicException
+{
+ //
+}
diff --git a/main/app/sprinkles/core/src/Util/Captcha.php b/main/app/sprinkles/core/src/Util/Captcha.php
index 4c221cb..b93cc73 100644
--- a/main/app/sprinkles/core/src/Util/Captcha.php
+++ b/main/app/sprinkles/core/src/Util/Captcha.php
@@ -1,154 +1,154 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-use UserFrosting\Session\Session;
-
-/**
- * Captcha Class
- *
- * Implements the captcha for user registration.
- *
- * @author r3wt
- * @author Alex Weissman (https://alexanderweissman.com)
- * @see http://www.userfrosting.com/components/#messages
- */
-class Captcha
-{
- /**
- * @var string The randomly generated captcha code.
- */
- protected $code;
-
- /**
- * @var string The captcha image, represented as a binary string.
- */
- protected $image;
-
- /**
- * @var UserFrosting\Session\Session We use the session object so that the hashed captcha token will automatically appear in the session.
- */
- protected $session;
-
- /**
- * @var string
- */
- protected $key;
-
- /**
- * Create a new captcha.
- */
- public function __construct($session, $key) {
- $this->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+use UserFrosting\Session\Session;
+
+/**
+ * Captcha Class
+ *
+ * Implements the captcha for user registration.
+ *
+ * @author r3wt
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/components/#messages
+ */
+class Captcha
+{
+ /**
+ * @var string The randomly generated captcha code.
+ */
+ protected $code;
+
+ /**
+ * @var string The captcha image, represented as a binary string.
+ */
+ protected $image;
+
+ /**
+ * @var UserFrosting\Session\Session We use the session object so that the hashed captcha token will automatically appear in the session.
+ */
+ protected $session;
+
+ /**
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * Create a new captcha.
+ */
+ public function __construct($session, $key) {
+ $this->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
index 05b555f..b8e5ec8 100644
--- a/main/app/sprinkles/core/src/Util/CheckEnvironment.php
+++ b/main/app/sprinkles/core/src/Util/CheckEnvironment.php
@@ -1,331 +1,331 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use Slim\Http\Body;
-
-/**
- * Performs pre-flight tests on your server environment to check that it meets the requirements.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class CheckEnvironment
-{
- /**
- * @var \RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator Locator service for stream resources.
- */
- protected $locator;
-
- /**
- * @var array The results of any failed checks performed.
- */
- protected $resultsFailed = [];
-
- /**
- * @var array The results of any successful checks performed.
- */
- protected $resultsSuccess = [];
-
- /**
- * @var \Slim\Views\Twig The view object, needed for rendering error page.
- */
- protected $view;
-
- /**
- * @var \Illuminate\Cache\CacheManager Cache service for cache access.
- */
- protected $cache;
-
- /**
- * Constructor.
- *
- * @param $view \Slim\Views\Twig The view object, needed for rendering error page.
- * @param $locator \RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator Locator service for stream resources.
- */
- public function __construct($view, $locator, $cache) {
- $this->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');
- }
- } else if (!$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" => "<i class='fa fa-server fa-fw'></i> Missing Apache module <b>$module</b>.",
- "message" => "Please make sure that the <code>$module</code> 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" => "<i class='fa fa-server fa-fw'></i> Apache module <b>$module</b> is installed and enabled.",
- "message" => "Great, we found the <code>$module</code> 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" => "<i class='fa fa-image fa-fw'></i> GD library not installed",
- "message" => "We could not confirm that the <code>GD</code> 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" => "<i class='fa fa-image fa-fw'></i> GD library installed!",
- "message" => "Great, you have <code>GD</code> 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" => "<i class='fa fa-code fa-fw'></i> Missing image manipulation function.",
- "message" => "It appears that function <code>$func</code> is not available. UserFrosting needs this to render captchas.",
- "success" => FALSE
- ];
- } else {
- $this->resultsSuccess['function-' . $func] = [
- "title" => "<i class='fa fa-code fa-fw'></i> Function <b>$func</b> 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" => "<i class='fa fa-database fa-fw'></i> 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 <a href='http://php.net/manual/en/book.pdo.php'>http://php.net/manual/en/book.pdo.php</a>.",
- "success" => FALSE
- ];
- } else {
- $this->resultsSuccess['pdo'] = [
- "title" => "<i class='fa fa-database fa-fw'></i> 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" => "<i class='fa fa-file-o fa-fw'></i> File or directory does not exist.",
- "message" => "We could not find the file or directory <code>$file</code>.",
- "success" => FALSE
- ];
- } else {
- $writeable = is_writable($file);
- if ($assertWriteable !== $writeable) {
- $problemsFound = TRUE;
- $this->resultsFailed['file-' . $file] = [
- "title" => "<i class='fa fa-file-o fa-fw'></i> Incorrect permissions for file or directory.",
- "message" => "<code>$file</code> 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 <b>"
- . exec('whoami') . "</b> "
- . ($assertWriteable ? "has" : "does not have") . " write permissions for this directory.",
- "success" => FALSE
- ];
- } else {
- $this->resultsSuccess['file-' . $file] = [
- "title" => "<i class='fa fa-file-o fa-fw'></i> File/directory check passed!",
- "message" => "<code>$file</code> exists and is correctly set as <b>"
- . ($writeable ? "writeable" : "not writeable")
- . "</b>.",
- "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" => "<i class='fa fa-code fa-fw'></i> 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" => "<i class='fa fa-code fa-fw'></i> 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');
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Slim\Http\Body;
+
+/**
+ * Performs pre-flight tests on your server environment to check that it meets the requirements.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class CheckEnvironment
+{
+ /**
+ * @var \RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator Locator service for stream resources.
+ */
+ protected $locator;
+
+ /**
+ * @var array The results of any failed checks performed.
+ */
+ protected $resultsFailed = [];
+
+ /**
+ * @var array The results of any successful checks performed.
+ */
+ protected $resultsSuccess = [];
+
+ /**
+ * @var \Slim\Views\Twig The view object, needed for rendering error page.
+ */
+ protected $view;
+
+ /**
+ * @var \Illuminate\Cache\CacheManager Cache service for cache access.
+ */
+ protected $cache;
+
+ /**
+ * Constructor.
+ *
+ * @param $view \Slim\Views\Twig The view object, needed for rendering error page.
+ * @param $locator \RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator Locator service for stream resources.
+ */
+ public function __construct($view, $locator, $cache) {
+ $this->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');
+ }
+ } else if (!$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" => "<i class='fa fa-server fa-fw'></i> Missing Apache module <b>$module</b>.",
+ "message" => "Please make sure that the <code>$module</code> 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" => "<i class='fa fa-server fa-fw'></i> Apache module <b>$module</b> is installed and enabled.",
+ "message" => "Great, we found the <code>$module</code> 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" => "<i class='fa fa-image fa-fw'></i> GD library not installed",
+ "message" => "We could not confirm that the <code>GD</code> 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" => "<i class='fa fa-image fa-fw'></i> GD library installed!",
+ "message" => "Great, you have <code>GD</code> 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" => "<i class='fa fa-code fa-fw'></i> Missing image manipulation function.",
+ "message" => "It appears that function <code>$func</code> is not available. UserFrosting needs this to render captchas.",
+ "success" => FALSE
+ ];
+ } else {
+ $this->resultsSuccess['function-' . $func] = [
+ "title" => "<i class='fa fa-code fa-fw'></i> Function <b>$func</b> 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" => "<i class='fa fa-database fa-fw'></i> 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 <a href='http://php.net/manual/en/book.pdo.php'>http://php.net/manual/en/book.pdo.php</a>.",
+ "success" => FALSE
+ ];
+ } else {
+ $this->resultsSuccess['pdo'] = [
+ "title" => "<i class='fa fa-database fa-fw'></i> 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" => "<i class='fa fa-file-o fa-fw'></i> File or directory does not exist.",
+ "message" => "We could not find the file or directory <code>$file</code>.",
+ "success" => FALSE
+ ];
+ } else {
+ $writeable = is_writable($file);
+ if ($assertWriteable !== $writeable) {
+ $problemsFound = TRUE;
+ $this->resultsFailed['file-' . $file] = [
+ "title" => "<i class='fa fa-file-o fa-fw'></i> Incorrect permissions for file or directory.",
+ "message" => "<code>$file</code> 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 <b>"
+ . exec('whoami') . "</b> "
+ . ($assertWriteable ? "has" : "does not have") . " write permissions for this directory.",
+ "success" => FALSE
+ ];
+ } else {
+ $this->resultsSuccess['file-' . $file] = [
+ "title" => "<i class='fa fa-file-o fa-fw'></i> File/directory check passed!",
+ "message" => "<code>$file</code> exists and is correctly set as <b>"
+ . ($writeable ? "writeable" : "not writeable")
+ . "</b>.",
+ "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" => "<i class='fa fa-code fa-fw'></i> 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" => "<i class='fa fa-code fa-fw'></i> 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
index 11720f6..e29c524 100644
--- a/main/app/sprinkles/core/src/Util/ClassMapper.php
+++ b/main/app/sprinkles/core/src/Util/ClassMapper.php
@@ -1,90 +1,90 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-/**
- * UserFrosting class mapper.
- *
- * This creates an abstraction layer for overrideable classes.
- * For example, if we want to replace usages of the User class with MyUser, this abstraction layer handles that.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- * @author Roger Ardibee
- */
-class ClassMapper
-{
- /**
- * Mapping of generic class identifiers to specific class names.
- */
- protected $classMappings = [];
-
- /**
- * Creates an instance for a requested class identifier.
- *
- * @param string $identifier The identifier for the class, e.g. 'user'
- * @param mixed ...$arg Whatever needs to be passed to the constructor.
- */
- public function createInstance($identifier) {
- $className = $this->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);
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+/**
+ * UserFrosting class mapper.
+ *
+ * This creates an abstraction layer for overrideable classes.
+ * For example, if we want to replace usages of the User class with MyUser, this abstraction layer handles that.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @author Roger Ardibee
+ */
+class ClassMapper
+{
+ /**
+ * Mapping of generic class identifiers to specific class names.
+ */
+ protected $classMappings = [];
+
+ /**
+ * Creates an instance for a requested class identifier.
+ *
+ * @param string $identifier The identifier for the class, e.g. 'user'
+ * @param mixed ...$arg Whatever needs to be passed to the constructor.
+ */
+ public function createInstance($identifier) {
+ $className = $this->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
index 116a59e..e0e9d49 100644
--- a/main/app/sprinkles/core/src/Util/EnvironmentInfo.php
+++ b/main/app/sprinkles/core/src/Util/EnvironmentInfo.php
@@ -1,67 +1,67 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-use Illuminate\Database\Capsule\Manager as Capsule;
-
-/**
- * EnvironmentInfo Class
- *
- * Gets basic information about the application environment.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class EnvironmentInfo
-{
- /**
- * @var ContainerInterface The DI container for your application.
- */
- public static $ci;
-
- /**
- * Get an array of key-value pairs containing basic information about the default database.
- *
- * @return string[] the properties of this database.
- */
- public static function database() {
- static::$ci['db'];
-
- $pdo = Capsule::connection()->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;
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+
+/**
+ * EnvironmentInfo Class
+ *
+ * Gets basic information about the application environment.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class EnvironmentInfo
+{
+ /**
+ * @var ContainerInterface The DI container for your application.
+ */
+ public static $ci;
+
+ /**
+ * Get an array of key-value pairs containing basic information about the default database.
+ *
+ * @return string[] the properties of this database.
+ */
+ public static function database() {
+ static::$ci['db'];
+
+ $pdo = Capsule::connection()->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
index 5447c8f..18e60ec 100644
--- a/main/app/sprinkles/core/src/Util/ShutdownHandler.php
+++ b/main/app/sprinkles/core/src/Util/ShutdownHandler.php
@@ -1,162 +1,162 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-use Interop\Container\ContainerInterface;
-use UserFrosting\Sprinkle\Core\Http\Concerns\DeterminesContentType;
-
-/**
- * Registers a handler to be invoked whenever the application shuts down.
- * If it shut down due to a fatal error, will generate a clean error message.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class ShutdownHandler
-{
- use DeterminesContentType;
-
- /**
- * @var ContainerInterface The global container object, which holds all your services.
- */
- protected $ci;
-
- /**
- * @var bool
- */
- protected $displayErrorInfo;
-
- /**
- * Constructor.
- *
- * @param ContainerInterface $ci The global container object, which holds all your services.
- * @param bool $displayErrorInfo
- */
- public function __construct(ContainerInterface $ci, $displayErrorInfo) {
- $this->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 <code>php.log_errors</code> is enabled, and then check the <strong>PHP</strong> 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 "<strong>" . $errorTypes[$error['type']] . "</strong>: $errstr in <strong>$errfile</strong> on line <strong>$errline</strong>";
- }
-
- /**
- * 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 = "<p>$message</p>";
-
- return sprintf(
- "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'>" .
- "<title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana," .
- "sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}" .
- "</style></head><body><h1>%s</h1>%s</body></html>",
- $title,
- $title,
- $html
- );
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+use Interop\Container\ContainerInterface;
+use UserFrosting\Sprinkle\Core\Http\Concerns\DeterminesContentType;
+
+/**
+ * Registers a handler to be invoked whenever the application shuts down.
+ * If it shut down due to a fatal error, will generate a clean error message.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ShutdownHandler
+{
+ use DeterminesContentType;
+
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * @var bool
+ */
+ protected $displayErrorInfo;
+
+ /**
+ * Constructor.
+ *
+ * @param ContainerInterface $ci The global container object, which holds all your services.
+ * @param bool $displayErrorInfo
+ */
+ public function __construct(ContainerInterface $ci, $displayErrorInfo) {
+ $this->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 <code>php.log_errors</code> is enabled, and then check the <strong>PHP</strong> 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 "<strong>" . $errorTypes[$error['type']] . "</strong>: $errstr in <strong>$errfile</strong> on line <strong>$errline</strong>";
+ }
+
+ /**
+ * 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 = "<p>$message</p>";
+
+ return sprintf(
+ "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'>" .
+ "<title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana," .
+ "sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}" .
+ "</style></head><body><h1>%s</h1>%s</body></html>",
+ $title,
+ $title,
+ $html
+ );
+ }
+}
diff --git a/main/app/sprinkles/core/src/Util/Util.php b/main/app/sprinkles/core/src/Util/Util.php
index 0db3b72..0cf3a56 100644
--- a/main/app/sprinkles/core/src/Util/Util.php
+++ b/main/app/sprinkles/core/src/Util/Util.php
@@ -1,174 +1,174 @@
-<?php
-/**
- * UserFrosting (http://www.userfrosting.com)
- *
- * @link https://github.com/userfrosting/UserFrosting
- * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
- */
-
-namespace UserFrosting\Sprinkle\Core\Util;
-
-/**
- * Util Class
- *
- * Static utility functions.
- *
- * @author Alex Weissman (https://alexanderweissman.com)
- */
-class Util
-{
- /**
- * Extracts specific fields from one associative array, and places them into another.
- *
- * @param mixed[] $inputArray
- * @param string[] $fieldArray
- * @param bool $remove
- * @return mixed[]
- */
- static public function extractFields(&$inputArray, $fieldArray, $remove = TRUE) {
- $result = [];
-
- foreach ($fieldArray as $name) {
- if (array_key_exists($name, $inputArray)) {
- $result[$name] = $inputArray[$name];
-
- // Optionally remove value from input array
- if ($remove) {
- unset($inputArray[$name]);
- }
- }
- }
-
- return $result;
- }
-
- /**
- * Extracts numeric portion of a string (for example, for normalizing phone numbers).
- *
- * @param string $str
- * @return string
- */
- static public function extractDigits($str) {
- return preg_replace('/[^0-9]/', '', $str);
- }
-
- /**
- * Formats a phone number as a standard 7- or 10-digit string (xxx) xxx-xxxx
- *
- * @param string $phone
- * @return string
- */
- static public function formatPhoneNumber($phone) {
- $num = static::extractDigits($phone);
-
- $len = strlen($num);
-
- if ($len == 7) {
- $num = preg_replace('/([0-9]{3})([0-9]{4})/', '$1-$2', $num);
- } else if ($len == 10) {
- $num = preg_replace('/([0-9]{3})([0-9]{3})([0-9]{4})/', '($1) $2-$3', $num);
- }
-
- return $num;
- }
-
- /**
- * Nicely format an array for printing.
- * See https://stackoverflow.com/a/9776726/2970321
- *
- * @param array $arr
- * @return string
- */
- static public function prettyPrintArray(array $arr) {
- $json = json_encode($arr);
- $result = '';
- $level = 0;
- $inQuotes = FALSE;
- $inEscape = FALSE;
- $endsLineLevel = NULL;
- $jsonLength = strlen($json);
-
- for ($i = 0; $i < $jsonLength; $i++) {
- $char = $json[$i];
- $newLineLevel = NULL;
- $post = '';
- if ($endsLineLevel !== NULL) {
- $newLineLevel = $endsLineLevel;
- $endsLineLevel = NULL;
- }
- if ($inEscape) {
- $inEscape = FALSE;
- } else if ($char === '"') {
- $inQuotes = !$inQuotes;
- } else if (!$inQuotes) {
- switch ($char) {
- case '}':
- case ']':
- $level--;
- $endsLineLevel = NULL;
- $newLineLevel = $level;
- break;
-
- case '{':
- case '[':
- $level++;
-
- case ',':
- $endsLineLevel = $level;
- break;
-
- case ':':
- $post = ' ';
- break;
-
- case ' ':
- case '\t':
- case '\n':
- case '\r':
- $char = '';
- $endsLineLevel = $newLineLevel;
- $newLineLevel = NULL;
- break;
- }
- } else if ($char === '\\') {
- $inEscape = TRUE;
- }
-
- if ($newLineLevel !== NULL) {
- $result .= '<br>' . str_repeat('&nbsp;&nbsp;', $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 '';
- }
-}
+<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Core\Util;
+
+/**
+ * Util Class
+ *
+ * Static utility functions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Util
+{
+ /**
+ * Extracts specific fields from one associative array, and places them into another.
+ *
+ * @param mixed[] $inputArray
+ * @param string[] $fieldArray
+ * @param bool $remove
+ * @return mixed[]
+ */
+ static public function extractFields(&$inputArray, $fieldArray, $remove = TRUE) {
+ $result = [];
+
+ foreach ($fieldArray as $name) {
+ if (array_key_exists($name, $inputArray)) {
+ $result[$name] = $inputArray[$name];
+
+ // Optionally remove value from input array
+ if ($remove) {
+ unset($inputArray[$name]);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Extracts numeric portion of a string (for example, for normalizing phone numbers).
+ *
+ * @param string $str
+ * @return string
+ */
+ static public function extractDigits($str) {
+ return preg_replace('/[^0-9]/', '', $str);
+ }
+
+ /**
+ * Formats a phone number as a standard 7- or 10-digit string (xxx) xxx-xxxx
+ *
+ * @param string $phone
+ * @return string
+ */
+ static public function formatPhoneNumber($phone) {
+ $num = static::extractDigits($phone);
+
+ $len = strlen($num);
+
+ if ($len == 7) {
+ $num = preg_replace('/([0-9]{3})([0-9]{4})/', '$1-$2', $num);
+ } else if ($len == 10) {
+ $num = preg_replace('/([0-9]{3})([0-9]{3})([0-9]{4})/', '($1) $2-$3', $num);
+ }
+
+ return $num;
+ }
+
+ /**
+ * Nicely format an array for printing.
+ * See https://stackoverflow.com/a/9776726/2970321
+ *
+ * @param array $arr
+ * @return string
+ */
+ static public function prettyPrintArray(array $arr) {
+ $json = json_encode($arr);
+ $result = '';
+ $level = 0;
+ $inQuotes = FALSE;
+ $inEscape = FALSE;
+ $endsLineLevel = NULL;
+ $jsonLength = strlen($json);
+
+ for ($i = 0; $i < $jsonLength; $i++) {
+ $char = $json[$i];
+ $newLineLevel = NULL;
+ $post = '';
+ if ($endsLineLevel !== NULL) {
+ $newLineLevel = $endsLineLevel;
+ $endsLineLevel = NULL;
+ }
+ if ($inEscape) {
+ $inEscape = FALSE;
+ } else if ($char === '"') {
+ $inQuotes = !$inQuotes;
+ } else if (!$inQuotes) {
+ switch ($char) {
+ case '}':
+ case ']':
+ $level--;
+ $endsLineLevel = NULL;
+ $newLineLevel = $level;
+ break;
+
+ case '{':
+ case '[':
+ $level++;
+
+ case ',':
+ $endsLineLevel = $level;
+ break;
+
+ case ':':
+ $post = ' ';
+ break;
+
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ $char = '';
+ $endsLineLevel = $newLineLevel;
+ $newLineLevel = NULL;
+ break;
+ }
+ } else if ($char === '\\') {
+ $inEscape = TRUE;
+ }
+
+ if ($newLineLevel !== NULL) {
+ $result .= '<br>' . str_repeat('&nbsp;&nbsp;', $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 '';
+ }
+}