Add XML export and export_category definition and Fix CS
nouveau fichier: core/lib/Thelia/Config/Resources/export.xml modifié: core/lib/Thelia/Controller/Admin/CustomerExportController.php modifié: core/lib/Thelia/Controller/Admin/ExportController.php modifié: core/lib/Thelia/Controller/Admin/ImportExportController.php modifié: core/lib/Thelia/Core/DependencyInjection/Compiler/RegisterArchiveBuilderPass.php modifié: core/lib/Thelia/Core/DependencyInjection/Compiler/RegisterFormatterPass.php modifié: core/lib/Thelia/Core/DependencyInjection/Loader/XmlFileLoader.php modifié: core/lib/Thelia/Core/DependencyInjection/Loader/schema/dic/config/thelia-1.0.xsd modifié: core/lib/Thelia/Core/Event/ImportExport/Export.php modifié: core/lib/Thelia/Core/FileFormat/Archive/ArchiveBuilderManager.php modifié: core/lib/Thelia/Core/FileFormat/Formatting/Formatter/JsonFormatter.php modifié: core/lib/Thelia/Core/FileFormat/Formatting/Formatter/XMLFormatter.php modifié: core/lib/Thelia/Core/FileFormat/Formatting/FormatterData.php modifié: core/lib/Thelia/Core/FileFormat/Formatting/FormatterManager.php modifié: core/lib/Thelia/Core/Template/Loop/ArchiveBuilder.php modifié: core/lib/Thelia/Core/Template/Loop/Export.php modifié: core/lib/Thelia/Core/Template/Loop/ExportCategory.php modifié: core/lib/Thelia/Core/Template/Loop/Formatter.php modifié: core/lib/Thelia/Core/Template/Loop/Import.php modifié: core/lib/Thelia/Core/Template/Loop/ImportCategory.php modifié: core/lib/Thelia/Core/Template/Loop/ImportExportCategory.php modifié: core/lib/Thelia/Core/Template/Loop/ImportExportType.php modifié: core/lib/Thelia/Exception/FileNotFoundException.php modifié: core/lib/Thelia/Exception/FileNotReadableException.php modifié: core/lib/Thelia/Exception/HttpUrlException.php modifié: core/lib/Thelia/Form/ExportForm.php modifié: core/lib/Thelia/ImportExport/Export/MailingExport.php modifié: core/lib/Thelia/Model/Base/Export.php modifié: core/lib/Thelia/Model/Base/ExportCategory.php modifié: core/lib/Thelia/Model/Base/ExportCategoryQuery.php modifié: core/lib/Thelia/Model/Base/ExportQuery.php modifié: core/lib/Thelia/Model/Base/Import.php modifié: core/lib/Thelia/Model/Base/ImportCategory.php modifié: core/lib/Thelia/Model/Base/ImportCategoryQuery.php modifié: core/lib/Thelia/Model/Base/ImportQuery.php modifié: core/lib/Thelia/Model/Export.php modifié: core/lib/Thelia/Model/ExportCategory.php modifié: core/lib/Thelia/Model/ExportCategoryI18nQuery.php modifié: core/lib/Thelia/Model/ExportCategoryQuery.php modifié: core/lib/Thelia/Model/ExportI18nQuery.php modifié: core/lib/Thelia/Model/ExportQuery.php modifié: core/lib/Thelia/Model/ImportCategoryI18nQuery.php modifié: core/lib/Thelia/Model/ImportCategoryQuery.php modifié: core/lib/Thelia/Model/ImportI18nQuery.php modifié: core/lib/Thelia/Model/ImportQuery.php modifié: core/lib/Thelia/Model/Map/ExportCategoryTableMap.php modifié: core/lib/Thelia/Model/Map/ExportTableMap.php modifié: core/lib/Thelia/Model/Map/ImportCategoryTableMap.php modifié: core/lib/Thelia/Model/Map/ImportTableMap.php modifié: core/lib/Thelia/Model/Tax.php modifié: local/config/schema.xml modifié: setup/thelia.sql modifié: templates/backOffice/default/ajax/export-modal.html modifié: templates/backOffice/default/export-page.html
This commit is contained in:
@@ -51,4 +51,4 @@ class RegisterArchiveBuilderPass implements CompilerPassInterface
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,4 @@ class RegisterFormatterPass implements CompilerPassInterface
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Thelia\Core\DependencyInjection\Loader;
|
||||
|
||||
use Propel\Runtime\Propel;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Config\Util\XmlUtils;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
@@ -24,6 +25,14 @@ use Symfony\Component\DependencyInjection\SimpleXMLElement;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Loader\FileLoader;
|
||||
use Thelia\Core\Translation\Translator;
|
||||
use Thelia\ImportExport\ExportHandler;
|
||||
use Thelia\Model\Export;
|
||||
use Thelia\Model\ExportCategory;
|
||||
use Thelia\Model\ExportCategoryQuery;
|
||||
use Thelia\Model\ExportQuery;
|
||||
use Thelia\Model\Map\ExportCategoryTableMap;
|
||||
use Thelia\Model\Map\ExportTableMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -63,6 +72,10 @@ class XmlFileLoader extends FileLoader
|
||||
$this->parseForms($xml);
|
||||
|
||||
$this->parseDefinitions($xml, $path);
|
||||
|
||||
$this->parseExportCategories($xml);
|
||||
|
||||
$this->parseExports($xml);
|
||||
}
|
||||
|
||||
protected function parseCommands(SimpleXMLElement $xml)
|
||||
@@ -276,6 +289,157 @@ class XmlFileLoader extends FileLoader
|
||||
$this->container->setDefinition($id, $definition);
|
||||
}
|
||||
|
||||
protected function parseExportCategories(SimpleXMLElement $xml)
|
||||
{
|
||||
if (false === $exportCategories = $xml->xpath('//config:export_categories/config:export_category')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$con = Propel::getWriteConnection(ExportCategoryTableMap::DATABASE_NAME);
|
||||
$con->beginTransaction();
|
||||
|
||||
try {
|
||||
/** @var SimpleXMLElement $exportCategory */
|
||||
foreach ($exportCategories as $exportCategory) {
|
||||
$id = (string) $exportCategory->getAttributeAsPhp("id");
|
||||
|
||||
$exportCategoryModel = ExportCategoryQuery::create()->findOneByRef($id);
|
||||
|
||||
if ($exportCategoryModel === null) {
|
||||
$exportCategoryModel = new ExportCategory();
|
||||
$exportCategoryModel
|
||||
->setRef($id)
|
||||
->setPositionToLast()
|
||||
->save($con)
|
||||
;
|
||||
}
|
||||
|
||||
/** @var SimpleXMLElement $child */
|
||||
foreach ($exportCategory->children() as $child) {
|
||||
$locale = (string) $child->getAttributeAsPhp("locale");
|
||||
$value = (string) $child;
|
||||
|
||||
$exportCategoryModel
|
||||
->setLocale($locale)
|
||||
->setTitle($value)
|
||||
->save($con);
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
} catch (\Exception $e) {
|
||||
$con->rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseExports(SimpleXMLElement $xml)
|
||||
{
|
||||
if (false === $exports = $xml->xpath('//config:exports/config:export')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$con = Propel::getWriteConnection(ExportTableMap::DATABASE_NAME);
|
||||
$con->beginTransaction();
|
||||
|
||||
try {
|
||||
/** @var SimpleXMLElement $export */
|
||||
foreach ($exports as $export) {
|
||||
$id = (string) $export->getAttributeAsPhp("id");
|
||||
$class = (string) $export->getAttributeAsPhp("class");
|
||||
$categoryRef = (string) $export->getAttributeAsPhp("category_id");
|
||||
|
||||
if (!class_exists($class)) {
|
||||
throw new \ErrorException(
|
||||
Translator::getInstance()->trans(
|
||||
"The class \"%class\" doesn't exist",
|
||||
[
|
||||
"%class" => $class
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$classInstance = new $class($this->container);
|
||||
|
||||
if (!$classInstance instanceof ExportHandler) {
|
||||
throw new \ErrorException(
|
||||
Translator::getInstance()->trans(
|
||||
"The class \"%class\" must extend %baseClass",
|
||||
[
|
||||
"%class" => $class,
|
||||
"%baseClass" => "Thelia\\ImportExport\\ExportHandler",
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$category = ExportCategoryQuery::create()->findOneByRef($categoryRef);
|
||||
|
||||
if (null === $category) {
|
||||
throw new \ErrorException(
|
||||
Translator::getInstance()->trans(
|
||||
"The export category \"%category\" doesn't exist",
|
||||
[
|
||||
"%category" => $categoryRef
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$exportModel = ExportQuery::create()->findOneByRef($id);
|
||||
|
||||
if (null === $exportModel) {
|
||||
$exportModel = new Export();
|
||||
$exportModel
|
||||
->setRef($id)
|
||||
->setPositionToLast()
|
||||
;
|
||||
}
|
||||
|
||||
$exportModel
|
||||
->setExportCategory($category)
|
||||
->setHandleClass($class)
|
||||
->save($con)
|
||||
;
|
||||
|
||||
/** @var SimpleXMLElement $descriptive */
|
||||
foreach ($export->children() as $descriptive) {
|
||||
$locale = $descriptive->getAttributeAsPhp("locale");
|
||||
$title = null;
|
||||
$description = null;
|
||||
|
||||
/** @var SimpleXMLElement $row */
|
||||
foreach ($descriptive->children() as $row) {
|
||||
switch ($row->getName()) {
|
||||
case "title":
|
||||
$title = (string) $row;
|
||||
break;
|
||||
case "description":
|
||||
$description = (string) $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$exportModel
|
||||
->setLocale($locale)
|
||||
->setTitle($title)
|
||||
->setDescription($description)
|
||||
->save($con)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
} catch (\Exception $e) {
|
||||
$con->rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a XML file.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<xsd:element name="commands" type="commands"/>
|
||||
<xsd:element name="forms" type="forms" />
|
||||
<xsd:element name="routing" type="routing" />
|
||||
<xsd:element name="export_categories" type="export_categories" />
|
||||
<xsd:element name="exports" type="exports" />
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -212,4 +214,51 @@
|
||||
<xsd:attribute name="function" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="export_categories">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="export_category" type="export_category"/>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="export_category">
|
||||
<xsd:choice maxOccurs="unbounded" minOccurs="1">
|
||||
<xsd:element name="title" type="export_category_title" />
|
||||
</xsd:choice>
|
||||
|
||||
<xsd:attribute name="id" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="export_category_title">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension id="title" base="xsd:string">
|
||||
<xsd:attribute name="locale" type="xsd:string" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="exports">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="export" type="export"/>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="export">
|
||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element name="export_descriptive" type="export_descriptive" />
|
||||
</xsd:choice>
|
||||
|
||||
<xsd:attribute name="id" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="class" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="category_id" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="export_descriptive">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="title" type="xsd:string" />
|
||||
<xsd:element minOccurs="0" maxOccurs="1" name="description" type="xsd:string" />
|
||||
</xsd:sequence>
|
||||
|
||||
<xsd:attribute name="locale" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
</xsd:schema>
|
||||
@@ -32,7 +32,7 @@ class Export extends ActionEvent
|
||||
/** @var \Thelia\Core\FileFormat\Archive\AbstractArchiveBuilder */
|
||||
protected $archiveBuilder;
|
||||
|
||||
function __construct(
|
||||
public function __construct(
|
||||
AbstractFormatter $formatter,
|
||||
ExportHandler $handler,
|
||||
AbstractArchiveBuilder $archiveBuilder = null
|
||||
@@ -43,7 +43,7 @@ class Export extends ActionEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractArchiveBuilder $archiveBuilder
|
||||
* @param AbstractArchiveBuilder $archiveBuilder
|
||||
* @return $this
|
||||
*/
|
||||
public function setArchiveBuilder(AbstractArchiveBuilder $archiveBuilder)
|
||||
@@ -62,7 +62,7 @@ class Export extends ActionEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractFormatter $formatter
|
||||
* @param AbstractFormatter $formatter
|
||||
* @return $this
|
||||
*/
|
||||
public function setFormatter(AbstractFormatter $formatter)
|
||||
@@ -81,7 +81,7 @@ class Export extends ActionEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportHandler $handler
|
||||
* @param ExportHandler $handler
|
||||
* @return $this
|
||||
*/
|
||||
public function setHandler(ExportHandler $handler)
|
||||
@@ -98,4 +98,4 @@ class Export extends ActionEvent
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class ArchiveBuilderManager
|
||||
$names = [];
|
||||
|
||||
/** @var AbstractArchiveBuilder $builder */
|
||||
foreach($this->archiveBuilders as $builder) {
|
||||
foreach ($this->archiveBuilders as $builder) {
|
||||
$names[] = $builder->getName();
|
||||
}
|
||||
|
||||
|
||||
@@ -92,4 +92,4 @@ class JsonFormatter extends AbstractFormatter
|
||||
{
|
||||
return ExportType::EXPORT_UNBOUNDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ class XMLFormatter extends AbstractFormatter
|
||||
}
|
||||
|
||||
$data = new FormatterData($this->getAliases());
|
||||
|
||||
return $data->setData($array);
|
||||
}
|
||||
|
||||
@@ -160,4 +161,4 @@ class XMLFormatter extends AbstractFormatter
|
||||
{
|
||||
return ExportType::EXPORT_UNBOUNDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class FormatterData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $data
|
||||
* @return $this
|
||||
*
|
||||
* Sets raw data with aliases
|
||||
@@ -75,6 +75,7 @@ class FormatterData
|
||||
{
|
||||
if (empty($this->aliases)) {
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ class FormatterData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ModelCriteria $criteria
|
||||
* @param ModelCriteria $criteria
|
||||
* @return $this|null
|
||||
*
|
||||
* Loads a model criteria.
|
||||
@@ -157,7 +158,7 @@ class FormatterData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $row
|
||||
* @param array $row
|
||||
* @return $this
|
||||
*/
|
||||
public function addRow(array $row)
|
||||
@@ -168,7 +169,7 @@ class FormatterData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param int $index
|
||||
* @return array|bool
|
||||
*/
|
||||
public function popRow($index = 0)
|
||||
@@ -183,7 +184,7 @@ class FormatterData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param int $index
|
||||
* @return array|bool
|
||||
* @throws \OutOfBoundsException
|
||||
*/
|
||||
@@ -208,8 +209,8 @@ class FormatterData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $aliases
|
||||
* @param array $data
|
||||
* @param array $aliases
|
||||
* @return array
|
||||
*/
|
||||
protected function reverseAliases(array $data, array $aliases)
|
||||
|
||||
@@ -77,7 +77,7 @@ class FormatterManager
|
||||
$names = [];
|
||||
|
||||
/** @var AbstractFormatter $formatter */
|
||||
foreach($this->formatters as $formatter) {
|
||||
foreach ($this->formatters as $formatter) {
|
||||
$names[] = $formatter->getName();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,15 +39,13 @@ class ArchiveBuilder extends BaseLoop implements ArraySearchLoopInterface
|
||||
|
||||
$rawArchiveBuilders = array_change_key_case($service->getAll());
|
||||
|
||||
|
||||
$allowedArchiveBuilder = $this->getAllowed_archive_builder();
|
||||
$archiveBuilders = [];
|
||||
|
||||
if ($allowedArchiveBuilder !== null) {
|
||||
$allowedArchiveBuilder = explode(",", $allowedArchiveBuilder);
|
||||
|
||||
|
||||
foreach($allowedArchiveBuilder as $archiveBuilder) {
|
||||
foreach ($allowedArchiveBuilder as $archiveBuilder) {
|
||||
$archiveBuilder = trim(strtolower($archiveBuilder));
|
||||
|
||||
if (isset($rawArchiveBuilders[$archiveBuilder])) {
|
||||
@@ -131,4 +129,4 @@ class ArchiveBuilder extends BaseLoop implements ArraySearchLoopInterface
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,4 @@ class Export extends ImportExportType
|
||||
{
|
||||
return "ExportCategoryId";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,4 @@ class ExportCategory extends ImportExportCategory
|
||||
return ExportCategoryQuery::create();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,4 +140,4 @@ class Formatter extends BaseLoop implements ArraySearchLoopInterface
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,4 @@ class Import extends ImportExportType
|
||||
{
|
||||
return "ImportCategoryId";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@ class ImportCategory extends ImportExportCategory
|
||||
{
|
||||
return ImportCategoryQuery::create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ abstract class ImportExportCategory extends BaseLoop implements PropelSearchLoop
|
||||
*/
|
||||
public function parseResults(LoopResult $loopResult)
|
||||
{
|
||||
foreach ($loopResult->getResultDataCollection() as $category)
|
||||
{
|
||||
foreach ($loopResult->getResultDataCollection() as $category) {
|
||||
$loopResultRow = new LoopResultRow($category);
|
||||
|
||||
$loopResultRow
|
||||
@@ -68,7 +67,7 @@ abstract class ImportExportCategory extends BaseLoop implements PropelSearchLoop
|
||||
|
||||
if (null !== $orders = $this->getOrder()) {
|
||||
foreach ($orders as $order) {
|
||||
switch($order) {
|
||||
switch ($order) {
|
||||
case "id":
|
||||
$query->orderById();
|
||||
break;
|
||||
@@ -133,4 +132,4 @@ abstract class ImportExportCategory extends BaseLoop implements PropelSearchLoop
|
||||
}
|
||||
|
||||
abstract protected function getQueryModel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Thelia\Core\Template\Loop;
|
||||
use Propel\Runtime\ActiveQuery\Criteria;
|
||||
use Propel\Runtime\ActiveQuery\ModelCriteria;
|
||||
use Thelia\Core\Template\Element\BaseI18nLoop;
|
||||
use Thelia\Core\Template\Element\BaseLoop;
|
||||
use Thelia\Core\Template\Element\LoopResult;
|
||||
use Thelia\Core\Template\Element\LoopResultRow;
|
||||
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
|
||||
@@ -56,6 +55,7 @@ abstract class ImportExportType extends BaseI18nLoop implements PropelSearchLoop
|
||||
->set("URL", $url)
|
||||
->set("POSITION", $type->getPosition())
|
||||
->set("CATEGORY_ID", $type->getByName($this->getCategoryName()))
|
||||
->set("HANDLE_CLASS", $type->getHandleClass())
|
||||
;
|
||||
|
||||
$loopResult->addRow($loopResultRow);
|
||||
@@ -76,7 +76,6 @@ abstract class ImportExportType extends BaseI18nLoop implements PropelSearchLoop
|
||||
|
||||
$this->configureI18nProcessing($query, array('TITLE', 'DESCRIPTION'));
|
||||
|
||||
|
||||
if (null !== $ids = $this->getId()) {
|
||||
$query->filterById($ids);
|
||||
}
|
||||
@@ -87,7 +86,7 @@ abstract class ImportExportType extends BaseI18nLoop implements PropelSearchLoop
|
||||
|
||||
if (null !== $orders = $this->getOrder()) {
|
||||
foreach ($orders as $order) {
|
||||
switch($order) {
|
||||
switch ($order) {
|
||||
case "id":
|
||||
$query->orderById();
|
||||
break;
|
||||
@@ -162,4 +161,4 @@ abstract class ImportExportType extends BaseI18nLoop implements PropelSearchLoop
|
||||
abstract protected function getQueryModel();
|
||||
|
||||
abstract protected function getCategoryName();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user