Refactor : Coupon effect inputs are now more customisable (input text, select, ajax, etc.. are usable) and unlimited amount of input for coupon effect are now possible too (Event are used for custom inputs)
This commit is contained in:
@@ -499,6 +499,10 @@
|
|||||||
<default key="_controller">Thelia\Controller\Admin\CouponController::readAction</default>
|
<default key="_controller">Thelia\Controller\Admin\CouponController::readAction</default>
|
||||||
<requirement key="couponId">\d+</requirement>
|
<requirement key="couponId">\d+</requirement>
|
||||||
</route>
|
</route>
|
||||||
|
<route id="admin.coupon.draw.inputs" path="/admin/coupon/draw/inputs/{couponServiceId}">
|
||||||
|
<default key="_controller">Thelia\Controller\Admin\CouponController::getBackOfficeInputsAction</default>
|
||||||
|
<requirement key="couponServiceId">.*</requirement>
|
||||||
|
</route>
|
||||||
<route id="admin.coupon.condition.input" path="/admin/coupon/condition/{conditionId}">
|
<route id="admin.coupon.condition.input" path="/admin/coupon/condition/{conditionId}">
|
||||||
<default key="_controller">Thelia\Controller\Admin\CouponController::getConditionInputAction</default>
|
<default key="_controller">Thelia\Controller\Admin\CouponController::getConditionInputAction</default>
|
||||||
<requirement key="conditionId">.*</requirement>
|
<requirement key="conditionId">.*</requirement>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use Thelia\Core\Security\Resource\AdminResources;
|
|||||||
use Thelia\Core\Event\Coupon\CouponCreateOrUpdateEvent;
|
use Thelia\Core\Event\Coupon\CouponCreateOrUpdateEvent;
|
||||||
use Thelia\Core\Event\TheliaEvents;
|
use Thelia\Core\Event\TheliaEvents;
|
||||||
use Thelia\Core\Security\AccessManager;
|
use Thelia\Core\Security\AccessManager;
|
||||||
|
use Thelia\Coupon\CouponFactory;
|
||||||
use Thelia\Coupon\CouponManager;
|
use Thelia\Coupon\CouponManager;
|
||||||
use Thelia\Condition\ConditionCollection;
|
use Thelia\Condition\ConditionCollection;
|
||||||
use Thelia\Coupon\Type\CouponInterface;
|
use Thelia\Coupon\Type\CouponInterface;
|
||||||
@@ -41,6 +42,7 @@ use Thelia\Model\Coupon;
|
|||||||
use Thelia\Model\CouponQuery;
|
use Thelia\Model\CouponQuery;
|
||||||
use Thelia\Model\Lang;
|
use Thelia\Model\Lang;
|
||||||
use Thelia\Tools\I18n;
|
use Thelia\Tools\I18n;
|
||||||
|
use Thelia\Tools\Rest\ResponseRest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by JetBrains PhpStorm.
|
* Created by JetBrains PhpStorm.
|
||||||
@@ -152,6 +154,11 @@ class CouponController extends BaseAdminController
|
|||||||
|
|
||||||
$args['dateFormat'] = $this->getSession()->getLang()->getDateFormat();
|
$args['dateFormat'] = $this->getSession()->getLang()->getDateFormat();
|
||||||
$args['availableCoupons'] = $this->getAvailableCoupons();
|
$args['availableCoupons'] = $this->getAvailableCoupons();
|
||||||
|
$args['urlAjaxAdminCouponDrawInputs'] = $this->getRoute(
|
||||||
|
'admin.coupon.draw.inputs',
|
||||||
|
array('couponServiceId' => 'couponServiceId'),
|
||||||
|
Router::ABSOLUTE_URL
|
||||||
|
);
|
||||||
$args['formAction'] = 'admin/coupon/create';
|
$args['formAction'] = 'admin/coupon/create';
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
@@ -181,6 +188,9 @@ class CouponController extends BaseAdminController
|
|||||||
return $this->pageNotFound();
|
return $this->pageNotFound();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/** @var CouponFactory $couponFactory */
|
||||||
|
$couponFactory = $this->container->get('thelia.coupon.factory');
|
||||||
|
$couponManager = $couponFactory->buildCouponFromModel($coupon);
|
||||||
|
|
||||||
// Parameters given to the template
|
// Parameters given to the template
|
||||||
$args = array();
|
$args = array();
|
||||||
@@ -233,6 +243,7 @@ class CouponController extends BaseAdminController
|
|||||||
'serviceId' => $condition->getServiceId(),
|
'serviceId' => $condition->getServiceId(),
|
||||||
'name' => $condition->getName(),
|
'name' => $condition->getName(),
|
||||||
'tooltip' => $condition->getToolTip(),
|
'tooltip' => $condition->getToolTip(),
|
||||||
|
'tooltip' => $condition->getToolTip(),
|
||||||
'validators' => $condition->getValidators()
|
'validators' => $condition->getValidators()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -247,6 +258,12 @@ class CouponController extends BaseAdminController
|
|||||||
}
|
}
|
||||||
$args['couponCode'] = $coupon->getCode();
|
$args['couponCode'] = $coupon->getCode();
|
||||||
$args['availableCoupons'] = $this->getAvailableCoupons();
|
$args['availableCoupons'] = $this->getAvailableCoupons();
|
||||||
|
$args['couponInputsHtml'] = $couponManager->drawBackOfficeInputs();
|
||||||
|
$args['urlAjaxAdminCouponDrawInputs'] = $this->getRoute(
|
||||||
|
'admin.coupon.draw.inputs',
|
||||||
|
array('couponServiceId' => 'couponServiceId'),
|
||||||
|
Router::ABSOLUTE_URL
|
||||||
|
);
|
||||||
$args['availableConditions'] = $this->getAvailableConditions();
|
$args['availableConditions'] = $this->getAvailableConditions();
|
||||||
$args['urlAjaxGetConditionInput'] = $this->getRoute(
|
$args['urlAjaxGetConditionInput'] = $this->getRoute(
|
||||||
'admin.coupon.condition.input',
|
'admin.coupon.condition.input',
|
||||||
@@ -461,6 +478,9 @@ class CouponController extends BaseAdminController
|
|||||||
$couponEvent = new CouponCreateOrUpdateEvent(
|
$couponEvent = new CouponCreateOrUpdateEvent(
|
||||||
$data['code'], $data['title'], $data['amount'], $data['type'], $data['shortDescription'], $data['description'], $data['isEnabled'], \DateTime::createFromFormat('Y-m-d', $data['expirationDate']), $data['isAvailableOnSpecialOffers'], $data['isCumulative'], $data['isRemovingPostage'], $data['maxUsage'], $data['locale']
|
$data['code'], $data['title'], $data['amount'], $data['type'], $data['shortDescription'], $data['description'], $data['isEnabled'], \DateTime::createFromFormat('Y-m-d', $data['expirationDate']), $data['isAvailableOnSpecialOffers'], $data['isCumulative'], $data['isRemovingPostage'], $data['maxUsage'], $data['locale']
|
||||||
);
|
);
|
||||||
|
$couponQuery = new CouponQuery();
|
||||||
|
$coupon = $couponQuery->findOneByCode($data['code']);
|
||||||
|
$couponEvent->setCouponModel($coupon);
|
||||||
|
|
||||||
// Dispatch Event to the Action
|
// Dispatch Event to the Action
|
||||||
$this->dispatch(
|
$this->dispatch(
|
||||||
@@ -524,7 +544,6 @@ class CouponController extends BaseAdminController
|
|||||||
$condition = array();
|
$condition = array();
|
||||||
$condition['serviceId'] = $availableCondition->getServiceId();
|
$condition['serviceId'] = $availableCondition->getServiceId();
|
||||||
$condition['name'] = $availableCondition->getName();
|
$condition['name'] = $availableCondition->getName();
|
||||||
// $condition['toolTip'] = $availableCondition->getToolTip();
|
|
||||||
$cleanedConditions[] = $condition;
|
$cleanedConditions[] = $condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +567,7 @@ class CouponController extends BaseAdminController
|
|||||||
$condition['serviceId'] = $availableCoupon->getServiceId();
|
$condition['serviceId'] = $availableCoupon->getServiceId();
|
||||||
$condition['name'] = $availableCoupon->getName();
|
$condition['name'] = $availableCoupon->getName();
|
||||||
$condition['toolTip'] = $availableCoupon->getToolTip();
|
$condition['toolTip'] = $availableCoupon->getToolTip();
|
||||||
|
$condition['inputName'] = $availableCoupon->getInputName();
|
||||||
$cleanedCoupons[] = $condition;
|
$cleanedCoupons[] = $condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,4 +592,26 @@ class CouponController extends BaseAdminController
|
|||||||
return $cleanedConditions;
|
return $cleanedConditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the input displayed in the BackOffice
|
||||||
|
* allowing Admin to set its Coupon effect
|
||||||
|
*
|
||||||
|
* @param string $couponServiceId Coupon service id
|
||||||
|
*
|
||||||
|
* @return ResponseRest
|
||||||
|
*/
|
||||||
|
public function getBackOfficeInputsAction($couponServiceId)
|
||||||
|
{
|
||||||
|
/** @var CouponInterface $coupon */
|
||||||
|
$coupon = $this->container->get($couponServiceId);
|
||||||
|
|
||||||
|
if (!$coupon instanceof CouponInterface) {
|
||||||
|
$this->pageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = new ResponseRest($coupon->drawBackOfficeInputs());
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,4 +167,11 @@ interface FacadeInterface
|
|||||||
*/
|
*/
|
||||||
public function getAvailableCurrencies();
|
public function getAvailableCurrencies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the event dispatcher,
|
||||||
|
*
|
||||||
|
* @return \Symfony\Component\EventDispatcher\EventDispatcher
|
||||||
|
*/
|
||||||
|
public function getDispatcher();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -307,4 +307,25 @@ abstract class CouponAbstract implements CouponInterface
|
|||||||
return $this->conditionEvaluator->isMatching($this->conditions);
|
return $this->conditionEvaluator->isMatching($this->conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the input displayed in the BackOffice
|
||||||
|
* allowing Admin to set its Coupon effect
|
||||||
|
*
|
||||||
|
* @return string HTML string
|
||||||
|
*/
|
||||||
|
public function drawBackOfficeInputs()
|
||||||
|
{
|
||||||
|
$label = $this->getInputName();
|
||||||
|
$value = $this->amount;
|
||||||
|
|
||||||
|
$html = '
|
||||||
|
<div class="form-group input-amount ">
|
||||||
|
<label for="amount" class="control-label">' . $label . '</label>
|
||||||
|
<input id="amount" type="text" class="form-control" name="thelia_coupon_creation[amount]" value="' . $value . '" placeholder="14.50">
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ interface CouponInterface
|
|||||||
*/
|
*/
|
||||||
public function getName();
|
public function getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get I18n amount input name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInputName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get I18n tooltip
|
* Get I18n tooltip
|
||||||
*
|
*
|
||||||
@@ -215,4 +222,12 @@ interface CouponInterface
|
|||||||
*/
|
*/
|
||||||
public function isMatching();
|
public function isMatching();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the input displayed in the BackOffice
|
||||||
|
* allowing Admin to set its Coupon effect
|
||||||
|
*
|
||||||
|
* @return string HTML string
|
||||||
|
*/
|
||||||
|
public function drawBackOfficeInputs();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,19 @@ class RemoveXAmount extends CouponAbstract
|
|||||||
{
|
{
|
||||||
return $this->facade
|
return $this->facade
|
||||||
->getTranslator()
|
->getTranslator()
|
||||||
->trans('Remove X amount to total cart', array(), 'constraint');
|
->trans('Remove X amount to total cart', array(), 'coupon');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get I18n amount input name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInputName()
|
||||||
|
{
|
||||||
|
return $this->facade
|
||||||
|
->getTranslator()
|
||||||
|
->trans('Amount removed from the cart', array(), 'coupon');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,7 +127,7 @@ class RemoveXAmount extends CouponAbstract
|
|||||||
->trans(
|
->trans(
|
||||||
'This coupon will remove the entered amount to the customer total checkout. If the discount is superior to the total checkout price the customer will only pay the postage. Unless if the coupon is set to remove postage too.',
|
'This coupon will remove the entered amount to the customer total checkout. If the discount is superior to the total checkout price the customer will only pay the postage. Unless if the coupon is set to remove postage too.',
|
||||||
array(),
|
array(),
|
||||||
'constraint'
|
'coupon'
|
||||||
);
|
);
|
||||||
|
|
||||||
return $toolTip;
|
return $toolTip;
|
||||||
|
|||||||
@@ -122,7 +122,19 @@ class RemoveXPercent extends CouponAbstract
|
|||||||
{
|
{
|
||||||
return $this->facade
|
return $this->facade
|
||||||
->getTranslator()
|
->getTranslator()
|
||||||
->trans('Remove X percent to total cart', array(), 'constraint');
|
->trans('Remove X percent to total cart', array(), 'coupon');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get I18n amount input name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInputName()
|
||||||
|
{
|
||||||
|
return $this->facade
|
||||||
|
->getTranslator()
|
||||||
|
->trans('Percentage removed from the cart', array(), 'coupon');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +149,7 @@ class RemoveXPercent extends CouponAbstract
|
|||||||
->trans(
|
->trans(
|
||||||
'This coupon will remove the entered percentage to the customer total checkout. If the discount is superior to the total checkout price the customer will only pay the postage. Unless if the coupon is set to remove postage too.',
|
'This coupon will remove the entered percentage to the customer total checkout. If the discount is superior to the total checkout price the customer will only pay the postage. Unless if the coupon is set to remove postage too.',
|
||||||
array(),
|
array(),
|
||||||
'constraint'
|
'coupon'
|
||||||
);
|
);
|
||||||
|
|
||||||
return $toolTip;
|
return $toolTip;
|
||||||
|
|||||||
@@ -127,13 +127,42 @@ $(function($){
|
|||||||
};
|
};
|
||||||
$.couponManager.onClickUpdateCondition();
|
$.couponManager.onClickUpdateCondition();
|
||||||
|
|
||||||
|
$.couponManager.displayEfffect = function(optionSelected) {
|
||||||
|
var mainDiv = $('#coupon-type');
|
||||||
|
mainDiv.find('.typeToolTip').html(optionSelected.attr('data-description'));
|
||||||
|
|
||||||
|
var inputsDiv = mainDiv.find('.inputs');
|
||||||
|
inputsDiv.html('<div class="loading" ></div>');
|
||||||
|
var url = $.couponManager.urlAjaxAdminCouponDrawInputs;
|
||||||
|
url = url.replace('couponServiceId', optionSelected.val());
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: url,
|
||||||
|
data: '',
|
||||||
|
statusCode: {
|
||||||
|
404: function() {
|
||||||
|
inputsDiv.html($.couponManager.intlPleaseRetry);
|
||||||
|
},
|
||||||
|
500: function() {
|
||||||
|
inputsDiv.html($.couponManager.intlPleaseRetry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).done(function(data) {
|
||||||
|
inputsDiv.html(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Reload effect inputs when changing effect
|
// Reload effect inputs when changing effect
|
||||||
$.couponManager.onEffectChange = function() {
|
$.couponManager.onEffectChange = function() {
|
||||||
var optionSelected = $("option:selected", this);
|
var mainDiv = $('#coupon-type');
|
||||||
$('#effectToolTip').html(optionSelected.attr("data-description"));
|
var optionSelected = mainDiv.find('#type option:selected');
|
||||||
$('#effect').on('change', function () {
|
mainDiv.find('.typeToolTip').html(optionSelected.attr('data-description'));
|
||||||
var optionSelected = $("option:selected", this);
|
|
||||||
$('#effectToolTip').html(optionSelected.attr("data-description"));
|
mainDiv.find('#type').on('change', function () {
|
||||||
|
var optionSelected = $('option:selected', this);
|
||||||
|
$.couponManager.displayEfffect(optionSelected);
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
$.couponManager.onEffectChange();
|
$.couponManager.onEffectChange();
|
||||||
|
|||||||
@@ -59,6 +59,11 @@
|
|||||||
filemanager_title:"{intl l='Files manager'}" ,
|
filemanager_title:"{intl l='Files manager'}" ,
|
||||||
external_plugins: { "filemanager" : "{url file='/tinymce/plugins/filemanager/plugin.min.js'}"}
|
external_plugins: { "filemanager" : "{url file='/tinymce/plugins/filemanager/plugin.min.js'}"}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Url alowing to get coupon inputs
|
||||||
|
$.couponManager.urlAjaxAdminCouponDrawInputs = "{$urlAjaxAdminCouponDrawInputs}";
|
||||||
|
$.couponManager.intlPleaseRetry = '{intl l='Please retry'}';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{/block}
|
{/block}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
<script src="{$asset_url}"></script>
|
<script src="{$asset_url}"></script>
|
||||||
{/javascripts}
|
{/javascripts}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
{javascripts file='assets/js/coupon.js'}
|
{javascripts file='assets/js/coupon.js'}
|
||||||
<script src="{$asset_url}"></script>
|
<script src="{$asset_url}"></script>
|
||||||
{/javascripts}
|
{/javascripts}
|
||||||
@@ -62,6 +64,10 @@
|
|||||||
$(function($){
|
$(function($){
|
||||||
// miniBrowser(0, '/test_to_remove/datas_coupon_edit.json');
|
// miniBrowser(0, '/test_to_remove/datas_coupon_edit.json');
|
||||||
|
|
||||||
|
// Url alowing to get coupon inputs
|
||||||
|
$.couponManager.urlAjaxAdminCouponDrawInputs = "{$urlAjaxAdminCouponDrawInputs}";
|
||||||
|
$.couponManager.intlPleaseRetry = '{intl l='Please retry'}';
|
||||||
|
|
||||||
// Init Conditions
|
// Init Conditions
|
||||||
$.couponManager.initConditions = function() {
|
$.couponManager.initConditions = function() {
|
||||||
var conditions = [];
|
var conditions = [];
|
||||||
|
|||||||
@@ -99,32 +99,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="well clearfix">
|
<div id="coupon-type" class="well clearfix">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{form_field form=$form field='type'}
|
{form_field form=$form field='type'}
|
||||||
<div class="form-group {if $error}has-error{/if}">
|
<div class="form-group {if $error}has-error{/if}">
|
||||||
<label for="type" class="control-label">{intl l='Type :'}</label>
|
<label for="type" class="control-label">{intl l='Type :'}</label>
|
||||||
<select name="{$name}" id="type" class="col-md-12 form-control">
|
<select name="{$name}" id="type" class="col-md-12 form-control">
|
||||||
<option value="-1" data-description="">{intl l='Please select a coupon type'}</option>
|
<option value="-1" data-description="" data-inputName="">{intl l='Please select a coupon type'}</option>
|
||||||
{foreach from=$availableCoupons item=availableCoupon}
|
{foreach from=$availableCoupons item=availableCoupon}
|
||||||
<option value="{$availableCoupon.serviceId}" {if $value == $availableCoupon.serviceId}selected{/if}>
|
<option value="{$availableCoupon.serviceId}" data-description="{$availableCoupon.toolTip}" data-inputName="{$availableCoupon.inputName}" {if $value == $availableCoupon.serviceId}selected{/if}>
|
||||||
{$availableCoupon.name}
|
{$availableCoupon.name}
|
||||||
</option>
|
</option>
|
||||||
{/foreach}
|
{/foreach}
|
||||||
</select>
|
</select>
|
||||||
{if $error}{$message}{/if}
|
{if $error}{$message}{/if}
|
||||||
<span id="typeToolTip" class="help-block">{$availableCoupons.0.toolTip}</span>
|
<span class="help-block typeToolTip">{$availableCoupons.0.toolTip}</span>
|
||||||
</div>
|
</div>
|
||||||
{/form_field}
|
{/form_field}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6 inputs">
|
||||||
{form_field form=$form field='amount'}
|
{form_field form=$form field='amount'}
|
||||||
<div class="form-group {if $error}has-error{/if}">
|
{$couponInputsHtml nofilter}
|
||||||
<label for="amount" class="control-label">{intl l='Amount :'}</label>
|
|
||||||
<input id="amount" type="text" class="form-control" name="{$name}" value="{$value}" placeholder="{intl l='14.50'}">
|
|
||||||
{if $error}{$message}{/if}
|
|
||||||
</div>
|
|
||||||
{/form_field}
|
{/form_field}
|
||||||
{*<div class="form-group {if $error}has-error{/if}">*}
|
{*<div class="form-group {if $error}has-error{/if}">*}
|
||||||
{*<label for="category">Category :</label>*}
|
{*<label for="category">Category :</label>*}
|
||||||
@@ -208,7 +204,7 @@
|
|||||||
<select name="categoryCondition" id="category-condition" class="form-control">
|
<select name="categoryCondition" id="category-condition" class="form-control">
|
||||||
<option value="-1" >{intl l='Please select a condition category'}</option>
|
<option value="-1" >{intl l='Please select a condition category'}</option>
|
||||||
{foreach from=$availableConditions item=availableCondition}
|
{foreach from=$availableConditions item=availableCondition}
|
||||||
<option value="{$availableCondition.serviceId}" data-description="{$availableCondition.toolTip}">{$availableCondition.name}</option>
|
<option value="{$availableCondition.serviceId}" >{$availableCondition.name}</option>
|
||||||
{/foreach}
|
{/foreach}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user