forked from adphi/occweb
fix #1 remove dirty hack
This commit is contained in:
parent
cee4b7a7e1
commit
77b1f007cf
@ -5,7 +5,7 @@
|
|||||||
<name>OCC Web</name>
|
<name>OCC Web</name>
|
||||||
<summary>OCC Commands in a web terminal</summary>
|
<summary>OCC Commands in a web terminal</summary>
|
||||||
<description><![CDATA[Run OCC Commands in a web terminal]]></description>
|
<description><![CDATA[Run OCC Commands in a web terminal]]></description>
|
||||||
<version>0.0.2</version>
|
<version>0.0.3</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author mail="adphi.apps@gmail.com" >Adphi</author>
|
<author mail="adphi.apps@gmail.com" >Adphi</author>
|
||||||
<namespace>OCCWeb</namespace>
|
<namespace>OCCWeb</namespace>
|
||||||
|
225
lib/Controller/OCCApplication.php
Normal file
225
lib/Controller/OCCApplication.php
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||||
|
*
|
||||||
|
* @author Joas Schilling <coding@schilljs.com>
|
||||||
|
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||||
|
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
* @author Miha Frangez <miha.frangez@gmail.com>
|
||||||
|
* @author Morris Jobke <hey@morrisjobke.de>
|
||||||
|
* @author noveens <noveen.sachdeva@research.iiit.ac.in>
|
||||||
|
* @author Robin Appelman <robin@icewind.nl>
|
||||||
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||||
|
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0
|
||||||
|
*
|
||||||
|
* This code is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License, version 3,
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\OCCWeb\Controller;
|
||||||
|
|
||||||
|
use OC\MemoryInfo;
|
||||||
|
use OC\NeedsUpdateException;
|
||||||
|
use OC_App;
|
||||||
|
use OCP\AppFramework\QueryException;
|
||||||
|
use OCP\Console\ConsoleEvent;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\ILogger;
|
||||||
|
use Symfony\Component\Console\Application as SymfonyApplication;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
class OCCApplication {
|
||||||
|
/** @var IConfig */
|
||||||
|
private $config;
|
||||||
|
/** @var EventDispatcherInterface */
|
||||||
|
private $dispatcher;
|
||||||
|
/** @var ILogger */
|
||||||
|
private $logger;
|
||||||
|
/** @var MemoryInfo */
|
||||||
|
private $memoryInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IConfig $config
|
||||||
|
* @param EventDispatcherInterface $dispatcher
|
||||||
|
* @param ILogger $logger
|
||||||
|
* @param MemoryInfo $memoryInfo
|
||||||
|
*/
|
||||||
|
public function __construct(IConfig $config,
|
||||||
|
EventDispatcherInterface $dispatcher,
|
||||||
|
ILogger $logger,
|
||||||
|
MemoryInfo $memoryInfo) {
|
||||||
|
$defaults = \OC::$server->getThemingDefaults();
|
||||||
|
$this->config = $config;
|
||||||
|
$this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString());
|
||||||
|
$this->dispatcher = $dispatcher;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->memoryInfo = $memoryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param ConsoleOutputInterface $output
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function loadCommands(
|
||||||
|
InputInterface $input,
|
||||||
|
ConsoleOutputInterface $output
|
||||||
|
) {
|
||||||
|
// $application is required to be defined in the register_command scripts
|
||||||
|
$application = $this->application;
|
||||||
|
$inputDefinition = $application->getDefinition();
|
||||||
|
$inputDefinition->addOption(
|
||||||
|
new InputOption(
|
||||||
|
'no-warnings',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Skip global warnings, show command output only',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
$input->bind($inputDefinition);
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
//expected if there are extra options
|
||||||
|
}
|
||||||
|
if ($input->getOption('no-warnings')) {
|
||||||
|
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->memoryInfo->isMemoryLimitSufficient() === false) {
|
||||||
|
$output->getErrorOutput()->writeln(
|
||||||
|
'<comment>The current PHP memory limit ' .
|
||||||
|
'is below the recommended value of 512MB.</comment>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once __DIR__ . '/../../../../core/register_command.php';
|
||||||
|
if ($this->config->getSystemValue('installed', false)) {
|
||||||
|
if (\OCP\Util::needUpgrade()) {
|
||||||
|
throw new NeedsUpdateException();
|
||||||
|
} elseif ($this->config->getSystemValue('maintenance', false)) {
|
||||||
|
$this->writeMaintenanceModeInfo($input, $output);
|
||||||
|
} else {
|
||||||
|
OC_App::loadApps();
|
||||||
|
foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) {
|
||||||
|
$appPath = \OC_App::getAppPath($app);
|
||||||
|
if ($appPath === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// load commands using info.xml
|
||||||
|
$info = \OC_App::getAppInfo($app);
|
||||||
|
if (isset($info['commands'])) {
|
||||||
|
$this->loadCommandsFromInfoXml($info['commands']);
|
||||||
|
}
|
||||||
|
// load from register_command.php
|
||||||
|
\OC_App::registerAutoloading($app, $appPath);
|
||||||
|
$file = $appPath . '/appinfo/register_command.php';
|
||||||
|
if (file_exists($file)) {
|
||||||
|
try {
|
||||||
|
require $file;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->logException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($input->getArgument('command') !== '_completion' && $input->getArgument('command') !== 'maintenance:install') {
|
||||||
|
$output->writeln("Nextcloud is not installed - only a limited number of commands are available");
|
||||||
|
}
|
||||||
|
} catch(NeedsUpdateException $e) {
|
||||||
|
if ($input->getArgument('command') !== '_completion') {
|
||||||
|
$output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available");
|
||||||
|
$output->writeln("You may use your browser or the occ upgrade command to do the upgrade");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getFirstArgument() !== 'check') {
|
||||||
|
$errors = \OC_Util::checkServer(\OC::$server->getSystemConfig());
|
||||||
|
if (!empty($errors)) {
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
$output->writeln((string)$error['error']);
|
||||||
|
$output->writeln((string)$error['hint']);
|
||||||
|
$output->writeln('');
|
||||||
|
}
|
||||||
|
throw new \Exception("Environment not properly prepared.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a maintenance mode info.
|
||||||
|
* The commands "_completion" and "maintenance:mode" are excluded.
|
||||||
|
*
|
||||||
|
* @param InputInterface $input The input implementation for reading inputs.
|
||||||
|
* @param ConsoleOutputInterface $output The output implementation
|
||||||
|
* for writing outputs.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function writeMaintenanceModeInfo(
|
||||||
|
InputInterface $input, ConsoleOutputInterface $output
|
||||||
|
) {
|
||||||
|
if ($input->getArgument('command') !== '_completion'
|
||||||
|
&& $input->getArgument('command') !== 'maintenance:mode') {
|
||||||
|
$errOutput = $output->getErrorOutput();
|
||||||
|
$errOutput->writeln(
|
||||||
|
'<comment>Nextcloud is in maintenance mode - ' .
|
||||||
|
'no apps have been loaded</comment>' . PHP_EOL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to automatically exit after a command execution or not.
|
||||||
|
*
|
||||||
|
* @param bool $boolean Whether to automatically exit after a command execution or not
|
||||||
|
*/
|
||||||
|
public function setAutoExit($boolean) {
|
||||||
|
$this->application->setAutoExit($boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function run(InputInterface $input = null, OutputInterface $output = null) {
|
||||||
|
$this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent(
|
||||||
|
ConsoleEvent::EVENT_RUN,
|
||||||
|
['occ']
|
||||||
|
));
|
||||||
|
return $this->application->run($input, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadCommandsFromInfoXml($commands) {
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
try {
|
||||||
|
$c = \OC::$server->query($command);
|
||||||
|
} catch (QueryException $e) {
|
||||||
|
if (class_exists($command)) {
|
||||||
|
$c = new $command();
|
||||||
|
} else {
|
||||||
|
throw new \Exception("Console command '$command' is unknown and could not be loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->application->add($c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace OCA\OCCWeb\Controller;
|
namespace OCA\OCCWeb\Controller;
|
||||||
|
|
||||||
use OC\Console\Application;
|
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
@ -11,7 +10,7 @@ use OCP\ILogger;
|
|||||||
use Symfony\Component\Console\Input\StringInput;
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class OccController extends Controller implements IRequest
|
class OccController extends Controller
|
||||||
{
|
{
|
||||||
private $logger;
|
private $logger;
|
||||||
private $userId;
|
private $userId;
|
||||||
@ -30,10 +29,9 @@ class OccController extends Controller implements IRequest
|
|||||||
$this->server = array(
|
$this->server = array(
|
||||||
'argv' => ["occ"],
|
'argv' => ["occ"],
|
||||||
);
|
);
|
||||||
$this->application = new Application(
|
$this->application = new OCCApplication(
|
||||||
\OC::$server->getConfig(),
|
\OC::$server->getConfig(),
|
||||||
\OC::$server->getEventDispatcher(),
|
\OC::$server->getEventDispatcher(),
|
||||||
$this,
|
|
||||||
\OC::$server->getLogger(),
|
\OC::$server->getLogger(),
|
||||||
\OC::$server->query(\OC\MemoryInfo::class)
|
\OC::$server->query(\OC\MemoryInfo::class)
|
||||||
);
|
);
|
||||||
@ -95,264 +93,6 @@ class OccController extends Controller implements IRequest
|
|||||||
}
|
}
|
||||||
return new DataResponse($cmds);
|
return new DataResponse($cmds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lets you access post and get parameters by the index
|
|
||||||
* In case of json requests the encoded json body is accessed
|
|
||||||
*
|
|
||||||
* @param string $key the key which you want to access in the URL Parameter
|
|
||||||
* placeholder, $_POST or $_GET array.
|
|
||||||
* The priority how they're returned is the following:
|
|
||||||
* 1. URL parameters
|
|
||||||
* 2. POST parameters
|
|
||||||
* 3. GET parameters
|
|
||||||
* @param mixed $default If the key is not found, this value will be returned
|
|
||||||
* @return mixed the content of the array
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getParam(string $key, $default = null)
|
|
||||||
{
|
|
||||||
// TODO: Implement getParam() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all params that were received, be it from the request
|
|
||||||
*
|
|
||||||
* (as GET or POST) or through the URL by the route
|
|
||||||
*
|
|
||||||
* @return array the array with all parameters
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getParams(): array
|
|
||||||
{
|
|
||||||
// TODO: Implement getParams() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the method of the request
|
|
||||||
*
|
|
||||||
* @return string the method of the request (POST, GET, etc)
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getMethod(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getMethod() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for accessing an uploaded file through the $_FILES array
|
|
||||||
*
|
|
||||||
* @param string $key the key that will be taken from the $_FILES array
|
|
||||||
* @return array the file in the $_FILES element
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getUploadedFile(string $key)
|
|
||||||
{
|
|
||||||
// TODO: Implement getUploadedFile() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for getting env variables
|
|
||||||
*
|
|
||||||
* @param string $key the key that will be taken from the $_ENV array
|
|
||||||
* @return array the value in the $_ENV element
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getEnv(string $key)
|
|
||||||
{
|
|
||||||
// TODO: Implement getEnv() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for getting cookie variables
|
|
||||||
*
|
|
||||||
* @param string $key the key that will be taken from the $_COOKIE array
|
|
||||||
* @return string|null the value in the $_COOKIE element
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getCookie(string $key)
|
|
||||||
{
|
|
||||||
// TODO: Implement getCookie() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the CSRF check was correct
|
|
||||||
*
|
|
||||||
* @return bool true if CSRF check passed
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function passesCSRFCheck(): bool
|
|
||||||
{
|
|
||||||
// TODO: Implement passesCSRFCheck() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the strict cookie has been sent with the request if the request
|
|
||||||
* is including any cookies.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @since 9.0.0
|
|
||||||
*/
|
|
||||||
public function passesStrictCookieCheck(): bool
|
|
||||||
{
|
|
||||||
// TODO: Implement passesStrictCookieCheck() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the lax cookie has been sent with the request if the request
|
|
||||||
* is including any cookies.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @since 9.0.0
|
|
||||||
*/
|
|
||||||
public function passesLaxCookieCheck(): bool
|
|
||||||
{
|
|
||||||
// TODO: Implement passesLaxCookieCheck() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
|
|
||||||
* If `mod_unique_id` is installed this value will be taken.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getId(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getId() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the remote address, if the connection came from a trusted proxy
|
|
||||||
* and `forwarded_for_headers` has been configured then the IP address
|
|
||||||
* specified in this header will be returned instead.
|
|
||||||
* Do always use this instead of $_SERVER['REMOTE_ADDR']
|
|
||||||
*
|
|
||||||
* @return string IP address
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getRemoteAddress(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getRemoteAddress() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server protocol. It respects reverse proxy servers and load
|
|
||||||
* balancers.
|
|
||||||
*
|
|
||||||
* @return string Server protocol (http or https)
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getServerProtocol(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getServerProtocol() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the used HTTP protocol.
|
|
||||||
*
|
|
||||||
* @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
|
|
||||||
* @since 8.2.0
|
|
||||||
*/
|
|
||||||
public function getHttpProtocol(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getHttpProtocol() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request uri, even if the website uses one or more
|
|
||||||
* reverse proxies
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getRequestUri(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getRequestUri() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get raw PathInfo from request (not urldecoded)
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
* @return string Path info
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getRawPathInfo(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getRawPathInfo() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get PathInfo from request
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
* @return string|false Path info or false when not found
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getPathInfo()
|
|
||||||
{
|
|
||||||
// TODO: Implement getPathInfo() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the script name, even if the website uses one or more
|
|
||||||
* reverse proxies
|
|
||||||
*
|
|
||||||
* @return string the script name
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getScriptName(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getScriptName() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the user agent matches a given regex
|
|
||||||
*
|
|
||||||
* @param array $agent array of agent names
|
|
||||||
* @return bool true if at least one of the given agent matches, false otherwise
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function isUserAgent(array $agent): bool
|
|
||||||
{
|
|
||||||
// TODO: Implement isUserAgent() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the unverified server host from the headers without checking
|
|
||||||
* whether it is a trusted domain
|
|
||||||
*
|
|
||||||
* @return string Server host
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getInsecureServerHost(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getInsecureServerHost() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server host from the headers, or the first configured
|
|
||||||
* trusted domain if the host isn't in the trusted list
|
|
||||||
*
|
|
||||||
* @return string Server host
|
|
||||||
* @since 8.1.0
|
|
||||||
*/
|
|
||||||
public function getServerHost(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getServerHost() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @since 6.0.0
|
|
||||||
*/
|
|
||||||
public function getHeader(string $name): string
|
|
||||||
{
|
|
||||||
// TODO: Implement getHeader() method.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function exceptionHandler($exception)
|
function exceptionHandler($exception)
|
||||||
|
Loading…
Reference in New Issue
Block a user