Release 1.2.0-RC3
This commit is contained in:
parent
416d3895d0
commit
208d2783c7
|
@ -14,10 +14,11 @@
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1.0",
|
"php": ">=7.1.0",
|
||||||
"fuzeworks/mvcr": "1.2.0-RC2",
|
"fuzeworks/mvcr": "1.2.0-RC3",
|
||||||
"fuzeworks/core": "1.2.0-RC2"
|
"fuzeworks/core": "1.2.0-RC3"
|
||||||
},
|
},
|
||||||
"minimum-stability": "RC",
|
"minimum-stability": "RC",
|
||||||
|
"prefer-stable": true,
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^7"
|
"phpunit/phpunit": "^7"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?php /** @noinspection ALL */
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FuzeWorks WebComponent.
|
* FuzeWorks WebComponent.
|
||||||
|
@ -38,7 +38,6 @@
|
||||||
namespace FuzeWorks;
|
namespace FuzeWorks;
|
||||||
|
|
||||||
use FuzeWorks\ConfigORM\ConfigORM;
|
use FuzeWorks\ConfigORM\ConfigORM;
|
||||||
use Tracy\Debugger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Implement remaining methods from OldInput
|
* @todo Implement remaining methods from OldInput
|
||||||
|
@ -83,8 +82,8 @@ class Input
|
||||||
{
|
{
|
||||||
if (class_exists('\FuzeWorks\TracyComponent', true) && \FuzeWorks\TracyComponent::isEnabled())
|
if (class_exists('\FuzeWorks\TracyComponent', true) && \FuzeWorks\TracyComponent::isEnabled())
|
||||||
{
|
{
|
||||||
set_exception_handler([$this, 'tracyExceptionHandler']);
|
Core::addExceptionHandler([$this, 'restoreGlobalArrays'], Priority::HIGHEST);
|
||||||
set_error_handler([$this, 'tracyErrorHandler']);
|
Core::addErrorHandler([$this, 'restoreGlobalArrays'], Priority::HIGHEST);
|
||||||
}
|
}
|
||||||
Events::addListener(
|
Events::addListener(
|
||||||
[$this, 'restoreGlobalArrays'],
|
[$this, 'restoreGlobalArrays'],
|
||||||
|
@ -93,36 +92,6 @@ class Input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to restore global arrays before handling errors by Tracy
|
|
||||||
*
|
|
||||||
* @param $exception
|
|
||||||
* @param bool $exit
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function tracyExceptionHandler($exception, $exit = true)
|
|
||||||
{
|
|
||||||
$this->restoreGlobalArrays();
|
|
||||||
Debugger::exceptionHandler($exception, $exit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to restore global arrays before handling errors by Tracy
|
|
||||||
*
|
|
||||||
* @param $severity
|
|
||||||
* @param $message
|
|
||||||
* @param $file
|
|
||||||
* @param $line
|
|
||||||
* @param array $context
|
|
||||||
* @throws \ErrorException
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function tracyErrorHandler($severity, $message, $file, $line, $context = [])
|
|
||||||
{
|
|
||||||
$this->restoreGlobalArrays();
|
|
||||||
Debugger::errorHandler($severity, $message, $file, $line, $context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores global arrays before handling by processes outside of FuzeWorks
|
* Restores global arrays before handling by processes outside of FuzeWorks
|
||||||
*
|
*
|
||||||
|
|
|
@ -53,6 +53,13 @@ class Output
|
||||||
*/
|
*/
|
||||||
private $input;
|
private $input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal URI class
|
||||||
|
*
|
||||||
|
* @var URI
|
||||||
|
*/
|
||||||
|
private $uri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebCfg
|
* WebCfg
|
||||||
*
|
*
|
||||||
|
@ -84,12 +91,38 @@ class Output
|
||||||
public $mimes = [];
|
public $mimes = [];
|
||||||
protected $mimeType = 'text/html';
|
protected $mimeType = 'text/html';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of time the current page is cached
|
||||||
|
*
|
||||||
|
* @var int $cacheTime
|
||||||
|
*/
|
||||||
|
protected $cacheTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a cache file is being used now
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $usingCache = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status code that will be sent to the client
|
||||||
|
*
|
||||||
|
* @var int $statusCode
|
||||||
|
*/
|
||||||
protected $statusCode = 200;
|
protected $statusCode = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status code text that will be sent along with $statusCode
|
||||||
|
*
|
||||||
|
* @var string $statusText
|
||||||
|
*/
|
||||||
protected $statusText = 'OK';
|
protected $statusText = 'OK';
|
||||||
|
|
||||||
public function init()
|
public function init()
|
||||||
{
|
{
|
||||||
$this->input = Factory::getInstance()->input;
|
$this->input = Factory::getInstance()->input;
|
||||||
|
$this->uri = Factory::getInstance()->uri;
|
||||||
$this->mimes = Factory::getInstance()->config->getConfig('mimes')->toArray();
|
$this->mimes = Factory::getInstance()->config->getConfig('mimes')->toArray();
|
||||||
$this->config = Factory::getInstance()->config->getConfig('web');
|
$this->config = Factory::getInstance()->config->getConfig('web');
|
||||||
|
|
||||||
|
@ -97,6 +130,200 @@ class Output
|
||||||
$this->compressOutput = (!$zlib && $this->config->get('compress_output') && extension_loaded('zlib'));
|
$this->compressOutput = (!$zlib && $this->config->get('compress_output') && extension_loaded('zlib'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display Output
|
||||||
|
*
|
||||||
|
* Processes and sends finalized output data to the browser along
|
||||||
|
* with any server headers.
|
||||||
|
*
|
||||||
|
* @param string $output Output data override
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function display(string $output = null)
|
||||||
|
{
|
||||||
|
// Set the output data
|
||||||
|
$output = is_null($output) ? $this->output : $output;
|
||||||
|
|
||||||
|
// Write cache if requested to do so
|
||||||
|
if ($this->cacheTime > 0)
|
||||||
|
$this->writeCache($output);
|
||||||
|
|
||||||
|
// First send status code
|
||||||
|
http_response_code($this->statusCode);
|
||||||
|
@header('Status: ' . $this->statusCode . ' ' . $this->statusText, true);
|
||||||
|
|
||||||
|
// If compression is requested, start buffering
|
||||||
|
if (
|
||||||
|
$this->compressOutput && !$this->usingCache &&
|
||||||
|
!is_null($this->input->server('HTTP_ACCEPT_ENCODING')) &&
|
||||||
|
strpos($this->input->server('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Logger::log("Compressing output...");
|
||||||
|
ob_start('ob_gzhandler');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send gzip headers when using cache
|
||||||
|
if ($this->usingCache && $this->compressOutput)
|
||||||
|
{
|
||||||
|
if (!is_null($this->input->server('HTTP_ACCEPT_ENCODING')) &&
|
||||||
|
strpos($this->input->server('HTTP_ACCEPT_ENCODING'), 'gzip') !== false)
|
||||||
|
{
|
||||||
|
header('Content-Encoding: gzip');
|
||||||
|
header('Content-Length: '.strlen($output));
|
||||||
|
}
|
||||||
|
// If the cache is zipped, but the client doesn't support it, decompress the output
|
||||||
|
else
|
||||||
|
$output = gzinflate(substr($output, 10, -8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all available headers
|
||||||
|
if (!empty($this->headers))
|
||||||
|
foreach ($this->headers as $header)
|
||||||
|
@header($header[0], $header[1]);
|
||||||
|
|
||||||
|
echo $output;
|
||||||
|
|
||||||
|
Logger::log('Output sent to browser');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the current page to be cached
|
||||||
|
*
|
||||||
|
* Set the amount of time with the $time parameter.
|
||||||
|
*
|
||||||
|
* @param int $time In minutes
|
||||||
|
*/
|
||||||
|
public function cache(int $time)
|
||||||
|
{
|
||||||
|
$this->cacheTime = $time > 0 ? $time : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCache(string $selector): bool
|
||||||
|
{
|
||||||
|
// If empty, index page is requested
|
||||||
|
$selector = empty($selector) ? 'index' : $selector;
|
||||||
|
|
||||||
|
// Generate the full uri
|
||||||
|
$uri = $this->config->get('base_url') . $selector;
|
||||||
|
|
||||||
|
// Determine the file that holds the cache
|
||||||
|
if ($this->compressOutput)
|
||||||
|
$file = Core::$tempDir . DS . 'OutputCache' . DS . md5($uri) . '_gzip.fwcache';
|
||||||
|
else
|
||||||
|
$file = Core::$tempDir . DS . 'OutputCache' . DS . md5($uri) . '.fwcache';
|
||||||
|
|
||||||
|
|
||||||
|
// Determine if file exists
|
||||||
|
if (!file_exists($file))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Retrieve cache
|
||||||
|
$cache = file_get_contents($file);
|
||||||
|
|
||||||
|
// Verify that this is a cache file
|
||||||
|
if (!preg_match('/^(.*)EndFuzeWorksCache--->/', $cache, $match))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Retrieve data from cache file
|
||||||
|
$cacheInfo = unserialize($match[1]);
|
||||||
|
|
||||||
|
// Test if the cache has expired
|
||||||
|
if (time() > $cacheInfo['expire'])
|
||||||
|
{
|
||||||
|
// If not writeable, log warning and do not remove
|
||||||
|
if (!Core::isReallyWritable($file))
|
||||||
|
{
|
||||||
|
Logger::logWarning("Found expired output cache. Could not remove!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file if expired
|
||||||
|
@unlink($file);
|
||||||
|
Logger::logInfo("Found expired output cache. Removed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo Send cache header
|
||||||
|
|
||||||
|
// Send all the headers cached in the file
|
||||||
|
foreach ($cacheInfo['headers'] as $header)
|
||||||
|
$this->setHeader($header[0], $header[1]);
|
||||||
|
|
||||||
|
// And save the output
|
||||||
|
$this->usingCache = true;
|
||||||
|
$this->setOutput(substr($cache, strlen($match[0])));
|
||||||
|
Logger::logInfo("Found output cache. Set output.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeCache(string $output)
|
||||||
|
{
|
||||||
|
// First create cache directory
|
||||||
|
$cachePath = Core::$tempDir . DS . 'OutputCache';
|
||||||
|
|
||||||
|
// Attempt to create the OutputCache directory in the TempDirectory
|
||||||
|
if (!is_dir($cachePath) && !mkdir($cachePath, 0777, false))
|
||||||
|
{
|
||||||
|
Logger::logError("Could not write output cache. Cannot create directory. Are permissions set correctly?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If directory is not writable, return error
|
||||||
|
if (!Core::isReallyWritable($cachePath))
|
||||||
|
{
|
||||||
|
Logger::logError("Could not write output cache. No file permissions. Are permissions set correctly?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the full uri
|
||||||
|
$uri = $this->config->get('base_url') . (empty($this->uri->uriString()) ? 'index' : $this->uri->uriString());
|
||||||
|
|
||||||
|
// Determine the file that holds the cache
|
||||||
|
if ($this->compressOutput)
|
||||||
|
$file = $cachePath . DS . md5($uri) . '_gzip.fwcache';
|
||||||
|
else
|
||||||
|
$file = $cachePath . DS . md5($uri) . '.fwcache';
|
||||||
|
|
||||||
|
|
||||||
|
// If compression is enabled, compress the output
|
||||||
|
if ($this->compressOutput)
|
||||||
|
{
|
||||||
|
$output = gzencode($output);
|
||||||
|
if ($this->getHeader('content-type') === null)
|
||||||
|
$this->setContentType($this->mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate expiry time
|
||||||
|
$expire = time() + ($this->cacheTime * 60);
|
||||||
|
|
||||||
|
// Prepare the cache contents
|
||||||
|
$cache = [
|
||||||
|
'expire' => $expire,
|
||||||
|
'headers' => $this->headers
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create cache file contents
|
||||||
|
$cache = serialize($cache) . 'EndFuzeWorksCache--->' . $output;
|
||||||
|
|
||||||
|
// Write the cache
|
||||||
|
if (file_put_contents($file, $cache, LOCK_EX) === false)
|
||||||
|
{
|
||||||
|
@unlink($file);
|
||||||
|
Logger::logError("Could not write output cache. File error. Deleting cache file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lowering permissions to read only
|
||||||
|
chmod($cachePath, 0640);
|
||||||
|
|
||||||
|
// And report back
|
||||||
|
Logger::logInfo("Output cache has been saved.");
|
||||||
|
|
||||||
|
// @todo Set cache header
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Output
|
* Get Output
|
||||||
*
|
*
|
||||||
|
@ -134,48 +361,6 @@ class Output
|
||||||
$this->output .= $output;
|
$this->output .= $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Display Output
|
|
||||||
*
|
|
||||||
* Processes and sends finalized output data to the browser along
|
|
||||||
* with any server headers.
|
|
||||||
*
|
|
||||||
* @param string $output Output data override
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function display(string $output = null)
|
|
||||||
{
|
|
||||||
// Set the output data
|
|
||||||
$output = is_null($output) ? $this->output : $output;
|
|
||||||
|
|
||||||
// First send status code
|
|
||||||
http_response_code($this->statusCode);
|
|
||||||
@header('Status: ' . $this->statusCode . ' ' . $this->statusText, true);
|
|
||||||
|
|
||||||
// If compression is requested, start buffering
|
|
||||||
if (
|
|
||||||
$this->compressOutput &&
|
|
||||||
!is_null($this->input->server('HTTP_ACCEPT_ENCODING')) &&
|
|
||||||
strpos($this->input->server('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Logger::log("Compressing output...");
|
|
||||||
ob_start('ob_gzhandler');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the X-Powered-By header, since it's a security risk
|
|
||||||
header_remove("X-Powered-By");
|
|
||||||
|
|
||||||
// Send all available headers
|
|
||||||
if (!empty($this->headers))
|
|
||||||
foreach ($this->headers as $header)
|
|
||||||
@header($header[0], $header[1]);
|
|
||||||
|
|
||||||
echo $output;
|
|
||||||
|
|
||||||
Logger::log('Output sent to browser');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set Header
|
* Set Header
|
||||||
*
|
*
|
||||||
|
@ -186,6 +371,11 @@ class Output
|
||||||
*/
|
*/
|
||||||
public function setHeader(string $header, bool $replace = true)
|
public function setHeader(string $header, bool $replace = true)
|
||||||
{
|
{
|
||||||
|
// If compression is enabled content-length should be suppressed, since it won't match the length
|
||||||
|
// of the compressed output.
|
||||||
|
if ($this->compressOutput && strncasecmp($header, 'content-length', 14) === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
$this->headers[] = [$header, $replace];
|
$this->headers[] = [$header, $replace];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +409,6 @@ class Output
|
||||||
* @param string $mimeType Extension of the file we're outputting
|
* @param string $mimeType Extension of the file we're outputting
|
||||||
* @param string $charset Character set (default: NULL)
|
* @param string $charset Character set (default: NULL)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function setContentType(string $mimeType, $charset = null)
|
public function setContentType(string $mimeType, $charset = null)
|
||||||
{
|
{
|
||||||
if (strpos($mimeType, '/') === false)
|
if (strpos($mimeType, '/') === false)
|
||||||
|
|
|
@ -39,6 +39,7 @@ namespace FuzeWorks;
|
||||||
use FuzeWorks\Event\HaltExecutionEvent;
|
use FuzeWorks\Event\HaltExecutionEvent;
|
||||||
use FuzeWorks\Event\LayoutLoadEvent;
|
use FuzeWorks\Event\LayoutLoadEvent;
|
||||||
use FuzeWorks\Event\RouterCallViewEvent;
|
use FuzeWorks\Event\RouterCallViewEvent;
|
||||||
|
use FuzeWorks\Exception\ConfigException;
|
||||||
use FuzeWorks\Exception\CSRFException;
|
use FuzeWorks\Exception\CSRFException;
|
||||||
use FuzeWorks\Exception\EventException;
|
use FuzeWorks\Exception\EventException;
|
||||||
use FuzeWorks\Exception\Exception;
|
use FuzeWorks\Exception\Exception;
|
||||||
|
@ -133,10 +134,10 @@ class WebComponent implements iComponent
|
||||||
* appends output and adds listener to view output on shutdown.
|
* appends output and adds listener to view output on shutdown.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
* @throws HaltException
|
|
||||||
* @throws OutputException
|
* @throws OutputException
|
||||||
* @throws RouterException
|
* @throws RouterException
|
||||||
* @throws WebException
|
* @throws WebException
|
||||||
|
* @throws EventException
|
||||||
*/
|
*/
|
||||||
public function routeWebRequest(): bool
|
public function routeWebRequest(): bool
|
||||||
{
|
{
|
||||||
|
@ -158,6 +159,9 @@ class WebComponent implements iComponent
|
||||||
throw new WebException("Could not route web request. coreShutdownEvent threw EventException: '".$e->getMessage()."'");
|
throw new WebException("Could not route web request. coreShutdownEvent threw EventException: '".$e->getMessage()."'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the X-Powered-By header, since it's a security risk
|
||||||
|
header_remove("X-Powered-By");
|
||||||
|
|
||||||
/** @var Router $router */
|
/** @var Router $router */
|
||||||
/** @var URI $uri */
|
/** @var URI $uri */
|
||||||
/** @var Output $output */
|
/** @var Output $output */
|
||||||
|
@ -170,6 +174,11 @@ class WebComponent implements iComponent
|
||||||
// And start logging the request
|
// And start logging the request
|
||||||
Logger::newLevel("Routing web request...");
|
Logger::newLevel("Routing web request...");
|
||||||
|
|
||||||
|
// First check if a cached page is available
|
||||||
|
$uriString = $uri->uriString();
|
||||||
|
if ($output->getCache($uriString))
|
||||||
|
return true;
|
||||||
|
|
||||||
// First test for Cross Site Request Forgery
|
// First test for Cross Site Request Forgery
|
||||||
try {
|
try {
|
||||||
$security->csrf_verify();
|
$security->csrf_verify();
|
||||||
|
@ -183,7 +192,6 @@ class WebComponent implements iComponent
|
||||||
|
|
||||||
// Attempt to load the requested page
|
// Attempt to load the requested page
|
||||||
try {
|
try {
|
||||||
$uriString = $uri->uriString();
|
|
||||||
$viewOutput = $router->route($uriString);
|
$viewOutput = $router->route($uriString);
|
||||||
} catch (NotFoundException $e) {
|
} catch (NotFoundException $e) {
|
||||||
Logger::logWarning("Requested page not found. Requesting Error/error404 View");
|
Logger::logWarning("Requested page not found. Requesting Error/error404 View");
|
||||||
|
@ -256,6 +264,8 @@ class WebComponent implements iComponent
|
||||||
* Fired when FuzeWorks halts it's execution. Loads an error 500 page.
|
* Fired when FuzeWorks halts it's execution. Loads an error 500 page.
|
||||||
*
|
*
|
||||||
* @param $event
|
* @param $event
|
||||||
|
* @throws EventException
|
||||||
|
* @TODO remove FuzeWorks\Layout dependency
|
||||||
*/
|
*/
|
||||||
public function haltEventListener(HaltExecutionEvent $event)
|
public function haltEventListener(HaltExecutionEvent $event)
|
||||||
{
|
{
|
||||||
|
@ -263,12 +273,17 @@ class WebComponent implements iComponent
|
||||||
/** @var Output $output */
|
/** @var Output $output */
|
||||||
/** @var Router $router */
|
/** @var Router $router */
|
||||||
/** @var Event $event */
|
/** @var Event $event */
|
||||||
|
/** @var Layout $layout */
|
||||||
$output = Factory::getInstance()->output;
|
$output = Factory::getInstance()->output;
|
||||||
$router = Factory::getInstance()->router;
|
$router = Factory::getInstance()->router;
|
||||||
|
$layout = Factory::getInstance()->layouts;
|
||||||
|
|
||||||
// Cancel event
|
// Cancel event
|
||||||
$event->setCancelled(true);
|
$event->setCancelled(true);
|
||||||
|
|
||||||
|
// Reset the layout engine
|
||||||
|
$layout->reset();
|
||||||
|
|
||||||
// Remove listener so that error pages won't be intercepted
|
// Remove listener so that error pages won't be intercepted
|
||||||
Events::removeListener([$this, 'callViewEventListener'], 'routerCallViewEvent',Priority::HIGHEST);
|
Events::removeListener([$this, 'callViewEventListener'], 'routerCallViewEvent',Priority::HIGHEST);
|
||||||
|
|
||||||
|
@ -291,10 +306,10 @@ class WebComponent implements iComponent
|
||||||
*
|
*
|
||||||
* Assigns variables from the WebComponent to Layout engines.
|
* Assigns variables from the WebComponent to Layout engines.
|
||||||
*
|
*
|
||||||
* @param $event
|
* @param LayoutLoadEvent $event
|
||||||
* @throws Exception\ConfigException
|
* @throws ConfigException
|
||||||
*/
|
*/
|
||||||
public function layoutLoadEventListener(LayoutLoadEvent $event)
|
public function layoutLoadEventListener($event)
|
||||||
{
|
{
|
||||||
// Dependencies
|
// Dependencies
|
||||||
/** @var Security $security */
|
/** @var Security $security */
|
||||||
|
|
Loading…
Reference in New Issue