Нет описания

Segments.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. namespace MailPoet\API\JSON\v1;
  3. if (!defined('ABSPATH')) exit;
  4. use Exception;
  5. use InvalidArgumentException;
  6. use MailPoet\API\JSON\Endpoint as APIEndpoint;
  7. use MailPoet\API\JSON\Error as APIError;
  8. use MailPoet\API\JSON\Response;
  9. use MailPoet\API\JSON\ResponseBuilders\SegmentsResponseBuilder;
  10. use MailPoet\Config\AccessControl;
  11. use MailPoet\Cron\CronWorkerScheduler;
  12. use MailPoet\Cron\Workers\WooCommerceSync;
  13. use MailPoet\Doctrine\Validator\ValidationException;
  14. use MailPoet\Entities\SegmentEntity;
  15. use MailPoet\Entities\SubscriberEntity;
  16. use MailPoet\Form\FormsRepository;
  17. use MailPoet\Listing;
  18. use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
  19. use MailPoet\Segments\SegmentListingRepository;
  20. use MailPoet\Segments\SegmentSaveController;
  21. use MailPoet\Segments\SegmentsRepository;
  22. use MailPoet\Segments\WooCommerce;
  23. use MailPoet\Segments\WP;
  24. use MailPoet\Subscribers\SubscribersRepository;
  25. use MailPoet\UnexpectedValueException;
  26. use MailPoet\WP\Functions as WPFunctions;
  27. class Segments extends APIEndpoint {
  28. public $permissions = [
  29. 'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
  30. ];
  31. /** @var Listing\Handler */
  32. private $listingHandler;
  33. /** @var SegmentsRepository */
  34. private $segmentsRepository;
  35. /** @var SegmentsResponseBuilder */
  36. private $segmentsResponseBuilder;
  37. /** @var SegmentSaveController */
  38. private $segmentSavecontroller;
  39. /** @var SubscribersRepository */
  40. private $subscribersRepository;
  41. /** @var WooCommerce */
  42. private $wooCommerceSync;
  43. /** @var WP */
  44. private $wpSegment;
  45. /** @var SegmentListingRepository */
  46. private $segmentListingRepository;
  47. /** @var NewsletterSegmentRepository */
  48. private $newsletterSegmentRepository;
  49. /** @var CronWorkerScheduler */
  50. private $cronWorkerScheduler;
  51. /** @var FormsRepository */
  52. private $formsRepository;
  53. public function __construct(
  54. Listing\Handler $listingHandler,
  55. SegmentsRepository $segmentsRepository,
  56. SegmentListingRepository $segmentListingRepository,
  57. SegmentsResponseBuilder $segmentsResponseBuilder,
  58. SegmentSaveController $segmentSavecontroller,
  59. SubscribersRepository $subscribersRepository,
  60. WooCommerce $wooCommerce,
  61. WP $wpSegment,
  62. NewsletterSegmentRepository $newsletterSegmentRepository,
  63. CronWorkerScheduler $cronWorkerScheduler,
  64. FormsRepository $formsRepository
  65. ) {
  66. $this->listingHandler = $listingHandler;
  67. $this->wooCommerceSync = $wooCommerce;
  68. $this->segmentsRepository = $segmentsRepository;
  69. $this->segmentsResponseBuilder = $segmentsResponseBuilder;
  70. $this->segmentSavecontroller = $segmentSavecontroller;
  71. $this->subscribersRepository = $subscribersRepository;
  72. $this->wpSegment = $wpSegment;
  73. $this->segmentListingRepository = $segmentListingRepository;
  74. $this->newsletterSegmentRepository = $newsletterSegmentRepository;
  75. $this->cronWorkerScheduler = $cronWorkerScheduler;
  76. $this->formsRepository = $formsRepository;
  77. }
  78. public function get($data = []) {
  79. $id = (isset($data['id']) ? (int)$data['id'] : false);
  80. $segment = $this->segmentsRepository->findOneById($id);
  81. if ($segment instanceof SegmentEntity) {
  82. return $this->successResponse($this->segmentsResponseBuilder->build($segment));
  83. } else {
  84. return $this->errorResponse([
  85. APIError::NOT_FOUND => WPFunctions::get()->__('This list does not exist.', 'mailpoet'),
  86. ]);
  87. }
  88. }
  89. public function listing($data = []) {
  90. $data['params'] = $data['params'] ?? ['lists']; // Dummy param to apply constraints properly
  91. $definition = $this->listingHandler->getListingDefinition($data);
  92. $items = $this->segmentListingRepository->getData($definition);
  93. $count = $this->segmentListingRepository->getCount($definition);
  94. $filters = $this->segmentListingRepository->getFilters($definition);
  95. $groups = $this->segmentListingRepository->getGroups($definition);
  96. $segments = $this->segmentsResponseBuilder->buildForListing($items);
  97. return $this->successResponse($segments, [
  98. 'count' => $count,
  99. 'filters' => $filters,
  100. 'groups' => $groups,
  101. ]);
  102. }
  103. public function save($data = []) {
  104. try {
  105. $segment = $this->segmentSavecontroller->save($data);
  106. } catch (ValidationException $exception) {
  107. return $this->badRequest([
  108. APIError::BAD_REQUEST => __('Please specify a name.', 'mailpoet'),
  109. ]);
  110. } catch (InvalidArgumentException $exception) {
  111. return $this->badRequest([
  112. APIError::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'),
  113. ]);
  114. }
  115. $response = $this->segmentsResponseBuilder->build($segment);
  116. return $this->successResponse($response);
  117. }
  118. public function restore($data = []) {
  119. $segment = $this->getSegment($data);
  120. if ($segment instanceof SegmentEntity) {
  121. if (!$this->isTrashOrRestoreAllowed($segment)) {
  122. return $this->errorResponse([
  123. APIError::FORBIDDEN => WPFunctions::get()->__('This list cannot be moved to trash.', 'mailpoet'),
  124. ]);
  125. }
  126. // When the segment is of type WP_USERS we want to restore all its subscribers
  127. if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
  128. $subscribers = $this->subscribersRepository->findBySegment((int)$segment->getId());
  129. $subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int {
  130. return (int)$subscriberEntity->getId();
  131. }, $subscribers);
  132. $this->subscribersRepository->bulkRestore($subscriberIds);
  133. }
  134. $this->segmentsRepository->bulkRestore([$segment->getId()], $segment->getType());
  135. $this->segmentsRepository->refresh($segment);
  136. return $this->successResponse(
  137. $this->segmentsResponseBuilder->build($segment),
  138. ['count' => 1]
  139. );
  140. } else {
  141. return $this->errorResponse([
  142. APIError::NOT_FOUND => WPFunctions::get()->__('This list does not exist.', 'mailpoet'),
  143. ]);
  144. }
  145. }
  146. public function trash($data = []) {
  147. $segment = $this->getSegment($data);
  148. if (!$segment instanceof SegmentEntity) {
  149. return $this->errorResponse([
  150. APIError::NOT_FOUND => WPFunctions::get()->__('This list does not exist.', 'mailpoet'),
  151. ]);
  152. }
  153. if (!$this->isTrashOrRestoreAllowed($segment)) {
  154. return $this->errorResponse([
  155. APIError::FORBIDDEN => WPFunctions::get()->__('This list cannot be moved to trash.', 'mailpoet'),
  156. ]);
  157. }
  158. $activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$segment->getId()]);
  159. if (isset($activelyUsedNewslettersSubjects[$segment->getId()])) {
  160. return $this->badRequest([
  161. APIError::BAD_REQUEST => str_replace(
  162. '%$1s',
  163. "'" . join("', '", $activelyUsedNewslettersSubjects[$segment->getId()] ) . "'",
  164. _x('List cannot be deleted because it’s used for %$1s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet')
  165. ),
  166. ]);
  167. }
  168. $activelyUsedFormNames = $this->formsRepository->getNamesOfFormsForSegments();
  169. if (isset($activelyUsedFormNames[$segment->getId()])) {
  170. return $this->badRequest([
  171. APIError::BAD_REQUEST => str_replace(
  172. '%$1s',
  173. "'" . join("', '", $activelyUsedFormNames[$segment->getId()] ) . "'",
  174. _nx(
  175. 'List cannot be deleted because it’s used for %$1s form',
  176. 'List cannot be deleted because it’s used for %$1s forms',
  177. count($activelyUsedFormNames[$segment->getId()]),
  178. 'Alert shown when trying to delete segment, when it is assigned to a form.',
  179. 'mailpoet'
  180. )
  181. ),
  182. ]);
  183. }
  184. // When the segment is of type WP_USERS we want to trash all subscribers who aren't subscribed in another list
  185. if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
  186. $subscribers = $this->subscribersRepository->findExclusiveSubscribersBySegment((int)$segment->getId());
  187. $subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int {
  188. return (int)$subscriberEntity->getId();
  189. }, $subscribers);
  190. $this->subscribersRepository->bulkTrash($subscriberIds);
  191. }
  192. $this->segmentsRepository->doTrash([$segment->getId()], $segment->getType());
  193. $this->segmentsRepository->refresh($segment);
  194. return $this->successResponse(
  195. $this->segmentsResponseBuilder->build($segment),
  196. ['count' => 1]
  197. );
  198. }
  199. public function delete($data = []) {
  200. $segment = $this->getSegment($data);
  201. if ($segment instanceof SegmentEntity) {
  202. $this->segmentsRepository->bulkDelete([$segment->getId()]);
  203. return $this->successResponse(null, ['count' => 1]);
  204. } else {
  205. return $this->errorResponse([
  206. APIError::NOT_FOUND => WPFunctions::get()->__('This list does not exist.', 'mailpoet'),
  207. ]);
  208. }
  209. }
  210. public function duplicate($data = []) {
  211. $segment = $this->getSegment($data);
  212. if ($segment instanceof SegmentEntity) {
  213. try {
  214. $duplicate = $this->segmentSavecontroller->duplicate($segment);
  215. } catch (Exception $e) {
  216. return $this->errorResponse([
  217. APIError::UNKNOWN => __('Duplicating of segment failed.', 'mailpoet'),
  218. ], [], Response::STATUS_UNKNOWN);
  219. }
  220. return $this->successResponse(
  221. $this->segmentsResponseBuilder->build($duplicate),
  222. ['count' => 1]
  223. );
  224. } else {
  225. return $this->errorResponse([
  226. APIError::NOT_FOUND => WPFunctions::get()->__('This list does not exist.', 'mailpoet'),
  227. ]);
  228. }
  229. }
  230. public function synchronize($data) {
  231. try {
  232. if ($data['type'] === SegmentEntity::TYPE_WC_USERS) {
  233. $this->cronWorkerScheduler->scheduleImmediatelyIfNotRunning(WooCommerceSync::TASK_TYPE);
  234. } else {
  235. $this->wpSegment->synchronizeUsers();
  236. }
  237. } catch (\Exception $e) {
  238. return $this->errorResponse([
  239. $e->getCode() => $e->getMessage(),
  240. ]);
  241. }
  242. return $this->successResponse(null);
  243. }
  244. public function bulkAction($data = []) {
  245. $definition = $this->listingHandler->getListingDefinition($data['listing']);
  246. $ids = $this->segmentListingRepository->getActionableIds($definition);
  247. $count = 0;
  248. if ($data['action'] === 'trash') {
  249. $count = $this->segmentsRepository->bulkTrash($ids);
  250. } elseif ($data['action'] === 'restore') {
  251. $count = $this->segmentsRepository->bulkRestore($ids);
  252. } elseif ($data['action'] === 'delete') {
  253. $count = $this->segmentsRepository->bulkDelete($ids);
  254. } else {
  255. throw UnexpectedValueException::create()
  256. ->withErrors([APIError::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
  257. }
  258. return $this->successResponse(null, ['count' => $count]);
  259. }
  260. private function isTrashOrRestoreAllowed(SegmentEntity $segment): bool {
  261. $allowedSegmentTypes = [
  262. SegmentEntity::TYPE_DEFAULT,
  263. SegmentEntity::TYPE_WP_USERS,
  264. ];
  265. if (in_array($segment->getType(), $allowedSegmentTypes, true)) {
  266. return true;
  267. }
  268. return false;
  269. }
  270. private function getSegment(array $data): ?SegmentEntity {
  271. return isset($data['id'])
  272. ? $this->segmentsRepository->findOneById((int)$data['id'])
  273. : null;
  274. }
  275. }