diff options
author | Marvin Borner | 2018-06-08 20:03:25 +0200 |
---|---|---|
committer | Marvin Borner | 2018-06-08 20:03:25 +0200 |
commit | 92b7dd3335a6572debeacfb5faa82c63a5e67888 (patch) | |
tree | 7ebbca22595d542ec5e2912a24a0400ac8f6b113 /main/app/sprinkles/core/src | |
parent | 22a1bb27f94ea33042b0bdd35bef1a5cfa96cc0d (diff) |
Some minor fixes
Diffstat (limited to 'main/app/sprinkles/core/src')
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(' ', $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(' ', $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 '';
+ }
+}
|