<?php namespace MailPoet\API\JSON\v1; if (!defined('ABSPATH')) exit; use Exception; use MailPoet\API\JSON\Endpoint as APIEndpoint; use MailPoet\API\JSON\Error as APIError; use MailPoet\API\JSON\Response; use MailPoet\API\JSON\ResponseBuilders\SegmentsResponseBuilder; use MailPoet\Config\AccessControl; use MailPoet\ConflictException; use MailPoet\Cron\CronWorkerScheduler; use MailPoet\Cron\Workers\WooCommerceSync; use MailPoet\Doctrine\Validator\ValidationException; use MailPoet\Entities\SegmentEntity; use MailPoet\Entities\SubscriberEntity; use MailPoet\Form\FormsRepository; use MailPoet\Listing; use MailPoet\Newsletter\Segment\NewsletterSegmentRepository; use MailPoet\Segments\SegmentListingRepository; use MailPoet\Segments\SegmentSaveController; use MailPoet\Segments\SegmentsRepository; use MailPoet\Segments\WooCommerce; use MailPoet\Segments\WP; use MailPoet\Subscribers\SubscribersRepository; use MailPoet\UnexpectedValueException; class Segments extends APIEndpoint { public $permissions = [ 'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS, ]; /** @var Listing\Handler */ private $listingHandler; /** @var SegmentsRepository */ private $segmentsRepository; /** @var SegmentsResponseBuilder */ private $segmentsResponseBuilder; /** @var SegmentSaveController */ private $segmentSavecontroller; /** @var SubscribersRepository */ private $subscribersRepository; /** @var WooCommerce */ private $wooCommerceSync; /** @var WP */ private $wpSegment; /** @var SegmentListingRepository */ private $segmentListingRepository; /** @var NewsletterSegmentRepository */ private $newsletterSegmentRepository; /** @var CronWorkerScheduler */ private $cronWorkerScheduler; /** @var FormsRepository */ private $formsRepository; public function __construct( Listing\Handler $listingHandler, SegmentsRepository $segmentsRepository, SegmentListingRepository $segmentListingRepository, SegmentsResponseBuilder $segmentsResponseBuilder, SegmentSaveController $segmentSavecontroller, SubscribersRepository $subscribersRepository, WooCommerce $wooCommerce, WP $wpSegment, NewsletterSegmentRepository $newsletterSegmentRepository, CronWorkerScheduler $cronWorkerScheduler, FormsRepository $formsRepository ) { $this->listingHandler = $listingHandler; $this->wooCommerceSync = $wooCommerce; $this->segmentsRepository = $segmentsRepository; $this->segmentsResponseBuilder = $segmentsResponseBuilder; $this->segmentSavecontroller = $segmentSavecontroller; $this->subscribersRepository = $subscribersRepository; $this->wpSegment = $wpSegment; $this->segmentListingRepository = $segmentListingRepository; $this->newsletterSegmentRepository = $newsletterSegmentRepository; $this->cronWorkerScheduler = $cronWorkerScheduler; $this->formsRepository = $formsRepository; } public function get($data = []) { $id = (isset($data['id']) ? (int)$data['id'] : false); $segment = $this->segmentsRepository->findOneById($id); if ($segment instanceof SegmentEntity) { return $this->successResponse($this->segmentsResponseBuilder->build($segment)); } else { return $this->errorResponse([ APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'), ]); } } public function listing($data = []) { $data['params'] = $data['params'] ?? ['lists']; // Dummy param to apply constraints properly $definition = $this->listingHandler->getListingDefinition($data); $items = $this->segmentListingRepository->getData($definition); $count = $this->segmentListingRepository->getCount($definition); $filters = $this->segmentListingRepository->getFilters($definition); $groups = $this->segmentListingRepository->getGroups($definition); $segments = $this->segmentsResponseBuilder->buildForListing($items); return $this->successResponse($segments, [ 'count' => $count, 'filters' => $filters, 'groups' => $groups, ]); } public function save($data = []) { try { $segment = $this->segmentSavecontroller->save($data); } catch (ValidationException $exception) { return $this->badRequest([ APIError::BAD_REQUEST => __('Please specify a name.', 'mailpoet'), ]); } catch (ConflictException $exception) { return $this->badRequest([ APIError::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'), ]); } $response = $this->segmentsResponseBuilder->build($segment); return $this->successResponse($response); } public function restore($data = []) { $segment = $this->getSegment($data); if ($segment instanceof SegmentEntity) { if (!$this->isTrashOrRestoreAllowed($segment)) { return $this->errorResponse([ APIError::FORBIDDEN => __('This list cannot be moved to trash.', 'mailpoet'), ]); } // When the segment is of type WP_USERS we want to restore all its subscribers if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) { $subscribers = $this->subscribersRepository->findBySegment((int)$segment->getId()); $subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int { return (int)$subscriberEntity->getId(); }, $subscribers); $this->subscribersRepository->bulkRestore($subscriberIds); } $this->segmentsRepository->bulkRestore([$segment->getId()], $segment->getType()); $this->segmentsRepository->refresh($segment); return $this->successResponse( $this->segmentsResponseBuilder->build($segment), ['count' => 1] ); } else { return $this->errorResponse([ APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'), ]); } } public function trash($data = []) { $segment = $this->getSegment($data); if (!$segment instanceof SegmentEntity) { return $this->errorResponse([ APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'), ]); } if (!$this->isTrashOrRestoreAllowed($segment)) { return $this->errorResponse([ APIError::FORBIDDEN => __('This list cannot be moved to trash.', 'mailpoet'), ]); } $activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$segment->getId()]); if (isset($activelyUsedNewslettersSubjects[$segment->getId()])) { return $this->badRequest([ APIError::BAD_REQUEST => str_replace( '%1$s', "'" . join("', '", $activelyUsedNewslettersSubjects[$segment->getId()] ) . "'", // translators: %1$s is a comma-seperated list of emails for which the segment is used. _x('List cannot be deleted because it’s used for %1$s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet') ), ]); } $activelyUsedFormNames = $this->formsRepository->getNamesOfFormsForSegments(); if (isset($activelyUsedFormNames[$segment->getId()])) { return $this->badRequest([ APIError::BAD_REQUEST => str_replace( '%1$s', "'" . join("', '", $activelyUsedFormNames[$segment->getId()] ) . "'", // translators: %1$s is a comma-seperated list of forms for which the segment is used. _nx( 'List cannot be deleted because it’s used for %1$s form', 'List cannot be deleted because it’s used for %1$s forms', count($activelyUsedFormNames[$segment->getId()]), 'Alert shown when trying to delete segment, when it is assigned to a form.', 'mailpoet' ) ), ]); } // When the segment is of type WP_USERS we want to trash all subscribers who aren't subscribed in another list if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) { $subscribers = $this->subscribersRepository->findExclusiveSubscribersBySegment((int)$segment->getId()); $subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int { return (int)$subscriberEntity->getId(); }, $subscribers); $this->subscribersRepository->bulkTrash($subscriberIds); } $this->segmentsRepository->doTrash([$segment->getId()], $segment->getType()); $this->segmentsRepository->refresh($segment); return $this->successResponse( $this->segmentsResponseBuilder->build($segment), ['count' => 1] ); } public function delete($data = []) { $segment = $this->getSegment($data); if ($segment instanceof SegmentEntity) { $this->segmentsRepository->bulkDelete([$segment->getId()]); return $this->successResponse(null, ['count' => 1]); } else { return $this->errorResponse([ APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'), ]); } } public function duplicate($data = []) { $segment = $this->getSegment($data); if ($segment instanceof SegmentEntity) { try { $duplicate = $this->segmentSavecontroller->duplicate($segment); } catch (Exception $e) { return $this->errorResponse([ APIError::UNKNOWN => __('Duplicating of segment failed.', 'mailpoet'), ], [], Response::STATUS_UNKNOWN); } return $this->successResponse( $this->segmentsResponseBuilder->build($duplicate), ['count' => 1] ); } else { return $this->errorResponse([ APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'), ]); } } public function synchronize($data) { try { if ($data['type'] === SegmentEntity::TYPE_WC_USERS) { $this->cronWorkerScheduler->scheduleImmediatelyIfNotRunning(WooCommerceSync::TASK_TYPE); } else { $this->wpSegment->synchronizeUsers(); } } catch (\Exception $e) { return $this->errorResponse([ $e->getCode() => $e->getMessage(), ]); } return $this->successResponse(null); } public function bulkAction($data = []) { $definition = $this->listingHandler->getListingDefinition($data['listing']); $ids = $this->segmentListingRepository->getActionableIds($definition); $count = 0; if ($data['action'] === 'trash') { $count = $this->segmentsRepository->bulkTrash($ids); } elseif ($data['action'] === 'restore') { $count = $this->segmentsRepository->bulkRestore($ids); } elseif ($data['action'] === 'delete') { $count = $this->segmentsRepository->bulkDelete($ids); } else { throw UnexpectedValueException::create() ->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]); } return $this->successResponse(null, ['count' => $count]); } private function isTrashOrRestoreAllowed(SegmentEntity $segment): bool { $allowedSegmentTypes = [ SegmentEntity::TYPE_DEFAULT, SegmentEntity::TYPE_WP_USERS, ]; if (in_array($segment->getType(), $allowedSegmentTypes, true)) { return true; } return false; } private function getSegment(array $data): ?SegmentEntity { return isset($data['id']) ? $this->segmentsRepository->findOneById((int)$data['id']) : null; } }