mirror of
https://github.com/friendica/friendica
synced 2024-12-22 12:40:15 +00:00
Create interface for static Module calls
This commit is contained in:
parent
96996790f1
commit
018275919c
8 changed files with 170 additions and 93 deletions
|
@ -46,5 +46,6 @@ $a->runFrontend(
|
|||
$dice->create(\Friendica\Core\PConfig\Capability\IManagePersonalConfigValues::class),
|
||||
$dice->create(\Friendica\Security\Authentication::class),
|
||||
$dice->create(\Friendica\App\Page::class),
|
||||
$dice,
|
||||
$start_time
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica;
|
||||
|
||||
use Dice\Dice;
|
||||
use Exception;
|
||||
use Friendica\App\Arguments;
|
||||
use Friendica\App\BaseURL;
|
||||
|
@ -575,7 +576,7 @@ class App
|
|||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function runFrontend(App\Module $module, App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, float $start_time)
|
||||
public function runFrontend(App\Module $module, App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, Dice $dice, float $start_time)
|
||||
{
|
||||
$this->profiler->set($start_time, 'start');
|
||||
$this->profiler->set(microtime(true), 'classinit');
|
||||
|
@ -702,11 +703,11 @@ class App
|
|||
$page['page_title'] = $moduleName;
|
||||
|
||||
if (!$this->mode->isInstall() && !$this->mode->has(App\Mode::MAINTENANCEDISABLED)) {
|
||||
$module = new Module('maintenance', Maintenance::class);
|
||||
$module = new Module('maintenance', new Maintenance());
|
||||
} else {
|
||||
// determine the module class and save it to the module instance
|
||||
// @todo there's an implicit dependency due SESSION::start(), so it has to be called here (yet)
|
||||
$module = $module->determineClass($this->args, $router, $this->config);
|
||||
$module = $module->determineClass($this->args, $router, $this->config, $dice);
|
||||
}
|
||||
|
||||
// Let the module run it's internal process (init, get, post, ...)
|
||||
|
|
|
@ -21,8 +21,9 @@
|
|||
|
||||
namespace Friendica\App;
|
||||
|
||||
use Dice\Dice;
|
||||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Capabilities\ICanHandleRequests;
|
||||
use Friendica\Core;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\LegacyModule;
|
||||
|
@ -80,15 +81,10 @@ class Module
|
|||
private $module;
|
||||
|
||||
/**
|
||||
* @var BaseModule The module class
|
||||
* @var ICanHandleRequests The module class
|
||||
*/
|
||||
private $module_class;
|
||||
|
||||
/**
|
||||
* @var array The module parameters
|
||||
*/
|
||||
private $module_parameters;
|
||||
|
||||
/**
|
||||
* @var bool true, if the module is a backend module
|
||||
*/
|
||||
|
@ -108,21 +104,13 @@ class Module
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string The base class name
|
||||
* @return ICanHandleRequests The base class name
|
||||
*/
|
||||
public function getClassName()
|
||||
public function getClass(): ICanHandleRequests
|
||||
{
|
||||
return $this->module_class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array The module parameters extracted from the route
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->module_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True, if the current module is a backend module
|
||||
* @see Module::BACKEND_MODULES for a list
|
||||
|
@ -132,11 +120,12 @@ class Module
|
|||
return $this->isBackend;
|
||||
}
|
||||
|
||||
public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, array $moduleParameters = [], bool $isBackend = false, bool $printNotAllowedAddon = false)
|
||||
public function __construct(string $module = self::DEFAULT, ICanHandleRequests $module_class = null, bool $isBackend = false, bool $printNotAllowedAddon = false)
|
||||
{
|
||||
$defaultClass = static::DEFAULT_CLASS;
|
||||
|
||||
$this->module = $module;
|
||||
$this->module_class = $moduleClass;
|
||||
$this->module_parameters = $moduleParameters;
|
||||
$this->module_class = $module_class ?? new $defaultClass();
|
||||
$this->isBackend = $isBackend;
|
||||
$this->printNotAllowedAddon = $printNotAllowedAddon;
|
||||
}
|
||||
|
@ -164,21 +153,22 @@ class Module
|
|||
|
||||
$isBackend = in_array($module, Module::BACKEND_MODULES);;
|
||||
|
||||
return new Module($module, $this->module_class, [], $isBackend, $this->printNotAllowedAddon);
|
||||
return new Module($module,null, $isBackend, $this->printNotAllowedAddon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the class of the current module
|
||||
*
|
||||
* @param Arguments $args The Friendica execution arguments
|
||||
* @param Router $router The Friendica routing instance
|
||||
* @param Arguments $args The Friendica execution arguments
|
||||
* @param Router $router The Friendica routing instance
|
||||
* @param IManageConfigValues $config The Friendica Configuration
|
||||
* @param Dice $dice The Dependency Injection container
|
||||
*
|
||||
* @return Module The determined module of this call
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function determineClass(Arguments $args, Router $router, IManageConfigValues $config)
|
||||
public function determineClass(Arguments $args, Router $router, IManageConfigValues $config, Dice $dice)
|
||||
{
|
||||
$printNotAllowedAddon = false;
|
||||
|
||||
|
@ -222,7 +212,10 @@ class Module
|
|||
$module_class = $module_class ?: PageNotFound::class;
|
||||
}
|
||||
|
||||
return new Module($this->module, $module_class, $module_parameters, $this->isBackend, $printNotAllowedAddon);
|
||||
/** @var ICanHandleRequests $module */
|
||||
$module = $dice->create($module_class, [$module_parameters]);
|
||||
|
||||
return new Module($this->module, $module, $this->isBackend, $printNotAllowedAddon);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,32 +297,32 @@ class Module
|
|||
|
||||
Core\Hook::callAll($this->module . '_mod_init', $placeholder);
|
||||
|
||||
call_user_func([$this->module_class, 'init'], $this->module_parameters);
|
||||
$this->module_class::init($this->module_class::getParameters());
|
||||
|
||||
$profiler->set(microtime(true) - $timestamp, 'init');
|
||||
|
||||
if ($server['REQUEST_METHOD'] === Router::DELETE) {
|
||||
call_user_func([$this->module_class, 'delete'], $this->module_parameters);
|
||||
$this->module_class::delete($this->module_class::getParameters());
|
||||
}
|
||||
|
||||
if ($server['REQUEST_METHOD'] === Router::PATCH) {
|
||||
call_user_func([$this->module_class, 'patch'], $this->module_parameters);
|
||||
$this->module_class::patch($this->module_class::getParameters());
|
||||
}
|
||||
|
||||
if ($server['REQUEST_METHOD'] === Router::POST) {
|
||||
Core\Hook::callAll($this->module . '_mod_post', $post);
|
||||
call_user_func([$this->module_class, 'post'], $this->module_parameters);
|
||||
$this->module_class::post($this->module_class::getParameters());
|
||||
}
|
||||
|
||||
if ($server['REQUEST_METHOD'] === Router::PUT) {
|
||||
call_user_func([$this->module_class, 'put'], $this->module_parameters);
|
||||
$this->module_class::put($this->module_class::getParameters());
|
||||
}
|
||||
|
||||
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
||||
call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
|
||||
$this->module_class::afterpost($this->module_class::getParameters());
|
||||
|
||||
// "rawContent" is especially meant for technical endpoints.
|
||||
// This endpoint doesn't need any theme initialization or other comparable stuff.
|
||||
call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
|
||||
$this->module_class::rawContent($this->module_class::getParameters());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -347,13 +347,13 @@ class Page implements ArrayAccess
|
|||
$content = '';
|
||||
|
||||
try {
|
||||
$moduleClass = $module->getClassName();
|
||||
$moduleClass = $module->getClass();
|
||||
|
||||
$arr = ['content' => $content];
|
||||
Hook::callAll($moduleClass . '_mod_content', $arr);
|
||||
Hook::callAll( $moduleClass::getClassName() . '_mod_content', $arr);
|
||||
$content = $arr['content'];
|
||||
$arr = ['content' => call_user_func([$moduleClass, 'content'], $module->getParameters())];
|
||||
Hook::callAll($moduleClass . '_mod_aftercontent', $arr);
|
||||
$arr = ['content' => $moduleClass::content($moduleClass::getParameters())];
|
||||
Hook::callAll($moduleClass::getClassName() . '_mod_aftercontent', $arr);
|
||||
$content .= $arr['content'];
|
||||
} catch (HTTPException $e) {
|
||||
$content = ModuleHTTPException::content($e);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica;
|
||||
|
||||
use Friendica\Capabilities\ICanHandleRequests;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Model\User;
|
||||
|
||||
|
@ -33,23 +34,33 @@ use Friendica\Model\User;
|
|||
*
|
||||
* @author Hypolite Petovan <hypolite@mrpetovan.com>
|
||||
*/
|
||||
abstract class BaseModule
|
||||
abstract class BaseModule implements ICanHandleRequests
|
||||
{
|
||||
/** @var array */
|
||||
protected static $parameters = [];
|
||||
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
static::$parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization method common to both content() and post()
|
||||
*
|
||||
* Extend this method if you need to do any shared processing before both
|
||||
* content() or post()
|
||||
* @return array
|
||||
*/
|
||||
public static function getParameters(): array
|
||||
{
|
||||
return self::$parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function init(array $parameters = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Module GET method to display raw content from technical endpoints
|
||||
*
|
||||
* Extend this method if the module is supposed to return communication data,
|
||||
* e.g. from protocol implementations.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
|
@ -58,46 +69,29 @@ abstract class BaseModule
|
|||
}
|
||||
|
||||
/**
|
||||
* Module GET method to display any content
|
||||
*
|
||||
* Extend this method if the module is supposed to return any display
|
||||
* through a GET request. It can be an HTML page through templating or a
|
||||
* XML feed or a JSON output.
|
||||
*
|
||||
* @return string
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function content(array $parameters = [])
|
||||
{
|
||||
$o = '';
|
||||
|
||||
return $o;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Module DELETE method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process DELETE requests.
|
||||
* Doesn't display any content
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function delete(array $parameters = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Module PATCH method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process PATCH requests.
|
||||
* Doesn't display any content
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function patch(array $parameters = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Module POST method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process POST requests.
|
||||
* Doesn't display any content
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
|
@ -105,24 +99,25 @@ abstract class BaseModule
|
|||
}
|
||||
|
||||
/**
|
||||
* Called after post()
|
||||
*
|
||||
* Unknown purpose
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function afterpost(array $parameters = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Module PUT method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process PUT requests.
|
||||
* Doesn't display any content
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function put(array $parameters = [])
|
||||
{
|
||||
}
|
||||
|
||||
/** Gets the name of the current class */
|
||||
public static function getClassName(): string
|
||||
{
|
||||
return static::class;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions used to protect against Cross-Site Request Forgery
|
||||
* The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key.
|
||||
|
|
79
src/Capabilities/ICanHandleRequests.php
Normal file
79
src/Capabilities/ICanHandleRequests.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Capabilities;
|
||||
|
||||
/**
|
||||
* This interface provides the capability to handle requests from clients and returns the desired outcome
|
||||
*/
|
||||
interface ICanHandleRequests
|
||||
{
|
||||
/**
|
||||
* Initialization method common to both content() and post()
|
||||
*
|
||||
* Extend this method if you need to do any shared processing before both
|
||||
* content() or post()
|
||||
*/
|
||||
public static function init(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Module GET method to display raw content from technical endpoints
|
||||
*
|
||||
* Extend this method if the module is supposed to return communication data,
|
||||
* e.g. from protocol implementations.
|
||||
*/
|
||||
public static function rawContent(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Module GET method to display any content
|
||||
*
|
||||
* Extend this method if the module is supposed to return any display
|
||||
* through a GET request. It can be an HTML page through templating or a
|
||||
* XML feed or a JSON output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function content(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Module DELETE method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process DELETE requests.
|
||||
* Doesn't display any content
|
||||
*/
|
||||
public static function delete(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Module PATCH method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process PATCH requests.
|
||||
* Doesn't display any content
|
||||
*/
|
||||
public static function patch(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Module POST method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process POST requests.
|
||||
* Doesn't display any content
|
||||
*/
|
||||
public static function post(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Called after post()
|
||||
*
|
||||
* Unknown purpose
|
||||
*/
|
||||
public static function afterpost(array $parameters = []);
|
||||
|
||||
/**
|
||||
* Module PUT method to process submitted data
|
||||
*
|
||||
* Extend this method if the module is supposed to process PUT requests.
|
||||
* Doesn't display any content
|
||||
*/
|
||||
public static function put(array $parameters = []);
|
||||
|
||||
public static function getClassName(): string;
|
||||
|
||||
public static function getParameters(): array;
|
||||
}
|
|
@ -218,7 +218,7 @@ class ModeTest extends MockedTest
|
|||
public function testIsBackendButIndex()
|
||||
{
|
||||
$server = [];
|
||||
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], true);
|
||||
$module = new Module(Module::DEFAULT, null, true);
|
||||
$mobileDetect = new MobileDetect();
|
||||
|
||||
$mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect);
|
||||
|
@ -232,7 +232,7 @@ class ModeTest extends MockedTest
|
|||
public function testIsNotBackend()
|
||||
{
|
||||
$server = [];
|
||||
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false);
|
||||
$module = new Module(Module::DEFAULT, null, false);
|
||||
$mobileDetect = new MobileDetect();
|
||||
|
||||
$mode = (new Mode())->determineRunMode(false, $module, $server, $mobileDetect);
|
||||
|
@ -250,7 +250,7 @@ class ModeTest extends MockedTest
|
|||
'HTTP_X_REQUESTED_WITH' => 'xmlhttprequest',
|
||||
];
|
||||
|
||||
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false);
|
||||
$module = new Module(Module::DEFAULT, null, false);
|
||||
$mobileDetect = new MobileDetect();
|
||||
|
||||
$mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect);
|
||||
|
@ -264,7 +264,7 @@ class ModeTest extends MockedTest
|
|||
public function testIsNotAjax()
|
||||
{
|
||||
$server = [];
|
||||
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false);
|
||||
$module = new Module(Module::DEFAULT, null, false);
|
||||
$mobileDetect = new MobileDetect();
|
||||
|
||||
$mode = (new Mode())->determineRunMode(true, $module, $server, $mobileDetect);
|
||||
|
@ -278,7 +278,7 @@ class ModeTest extends MockedTest
|
|||
public function testIsMobileIsTablet()
|
||||
{
|
||||
$server = [];
|
||||
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false);
|
||||
$module = new Module(Module::DEFAULT, null, false);
|
||||
$mobileDetect = Mockery::mock(MobileDetect::class);
|
||||
$mobileDetect->shouldReceive('isMobile')->andReturn(true);
|
||||
$mobileDetect->shouldReceive('isTablet')->andReturn(true);
|
||||
|
@ -296,7 +296,7 @@ class ModeTest extends MockedTest
|
|||
public function testIsNotMobileIsNotTablet()
|
||||
{
|
||||
$server = [];
|
||||
$module = new Module(Module::DEFAULT, Module::DEFAULT_CLASS, [], false);
|
||||
$module = new Module(Module::DEFAULT, null, false);
|
||||
$mobileDetect = Mockery::mock(MobileDetect::class);
|
||||
$mobileDetect->shouldReceive('isMobile')->andReturn(false);
|
||||
$mobileDetect->shouldReceive('isTablet')->andReturn(false);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica\Test\src\App;
|
||||
|
||||
use Dice\Dice;
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Cache\Capability\ICanCache;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
|
@ -38,7 +39,7 @@ class ModuleTest extends DatabaseTest
|
|||
{
|
||||
self::assertEquals($assert['isBackend'], $module->isBackend());
|
||||
self::assertEquals($assert['name'], $module->getName());
|
||||
self::assertEquals($assert['class'], $module->getClassName());
|
||||
self::assertEquals($assert['class'], $module->getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,21 +49,25 @@ class ModuleTest extends DatabaseTest
|
|||
{
|
||||
$module = new App\Module();
|
||||
|
||||
$defaultClass = App\Module::DEFAULT_CLASS;
|
||||
|
||||
self::assertModule([
|
||||
'isBackend' => false,
|
||||
'name' => App\Module::DEFAULT,
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
], $module);
|
||||
}
|
||||
|
||||
public function dataModuleName()
|
||||
{
|
||||
$defaultClass = App\Module::DEFAULT_CLASS;
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'assert' => [
|
||||
'isBackend' => false,
|
||||
'name' => 'network',
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
],
|
||||
'args' => new App\Arguments('network/data/in',
|
||||
'network/data/in',
|
||||
|
@ -73,7 +78,7 @@ class ModuleTest extends DatabaseTest
|
|||
'assert' => [
|
||||
'isBackend' => false,
|
||||
'name' => 'with_strike_and_point',
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
],
|
||||
'args' => new App\Arguments('with-strike.and-point/data/in',
|
||||
'with-strike.and-point/data/in',
|
||||
|
@ -84,7 +89,7 @@ class ModuleTest extends DatabaseTest
|
|||
'assert' => [
|
||||
'isBackend' => false,
|
||||
'name' => App\Module::DEFAULT,
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
],
|
||||
'args' => new App\Arguments(),
|
||||
],
|
||||
|
@ -92,7 +97,7 @@ class ModuleTest extends DatabaseTest
|
|||
'assert' => [
|
||||
'isBackend' => false,
|
||||
'name' => App\Module::DEFAULT,
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
],
|
||||
'args' => new App\Arguments(),
|
||||
],
|
||||
|
@ -100,7 +105,7 @@ class ModuleTest extends DatabaseTest
|
|||
'assert' => [
|
||||
'isBackend' => true,
|
||||
'name' => App\Module::BACKEND_MODULES[0],
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
],
|
||||
'args' => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in',
|
||||
App\Module::BACKEND_MODULES[0] . '/data/in',
|
||||
|
@ -111,7 +116,7 @@ class ModuleTest extends DatabaseTest
|
|||
'assert' => [
|
||||
'isBackend' => false,
|
||||
'name' => 'login',
|
||||
'class' => App\Module::DEFAULT_CLASS,
|
||||
'class' => new $defaultClass(),
|
||||
],
|
||||
'args' => new App\Arguments('users/sign_in',
|
||||
'users/sign_in',
|
||||
|
@ -187,9 +192,12 @@ class ModuleTest extends DatabaseTest
|
|||
|
||||
$router = (new App\Router([], __DIR__ . '/../../../static/routes.config.php', $l10n, $cache, $lock));
|
||||
|
||||
$module = (new App\Module($name))->determineClass(new App\Arguments('', $command), $router, $config);
|
||||
$dice = Mockery::mock(Dice::class);
|
||||
$dice->shouldReceive('create')->andReturn(new $assert());
|
||||
|
||||
self::assertEquals($assert, $module->getClassName());
|
||||
$module = (new App\Module($name))->determineClass(new App\Arguments('', $command), $router, $config, $dice);
|
||||
|
||||
self::assertEquals($assert, $module->getClass()::getClassName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue