AccessControl::PERMISSION_MANAGE_SUBSCRIBERS, 'methods' => ['subscribe' => AccessControl::NO_ACCESS_RESTRICTION], ]; /** @var Listing\BulkActionController */ private $bulkActionController; /** @var SubscribersListings */ private $subscribersListings; /** @var SubscriberActions */ private $subscriberActions; /** @var RequiredCustomFieldValidator */ private $requiredCustomFieldValidator; /** @var Listing\Handler */ private $listingHandler; /** @var Captcha */ private $subscriptionCaptcha; /** @var WPFunctions */ private $wp; /** @var SettingsController */ private $settings; /** @var CaptchaSession */ private $captchaSession; /** @var ConfirmationEmailMailer; */ private $confirmationEmailMailer; /** @var SubscriptionUrlFactory */ private $subscriptionUrlFactory; public function __construct( Listing\BulkActionController $bulkActionController, SubscribersListings $subscribersListings, SubscriberActions $subscriberActions, RequiredCustomFieldValidator $requiredCustomFieldValidator, Listing\Handler $listingHandler, Captcha $subscriptionCaptcha, WPFunctions $wp, SettingsController $settings, CaptchaSession $captchaSession, ConfirmationEmailMailer $confirmationEmailMailer, SubscriptionUrlFactory $subscriptionUrlFactory ) { $this->bulkActionController = $bulkActionController; $this->subscribersListings = $subscribersListings; $this->subscriberActions = $subscriberActions; $this->requiredCustomFieldValidator = $requiredCustomFieldValidator; $this->listingHandler = $listingHandler; $this->subscriptionCaptcha = $subscriptionCaptcha; $this->wp = $wp; $this->settings = $settings; $this->captchaSession = $captchaSession; $this->confirmationEmailMailer = $confirmationEmailMailer; $this->subscriptionUrlFactory = $subscriptionUrlFactory; } public function get($data = []) { $id = (isset($data['id']) ? (int)$data['id'] : false); $subscriber = Subscriber::findOne($id); if ($subscriber === false) { return $this->errorResponse([ APIError::NOT_FOUND => WPFunctions::get()->__('This subscriber does not exist.', 'mailpoet'), ]); } else { return $this->successResponse( $subscriber ->withCustomFields() ->withSubscriptions() ->asArray() ); } } public function listing($data = []) { if (!isset($data['filter']['segment'])) { $listingData = $this->listingHandler->get('\MailPoet\Models\Subscriber', $data); } else { $listingData = $this->subscribersListings->getListingsInSegment($data); } $result = []; foreach ($listingData['items'] as $subscriber) { $subscriberResult = $subscriber ->withSubscriptions() ->asArray(); if (isset($data['filter']['segment'])) { $subscriberResult = $this->preferUnsubscribedStatusFromSegment($subscriberResult, $data['filter']['segment']); } $result[] = $subscriberResult; } $listingData['filters']['segment'] = $this->wp->applyFilters( 'mailpoet_subscribers_listings_filters_segments', $listingData['filters']['segment'] ); return $this->successResponse($result, [ 'count' => $listingData['count'], 'filters' => $listingData['filters'], 'groups' => $listingData['groups'], ]); } private function preferUnsubscribedStatusFromSegment(array $subscriber, $segmentId) { $segmentStatus = $this->findSegmentStatus($subscriber, $segmentId); if ($segmentStatus === Subscriber::STATUS_UNSUBSCRIBED) { $subscriber['status'] = $segmentStatus; } return $subscriber; } private function findSegmentStatus(array $subscriber, $segmentId) { foreach ($subscriber['subscriptions'] as $segment) { if ($segment['segment_id'] === $segmentId) { return $segment['status']; } } } public function subscribe($data = []) { $formId = (isset($data['form_id']) ? (int)$data['form_id'] : false); $form = Form::findOne($formId); unset($data['form_id']); if (!$form instanceof Form) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Please specify a valid form ID.', 'mailpoet'), ]); } if (!empty($data['email'])) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Please leave the first field empty.', 'mailpoet'), ]); } $captchaSettings = $this->settings->get('captcha'); if (!empty($captchaSettings['type']) && $captchaSettings['type'] === Captcha::TYPE_BUILTIN ) { $captchaSessionId = isset($data['captcha_session_id']) ? $data['captcha_session_id'] : null; $this->captchaSession->init($captchaSessionId); if (!isset($data['captcha'])) { // Save form data to session $this->captchaSession->setFormData(array_merge($data, ['form_id' => $formId])); } elseif ($this->captchaSession->getFormData()) { // Restore form data from session $data = array_merge($this->captchaSession->getFormData(), ['captcha' => $data['captcha']]); } // Otherwise use the post data } $data = $this->deobfuscateFormPayload($data); try { $this->requiredCustomFieldValidator->validate($data, $form); } catch (\Exception $e) { return $this->badRequest([APIError::BAD_REQUEST => $e->getMessage()]); } $segmentIds = (!empty($data['segments']) ? (array)$data['segments'] : [] ); $segmentIds = $form->filterSegments($segmentIds); unset($data['segments']); if (empty($segmentIds)) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Please select a list.', 'mailpoet'), ]); } $captchaValidationResult = $this->validateCaptcha($captchaSettings, $data); if ($captchaValidationResult instanceof APIResponse) { return $captchaValidationResult; } // only accept fields defined in the form $formFields = $form->getFieldList(); $data = array_intersect_key($data, array_flip($formFields)); // make sure we don't allow too many subscriptions with the same ip address $timeout = SubscriptionThrottling::throttle(); if ($timeout > 0) { $timeToWait = SubscriptionThrottling::secondsToTimeString($timeout); $meta = []; $meta['refresh_captcha'] = true; return $this->badRequest([ APIError::BAD_REQUEST => sprintf(WPFunctions::get()->__('You need to wait %s before subscribing again.', 'mailpoet'), $timeToWait), ], $meta); } $subscriber = $this->subscriberActions->subscribe($data, $segmentIds); $errors = $subscriber->getErrors(); if ($errors !== false) { return $this->badRequest($errors); } else { if (!empty($captchaSettings['type']) && $captchaSettings['type'] === Captcha::TYPE_BUILTIN) { // Captcha has been verified, invalidate the session vars $this->captchaSession->reset(); } $meta = []; if ($form !== false) { // record form statistics StatisticsForms::record($form->id, $subscriber->id); $form = $form->asArray(); if (!empty($form['settings']['on_success'])) { if ($form['settings']['on_success'] === 'page') { // redirect to a page on a success, pass the page url in the meta $meta['redirect_url'] = WPFunctions::get()->getPermalink($form['settings']['success_page']); } else if ($form['settings']['on_success'] === 'url') { $meta['redirect_url'] = $form['settings']['success_url']; } } } return $this->successResponse( [], $meta ); } } private function deobfuscateFormPayload($data) { $obfuscator = new FieldNameObfuscator(); return $obfuscator->deobfuscateFormPayload($data); } private function validateCaptcha($captchaSettings, $data) { if (empty($captchaSettings['type'])) { return true; } $isBuiltinCaptchaRequired = false; if ($captchaSettings['type'] === Captcha::TYPE_BUILTIN) { $isBuiltinCaptchaRequired = $this->subscriptionCaptcha->isRequired(isset($data['email']) ? $data['email'] : ''); if ($isBuiltinCaptchaRequired && empty($data['captcha'])) { $meta = []; $meta['redirect_url'] = $this->subscriptionUrlFactory->getCaptchaUrl($this->captchaSession->getId()); return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Please fill in the CAPTCHA.', 'mailpoet'), ], $meta); } } if ($captchaSettings['type'] === Captcha::TYPE_RECAPTCHA && empty($data['recaptcha'])) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Please check the CAPTCHA.', 'mailpoet'), ]); } if ($captchaSettings['type'] === Captcha::TYPE_RECAPTCHA) { $res = empty($data['recaptcha']) ? $data['recaptcha-no-js'] : $data['recaptcha']; $res = WPFunctions::get()->wpRemotePost('https://www.google.com/recaptcha/api/siteverify', [ 'body' => [ 'secret' => $captchaSettings['recaptcha_secret_token'], 'response' => $res, ], ]); if (is_wp_error($res)) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Error while validating the CAPTCHA.', 'mailpoet'), ]); } $res = json_decode(wp_remote_retrieve_body($res)); if (empty($res->success)) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Error while validating the CAPTCHA.', 'mailpoet'), ]); } } elseif ($captchaSettings['type'] === Captcha::TYPE_BUILTIN && $isBuiltinCaptchaRequired) { $captchaHash = $this->captchaSession->getCaptchaHash(); if (empty($captchaHash)) { return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('Please regenerate the CAPTCHA.', 'mailpoet'), ]); } elseif (!hash_equals(strtolower($data['captcha']), $captchaHash)) { $this->captchaSession->setCaptchaHash(null); $meta = []; $meta['refresh_captcha'] = true; return $this->badRequest([ APIError::BAD_REQUEST => WPFunctions::get()->__('The characters entered do not match with the previous CAPTCHA.', 'mailpoet'), ], $meta); } } return true; } public function save($data = []) { if (empty($data['segments'])) { $data['segments'] = []; } $data['segments'] = array_merge($data['segments'], $this->getNonDefaultSubscribedSegments($data)); $newSegments = $this->findNewSegments($data); $subscriber = Subscriber::createOrUpdate($data); $errors = $subscriber->getErrors(); if (!empty($errors)) { return $this->badRequest($errors); } if ($subscriber->isNew()) { $subscriber = Source::setSource($subscriber, Source::ADMINISTRATOR); $subscriber->save(); } if (!empty($newSegments)) { $scheduler = new WelcomeScheduler(); $scheduler->scheduleSubscriberWelcomeNotification($subscriber->id, $newSegments); } return $this->successResponse( Subscriber::findOne($subscriber->id)->asArray() ); } private function getNonDefaultSubscribedSegments(array $data) { if (!isset($data['id']) || (int)$data['id'] <= 0) { return []; } $subscribedSegmentIds = []; $nonDefaultSegment = Segment::select('id') ->whereNotEqual('type', Segment::TYPE_DEFAULT) ->findArray(); $nonDefaultSegmentIds = array_map(function($segment) { return $segment['id']; }, $nonDefaultSegment); $subscribedSegments = SubscriberSegment::select('segment_id') ->where('subscriber_id', $data['id']) ->where('status', Subscriber::STATUS_SUBSCRIBED) ->whereIn('segment_id', $nonDefaultSegmentIds) ->findArray(); $subscribedSegmentIds = array_map(function($segment) { return $segment['segment_id']; }, $subscribedSegments); return $subscribedSegmentIds; } private function findNewSegments(array $data) { $oldSegmentIds = []; if (isset($data['id']) && (int)$data['id'] > 0) { $oldSegments = SubscriberSegment::where('subscriber_id', $data['id'])->findMany(); foreach ($oldSegments as $oldSegment) { $oldSegmentIds[] = $oldSegment->segmentId; } } return array_diff($data['segments'], $oldSegmentIds); } public function restore($data = []) { $id = (isset($data['id']) ? (int)$data['id'] : false); $subscriber = Subscriber::findOne($id); if ($subscriber instanceof Subscriber) { $subscriber->restore(); $subscriber = Subscriber::findOne($subscriber->id); if(!$subscriber instanceof Subscriber) return $this->errorResponse(); return $this->successResponse( $subscriber->asArray(), ['count' => 1] ); } else { return $this->errorResponse([ APIError::NOT_FOUND => WPFunctions::get()->__('This subscriber does not exist.', 'mailpoet'), ]); } } public function trash($data = []) { $id = (isset($data['id']) ? (int)$data['id'] : false); $subscriber = Subscriber::findOne($id); if ($subscriber instanceof Subscriber) { $subscriber->trash(); $subscriber = Subscriber::findOne($subscriber->id); if(!$subscriber instanceof Subscriber) return $this->errorResponse(); return $this->successResponse( $subscriber->asArray(), ['count' => 1] ); } else { return $this->errorResponse([ APIError::NOT_FOUND => WPFunctions::get()->__('This subscriber does not exist.', 'mailpoet'), ]); } } public function delete($data = []) { $id = (isset($data['id']) ? (int)$data['id'] : false); $subscriber = Subscriber::findOne($id); if ($subscriber instanceof Subscriber) { $subscriber->delete(); return $this->successResponse(null, ['count' => 1]); } else { return $this->errorResponse([ APIError::NOT_FOUND => WPFunctions::get()->__('This subscriber does not exist.', 'mailpoet'), ]); } } public function sendConfirmationEmail($data = []) { $id = (isset($data['id']) ? (int)$data['id'] : false); $subscriber = Subscriber::findOne($id); if ($subscriber instanceof Subscriber) { if ($this->confirmationEmailMailer->sendConfirmationEmail($subscriber)) { return $this->successResponse(); } return $this->errorResponse(); } else { return $this->errorResponse([ APIError::NOT_FOUND => WPFunctions::get()->__('This subscriber does not exist.', 'mailpoet'), ]); } } public function bulkAction($data = []) { try { if (!isset($data['listing']['filter']['segment'])) { return $this->successResponse( null, $this->bulkActionController->apply('\MailPoet\Models\Subscriber', $data) ); } else { $bulkAction = new BulkAction($data); return $this->successResponse(null, $bulkAction->apply()); } } catch (\Exception $e) { return $this->errorResponse([ $e->getCode() => $e->getMessage(), ]); } } }