Нет описания

Pages.php 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. <?php
  2. namespace MailPoet\Subscription;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Config\Renderer as TemplateRenderer;
  5. use MailPoet\Entities\StatisticsUnsubscribeEntity;
  6. use MailPoet\Form\AssetsController;
  7. use MailPoet\Models\Subscriber;
  8. use MailPoet\Models\SubscriberSegment;
  9. use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
  10. use MailPoet\Settings\SettingsController;
  11. use MailPoet\Statistics\Track\Unsubscribes;
  12. use MailPoet\Subscribers\LinkTokens;
  13. use MailPoet\Subscribers\NewSubscriberNotificationMailer;
  14. use MailPoet\Subscribers\SubscribersRepository;
  15. use MailPoet\Util\Helpers;
  16. use MailPoet\WP\Functions as WPFunctions;
  17. use MailPoetVendor\Carbon\Carbon;
  18. class Pages {
  19. const DEMO_EMAIL = 'demo@mailpoet.com';
  20. const ACTION_CAPTCHA = 'captcha';
  21. const ACTION_CONFIRM = 'confirm';
  22. const ACTION_CONFIRM_UNSUBSCRIBE = 'confirm_unsubscribe';
  23. const ACTION_MANAGE = 'manage';
  24. const ACTION_UNSUBSCRIBE = 'unsubscribe';
  25. private $action;
  26. private $data;
  27. private $subscriber;
  28. /** @var NewSubscriberNotificationMailer */
  29. private $newSubscriberNotificationSender;
  30. /** @var SettingsController */
  31. private $settings;
  32. /** @var WPFunctions */
  33. private $wp;
  34. /** @var CaptchaRenderer */
  35. private $captchaRenderer;
  36. /** @var WelcomeScheduler */
  37. private $welcomeScheduler;
  38. /** @var LinkTokens */
  39. private $linkTokens;
  40. /** @var SubscriptionUrlFactory */
  41. private $subscriptionUrlFactory;
  42. /** @var AssetsController */
  43. private $assetsController;
  44. /** @var TemplateRenderer */
  45. private $templateRenderer;
  46. /** @var Unsubscribes */
  47. private $unsubscribesTracker;
  48. /** @var ManageSubscriptionFormRenderer */
  49. private $manageSubscriptionFormRenderer;
  50. /** @var SubscribersRepository */
  51. private $subscribersRepository;
  52. public function __construct(
  53. NewSubscriberNotificationMailer $newSubscriberNotificationSender,
  54. WPFunctions $wp,
  55. SettingsController $settings,
  56. CaptchaRenderer $captchaRenderer,
  57. WelcomeScheduler $welcomeScheduler,
  58. LinkTokens $linkTokens,
  59. SubscriptionUrlFactory $subscriptionUrlFactory,
  60. AssetsController $assetsController,
  61. TemplateRenderer $templateRenderer,
  62. Unsubscribes $unsubscribesTracker,
  63. ManageSubscriptionFormRenderer $manageSubscriptionFormRenderer,
  64. SubscribersRepository $subscribersRepository
  65. ) {
  66. $this->wp = $wp;
  67. $this->newSubscriberNotificationSender = $newSubscriberNotificationSender;
  68. $this->settings = $settings;
  69. $this->captchaRenderer = $captchaRenderer;
  70. $this->welcomeScheduler = $welcomeScheduler;
  71. $this->linkTokens = $linkTokens;
  72. $this->subscriptionUrlFactory = $subscriptionUrlFactory;
  73. $this->assetsController = $assetsController;
  74. $this->templateRenderer = $templateRenderer;
  75. $this->unsubscribesTracker = $unsubscribesTracker;
  76. $this->manageSubscriptionFormRenderer = $manageSubscriptionFormRenderer;
  77. $this->subscribersRepository = $subscribersRepository;
  78. }
  79. public function init($action = false, $data = [], $initShortcodes = false, $initPageFilters = false) {
  80. $this->action = $action;
  81. $this->data = $data;
  82. $this->subscriber = $this->getSubscriber();
  83. if ($initPageFilters) $this->initPageFilters();
  84. if ($initShortcodes) $this->initShortcodes();
  85. return $this;
  86. }
  87. private function isPreview() {
  88. return (array_key_exists('preview', $_GET) || array_key_exists('preview', $this->data));
  89. }
  90. public function initPageFilters() {
  91. $this->wp->addFilter('wp_title', [$this,'setWindowTitle'], 10, 3);
  92. $this->wp->addFilter('document_title_parts', [$this,'setWindowTitleParts'], 10, 1);
  93. $this->wp->addFilter('the_title', [$this,'setPageTitle'], 10, 1);
  94. $this->wp->addFilter('the_content', [$this,'setPageContent'], 10, 1);
  95. $this->wp->removeAction('wp_head', 'noindex', 1);
  96. $this->wp->addAction('wp_head', [$this, 'setMetaRobots'], 1);
  97. }
  98. public function initShortcodes() {
  99. $this->wp->addShortcode('mailpoet_manage', [$this, 'getManageLink']);
  100. $this->wp->addShortcode('mailpoet_manage_subscription', [$this, 'getManageContent']);
  101. }
  102. /**
  103. * @return Subscriber|null
  104. */
  105. private function getSubscriber() {
  106. if (!is_null($this->subscriber)) {
  107. return $this->subscriber;
  108. }
  109. $token = (isset($this->data['token'])) ? $this->data['token'] : null;
  110. $email = (isset($this->data['email'])) ? $this->data['email'] : null;
  111. $wpUser = $this->wp->wpGetCurrentUser();
  112. if (!$email && $wpUser->exists()) {
  113. $subscriber = Subscriber::where('wp_user_id', $wpUser->ID)->findOne();
  114. return $subscriber !== false ? $subscriber : null;
  115. }
  116. if (!$email) {
  117. return null;
  118. }
  119. $subscriber = Subscriber::where('email', $email)->findOne();
  120. $subscriberEntity = $subscriber ? $this->subscribersRepository->findOneById($subscriber->id) : null;
  121. return ($subscriber && $subscriberEntity && $this->linkTokens->verifyToken($subscriberEntity, $token)) ? $subscriber : null;
  122. }
  123. public function confirm() {
  124. $this->subscriber = $this->getSubscriber();
  125. if ($this->subscriber === null) {
  126. return false;
  127. }
  128. $subscriberData = $this->subscriber->getUnconfirmedData();
  129. $originalStatus = $this->subscriber->status;
  130. $this->subscriber->status = Subscriber::STATUS_SUBSCRIBED;
  131. $this->subscriber->confirmedIp = Helpers::getIP();
  132. $this->subscriber->confirmedAt = Carbon::createFromTimestamp($this->wp->currentTime('timestamp'));
  133. $this->subscriber->lastSubscribedAt = Carbon::createFromTimestamp($this->wp->currentTime('timestamp'));
  134. $this->subscriber->unconfirmedData = null;
  135. $this->subscriber->save();
  136. if ($this->subscriber->getErrors() !== false) {
  137. return false;
  138. }
  139. // Schedule welcome emails
  140. $subscriberSegments = $this->subscriber->segments()->findMany();
  141. if ($subscriberSegments) {
  142. $this->welcomeScheduler->scheduleSubscriberWelcomeNotification(
  143. $this->subscriber->id,
  144. array_map(function ($segment) {
  145. return $segment->get('id');
  146. }, $subscriberSegments)
  147. );
  148. }
  149. // Send new subscriber notification only when status changes to subscribed or there are unconfirmed data to avoid spamming
  150. if ($originalStatus !== Subscriber::STATUS_SUBSCRIBED || $subscriberData !== null) {
  151. $this->newSubscriberNotificationSender->send($this->subscriber, $subscriberSegments);
  152. }
  153. // Update subscriber from stored data after confirmation
  154. if (!empty($subscriberData)) {
  155. Subscriber::createOrUpdate($subscriberData);
  156. }
  157. }
  158. public function unsubscribe() {
  159. if (!$this->isPreview()
  160. && ($this->subscriber !== null)
  161. && ($this->subscriber->status !== Subscriber::STATUS_UNSUBSCRIBED)
  162. ) {
  163. if ((bool)$this->settings->get('tracking.enabled') && isset($this->data['queueId'])) {
  164. $this->unsubscribesTracker->track(
  165. (int)$this->subscriber->id,
  166. StatisticsUnsubscribeEntity::SOURCE_NEWSLETTER,
  167. (int)$this->data['queueId']
  168. );
  169. }
  170. $this->subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
  171. $this->subscriber->save();
  172. SubscriberSegment::unsubscribeFromSegments($this->subscriber);
  173. }
  174. }
  175. public function setMetaRobots() {
  176. echo '<meta name="robots" content="noindex,nofollow">';
  177. }
  178. public function setPageTitle($pageTitle = '') {
  179. global $post;
  180. if (
  181. ($post->post_title !== $this->wp->__('MailPoet Page', 'mailpoet')) // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  182. ||
  183. ($pageTitle !== $this->wp->singlePostTitle('', false))
  184. ) {
  185. // when it's a custom page, just return the original page title
  186. return $pageTitle;
  187. } elseif ($this->action !== self::ACTION_CAPTCHA && $this->isPreview() === false && $this->subscriber === null) {
  188. return $this->wp->__("Hmmm... we don't have a record of you.", 'mailpoet');
  189. } else {
  190. // when it's our own page, generate page title based on requested action
  191. switch ($this->action) {
  192. case self::ACTION_CAPTCHA:
  193. return $this->captchaRenderer->getCaptchaPageTitle();
  194. case self::ACTION_CONFIRM:
  195. return $this->getConfirmTitle();
  196. case self::ACTION_CONFIRM_UNSUBSCRIBE:
  197. return $this->getConfirmUnsubscribeTitle();
  198. case self::ACTION_MANAGE:
  199. return $this->getManageTitle();
  200. case self::ACTION_UNSUBSCRIBE:
  201. return $this->getUnsubscribeTitle();
  202. }
  203. }
  204. }
  205. public function setPageContent($pageContent = '[mailpoet_page]') {
  206. // if we're not in preview mode or captcha page and the subscriber does not exist
  207. if ($this->action !== self::ACTION_CAPTCHA && $this->isPreview() === false && $this->subscriber === null) {
  208. return $this->wp->__("Your email address doesn't appear in our lists anymore. Sign up again or contact us if this appears to be a mistake.", 'mailpoet');
  209. }
  210. $this->assetsController->setupFrontEndDependencies();
  211. if (strpos($pageContent, '[mailpoet_page]') !== false) {
  212. $content = '';
  213. switch ($this->action) {
  214. case self::ACTION_CAPTCHA:
  215. $captchaSessionId = isset($this->data['captcha_session_id']) ? $this->data['captcha_session_id'] : null;
  216. $content = $this->captchaRenderer->getCaptchaPageContent($captchaSessionId);
  217. break;
  218. case self::ACTION_CONFIRM:
  219. $content = $this->getConfirmContent();
  220. break;
  221. case self::ACTION_CONFIRM_UNSUBSCRIBE:
  222. $content = $this->getConfirmUnsubscribeContent();
  223. break;
  224. case self::ACTION_MANAGE:
  225. $content = $this->getManageContent();
  226. break;
  227. case self::ACTION_UNSUBSCRIBE:
  228. $content = $this->getUnsubscribeContent();
  229. break;
  230. }
  231. return str_replace('[mailpoet_page]', trim($content), $pageContent);
  232. } else {
  233. return $pageContent;
  234. }
  235. }
  236. public function setWindowTitle($title, $separator, $separatorLocation = 'right') {
  237. $titleParts = explode(" $separator ", $title);
  238. if (!is_array($titleParts)) {
  239. return $title;
  240. }
  241. if ($separatorLocation === 'right') {
  242. // first part
  243. $titleParts[0] = $this->setPageTitle($titleParts[0]);
  244. } else {
  245. // last part
  246. $lastIndex = count($titleParts) - 1;
  247. $titleParts[$lastIndex] = $this->setPageTitle($titleParts[$lastIndex]);
  248. }
  249. return implode(" $separator ", $titleParts);
  250. }
  251. public function setWindowTitleParts($meta = []) {
  252. $meta['title'] = $this->setPageTitle($meta['title']);
  253. return $meta;
  254. }
  255. private function getConfirmTitle() {
  256. if ($this->isPreview()) {
  257. $title = sprintf(
  258. $this->wp->__("You have subscribed to: %s", 'mailpoet'),
  259. 'demo 1, demo 2'
  260. );
  261. } else {
  262. $segmentNames = array_map(function($segment) {
  263. return $segment->name;
  264. }, $this->subscriber->segments()->findMany());
  265. if (empty($segmentNames)) {
  266. $title = $this->wp->__("You are now subscribed!", 'mailpoet');
  267. } else {
  268. $title = sprintf(
  269. $this->wp->__("You have subscribed to: %s", 'mailpoet'),
  270. join(', ', $segmentNames)
  271. );
  272. }
  273. }
  274. return $title;
  275. }
  276. private function getManageTitle() {
  277. if ($this->isPreview() || $this->subscriber !== null) {
  278. return $this->wp->__("Manage your subscription", 'mailpoet');
  279. }
  280. }
  281. private function getUnsubscribeTitle() {
  282. if ($this->isPreview() || $this->subscriber !== null) {
  283. return $this->wp->__("You are now unsubscribed.", 'mailpoet');
  284. }
  285. }
  286. private function getConfirmUnsubscribeTitle() {
  287. if ($this->isPreview() || $this->subscriber !== null) {
  288. return $this->wp->__('Confirm you want to unsubscribe', 'mailpoet');
  289. }
  290. }
  291. private function getConfirmContent() {
  292. if ($this->isPreview() || $this->subscriber !== null) {
  293. return $this->wp->__("Yup, we've added you to our email list. You'll hear from us shortly.", 'mailpoet');
  294. }
  295. }
  296. public function getManageContent() {
  297. if ($this->isPreview()) {
  298. $subscriber = Subscriber::create();
  299. $subscriber->hydrate([
  300. 'email' => self::DEMO_EMAIL,
  301. 'first_name' => 'John',
  302. 'last_name' => 'Doe',
  303. 'link_token' => 'bfd0889dbc7f081e171fa0cee7401df2',
  304. ]);
  305. } else if ($this->subscriber !== null) {
  306. $subscriber = $this->subscriber
  307. ->withCustomFields()
  308. ->withSubscriptions();
  309. } else {
  310. return $this->wp->__('Subscription management form is only available to mailing lists subscribers.', 'mailpoet');
  311. }
  312. $formStatus = isset($_GET['success']) && $_GET['success']
  313. ? ManageSubscriptionFormRenderer::FORM_STATE_SUCCESS
  314. : ManageSubscriptionFormRenderer::FORM_STATE_NOT_SUBMITTED;
  315. return $this->wp->applyFilters(
  316. 'mailpoet_manage_subscription_page',
  317. $this->manageSubscriptionFormRenderer->renderForm($subscriber, $formStatus)
  318. );
  319. }
  320. private function getUnsubscribeContent() {
  321. $content = '';
  322. if ($this->isPreview() || $this->subscriber !== null) {
  323. $content .= '<p>' . __('Accidentally unsubscribed?', 'mailpoet') . ' <strong>';
  324. $content .= '[mailpoet_manage]';
  325. $content .= '</strong></p>';
  326. }
  327. return $content;
  328. }
  329. private function getConfirmUnsubscribeContent() {
  330. if (!$this->isPreview() && $this->subscriber === null) {
  331. return '';
  332. }
  333. $queueId = isset($this->data['queueId']) ? (int)$this->data['queueId'] : null;
  334. $subscriberEntity = $this->subscriber ? $this->subscribersRepository->findOneById($this->subscriber->id) : null;
  335. $unsubscribeUrl = $this->subscriptionUrlFactory->getUnsubscribeUrl($subscriberEntity, $queueId);
  336. $templateData = [
  337. 'unsubscribeUrl' => $unsubscribeUrl,
  338. ];
  339. return $this->wp->applyFilters(
  340. 'mailpoet_unsubscribe_confirmation_page',
  341. $this->templateRenderer->render('subscription/confirm_unsubscribe.html', $templateData),
  342. $unsubscribeUrl
  343. );
  344. }
  345. public function getManageLink($params) {
  346. if (!$this->subscriber instanceof Subscriber) return __('Link to subscription management page is only available to mailing lists subscribers.', 'mailpoet');
  347. // get label or display default label
  348. $text = (
  349. isset($params['text'])
  350. ? htmlspecialchars($params['text'])
  351. : $this->wp->__('Manage your subscription', 'mailpoet')
  352. );
  353. $subscriberEntity = $this->subscribersRepository->findOneById($this->subscriber->id);
  354. return '<a href="' . $this->subscriptionUrlFactory->getManageUrl($subscriberEntity) . '">' . $text . '</a>';
  355. }
  356. }