Нема описа

API.php 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. namespace MailPoet\API\MP\v1;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Models\Segment;
  5. use MailPoet\Models\Subscriber;
  6. use MailPoet\Models\SubscriberSegment;
  7. use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
  8. use MailPoet\Settings\SettingsController;
  9. use MailPoet\Subscribers\ConfirmationEmailMailer;
  10. use MailPoet\Subscribers\NewSubscriberNotificationMailer;
  11. use MailPoet\Subscribers\RequiredCustomFieldValidator;
  12. use MailPoet\Subscribers\Source;
  13. use MailPoet\Tasks\Sending;
  14. use MailPoet\Util\Helpers;
  15. use MailPoet\WP\Functions as WPFunctions;
  16. class API {
  17. /** @var NewSubscriberNotificationMailer */
  18. private $newSubscriberNotificationMailer;
  19. /** @var ConfirmationEmailMailer */
  20. private $confirmationEmailMailer;
  21. /** @var RequiredCustomFieldValidator */
  22. private $requiredCustomFieldValidator;
  23. /** @var WelcomeScheduler */
  24. private $welcomeScheduler;
  25. /** @var SettingsController */
  26. private $settings;
  27. /** @var CustomFields */
  28. private $customFields;
  29. public function __construct(
  30. NewSubscriberNotificationMailer $newSubscriberNotificationMailer,
  31. ConfirmationEmailMailer $confirmationEmailMailer,
  32. RequiredCustomFieldValidator $requiredCustomFieldValidator,
  33. WelcomeScheduler $welcomeScheduler,
  34. CustomFields $customFields,
  35. SettingsController $settings
  36. ) {
  37. $this->newSubscriberNotificationMailer = $newSubscriberNotificationMailer;
  38. $this->confirmationEmailMailer = $confirmationEmailMailer;
  39. $this->requiredCustomFieldValidator = $requiredCustomFieldValidator;
  40. $this->welcomeScheduler = $welcomeScheduler;
  41. $this->settings = $settings;
  42. $this->customFields = $customFields;
  43. }
  44. public function getSubscriberFields() {
  45. return $this->customFields->getSubscriberFields();
  46. }
  47. public function addSubscriberField(array $data = []) {
  48. try {
  49. return $this->customFields->addSubscriberField($data);
  50. } catch (\InvalidArgumentException $e) {
  51. throw new APIException($e->getMessage(), $e->getCode(), $e);
  52. }
  53. }
  54. public function subscribeToList($subscriberId, $listId, $options = []) {
  55. return $this->subscribeToLists($subscriberId, [$listId], $options);
  56. }
  57. public function subscribeToLists($subscriberId, array $listIds, $options = []) {
  58. $scheduleWelcomeEmail = (isset($options['schedule_welcome_email']) && $options['schedule_welcome_email'] === false) ? false : true;
  59. $sendConfirmationEmail = (isset($options['send_confirmation_email']) && $options['send_confirmation_email'] === false) ? false : true;
  60. $skipSubscriberNotification = (isset($options['skip_subscriber_notification']) && $options['skip_subscriber_notification'] === true) ? true : false;
  61. $signupConfirmationEnabled = (bool)$this->settings->get('signup_confirmation.enabled');
  62. if (empty($listIds)) {
  63. throw new APIException(__('At least one segment ID is required.', 'mailpoet'), APIException::SEGMENT_REQUIRED);
  64. }
  65. // throw exception when subscriber does not exist
  66. $subscriber = Subscriber::findOne($subscriberId);
  67. if (!$subscriber) {
  68. throw new APIException(__('This subscriber does not exist.', 'mailpoet'), APIException::SUBSCRIBER_NOT_EXISTS);
  69. }
  70. // throw exception when none of the segments exist
  71. $foundSegments = Segment::whereIn('id', $listIds)->findMany();
  72. if (!$foundSegments) {
  73. $exception = WPFunctions::get()->_n('This list does not exist.', 'These lists do not exist.', count($listIds), 'mailpoet');
  74. throw new APIException($exception, APIException::LIST_NOT_EXISTS);
  75. }
  76. // throw exception when trying to subscribe to WP Users or WooCommerce Customers segments
  77. $foundSegmentsIds = [];
  78. foreach ($foundSegments as $foundSegment) {
  79. if ($foundSegment->type === Segment::TYPE_WP_USERS) {
  80. throw new APIException(__(sprintf("Can't subscribe to a WordPress Users list with ID %d.", $foundSegment->id), 'mailpoet'), APIException::SUBSCRIBING_TO_WP_LIST_NOT_ALLOWED);
  81. }
  82. if ($foundSegment->type === Segment::TYPE_WC_USERS) {
  83. throw new APIException(__(sprintf("Can't subscribe to a WooCommerce Customers list with ID %d.", $foundSegment->id), 'mailpoet'), APIException::SUBSCRIBING_TO_WC_LIST_NOT_ALLOWED);
  84. }
  85. if ($foundSegment->type !== Segment::TYPE_DEFAULT) {
  86. throw new APIException(__(sprintf("Can't subscribe to a list with ID %d.", $foundSegment->id), 'mailpoet'), APIException::SUBSCRIBING_TO_LIST_NOT_ALLOWED);
  87. }
  88. $foundSegmentsIds[] = $foundSegment->id;
  89. }
  90. // throw an exception when one or more segments do not exist
  91. if (count($foundSegmentsIds) !== count($listIds)) {
  92. $missingIds = array_values(array_diff($listIds, $foundSegmentsIds));
  93. $exception = sprintf(
  94. WPFunctions::get()->_n('List with ID %s does not exist.', 'Lists with IDs %s do not exist.', count($missingIds), 'mailpoet'),
  95. implode(', ', $missingIds)
  96. );
  97. throw new APIException(sprintf($exception, implode(', ', $missingIds)), APIException::LIST_NOT_EXISTS);
  98. }
  99. SubscriberSegment::subscribeToSegments($subscriber, $foundSegmentsIds);
  100. // set status depending on signup confirmation setting
  101. if ($subscriber->status !== Subscriber::STATUS_SUBSCRIBED) {
  102. if ($signupConfirmationEnabled === true) {
  103. $subscriber->set('status', Subscriber::STATUS_UNCONFIRMED);
  104. } else {
  105. $subscriber->set('status', Subscriber::STATUS_SUBSCRIBED);
  106. }
  107. $subscriber->save();
  108. if ($subscriber->getErrors() !== false) {
  109. throw new APIException(
  110. __(sprintf('Failed to save a status of a subscriber : %s', strtolower(implode(', ', $subscriber->getErrors()))), 'mailpoet'),
  111. APIException::FAILED_TO_SAVE_SUBSCRIBER
  112. );
  113. }
  114. }
  115. // schedule welcome email
  116. if ($scheduleWelcomeEmail && $subscriber->status === Subscriber::STATUS_SUBSCRIBED) {
  117. $this->_scheduleWelcomeNotification($subscriber, $foundSegmentsIds);
  118. }
  119. // send confirmation email
  120. if ($sendConfirmationEmail) {
  121. $result = $this->_sendConfirmationEmail($subscriber);
  122. if (!$result && $subscriber->getErrors()) {
  123. throw new APIException(
  124. __(sprintf('Subscriber added to lists, but confirmation email failed to send: %s', strtolower(implode(', ', $subscriber->getErrors()))), 'mailpoet'),
  125. APIException::CONFIRMATION_FAILED_TO_SEND);
  126. }
  127. }
  128. if (!$skipSubscriberNotification && ($subscriber->status === Subscriber::STATUS_SUBSCRIBED)) {
  129. $this->sendSubscriberNotification($subscriber, $foundSegmentsIds);
  130. }
  131. return $subscriber->withCustomFields()->withSubscriptions()->asArray();
  132. }
  133. public function unsubscribeFromList($subscriberId, $listId) {
  134. return $this->unsubscribeFromLists($subscriberId, [$listId]);
  135. }
  136. public function unsubscribeFromLists($subscriberId, array $listIds) {
  137. if (empty($listIds)) {
  138. throw new APIException(__('At least one segment ID is required.', 'mailpoet'), APIException::SEGMENT_REQUIRED);
  139. }
  140. // throw exception when subscriber does not exist
  141. $subscriber = Subscriber::findOne($subscriberId);
  142. if (!$subscriber) {
  143. throw new APIException(__('This subscriber does not exist.', 'mailpoet'), APIException::SUBSCRIBER_NOT_EXISTS);
  144. }
  145. // throw exception when none of the segments exist
  146. $foundSegments = Segment::whereIn('id', $listIds)->findMany();
  147. if (!$foundSegments) {
  148. $exception = WPFunctions::get()->_n('This list does not exist.', 'These lists do not exist.', count($listIds), 'mailpoet');
  149. throw new APIException($exception, APIException::LIST_NOT_EXISTS);
  150. }
  151. // throw exception when trying to subscribe to WP Users or WooCommerce Customers segments
  152. $foundSegmentsIds = [];
  153. foreach ($foundSegments as $segment) {
  154. if ($segment->type === Segment::TYPE_WP_USERS) {
  155. throw new APIException(__(sprintf("Can't unsubscribe from a WordPress Users list with ID %d.", $segment->id), 'mailpoet'), APIException::SUBSCRIBING_TO_WP_LIST_NOT_ALLOWED);
  156. }
  157. if ($segment->type === Segment::TYPE_WC_USERS) {
  158. throw new APIException(__(sprintf("Can't unsubscribe from a WooCommerce Customers list with ID %d.", $segment->id), 'mailpoet'), APIException::SUBSCRIBING_TO_WC_LIST_NOT_ALLOWED);
  159. }
  160. $foundSegmentsIds[] = $segment->id;
  161. }
  162. // throw an exception when one or more segments do not exist
  163. if (count($foundSegmentsIds) !== count($listIds)) {
  164. $missingIds = array_values(array_diff($listIds, $foundSegmentsIds));
  165. $exception = sprintf(
  166. WPFunctions::get()->_n('List with ID %s does not exist.', 'Lists with IDs %s do not exist.', count($missingIds), 'mailpoet'),
  167. implode(', ', $missingIds)
  168. );
  169. throw new APIException($exception, APIException::LIST_NOT_EXISTS);
  170. }
  171. SubscriberSegment::unsubscribeFromSegments($subscriber, $foundSegmentsIds);
  172. return $subscriber->withCustomFields()->withSubscriptions()->asArray();
  173. }
  174. public function getLists() {
  175. return Segment::where('type', Segment::TYPE_DEFAULT)
  176. ->findArray();
  177. }
  178. public function addSubscriber(array $subscriber, $listIds = [], $options = []) {
  179. $sendConfirmationEmail = (isset($options['send_confirmation_email']) && $options['send_confirmation_email'] === false) ? false : true;
  180. $scheduleWelcomeEmail = (isset($options['schedule_welcome_email']) && $options['schedule_welcome_email'] === false) ? false : true;
  181. $skipSubscriberNotification = (isset($options['skip_subscriber_notification']) && $options['skip_subscriber_notification'] === true) ? true : false;
  182. // throw exception when subscriber email is missing
  183. if (empty($subscriber['email'])) {
  184. throw new APIException(
  185. __('Subscriber email address is required.', 'mailpoet'),
  186. APIException::EMAIL_ADDRESS_REQUIRED
  187. );
  188. }
  189. // throw exception when subscriber already exists
  190. if (Subscriber::findOne($subscriber['email'])) {
  191. throw new APIException(
  192. __('This subscriber already exists.', 'mailpoet'),
  193. APIException::SUBSCRIBER_EXISTS
  194. );
  195. }
  196. if (empty($subscriber['subscribed_ip'])) {
  197. $subscriber['subscribed_ip'] = Helpers::getIP();
  198. }
  199. // separate data into default and custom fields
  200. [$defaultFields, $customFields] = Subscriber::extractCustomFieldsFromFromObject($subscriber);
  201. // filter out all incoming data that we don't want to change, like status ...
  202. $defaultFields = array_intersect_key($defaultFields, array_flip(['email', 'first_name', 'last_name', 'subscribed_ip']));
  203. // if some required default fields are missing, set their values
  204. $defaultFields = Subscriber::setRequiredFieldsDefaultValues($defaultFields);
  205. $this->requiredCustomFieldValidator->validate($customFields);
  206. // add subscriber
  207. $newSubscriber = Subscriber::create();
  208. $newSubscriber->hydrate($defaultFields);
  209. $newSubscriber = Source::setSource($newSubscriber, Source::API);
  210. $newSubscriber->save();
  211. if ($newSubscriber->getErrors() !== false) {
  212. throw new APIException(
  213. __(sprintf('Failed to add subscriber: %s', strtolower(implode(', ', $newSubscriber->getErrors()))), 'mailpoet'),
  214. APIException::FAILED_TO_SAVE_SUBSCRIBER
  215. );
  216. }
  217. if (!empty($customFields)) {
  218. $newSubscriber->saveCustomFields($customFields);
  219. }
  220. // reload subscriber to get the saved status/created|updated|delete dates/other fields
  221. $newSubscriber = Subscriber::findOne($newSubscriber->id);
  222. // subscribe to segments and optionally: 1) send confirmation email, 2) schedule welcome email(s)
  223. if (!empty($listIds)) {
  224. $this->subscribeToLists($newSubscriber->id, $listIds, [
  225. 'send_confirmation_email' => $sendConfirmationEmail,
  226. 'schedule_welcome_email' => $scheduleWelcomeEmail,
  227. 'skip_subscriber_notification' => $skipSubscriberNotification,
  228. ]);
  229. }
  230. return $newSubscriber->withCustomFields()->withSubscriptions()->asArray();
  231. }
  232. public function addList(array $list) {
  233. // throw exception when list name is missing
  234. if (empty($list['name'])) {
  235. throw new APIException(
  236. __('List name is required.', 'mailpoet'),
  237. APIException::LIST_NAME_REQUIRED
  238. );
  239. }
  240. // throw exception when list already exists
  241. if (Segment::where('name', $list['name'])->findOne()) {
  242. throw new APIException(
  243. __('This list already exists.', 'mailpoet'),
  244. APIException::LIST_EXISTS
  245. );
  246. }
  247. // filter out all incoming data that we don't want to change, like type,
  248. $list = array_intersect_key($list, array_flip(['name', 'description']));
  249. // add list
  250. $newList = Segment::create();
  251. $newList->hydrate($list);
  252. $newList->save();
  253. if ($newList->getErrors() !== false) {
  254. throw new APIException(
  255. __(sprintf('Failed to add list: %s', strtolower(implode(', ', $newList->getErrors()))), 'mailpoet'),
  256. APIException::FAILED_TO_SAVE_LIST
  257. );
  258. }
  259. // reload list to get the saved created|updated|delete dates/other fields
  260. $newList = Segment::findOne($newList->id);
  261. if (!$newList instanceof Segment) {
  262. throw new APIException(__('Failed to add list', 'mailpoet'), APIException::FAILED_TO_SAVE_LIST);
  263. }
  264. return $newList->asArray();
  265. }
  266. public function getSubscriber($subscriberEmail) {
  267. $subscriber = Subscriber::findOne($subscriberEmail);
  268. // throw exception when subscriber does not exist
  269. if (!$subscriber) {
  270. throw new APIException(__('This subscriber does not exist.', 'mailpoet'), APIException::SUBSCRIBER_NOT_EXISTS);
  271. }
  272. return $subscriber->withCustomFields()->withSubscriptions()->asArray();
  273. }
  274. protected function _sendConfirmationEmail(Subscriber $subscriber) {
  275. return $this->confirmationEmailMailer->sendConfirmationEmailOnce($subscriber);
  276. }
  277. protected function _scheduleWelcomeNotification(Subscriber $subscriber, array $segments) {
  278. $result = $this->welcomeScheduler->scheduleSubscriberWelcomeNotification($subscriber->id, $segments);
  279. if (is_array($result)) {
  280. foreach ($result as $queue) {
  281. if ($queue instanceof Sending && $queue->getErrors()) {
  282. throw new APIException(
  283. __(sprintf('Subscriber added, but welcome email failed to send: %s', strtolower(implode(', ', $queue->getErrors()))), 'mailpoet'),
  284. APIException::WELCOME_FAILED_TO_SEND
  285. );
  286. }
  287. }
  288. }
  289. return $result;
  290. }
  291. private function sendSubscriberNotification(Subscriber $subscriber, array $segmentIds) {
  292. $this->newSubscriberNotificationMailer->send($subscriber, Segment::whereIn('id', $segmentIds)->findMany());
  293. }
  294. }