mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-23 07:33:58 -06:00
Merge branch 'feature/15' into develop
This commit is contained in:
commit
a3554db1c3
@ -11,6 +11,10 @@ php:
|
|||||||
- 7.1
|
- 7.1
|
||||||
- hhvm
|
- hhvm
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- hhvm
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- composer self-update
|
- composer self-update
|
||||||
- composer install --no-interaction
|
- composer install --no-interaction
|
||||||
|
@ -5,7 +5,7 @@ return [
|
|||||||
'driver' => 'pdo_mysql',
|
'driver' => 'pdo_mysql',
|
||||||
'user' => getenv('DB_USER'),
|
'user' => getenv('DB_USER'),
|
||||||
'password' => getenv('DB_PASSWORD'),
|
'password' => getenv('DB_PASSWORD'),
|
||||||
'dbname' => getenv('DB_NAME') ?: 'acelaya_url_shortener',
|
'dbname' => getenv('DB_NAME') ?: 'shlink',
|
||||||
'charset' => 'utf8',
|
'charset' => 'utf8',
|
||||||
'driverOptions' => [
|
'driverOptions' => [
|
||||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
|
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Common\Expressive\ContentBasedErrorHandler;
|
||||||
|
use Zend\Expressive\Container\WhoopsErrorHandlerFactory;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'services' => [
|
'services' => [
|
||||||
'invokables' => [
|
'invokables' => [
|
||||||
'Zend\Expressive\Whoops' => Whoops\Run::class,
|
'Zend\Expressive\Whoops' => Whoops\Run::class,
|
||||||
'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
|
'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
|
||||||
],
|
],
|
||||||
'factories' => [
|
|
||||||
'Zend\Expressive\FinalHandler' => Zend\Expressive\Container\WhoopsErrorHandlerFactory::class,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'whoops' => [
|
'whoops' => [
|
||||||
@ -18,4 +18,12 @@ return [
|
|||||||
'ajax_only' => true,
|
'ajax_only' => true,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'error_handler' => [
|
||||||
|
'plugins' => [
|
||||||
|
'factories' => [
|
||||||
|
ContentBasedErrorHandler::DEFAULT_CONTENT => WhoopsErrorHandlerFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
@ -26,11 +26,5 @@ return [
|
|||||||
],
|
],
|
||||||
'priority' => 1,
|
'priority' => 1,
|
||||||
],
|
],
|
||||||
|
|
||||||
'error' => [
|
|
||||||
'middleware' => [],
|
|
||||||
'error' => true,
|
|
||||||
'priority' => -10000,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use Shlinkio\Shlink\Common\Expressive\ContentBasedErrorHandlerFactory;
|
||||||
|
use Shlinkio\Shlink\Rest\Expressive\JsonErrorHandler;
|
||||||
use Zend\Expressive;
|
use Zend\Expressive;
|
||||||
use Zend\Expressive\Container;
|
use Zend\Expressive\Container;
|
||||||
use Zend\Expressive\Helper;
|
use Zend\Expressive\Helper;
|
||||||
@ -21,7 +23,7 @@ return [
|
|||||||
Router\FastRouteRouter::class => InvokableFactory::class,
|
Router\FastRouteRouter::class => InvokableFactory::class,
|
||||||
|
|
||||||
// View
|
// View
|
||||||
'Zend\Expressive\FinalHandler' => Container\TemplatedErrorHandlerFactory::class,
|
'Zend\Expressive\FinalHandler' => ContentBasedErrorHandlerFactory::class,
|
||||||
Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class,
|
Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class,
|
||||||
],
|
],
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
|
22
module/Common/config/error-handler.config.php
Normal file
22
module/Common/config/error-handler.config.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
use Shlinkio\Shlink\Common\Expressive\ContentBasedErrorHandler;
|
||||||
|
use Zend\Expressive\Container\TemplatedErrorHandlerFactory;
|
||||||
|
use Zend\Stratigility\FinalHandler;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'error_handler' => [
|
||||||
|
'plugins' => [
|
||||||
|
'invokables' => [
|
||||||
|
'text/plain' => FinalHandler::class,
|
||||||
|
],
|
||||||
|
'factories' => [
|
||||||
|
ContentBasedErrorHandler::DEFAULT_CONTENT => TemplatedErrorHandlerFactory::class,
|
||||||
|
],
|
||||||
|
'aliases' => [
|
||||||
|
'application/xhtml+xml' => ContentBasedErrorHandler::DEFAULT_CONTENT,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
64
module/Common/src/Expressive/ContentBasedErrorHandler.php
Normal file
64
module/Common/src/Expressive/ContentBasedErrorHandler.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Common\Expressive;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface as Request;
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||||
|
use Zend\ServiceManager\AbstractPluginManager;
|
||||||
|
use Zend\ServiceManager\Exception\InvalidServiceException;
|
||||||
|
|
||||||
|
class ContentBasedErrorHandler extends AbstractPluginManager implements ErrorHandlerInterface
|
||||||
|
{
|
||||||
|
const DEFAULT_CONTENT = 'text/html';
|
||||||
|
|
||||||
|
public function validate($instance)
|
||||||
|
{
|
||||||
|
if (is_callable($instance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidServiceException(sprintf(
|
||||||
|
'Only callables are valid plugins for "%s". "%s" provided',
|
||||||
|
__CLASS__,
|
||||||
|
is_object($instance) ? get_class($instance) : gettype($instance)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final handler for an application.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Response $response
|
||||||
|
* @param null|mixed $err
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request, Response $response, $err = null)
|
||||||
|
{
|
||||||
|
// Try to get an error handler for provided request accepted type
|
||||||
|
$errorHandler = $this->resolveErrorHandlerFromAcceptHeader($request);
|
||||||
|
return $errorHandler($request, $response, $err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to resolve
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return callable
|
||||||
|
*/
|
||||||
|
protected function resolveErrorHandlerFromAcceptHeader(Request $request)
|
||||||
|
{
|
||||||
|
$accepts = $request->hasHeader('Accept') ? $request->getHeaderLine('Accept') : self::DEFAULT_CONTENT;
|
||||||
|
$accepts = explode(',', $accepts);
|
||||||
|
foreach ($accepts as $accept) {
|
||||||
|
if (! $this->has($accept)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->get($accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'It wasn\'t possible to find an error handler for '
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Common\Expressive;
|
||||||
|
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Interop\Container\Exception\ContainerException;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
|
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||||
|
|
||||||
|
class ContentBasedErrorHandlerFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an object
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container
|
||||||
|
* @param string $requestedName
|
||||||
|
* @param null|array $options
|
||||||
|
* @return object
|
||||||
|
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||||
|
* @throws ServiceNotCreatedException if an exception is raised when
|
||||||
|
* creating a service.
|
||||||
|
* @throws ContainerException if any other error occurs
|
||||||
|
*/
|
||||||
|
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||||
|
{
|
||||||
|
$config = $container->get('config')['error_handler'];
|
||||||
|
$plugins = isset($config['plugins']) ? $config['plugins'] : [];
|
||||||
|
return new ContentBasedErrorHandler($container, $plugins);
|
||||||
|
}
|
||||||
|
}
|
18
module/Common/src/Expressive/ErrorHandlerInterface.php
Normal file
18
module/Common/src/Expressive/ErrorHandlerInterface.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Common\Expressive;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface as Request;
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
|
||||||
|
interface ErrorHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Final handler for an application.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Response $response
|
||||||
|
* @param null|mixed $err
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request, Response $response, $err = null);
|
||||||
|
}
|
@ -67,8 +67,8 @@ class RedirectMiddleware implements MiddlewareInterface
|
|||||||
try {
|
try {
|
||||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||||
|
|
||||||
// If provided shortCode does not belong to a valid long URL, dispatch next middleware, which is 404
|
// If provided shortCode does not belong to a valid long URL, dispatch next middleware, which will trigger
|
||||||
// middleware
|
// a not-found error
|
||||||
if (! isset($longUrl)) {
|
if (! isset($longUrl)) {
|
||||||
return $out($request, $response);
|
return $out($request, $response);
|
||||||
}
|
}
|
||||||
|
18
module/Rest/config/error-handler.config.php
Normal file
18
module/Rest/config/error-handler.config.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
use Shlinkio\Shlink\Rest\Expressive\JsonErrorHandler;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'error_handler' => [
|
||||||
|
'plugins' => [
|
||||||
|
'invokables' => [
|
||||||
|
'application/json' => JsonErrorHandler::class,
|
||||||
|
],
|
||||||
|
'aliases' => [
|
||||||
|
'application/x-json' => 'application/json',
|
||||||
|
'text/json' => 'application/json',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
@ -12,5 +12,13 @@ return [
|
|||||||
],
|
],
|
||||||
'priority' => 5,
|
'priority' => 5,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'rest-not-found' => [
|
||||||
|
'path' => '/rest',
|
||||||
|
'middleware' => [
|
||||||
|
Middleware\NotFoundMiddleware::class,
|
||||||
|
],
|
||||||
|
'priority' => -1,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -23,7 +23,7 @@ return [
|
|||||||
'allowed_methods' => ['GET', 'OPTIONS'],
|
'allowed_methods' => ['GET', 'OPTIONS'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'rest-lActionist-shortened-url',
|
'name' => 'rest-list-shortened-url',
|
||||||
'path' => '/rest/short-codes',
|
'path' => '/rest/short-codes',
|
||||||
'middleware' => Action\ListShortcodesMiddleware::class,
|
'middleware' => Action\ListShortcodesMiddleware::class,
|
||||||
'allowed_methods' => ['GET'],
|
'allowed_methods' => ['GET'],
|
||||||
|
@ -19,6 +19,7 @@ return [
|
|||||||
|
|
||||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||||
Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class,
|
Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class,
|
||||||
|
Middleware\NotFoundMiddleware::class => AnnotatedFactory::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
Binary file not shown.
@ -1,9 +1,9 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Shlink 1.0\n"
|
"Project-Id-Version: Shlink 1.0\n"
|
||||||
"POT-Creation-Date: 2016-07-21 16:39+0200\n"
|
"POT-Creation-Date: 2016-07-27 08:53+0200\n"
|
||||||
"PO-Revision-Date: 2016-07-21 16:40+0200\n"
|
"PO-Revision-Date: 2016-07-27 08:53+0200\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -52,9 +52,12 @@ msgid ""
|
|||||||
"Missing or invalid auth token provided. Perform a new authentication request "
|
"Missing or invalid auth token provided. Perform a new authentication request "
|
||||||
"and send provided token on every new request on the \"%s\" header"
|
"and send provided token on every new request on the \"%s\" header"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"No se ha proporcionado token de autenticación o este es inválido. Realia una "
|
"No se ha proporcionado token de autenticación o este es inválido. Realiza "
|
||||||
"nueva petición de autenticación y envía el token proporcionado en cada nueva "
|
"una nueva petición de autenticación y envía el token proporcionado en cada "
|
||||||
"petición en la cabecera \"%s\""
|
"nueva petición en la cabecera \"%s\""
|
||||||
|
|
||||||
|
msgid "Requested route does not exist."
|
||||||
|
msgstr "La ruta solicitada no existe."
|
||||||
|
|
||||||
#~ msgid "RestToken not found for token \"%s\""
|
#~ msgid "RestToken not found for token \"%s\""
|
||||||
#~ msgstr "No se ha encontrado un RestToken para el token \"%s\""
|
#~ msgstr "No se ha encontrado un RestToken para el token \"%s\""
|
||||||
|
39
module/Rest/src/Expressive/JsonErrorHandler.php
Normal file
39
module/Rest/src/Expressive/JsonErrorHandler.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Rest\Expressive;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface as Request;
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Shlinkio\Shlink\Common\Expressive\ErrorHandlerInterface;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
|
||||||
|
class JsonErrorHandler implements ErrorHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Final handler for an application.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Response $response
|
||||||
|
* @param null|mixed $err
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request, Response $response, $err = null)
|
||||||
|
{
|
||||||
|
$status = $response->getStatusCode();
|
||||||
|
$responsePhrase = $status < 400 ? 'Internal Server Error' : $response->getReasonPhrase();
|
||||||
|
$status = $status < 400 ? 500 : $status;
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'error' => $this->responsePhraseToCode($responsePhrase),
|
||||||
|
'message' => $responsePhrase,
|
||||||
|
], $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $responsePhrase
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function responsePhraseToCode($responsePhrase)
|
||||||
|
{
|
||||||
|
return strtoupper(str_replace(' ', '_', $responsePhrase));
|
||||||
|
}
|
||||||
|
}
|
@ -69,7 +69,9 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface
|
|||||||
// If current route is the authenticate route or an OPTIONS request, continue to the next middleware
|
// If current route is the authenticate route or an OPTIONS request, continue to the next middleware
|
||||||
/** @var RouteResult $routeResult */
|
/** @var RouteResult $routeResult */
|
||||||
$routeResult = $request->getAttribute(RouteResult::class);
|
$routeResult = $request->getAttribute(RouteResult::class);
|
||||||
if ((isset($routeResult) && $routeResult->getMatchedRouteName() === 'rest-authenticate')
|
if (! isset($routeResult)
|
||||||
|
|| $routeResult->isFailure()
|
||||||
|
|| $routeResult->getMatchedRouteName() === 'rest-authenticate'
|
||||||
|| $request->getMethod() === 'OPTIONS'
|
|| $request->getMethod() === 'OPTIONS'
|
||||||
) {
|
) {
|
||||||
return $out($request, $response);
|
return $out($request, $response);
|
||||||
|
62
module/Rest/src/Middleware/NotFoundMiddleware.php
Normal file
62
module/Rest/src/Middleware/NotFoundMiddleware.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||||
|
|
||||||
|
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
use Zend\I18n\Translator\TranslatorInterface;
|
||||||
|
use Zend\Stratigility\MiddlewareInterface;
|
||||||
|
|
||||||
|
class NotFoundMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotFoundMiddleware constructor.
|
||||||
|
* @param TranslatorInterface $translator
|
||||||
|
*
|
||||||
|
* @Inject({"translator"})
|
||||||
|
*/
|
||||||
|
public function __construct(TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an incoming request and/or response.
|
||||||
|
*
|
||||||
|
* Accepts a server-side request and a response instance, and does
|
||||||
|
* something with them.
|
||||||
|
*
|
||||||
|
* If the response is not complete and/or further processing would not
|
||||||
|
* interfere with the work done in the middleware, or if the middleware
|
||||||
|
* wants to delegate to another process, it can use the `$out` callable
|
||||||
|
* if present.
|
||||||
|
*
|
||||||
|
* If the middleware does not return a value, execution of the current
|
||||||
|
* request is considered complete, and the response instance provided will
|
||||||
|
* be considered the response to return.
|
||||||
|
*
|
||||||
|
* Alternately, the middleware may return a response instance.
|
||||||
|
*
|
||||||
|
* Often, middleware will `return $out();`, with the assumption that a
|
||||||
|
* later middleware will return a response.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Response $response
|
||||||
|
* @param null|callable $out
|
||||||
|
* @return null|Response
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||||
|
{
|
||||||
|
return new JsonResponse([
|
||||||
|
'error' => RestUtils::NOT_FOUND_ERROR,
|
||||||
|
'message' => $this->translator->translate('Requested route does not exist.'),
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,8 @@ class RestUtils
|
|||||||
const INVALID_URL_ERROR = 'INVALID_URL';
|
const INVALID_URL_ERROR = 'INVALID_URL';
|
||||||
const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
|
const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT';
|
||||||
const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS';
|
const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS';
|
||||||
const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN_ERROR';
|
const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN';
|
||||||
|
const NOT_FOUND_ERROR = 'NOT_FOUND';
|
||||||
const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
|
const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
|
||||||
|
|
||||||
public static function getRestErrorCodeFromException(Common\ExceptionInterface $e)
|
public static function getRestErrorCodeFromException(Common\ExceptionInterface $e)
|
||||||
|
Loading…
Reference in New Issue
Block a user