Refactor Stats notifications to spearate classes
[MAILPOET-1571]
This commit is contained in:
@@ -23,6 +23,7 @@ class Daemon {
|
|||||||
$this->executeMigrationWorker();
|
$this->executeMigrationWorker();
|
||||||
$this->executeScheduleWorker();
|
$this->executeScheduleWorker();
|
||||||
$this->executeQueueWorker();
|
$this->executeQueueWorker();
|
||||||
|
$this->executeStatsNotificationsWorker();
|
||||||
$this->executeSendingServiceKeyCheckWorker();
|
$this->executeSendingServiceKeyCheckWorker();
|
||||||
$this->executePremiumKeyCheckWorker();
|
$this->executePremiumKeyCheckWorker();
|
||||||
$this->executeBounceWorker();
|
$this->executeBounceWorker();
|
||||||
@@ -43,6 +44,11 @@ class Daemon {
|
|||||||
return $queue->process();
|
return $queue->process();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function executeStatsNotificationsWorker() {
|
||||||
|
$worker = $this->workers_factory->createStatsNotificationsWorker($this->timer);
|
||||||
|
return $worker->process();
|
||||||
|
}
|
||||||
|
|
||||||
function executeSendingServiceKeyCheckWorker() {
|
function executeSendingServiceKeyCheckWorker() {
|
||||||
$worker = $this->workers_factory->createSendingServiceKeyCheckWorker($this->timer);
|
$worker = $this->workers_factory->createSendingServiceKeyCheckWorker($this->timer);
|
||||||
return $worker->process();
|
return $worker->process();
|
||||||
|
@@ -5,7 +5,7 @@ use MailPoet\Cron\CronHelper;
|
|||||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Links;
|
use MailPoet\Cron\Workers\SendingQueue\Tasks\Links;
|
||||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
|
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
|
||||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
|
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
|
||||||
use MailPoet\Cron\Workers\StatsNotifications;
|
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationsScheduler;
|
||||||
use MailPoet\Logging\Logger;
|
use MailPoet\Logging\Logger;
|
||||||
use MailPoet\Mailer\MailerError;
|
use MailPoet\Mailer\MailerError;
|
||||||
use MailPoet\Mailer\MailerLog;
|
use MailPoet\Mailer\MailerLog;
|
||||||
@@ -26,15 +26,15 @@ class SendingQueue {
|
|||||||
const BATCH_SIZE = 20;
|
const BATCH_SIZE = 20;
|
||||||
const TASK_BATCH_SIZE = 5;
|
const TASK_BATCH_SIZE = 5;
|
||||||
|
|
||||||
/** @var StatsNotifications */
|
/** @var StatsNotificationsScheduler */
|
||||||
public $stats_notifications_worker;
|
public $stats_notifications_scheduler;
|
||||||
|
|
||||||
/** @var SendingErrorHandler */
|
/** @var SendingErrorHandler */
|
||||||
private $error_handler;
|
private $error_handler;
|
||||||
|
|
||||||
function __construct(SendingErrorHandler $error_handler, StatsNotifications $stats_notifications_worker, $timer = false, $mailer_task = false, $newsletter_task = false) {
|
function __construct(SendingErrorHandler $error_handler, StatsNotificationsScheduler $stats_notifications_scheduler, $timer = false, $mailer_task = false, $newsletter_task = false) {
|
||||||
$this->error_handler = $error_handler;
|
$this->error_handler = $error_handler;
|
||||||
$this->stats_notifications_worker = $stats_notifications_worker;
|
$this->stats_notifications_scheduler = $stats_notifications_scheduler;
|
||||||
$this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask();
|
$this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask();
|
||||||
$this->newsletter_task = ($newsletter_task) ? $newsletter_task : new NewsletterTask();
|
$this->newsletter_task = ($newsletter_task) ? $newsletter_task : new NewsletterTask();
|
||||||
$this->timer = ($timer) ? $timer : microtime(true);
|
$this->timer = ($timer) ? $timer : microtime(true);
|
||||||
@@ -103,7 +103,7 @@ class SendingQueue {
|
|||||||
);
|
);
|
||||||
if($queue->status === ScheduledTaskModel::STATUS_COMPLETED) {
|
if($queue->status === ScheduledTaskModel::STATUS_COMPLETED) {
|
||||||
$this->newsletter_task->markNewsletterAsSent($newsletter, $queue);
|
$this->newsletter_task->markNewsletterAsSent($newsletter, $queue);
|
||||||
$this->stats_notifications_worker->schedule($newsletter);
|
$this->stats_notifications_scheduler->schedule($newsletter);
|
||||||
}
|
}
|
||||||
$this->enforceSendingAndExecutionLimits();
|
$this->enforceSendingAndExecutionLimits();
|
||||||
}
|
}
|
||||||
|
@@ -1,25 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace MailPoet\Cron\Workers;
|
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use MailPoet\Models\Newsletter;
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Models\SendingQueue;
|
|
||||||
use MailPoet\Models\ScheduledTask;
|
use MailPoet\Models\ScheduledTask;
|
||||||
use MailPoet\Models\Setting;
|
use MailPoet\Models\Setting;
|
||||||
use MailPoet\Models\StatsNotification;
|
use MailPoet\Models\StatsNotification;
|
||||||
|
|
||||||
/**
|
class Scheduler {
|
||||||
* TODO:
|
|
||||||
* - add processing of this task to Daemon
|
|
||||||
* - check JIRA what to do next and how to send the newsletter
|
|
||||||
* - see \MailPoet\Subscribers\NewSubscriberNotificationMailer how to send an email, now with DI everything should be easy
|
|
||||||
*/
|
|
||||||
|
|
||||||
class StatsNotifications {
|
|
||||||
|
|
||||||
const TASK_TYPE = 'stats_notification';
|
|
||||||
const SETTINGS_KEY = 'stats_notifications';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many hours after the newsletter will be the stats notification sent
|
* How many hours after the newsletter will be the stats notification sent
|
||||||
@@ -33,7 +22,7 @@ class StatsNotifications {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$task = ScheduledTask::create();
|
$task = ScheduledTask::create();
|
||||||
$task->type = self::TASK_TYPE;
|
$task->type = Worker::TASK_TYPE;
|
||||||
$task->status = ScheduledTask::STATUS_SCHEDULED;
|
$task->status = ScheduledTask::STATUS_SCHEDULED;
|
||||||
$task->scheduled_at = $this->getNextRunDate();
|
$task->scheduled_at = $this->getNextRunDate();
|
||||||
$task->save();
|
$task->save();
|
||||||
@@ -44,10 +33,6 @@ class StatsNotifications {
|
|||||||
$stats_notifications->save();
|
$stats_notifications->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function process() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function shouldSchedule(Newsletter $newsletter) {
|
private function shouldSchedule(Newsletter $newsletter) {
|
||||||
if($this->isDisabled()) {
|
if($this->isDisabled()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -62,7 +47,7 @@ class StatsNotifications {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function isDisabled() {
|
private function isDisabled() {
|
||||||
$settings = Setting::getValue(self::SETTINGS_KEY);
|
$settings = Setting::getValue(Worker::SETTINGS_KEY);
|
||||||
if(!is_array($settings)) {
|
if(!is_array($settings)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -81,7 +66,7 @@ class StatsNotifications {
|
|||||||
private function isTaskScheduled($newsletter_id) {
|
private function isTaskScheduled($newsletter_id) {
|
||||||
$existing = ScheduledTask::table_alias('tasks')
|
$existing = ScheduledTask::table_alias('tasks')
|
||||||
->join(StatsNotification::$_table, 'tasks.id = notification.task_id', 'notification')
|
->join(StatsNotification::$_table, 'tasks.id = notification.task_id', 'notification')
|
||||||
->where('tasks.type', self::TASK_TYPE)
|
->where('tasks.type', Worker::TASK_TYPE)
|
||||||
->where('notification.newsletter_id', $newsletter_id)
|
->where('notification.newsletter_id', $newsletter_id)
|
||||||
->findMany();
|
->findMany();
|
||||||
return (bool)$existing;
|
return (bool)$existing;
|
79
lib/Cron/Workers/StatsNotifications/Worker.php
Normal file
79
lib/Cron/Workers/StatsNotifications/Worker.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||||
|
|
||||||
|
use MailPoet\Config\Renderer;
|
||||||
|
use MailPoet\Cron\CronHelper;
|
||||||
|
use MailPoet\Mailer\Mailer;
|
||||||
|
use MailPoet\Models\Setting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO:
|
||||||
|
* - add processing of this task to Daemon
|
||||||
|
* - check JIRA what to do next and how to send the newsletter
|
||||||
|
* - see \MailPoet\Subscribers\NewSubscriberNotificationMailer how to send an email, now with DI everything should be easy
|
||||||
|
*/
|
||||||
|
class Worker {
|
||||||
|
|
||||||
|
const TASK_TYPE = 'stats_notification';
|
||||||
|
const SETTINGS_KEY = 'stats_notifications';
|
||||||
|
|
||||||
|
const SENDER_EMAIL_PREFIX = 'wordpress@';
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $timer;
|
||||||
|
|
||||||
|
/** @var Renderer */
|
||||||
|
private $renderer;
|
||||||
|
|
||||||
|
/** @var \MailPoet\Mailer\Mailer */
|
||||||
|
private $mailer;
|
||||||
|
|
||||||
|
function __construct(Mailer $mailer, Renderer $renderer, $timer = false) {
|
||||||
|
$this->timer = $timer ?: microtime(true);
|
||||||
|
$this->renderer = $renderer;
|
||||||
|
$this->mailer = $mailer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws \Exception */
|
||||||
|
function process() {
|
||||||
|
$settings = Setting::getValue(self::SETTINGS_KEY);
|
||||||
|
try {
|
||||||
|
$this->mailer->getSenderNameAndAddress($this->constructSenderEmail());
|
||||||
|
$this->mailer->send($this->constructNewsletter(), $settings['address']);
|
||||||
|
} catch(\Exception $e) {
|
||||||
|
if(WP_DEBUG) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CronHelper::enforceExecutionLimit($this->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function constructSenderEmail() {
|
||||||
|
$url_parts = parse_url(home_url());
|
||||||
|
$site_name = strtolower($url_parts['host']);
|
||||||
|
if(strpos($site_name, 'www.') === 0) {
|
||||||
|
$site_name = substr($site_name, 4);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'address' => self::SENDER_EMAIL_PREFIX . $site_name,
|
||||||
|
'name' => self::SENDER_EMAIL_PREFIX . $site_name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function constructNewsletter() {
|
||||||
|
$context = [
|
||||||
|
'link_settings' => get_site_url(null, '/wp-admin/admin.php?page=mailpoet-settings'),
|
||||||
|
'link_premium' => get_site_url(null, '/wp-admin/admin.php?page=mailpoet-premium'),
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
'subject' => sprintf(__('New subscriber to ', 'mailpoet')),
|
||||||
|
'body' => [
|
||||||
|
'html' => $this->renderer->render('emails/newSubscriberNotification.html', $context),
|
||||||
|
'text' => $this->renderer->render('emails/newSubscriberNotification.txt', $context),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace MailPoet\Cron\Workers;
|
namespace MailPoet\Cron\Workers;
|
||||||
|
|
||||||
|
use MailPoet\Config\Renderer;
|
||||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||||
use MailPoet\Cron\Workers\SendingQueue\Migration as MigrationWorker;
|
use MailPoet\Cron\Workers\SendingQueue\Migration as MigrationWorker;
|
||||||
|
use MailPoet\Cron\Workers\StatsNotifications\Worker as StatsNotificationsWorker;
|
||||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
||||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||||
@@ -26,11 +28,17 @@ class WorkersFactory {
|
|||||||
|
|
||||||
/** @return SendingQueueWorker */
|
/** @return SendingQueueWorker */
|
||||||
function createQueueWorker($timer) {
|
function createQueueWorker($timer) {
|
||||||
return new SendingQueueWorker($this->sending_error_handler, $this->createStatsNotificationsWorker(), $timer);
|
return new SendingQueueWorker($this->sending_error_handler, $this->createStatsNotificationsWorker($timer), $timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStatsNotificationsWorker() {
|
function createStatsNotificationsWorker($timer) {
|
||||||
return new StatsNotifications();
|
//TODO get those from DI
|
||||||
|
$caching = !WP_DEBUG;
|
||||||
|
$debugging = WP_DEBUG;
|
||||||
|
$this->renderer = new Renderer($caching, $debugging);
|
||||||
|
$this->mailer = new \MailPoet\Mailer\Mailer();
|
||||||
|
|
||||||
|
return new StatsNotificationsWorker($this->mailer, $this->renderer, $timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return SendingServiceKeyCheckWorker */
|
/** @return SendingServiceKeyCheckWorker */
|
||||||
|
@@ -12,7 +12,7 @@ use MailPoet\Cron\Workers\SendingQueue\SendingErrorHandler;
|
|||||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
|
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
|
||||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
|
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
|
||||||
use MailPoet\Cron\Workers\StatsNotifications;
|
use MailPoet\Cron\Workers\StatsNotifications\Scheduler as StatsNotificationsScheduler;
|
||||||
use MailPoet\Mailer\MailerLog;
|
use MailPoet\Mailer\MailerLog;
|
||||||
use MailPoet\Models\Newsletter;
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Models\NewsletterLink;
|
use MailPoet\Models\NewsletterLink;
|
||||||
@@ -37,7 +37,7 @@ class SendingQueueTest extends \MailPoetTest {
|
|||||||
/** @var SendingErrorHandler */
|
/** @var SendingErrorHandler */
|
||||||
private $sending_error_handler;
|
private $sending_error_handler;
|
||||||
|
|
||||||
/** @var StatsNotifications */
|
/** @var Scheduler */
|
||||||
private $stats_notifications_worker;
|
private $stats_notifications_worker;
|
||||||
|
|
||||||
function _before() {
|
function _before() {
|
||||||
@@ -80,7 +80,7 @@ class SendingQueueTest extends \MailPoetTest {
|
|||||||
$this->newsletter_link->hash = 'abcde';
|
$this->newsletter_link->hash = 'abcde';
|
||||||
$this->newsletter_link->save();
|
$this->newsletter_link->save();
|
||||||
$this->sending_error_handler = new SendingErrorHandler();
|
$this->sending_error_handler = new SendingErrorHandler();
|
||||||
$this->stats_notifications_worker = new StatsNotifications();
|
$this->stats_notifications_worker = new StatsNotificationsScheduler();
|
||||||
$this->sending_queue_worker = new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker);
|
$this->sending_queue_worker = new SendingQueueWorker($this->sending_error_handler, $this->stats_notifications_worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,21 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace MailPoet\Test\Cron\Workers;
|
namespace MailPoet\Cron\Workers\StatsNotifications;
|
||||||
|
|
||||||
use MailPoet\Cron\Workers\StatsNotifications;
|
|
||||||
use MailPoet\Models\Newsletter;
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Models\ScheduledTask;
|
use MailPoet\Models\ScheduledTask;
|
||||||
use MailPoet\Models\Setting;
|
use MailPoet\Models\Setting;
|
||||||
use MailPoet\Models\StatsNotification;
|
use MailPoet\Models\StatsNotification;
|
||||||
|
|
||||||
class StatsNotificationsTest extends \MailPoetTest {
|
class SchedulerTest extends \MailPoetTest {
|
||||||
|
|
||||||
/** @var StatsNotifications */
|
/** @var Scheduler */
|
||||||
private $stats_notifications;
|
private $stats_notifications;
|
||||||
|
|
||||||
function _before() {
|
function _before() {
|
||||||
$this->stats_notifications = new StatsNotifications();
|
$this->stats_notifications = new Scheduler();
|
||||||
Setting::setValue(StatsNotifications::SETTINGS_KEY, [
|
Setting::setValue(Worker::SETTINGS_KEY, [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'address' => 'email@example.com'
|
'address' => 'email@example.com'
|
||||||
]);
|
]);
|
||||||
@@ -33,7 +32,7 @@ class StatsNotificationsTest extends \MailPoetTest {
|
|||||||
|
|
||||||
function testShouldNotScheduleIfDisabled() {
|
function testShouldNotScheduleIfDisabled() {
|
||||||
$newsletter_id = 6;
|
$newsletter_id = 6;
|
||||||
Setting::setValue(StatsNotifications::SETTINGS_KEY, [
|
Setting::setValue(Worker::SETTINGS_KEY, [
|
||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
'address' => 'email@example.com'
|
'address' => 'email@example.com'
|
||||||
]);
|
]);
|
||||||
@@ -45,7 +44,7 @@ class StatsNotificationsTest extends \MailPoetTest {
|
|||||||
|
|
||||||
function testShouldNotScheduleIfSettingsMissing() {
|
function testShouldNotScheduleIfSettingsMissing() {
|
||||||
$newsletter_id = 7;
|
$newsletter_id = 7;
|
||||||
Setting::setValue(StatsNotifications::SETTINGS_KEY, []);
|
Setting::setValue(Worker::SETTINGS_KEY, []);
|
||||||
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
|
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
|
||||||
$this->stats_notifications->schedule($newsletter);
|
$this->stats_notifications->schedule($newsletter);
|
||||||
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
|
$notification = StatsNotification::where('newsletter_id', $newsletter_id)->findOne();
|
||||||
@@ -54,7 +53,7 @@ class StatsNotificationsTest extends \MailPoetTest {
|
|||||||
|
|
||||||
function testShouldNotScheduleIfEmailIsMissing() {
|
function testShouldNotScheduleIfEmailIsMissing() {
|
||||||
$newsletter_id = 8;
|
$newsletter_id = 8;
|
||||||
Setting::setValue(StatsNotifications::SETTINGS_KEY, [
|
Setting::setValue(Worker::SETTINGS_KEY, [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
]);
|
]);
|
||||||
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
|
$newsletter = Newsletter::createOrUpdate(['id' => $newsletter_id, 'type' => Newsletter::TYPE_STANDARD]);
|
||||||
@@ -65,7 +64,7 @@ class StatsNotificationsTest extends \MailPoetTest {
|
|||||||
|
|
||||||
function testShouldNotScheduleIfEmailIsEmpty() {
|
function testShouldNotScheduleIfEmailIsEmpty() {
|
||||||
$newsletter_id = 9;
|
$newsletter_id = 9;
|
||||||
Setting::setValue(StatsNotifications::SETTINGS_KEY, [
|
Setting::setValue(Worker::SETTINGS_KEY, [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'address' => ' '
|
'address' => ' '
|
||||||
]);
|
]);
|
||||||
@@ -78,7 +77,7 @@ class StatsNotificationsTest extends \MailPoetTest {
|
|||||||
function testShouldNotScheduleIfAlreadyScheduled() {
|
function testShouldNotScheduleIfAlreadyScheduled() {
|
||||||
$newsletter_id = 10;
|
$newsletter_id = 10;
|
||||||
$existing_task = ScheduledTask::createOrUpdate([
|
$existing_task = ScheduledTask::createOrUpdate([
|
||||||
'type' => StatsNotifications::TASK_TYPE,
|
'type' => Worker::TASK_TYPE,
|
||||||
'status' => ScheduledTask::STATUS_SCHEDULED,
|
'status' => ScheduledTask::STATUS_SCHEDULED,
|
||||||
'scheduled_at' => '2017-01-02 12:13:14',
|
'scheduled_at' => '2017-01-02 12:13:14',
|
||||||
]);
|
]);
|
Reference in New Issue
Block a user