fix for signer algorithm, test for signer
This commit is contained in:
parent
f6a0b56c69
commit
1b6e138e64
104
src/Component/AppData.php
Normal file
104
src/Component/AppData.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category AppData
|
||||
* @package RetailCrm\Component
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
namespace RetailCrm\Component;
|
||||
|
||||
use RetailCrm\Interfaces\AppDataInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Class AppData
|
||||
*
|
||||
* @category AppData
|
||||
* @package RetailCrm\Component
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see https://help.retailcrm.ru
|
||||
*/
|
||||
class AppData implements AppDataInterface
|
||||
{
|
||||
public const OVERSEAS_ENDPOINT = 'https://api.taobao.com/router/rest';
|
||||
public const CHINESE_ENDPOINT = 'https://eco.taobao.com/router/rest';
|
||||
public const AVAILABLE_ENDPOINTS = [self::OVERSEAS_ENDPOINT, self::CHINESE_ENDPOINT];
|
||||
|
||||
/**
|
||||
* @var string $serviceUrl
|
||||
* @Assert\Url()
|
||||
* @Assert\Choice(choices=Client::AVAILABLE_ENDPOINTS, message="Invalid endpoint provided.")
|
||||
*/
|
||||
protected $serviceUrl;
|
||||
|
||||
/**
|
||||
* @var string $appKey
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $appKey;
|
||||
|
||||
/**
|
||||
* @var string $appSecret
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
private $appSecret;
|
||||
|
||||
/**
|
||||
* AppData constructor.
|
||||
*
|
||||
* @param string $serviceUrl
|
||||
* @param string $appKey
|
||||
* @param string $appSecret
|
||||
*/
|
||||
public function __construct(string $serviceUrl, string $appKey, string $appSecret)
|
||||
{
|
||||
$this->serviceUrl = $serviceUrl;
|
||||
$this->appKey = $appKey;
|
||||
$this->appSecret = $appSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor shortcut
|
||||
*
|
||||
* @param string $serviceUrl
|
||||
* @param string $appKey
|
||||
* @param string $appSecret
|
||||
*
|
||||
* @return \RetailCrm\Component\AppData
|
||||
*/
|
||||
public static function create(string $serviceUrl, string $appKey, string $appSecret): AppDataInterface
|
||||
{
|
||||
return new self($serviceUrl, $appKey, $appSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceUrl(): string
|
||||
{
|
||||
return $this->serviceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppKey(): string
|
||||
{
|
||||
return $this->appKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppSecret(): string
|
||||
{
|
||||
return $this->appSecret;
|
||||
}
|
||||
}
|
@ -57,20 +57,4 @@ class TokenAuthenticator implements AuthenticatorInterface
|
||||
$request->appKey = $this->appKey;
|
||||
$request->session = $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppKey(): string
|
||||
{
|
||||
return $this->appKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppSecret(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace RetailCrm\Factory;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use RetailCrm\Component\Constants;
|
||||
use RetailCrm\Interfaces\AppDataInterface;
|
||||
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||
use RetailCrm\Interfaces\ContainerAwareInterface;
|
||||
use RetailCrm\Interfaces\FactoryInterface;
|
||||
@ -34,8 +35,8 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
|
||||
{
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/** @var string $serviceUrl */
|
||||
private $serviceUrl;
|
||||
/** @var \RetailCrm\Interfaces\AppDataInterface $appData */
|
||||
private $appData;
|
||||
|
||||
/*** @var AuthenticatorInterface $authenticator */
|
||||
protected $authenticator;
|
||||
@ -54,13 +55,13 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serviceUrl
|
||||
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
||||
*
|
||||
* @return $this
|
||||
* @return ClientFactory
|
||||
*/
|
||||
public function setServiceUrl(string $serviceUrl): self
|
||||
public function setAppData(AppDataInterface $appData): ClientFactory
|
||||
{
|
||||
$this->serviceUrl = $serviceUrl;
|
||||
$this->appData = $appData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -81,7 +82,7 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
|
||||
*/
|
||||
public function create(): Client
|
||||
{
|
||||
$client = new Client($this->serviceUrl, $this->authenticator);
|
||||
$client = new Client($this->appData, $this->authenticator);
|
||||
$client->setHttpClient($this->container->get(Constants::HTTP_CLIENT));
|
||||
$client->setSerializer($this->container->get(Constants::SERIALIZER));
|
||||
$client->setValidator($this->container->get(Constants::VALIDATOR));
|
||||
|
@ -15,6 +15,7 @@ namespace RetailCrm\Factory;
|
||||
use JMS\Serializer\GraphNavigatorInterface;
|
||||
use JMS\Serializer\Serializer;
|
||||
use RetailCrm\Component\Constants;
|
||||
use RetailCrm\Service\RequestSigner;
|
||||
use Shieldon\Psr17\StreamFactory;
|
||||
use Shieldon\Psr17\UploadedFileFactory as BaseFactory;
|
||||
use JMS\Serializer\SerializerBuilder;
|
||||
@ -109,6 +110,9 @@ class ContainerFactory implements FactoryInterface
|
||||
);
|
||||
$container->set(Constants::SERIALIZER, $this->getSerializer());
|
||||
$container->set(UploadedFileFactory::class, new UploadedFileFactory(new BaseFactory(), new StreamFactory()));
|
||||
$container->set(RequestSigner::class, function (ContainerInterface $container) {
|
||||
return new RequestSigner($container->get(Constants::SERIALIZER));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
42
src/Interfaces/AppDataInterface.php
Normal file
42
src/Interfaces/AppDataInterface.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category AppDataInterface
|
||||
* @package RetailCrm\Interfaces
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Interfaces;
|
||||
|
||||
/**
|
||||
* Interface AppDataInterface
|
||||
*
|
||||
* @category AppDataInterface
|
||||
* @package RetailCrm\Interfaces
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see https://help.retailcrm.ru
|
||||
*/
|
||||
interface AppDataInterface
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceUrl(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppKey(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppSecret(): string;
|
||||
}
|
@ -31,14 +31,4 @@ interface AuthenticatorInterface
|
||||
* @param \RetailCrm\Model\Request\BaseRequest $request
|
||||
*/
|
||||
public function authenticate(BaseRequest $request): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppKey(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppSecret(): string;
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ interface RequestSignerInterface
|
||||
/**
|
||||
* Signs provided request.
|
||||
*
|
||||
* @param \RetailCrm\Model\Request\BaseRequest $request
|
||||
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
|
||||
* @param \RetailCrm\Model\Request\BaseRequest $request
|
||||
* @param \RetailCrm\Interfaces\AppDataInterface $authenticator
|
||||
*/
|
||||
public function sign(BaseRequest $request, AuthenticatorInterface $authenticator): void;
|
||||
public function sign(BaseRequest $request, AppDataInterface $authenticator): void;
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ abstract class BaseRequest
|
||||
/**
|
||||
* @var \DateTime $timestamp
|
||||
*
|
||||
* @JMS\Type("DateTime<'yyyy-MM-dd H:i:s'>")
|
||||
* @JMS\Type("DateTime<'Y-m-d H:i:s'>")
|
||||
* @JMS\SerializedName("timestamp")
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
|
@ -14,7 +14,7 @@ namespace RetailCrm\Service;
|
||||
|
||||
use JMS\Serializer\SerializerInterface;
|
||||
use RetailCrm\Component\Constants;
|
||||
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||
use RetailCrm\Interfaces\AppDataInterface;
|
||||
use RetailCrm\Interfaces\RequestSignerInterface;
|
||||
use RetailCrm\Model\Request\BaseRequest;
|
||||
|
||||
@ -46,10 +46,10 @@ class RequestSigner implements RequestSignerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BaseRequest $request
|
||||
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
|
||||
* @param BaseRequest $request
|
||||
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
||||
*/
|
||||
public function sign(BaseRequest $request, AuthenticatorInterface $authenticator): void
|
||||
public function sign(BaseRequest $request, AppDataInterface $appData): void
|
||||
{
|
||||
$stringToBeSigned = '';
|
||||
$params = $this->getRequestData($request);
|
||||
@ -60,10 +60,12 @@ class RequestSigner implements RequestSignerInterface
|
||||
|
||||
switch ($request->signMethod) {
|
||||
case Constants::SIGN_TYPE_MD5:
|
||||
$request->sign = strtoupper(md5($authenticator->getAppSecret() . $stringToBeSigned));
|
||||
$request->sign = strtoupper(md5(
|
||||
$appData->getAppSecret() . $stringToBeSigned . $appData->getAppSecret()
|
||||
));
|
||||
break;
|
||||
case Constants::SIGN_TYPE_HMAC:
|
||||
$request->sign = strtoupper(hash_hmac('md5', $stringToBeSigned, $authenticator->getAppSecret()));
|
||||
$request->sign = strtoupper(hash_hmac('md5', $stringToBeSigned, $appData->getAppSecret()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace RetailCrm\TopClient;
|
||||
|
||||
use JMS\Serializer\SerializerInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use RetailCrm\Interfaces\AppDataInterface;
|
||||
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||
use RetailCrm\Traits\ValidatorAwareTrait;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@ -32,16 +33,10 @@ class Client
|
||||
{
|
||||
use ValidatorAwareTrait;
|
||||
|
||||
public const OVERSEAS_ENDPOINT = 'https://api.taobao.com/router/rest';
|
||||
public const CHINESE_ENDPOINT = 'https://eco.taobao.com/router/rest';
|
||||
public const AVAILABLE_ENDPOINTS = [self::OVERSEAS_ENDPOINT, self::CHINESE_ENDPOINT];
|
||||
|
||||
/**
|
||||
* @var string $serviceUrl
|
||||
* @Assert\Url()
|
||||
* @Assert\Choice(choices=Client::AVAILABLE_ENDPOINTS, message="Invalid endpoint provided.")
|
||||
* @var \RetailCrm\Interfaces\AppDataInterface $appData
|
||||
*/
|
||||
protected $serviceUrl;
|
||||
protected $appData;
|
||||
|
||||
/**
|
||||
* @var AuthenticatorInterface $authenticator
|
||||
@ -64,12 +59,12 @@ class Client
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param string $serviceUrl
|
||||
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
||||
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
|
||||
*/
|
||||
public function __construct(string $serviceUrl, AuthenticatorInterface $authenticator)
|
||||
public function __construct(AppDataInterface $appData, AuthenticatorInterface $authenticator)
|
||||
{
|
||||
$this->serviceUrl = $serviceUrl;
|
||||
$this->appData = $appData;
|
||||
$this->authenticator = $authenticator;
|
||||
}
|
||||
|
||||
|
33
tests/RetailCrm/Test/TestCase.php
Normal file
33
tests/RetailCrm/Test/TestCase.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace RetailCrm\Test;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use RetailCrm\Component\Environment;
|
||||
use RetailCrm\Factory\ContainerFactory;
|
||||
|
||||
/**
|
||||
* Class TestCase
|
||||
*
|
||||
* @category TestCase
|
||||
* @package ${NAMESPACE}
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see https://help.retailcrm.ru
|
||||
*/
|
||||
class TestCase extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private $container;
|
||||
|
||||
public function getContainer($recreate = false): ContainerInterface
|
||||
{
|
||||
if (null === $this->container || $recreate) {
|
||||
$this->container = ContainerFactory::withEnv(Environment::TEST)
|
||||
->withClient(new \GuzzleHttp\Client())
|
||||
->create();
|
||||
}
|
||||
|
||||
return $this->container;
|
||||
}
|
||||
}
|
66
tests/RetailCrm/Test/TestSignerRequest.php
Normal file
66
tests/RetailCrm/Test/TestSignerRequest.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category TestRequest
|
||||
* @package RetailCrm\Test
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
namespace RetailCrm\Test;
|
||||
|
||||
use JMS\Serializer\Annotation as JMS;
|
||||
use RetailCrm\Model\Request\BaseRequest;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Class TestRequest
|
||||
*
|
||||
* @category TestRequest
|
||||
* @package RetailCrm\Test
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see https://help.retailcrm.ru
|
||||
*/
|
||||
class TestSignerRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* @var string $serviceName
|
||||
*
|
||||
* @JMS\Type("string")
|
||||
* @JMS\SerializedName("service_name")
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public $serviceName;
|
||||
|
||||
/**
|
||||
* @var string $outRef
|
||||
*
|
||||
* @JMS\Type("string")
|
||||
* @JMS\SerializedName("out_ref")
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public $outRef;
|
||||
|
||||
/**
|
||||
* @var string $sendType
|
||||
*
|
||||
* @JMS\Type("string")
|
||||
* @JMS\SerializedName("send_type")
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public $sendType;
|
||||
|
||||
/**
|
||||
* @var string $logisitics_no
|
||||
*
|
||||
* @JMS\Type("string")
|
||||
* @JMS\SerializedName("logisitics_no")
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public $logisticsNo;
|
||||
}
|
@ -4,26 +4,25 @@
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @category ClientFactoryTest
|
||||
* @package Component
|
||||
* @package RetailCrm\Tests\Component
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
namespace Component;
|
||||
namespace RetailCrm\Tests\Component;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RetailCrm\Component\AppData;
|
||||
use RetailCrm\Component\Authenticator\TokenAuthenticator;
|
||||
use RetailCrm\Component\Environment;
|
||||
use RetailCrm\Factory\ClientFactory;
|
||||
use RetailCrm\Factory\ContainerFactory;
|
||||
use RetailCrm\Test\TestCase;
|
||||
use RetailCrm\TopClient\Client;
|
||||
|
||||
/**
|
||||
* Class ClientFactoryTest
|
||||
*
|
||||
* @category ClientFactoryTest
|
||||
* @package Component
|
||||
* @package RetailCrm\Tests\Component
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
@ -33,11 +32,8 @@ class ClientFactoryTest extends TestCase
|
||||
{
|
||||
public function testCreateClient()
|
||||
{
|
||||
$client = ClientFactory::withContainer(
|
||||
ContainerFactory::withEnv(Environment::DEV)
|
||||
->withClient(new \GuzzleHttp\Client())
|
||||
->create()
|
||||
)->setServiceUrl(Client::OVERSEAS_ENDPOINT)
|
||||
$client = ClientFactory::withContainer($this->getContainer())
|
||||
->setAppData(AppData::create(AppData::OVERSEAS_ENDPOINT, 'appKey', 'helloworld'))
|
||||
->setAuthenticator(new TokenAuthenticator('appKey', 'token'))
|
||||
->create();
|
||||
|
91
tests/RetailCrm/Tests/Service/RequestSignerTest.php
Normal file
91
tests/RetailCrm/Tests/Service/RequestSignerTest.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category RequestSigner
|
||||
* @package RetailCrm\Tests\Service
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
namespace RetailCrm\Tests\Service;
|
||||
|
||||
use DateTime;
|
||||
use RetailCrm\Component\AppData;
|
||||
use RetailCrm\Component\Authenticator\TokenAuthenticator;
|
||||
use RetailCrm\Component\Constants;
|
||||
use RetailCrm\Interfaces\AppDataInterface;
|
||||
use RetailCrm\Interfaces\RequestSignerInterface;
|
||||
use RetailCrm\Service\RequestSigner;
|
||||
use RetailCrm\Test\TestCase;
|
||||
use RetailCrm\Test\TestSignerRequest;
|
||||
|
||||
/**
|
||||
* Class RequestSigner
|
||||
*
|
||||
* @category RequestSigner
|
||||
* @package RetailCrm\Tests\Service
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see https://help.retailcrm.ru
|
||||
*/
|
||||
class RequestSignerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider signDataProvider
|
||||
*
|
||||
* @param \RetailCrm\Test\TestSignerRequest $request
|
||||
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
||||
* @param string $expectedHash
|
||||
*/
|
||||
public function testSign(TestSignerRequest $request, AppDataInterface $appData, string $expectedHash): void
|
||||
{
|
||||
/** @var RequestSignerInterface $signer */
|
||||
$signer = $this->getContainer()->get(RequestSigner::class);
|
||||
$signer->sign($request, $appData);
|
||||
|
||||
self::assertEquals($expectedHash, $request->sign);
|
||||
}
|
||||
|
||||
public function signDataProvider(): array
|
||||
{
|
||||
$appData = AppData::create(AppData::OVERSEAS_ENDPOINT, 'appKey', 'helloworld');
|
||||
|
||||
return [
|
||||
[
|
||||
$this->getRequestWithSignMethod(Constants::SIGN_TYPE_MD5),
|
||||
$appData,
|
||||
'4BC79C5FAA1B5E254E95A97E65BACEAB'
|
||||
],
|
||||
[
|
||||
$this->getRequestWithSignMethod(Constants::SIGN_TYPE_HMAC),
|
||||
$appData,
|
||||
'497FA7FCAD98F4F335EFAE2451F8291D'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $signMethod
|
||||
*
|
||||
* @return \RetailCrm\Test\TestSignerRequest
|
||||
*/
|
||||
private function getRequestWithSignMethod(string $signMethod): TestSignerRequest
|
||||
{
|
||||
$request = new TestSignerRequest();
|
||||
$request->method = 'aliexpress.solution.order.fulfill';
|
||||
$request->appKey = '12345678';
|
||||
$request->session = 'test';
|
||||
$request->timestamp = DateTime::createFromFormat('Y-m-d H:i:s', '2016-01-01 12:00:00');
|
||||
$request->signMethod = $signMethod;
|
||||
$request->serviceName = 'SPAIN_LOCAL_CORREOS';
|
||||
$request->outRef = '1000006270175804';
|
||||
$request->sendType = 'all';
|
||||
$request->logisticsNo = 'ES2019COM0000123456';
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user