PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / trunk
MailPoet – Newsletters, Email Marketing, and Automation vtrunk
5.37.0 5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Cron / Workers / StatsNotifications / Worker.php
mailpoet / lib / Cron / Workers / StatsNotifications Last commit date
AutomatedEmails.php 3 months ago NewsletterLinkRepository.php 3 months ago Scheduler.php 3 months ago StatsNotificationsRepository.php 2 years ago Worker.php 1 day ago index.php 3 years ago
Worker.php
255 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Cron\Workers\StatsNotifications;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Config\Renderer;
9 use MailPoet\Config\ServicesChecker;
10 use MailPoet\Cron\CronHelper;
11 use MailPoet\EmailEditor\Integrations\MailPoet\PersonalizationTags\PersonalizationTagLinkResolver;
12 use MailPoet\Entities\NewsletterEntity;
13 use MailPoet\Entities\NewsletterLinkEntity;
14 use MailPoet\Entities\ScheduledTaskEntity;
15 use MailPoet\Entities\SendingQueueEntity;
16 use MailPoet\Entities\StatsNotificationEntity;
17 use MailPoet\Mailer\MailerFactory;
18 use MailPoet\Mailer\MetaInfo;
19 use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
20 use MailPoet\Settings\SettingsController;
21 use MailPoet\Subscribers\SubscribersRepository;
22 use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
23 use MailPoet\WP\Functions as WPFunctions;
24 use MailPoet\WPCOM\DotcomHelperFunctions;
25 use MailPoetVendor\Carbon\Carbon;
26 use MailPoetVendor\Doctrine\ORM\EntityManager;
27
28 class Worker {
29
30 const TASK_TYPE = 'stats_notification';
31 const SETTINGS_KEY = 'stats_notifications';
32 const BATCH_SIZE = 5;
33
34 /** @var Renderer */
35 private $renderer;
36
37 /** @var MailerFactory */
38 private $mailerFactory;
39
40 /** @var SettingsController */
41 private $settings;
42
43 /** @var CronHelper */
44 private $cronHelper;
45
46 /** @var MetaInfo */
47 private $mailerMetaInfo;
48
49 /** @var StatsNotificationsRepository */
50 private $repository;
51
52 /** @var EntityManager */
53 private $entityManager;
54
55 /** @var NewsletterLinkRepository */
56 private $newsletterLinkRepository;
57
58 /** @var NewsletterStatisticsRepository */
59 private $newsletterStatisticsRepository;
60
61 /** @var SubscribersFeature */
62 private $subscribersFeature;
63
64 /** @var SubscribersRepository */
65 private $subscribersRepository;
66
67 /** @var ServicesChecker */
68 private $servicesChecker;
69
70 /** @var DotcomHelperFunctions */
71 private $dotcomHelperFunctions;
72
73 private PersonalizationTagLinkResolver $personalizationTagLinkResolver;
74
75 public function __construct(
76 MailerFactory $mailerFactory,
77 Renderer $renderer,
78 SettingsController $settings,
79 CronHelper $cronHelper,
80 MetaInfo $mailerMetaInfo,
81 StatsNotificationsRepository $repository,
82 NewsletterLinkRepository $newsletterLinkRepository,
83 NewsletterStatisticsRepository $newsletterStatisticsRepository,
84 EntityManager $entityManager,
85 SubscribersFeature $subscribersFeature,
86 SubscribersRepository $subscribersRepository,
87 ServicesChecker $servicesChecker,
88 DotcomHelperFunctions $dotcomHelperFunctions,
89 PersonalizationTagLinkResolver $personalizationTagLinkResolver
90 ) {
91 $this->renderer = $renderer;
92 $this->mailerFactory = $mailerFactory;
93 $this->settings = $settings;
94 $this->cronHelper = $cronHelper;
95 $this->mailerMetaInfo = $mailerMetaInfo;
96 $this->repository = $repository;
97 $this->entityManager = $entityManager;
98 $this->newsletterLinkRepository = $newsletterLinkRepository;
99 $this->newsletterStatisticsRepository = $newsletterStatisticsRepository;
100 $this->subscribersFeature = $subscribersFeature;
101 $this->subscribersRepository = $subscribersRepository;
102 $this->servicesChecker = $servicesChecker;
103 $this->dotcomHelperFunctions = $dotcomHelperFunctions;
104 $this->personalizationTagLinkResolver = $personalizationTagLinkResolver;
105 }
106
107 /** @throws \Exception */
108 public function process($timer = false) {
109 $timer = $timer ?: microtime(true);
110 $settings = $this->settings->get(self::SETTINGS_KEY);
111 // Cleanup potential orphaned task created due bug MAILPOET-3015
112 $this->repository->deleteOrphanedScheduledTasks();
113 foreach ($this->repository->findScheduled(self::BATCH_SIZE) as $statsNotificationEntity) {
114 try {
115 $extraParams = [
116 'meta' => $this->mailerMetaInfo->getStatsNotificationMetaInfo(),
117 ];
118 $this->mailerFactory->getDefaultMailer()->send($this->constructNewsletter($statsNotificationEntity, $settings), $settings['address'], $extraParams);
119 } catch (\Exception $e) {
120 if (WP_DEBUG) {
121 throw $e;
122 }
123 } finally {
124 $task = $statsNotificationEntity->getTask();
125 if ($task instanceof ScheduledTaskEntity) {
126 $this->markTaskAsFinished($task);
127 }
128 }
129 $this->cronHelper->enforceExecutionLimit($timer);
130 }
131 }
132
133 private function constructNewsletter(StatsNotificationEntity $statsNotificationEntity, array $settings) {
134 $newsletter = $statsNotificationEntity->getNewsletter();
135 if (!$newsletter instanceof NewsletterEntity) {
136 throw new \RuntimeException('Missing newsletter entity for statistic notification.');
137 }
138 $link = $this->newsletterLinkRepository->findTopLinkForNewsletter((int)$newsletter->getId());
139 $sendingQueue = $newsletter->getLatestQueue();
140 if (!$sendingQueue instanceof SendingQueueEntity) {
141 throw new \RuntimeException('Missing sending queue entity for statistic notification.');
142 }
143 $context = $this->prepareContext($newsletter, $sendingQueue, $link, $settings);
144 $subject = $sendingQueue->getNewsletterRenderedSubject();
145 if ($this->dotcomHelperFunctions->isGarden()) {
146 // translators: %s is the name of the email campaign.
147 $emailSubject = sprintf(__('Campaign performance summary: %s', 'mailpoet'), $subject);
148 } else {
149 // translators: %s is the subject of the email.
150 $emailSubject = sprintf(_x('Stats for email %s', 'title of an automatic email containing statistics (newsletter open rate, click rate, etc)', 'mailpoet'), $subject);
151 }
152 return [
153 'subject' => $emailSubject,
154 'body' => [
155 'html' => $this->renderer->render('emails/statsNotification.html', $context),
156 'text' => $this->renderer->render('emails/statsNotification.txt', $context),
157 ],
158 ];
159 }
160
161 private function prepareContext(NewsletterEntity $newsletter, SendingQueueEntity $sendingQueue, ?NewsletterLinkEntity $link = null, array $settings = []) {
162 $statistics = $this->newsletterStatisticsRepository->getStatistics($newsletter);
163 $totalSentCount = $statistics->getTotalSentCount() ?: 1;
164 $clicked = ($statistics->getClickCount() * 100) / $totalSentCount;
165 $opened = ($statistics->getOpenCount() * 100) / $totalSentCount;
166 $machineOpened = ($statistics->getMachineOpenCount() * 100) / $totalSentCount;
167 $unsubscribed = ($statistics->getUnsubscribeCount() * 100) / $totalSentCount;
168 $bounced = ($statistics->getBounceCount() * 100) / $totalSentCount;
169 $subject = $sendingQueue->getNewsletterRenderedSubject();
170 $subscribersCount = $this->subscribersRepository->getTotalSubscribers();
171 $hasValidApiKey = $this->subscribersFeature->hasValidApiKey();
172 $context = [
173 'subject' => $subject,
174 'preheader' => sprintf(
175 // translators: %1$s is the percentage of clicks, %2$s the percentage of opens and %3$s the number of unsubscribes.
176 _x(
177 '%1$s%% clicks, %2$s%% opens, %3$s%% unsubscribes in a nutshell.',
178 'newsletter open rate, click rate and unsubscribe rate',
179 'mailpoet'
180 ),
181 number_format($clicked, 2),
182 number_format($opened, 2),
183 number_format($unsubscribed, 2)
184 ),
185 'topLinkClicks' => 0,
186 'linkSettings' => WPFunctions::get()->applyFilters(
187 'mailpoet_stats_notification_link_settings',
188 WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-settings#basics')
189 ),
190 'linkStats' => WPFunctions::get()->applyFilters(
191 'mailpoet_stats_notification_link_stats',
192 WPFunctions::get()->getSiteUrl(null, '/wp-admin/admin.php?page=mailpoet-newsletters&stats=' . $newsletter->getId()),
193 $newsletter->getId()
194 ),
195 'clicked' => $clicked,
196 'opened' => $opened,
197 'machineOpened' => $machineOpened,
198 'unsubscribed' => $unsubscribed,
199 'bounced' => $bounced,
200 'subscribersLimitReached' => $this->subscribersFeature->check(),
201 'hasValidApiKey' => $hasValidApiKey,
202 'subscribersLimit' => $this->subscribersFeature->getSubscribersLimit(),
203 'upgradeNowLink' => $hasValidApiKey
204 ? 'https://account.mailpoet.com/orders/upgrade/' . $this->servicesChecker->generatePartialApiKey()
205 : 'https://account.mailpoet.com/?s=' . ($subscribersCount + 1),
206 ];
207 if ($link) {
208 $context['topLinkClicks'] = $link->getTotalClicksCount();
209 $mappings = self::getShortcodeLinksMapping();
210 $context['topLink'] = $mappings[$link->getUrl()]
211 ?? $this->personalizationTagLinkResolver->getDisplayName($link->getUrl())
212 ?? $link->getUrl();
213 }
214 $context['blogName'] = WPFunctions::get()->getBloginfo('name');
215 $context['recipientFirstName'] = $this->getRecipientFirstName($settings['address'] ?? '');
216 return $context;
217 }
218
219 private function getRecipientFirstName(string $email): string {
220 if (empty($email)) {
221 return '';
222 }
223 $user = WPFunctions::get()->getUserBy('email', $email);
224 if (!$user) {
225 return '';
226 }
227 $firstName = $user->first_name; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
228 if (!empty($firstName)) {
229 return $firstName;
230 }
231 $displayName = $user->display_name; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
232 return !empty($displayName) ? $displayName : '';
233 }
234
235 private function markTaskAsFinished(ScheduledTaskEntity $task) {
236 $task->setStatus(ScheduledTaskEntity::STATUS_COMPLETED);
237 $task->setProcessedAt(Carbon::now()->millisecond(0));
238 $task->setScheduledAt(null);
239 $this->entityManager->flush();
240 }
241
242 /**
243 * Labels for legacy shortcode system links. Block-email system links are stored as
244 * personalization tag tokens and labelled with the tag name instead.
245 */
246 public static function getShortcodeLinksMapping() {
247 return [
248 NewsletterLinkEntity::UNSUBSCRIBE_LINK_SHORT_CODE => __('Unsubscribe link', 'mailpoet'),
249 NewsletterLinkEntity::INSTANT_UNSUBSCRIBE_LINK_SHORT_CODE => __('Unsubscribe link (without confirmation)', 'mailpoet'),
250 '[link:subscription_manage_url]' => __('Manage subscription link', 'mailpoet'),
251 '[link:newsletter_view_in_browser_url]' => __('View in browser link', 'mailpoet'),
252 ];
253 }
254 }
255