Add bulk action for tagging subscribers

[MAILPOET-5454]
This commit is contained in:
Jan Lysý
2023-07-07 12:30:06 +02:00
committed by Aschepikov
parent a12886ded9
commit aa12fd57d7
5 changed files with 139 additions and 0 deletions

View File

@@ -275,6 +275,36 @@ const bulkActions = [
);
},
},
{
name: 'addTag',
label: MailPoet.I18n.t('addTag'),
onSelect: function onSelect(submitModal, closeModal) {
const field = {
id: 'add_tag',
name: 'add_tag',
endpoint: 'tags',
};
return createModal(
submitModal,
closeModal,
field,
MailPoet.I18n.t('addTag'),
);
},
getData: function getData() {
return {
tag_id: Number(jQuery('#add_tag').val()),
};
},
onSuccess: function onSuccess(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('tagAddedToMultipleSubscribers')
.replace('%1$s', response.meta.tag)
.replace('%2$d', Number(response.meta.count).toLocaleString()),
);
},
},
];
const itemActions = [

View File

@@ -13,6 +13,7 @@ use MailPoet\ConflictException;
use MailPoet\Doctrine\Validator\ValidationException;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Entities\TagEntity;
use MailPoet\Exception;
use MailPoet\Listing;
use MailPoet\Segments\SegmentsRepository;
@@ -22,6 +23,7 @@ use MailPoet\Subscribers\SubscriberListingRepository;
use MailPoet\Subscribers\SubscriberSaveController;
use MailPoet\Subscribers\SubscribersRepository;
use MailPoet\Subscribers\SubscriberSubscribeController;
use MailPoet\Tags\TagRepository;
use MailPoet\UnexpectedValueException;
use MailPoet\Util\Helpers;
@@ -51,6 +53,9 @@ class Subscribers extends APIEndpoint {
/** @var SegmentsRepository */
private $segmentsRepository;
/** @var TagRepository */
private $tagRepository;
/** @var SubscriberSaveController */
private $saveController;
@@ -67,6 +72,7 @@ class Subscribers extends APIEndpoint {
SubscribersResponseBuilder $subscribersResponseBuilder,
SubscriberListingRepository $subscriberListingRepository,
SegmentsRepository $segmentsRepository,
TagRepository $tagRepository,
SubscriberSaveController $saveController,
SubscriberSubscribeController $subscribeController,
SettingsController $settings
@@ -77,6 +83,7 @@ class Subscribers extends APIEndpoint {
$this->subscribersResponseBuilder = $subscribersResponseBuilder;
$this->subscriberListingRepository = $subscriberListingRepository;
$this->segmentsRepository = $segmentsRepository;
$this->tagRepository = $tagRepository;
$this->saveController = $saveController;
$this->subscribeController = $subscribeController;
$this->settings = $settings;
@@ -258,6 +265,16 @@ class Subscribers extends APIEndpoint {
}
}
$tag = null;
if (isset($data['tag_id'])) {
$tag = $this->getTag($data);
if (!$tag) {
return $this->errorResponse([
APIError::NOT_FOUND => __('This tag does not exist.', 'mailpoet'),
]);
}
}
if ($data['action'] === 'trash') {
$count = $this->subscribersRepository->bulkTrash($ids);
} elseif ($data['action'] === 'restore') {
@@ -274,6 +291,8 @@ class Subscribers extends APIEndpoint {
$count = $this->subscribersRepository->bulkMoveToSegment($segment, $ids);
} elseif ($data['action'] === 'unsubscribe') {
$count = $this->subscribersRepository->bulkUnsubscribe($ids);
} elseif ($data['action'] === 'addTag' && $tag instanceof TagEntity) {
$count = $this->subscribersRepository->bulkAddTag($tag, $ids);
} else {
throw UnexpectedValueException::create()
->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
@@ -285,6 +304,9 @@ class Subscribers extends APIEndpoint {
if ($segment) {
$meta['segment'] = $segment->getName();
}
if ($tag) {
$meta['tag'] = $tag->getName();
}
return $this->successResponse(null, $meta);
}
@@ -304,6 +326,12 @@ class Subscribers extends APIEndpoint {
: null;
}
private function getTag(array $data): ?TagEntity {
return isset($data['tag_id'])
? $this->tagRepository->findOneById((int)$data['tag_id'])
: null;
}
private function getErrorMessage(ValidationException $exception): string {
$exceptionMessage = $exception->getMessage();
if (strpos($exceptionMessage, 'This value should not be blank.') !== false) {

View File

@@ -11,6 +11,7 @@ use MailPoet\Entities\SubscriberCustomFieldEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Entities\SubscriberSegmentEntity;
use MailPoet\Entities\SubscriberTagEntity;
use MailPoet\Entities\TagEntity;
use MailPoet\Util\License\Features\Subscribers;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Carbon\Carbon;
@@ -510,6 +511,15 @@ class SubscribersRepository extends Repository {
->getArrayResult();
}
/**
* @return int - number of processed ids
*/
public function bulkAddTag(TagEntity $tag, array $ids): int {
$count = $this->addTagToSubscribers($tag, $ids);
$this->changesNotifier->subscribersUpdated($ids);
return $count;
}
/**
* @return int - number of processed ids
*/
@@ -562,6 +572,37 @@ class SubscribersRepository extends Repository {
return count($subscribers);
}
/**
* @return int - number of processed ids
*/
private function addTagToSubscribers(TagEntity $tag, array $ids): int {
if (empty($ids)) {
return 0;
}
/** @var SubscriberEntity[] $subscribers */
$subscribers = $this->entityManager
->createQueryBuilder()
->select('s')
->from(SubscriberEntity::class, 's')
->leftJoin('s.subscriberTags', 'st', Join::WITH, 'st.tag = :tag')
->where('s.id IN (:ids)')
->andWhere('st.tag IS NULL')
->setParameter('ids', $ids)
->setParameter('tag', $tag)
->getQuery()->execute();
$this->entityManager->wrapInTransaction(function (EntityManager $entityManager) use ($subscribers, $tag) {
foreach ($subscribers as $subscriber) {
$subscriberTag = new SubscriberTagEntity($tag, $subscriber);
$entityManager->persist($subscriberTag);
}
$entityManager->flush();
});
return count($subscribers);
}
private function getCurrentDateTime(): CarbonImmutable {
return CarbonImmutable::createFromTimestamp((int)$this->wp->currentTime('timestamp'));
}

View File

@@ -15,6 +15,7 @@ use MailPoet\Entities\FormEntity;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Entities\SubscriberSegmentEntity;
use MailPoet\Entities\SubscriberTagEntity;
use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Listing\Handler;
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
@@ -26,13 +27,16 @@ use MailPoet\Subscribers\SubscriberListingRepository;
use MailPoet\Subscribers\SubscriberSaveController;
use MailPoet\Subscribers\SubscribersRepository;
use MailPoet\Subscribers\SubscriberSubscribeController;
use MailPoet\Subscribers\SubscriberTagRepository;
use MailPoet\Subscription\Captcha\CaptchaConstants;
use MailPoet\Subscription\Captcha\CaptchaSession;
use MailPoet\Tags\TagRepository;
use MailPoet\Test\DataFactories\CustomField as CustomFieldFactory;
use MailPoet\Test\DataFactories\DynamicSegment;
use MailPoet\Test\DataFactories\Newsletter as NewsletterFactory;
use MailPoet\Test\DataFactories\Segment as SegmentFactory;
use MailPoet\Test\DataFactories\Subscriber as SubscriberFactory;
use MailPoet\Test\DataFactories\Tag as TagFactory;
use MailPoet\UnexpectedValueException;
use MailPoet\WP\Functions;
use MailPoetVendor\Carbon\Carbon;
@@ -89,6 +93,7 @@ class SubscribersTest extends \MailPoetTest {
$this->responseBuilder,
$container->get(SubscriberListingRepository::class),
$container->get(SegmentsRepository::class),
$container->get(TagRepository::class),
$container->get(SubscriberSaveController::class),
$container->get(SubscriberSubscribeController::class),
$container->get(SettingsController::class)
@@ -1014,6 +1019,39 @@ class SubscribersTest extends \MailPoetTest {
expect($segments->get(1)->getId())->equals($wcSegment->getId());
}
public function testItCanBulkAddTagToSubscribers(): void {
$selectionIds = [
$this->subscriber1->getId(),
$this->subscriber2->getId(),
];
$tag = (new TagFactory())->withName('Tag 1')->create();
$bulkActionData = [
'action' => 'addTag',
'listing' => [
'selection' => $selectionIds,
],
'tag_id' => $tag->getId(),
];
$response = $this->endpoint->bulkAction($bulkActionData);
$subscriberTagRepository = $this->diContainer->get(SubscriberTagRepository::class);
$subscriberTag1 = $subscriberTagRepository->findOneBy(['subscriber' => $this->subscriber1, 'tag' => $tag]);
$subscriberTag2 = $subscriberTagRepository->findOneBy(['subscriber' => $this->subscriber2, 'tag' => $tag]);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->meta['count'])->equals(2);
expect($subscriberTag1)->isInstanceOf(SubscriberTagEntity::class);
expect($subscriberTag2)->isInstanceOf(SubscriberTagEntity::class);
// Testing that adding the same tag again does not return an error
$response = $this->endpoint->bulkAction($bulkActionData);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->meta['count'])->equals(0);
}
private function _createWelcomeNewsletter(): void {
$newsletterFactory = new NewsletterFactory();
$newsletterFactory

View File

@@ -156,6 +156,8 @@
'recalculateNow': __('Recalculate now'),
'tags': __('Tags'),
'addNewTag': __('Add new tag'),
'tagAddedToMultipleSubscribers': __('Tag <strong>%1$s</strong> was added to %2$d subscribers.'),
'addTag': __('Add tag...'),
}) %>
<% endblock %>