PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / Config / Populator.php
mailpoet / lib / Config Last commit date
PopulatorData 3 months ago AccessControl.php 2 years ago Activator.php 2 months ago AssetsLoader.php 3 weeks ago Capabilities.php 3 months ago Changelog.php 2 months ago DeactivationPoll.php 3 years ago DeferredAdminNotices.php 2 months ago Env.php 6 months ago Hooks.php 1 month ago HooksWooCommerce.php 1 month ago Initializer.php 1 month ago Installer.php 10 months ago Localizer.php 3 years ago Menu.php 1 month ago PersonalDataErasers.php 1 month ago PersonalDataExporters.php 1 month ago PluginActivatedHook.php 3 years ago Populator.php 3 weeks ago PrivacyPolicy.php 1 month ago Renderer.php 1 year ago RendererFactory.php 3 years ago RequirementsChecker.php 2 months ago Router.php 2 months ago ServicesChecker.php 3 years ago Shortcodes.php 1 month ago SilentUpgraderSkin.php 3 years ago SubscriberChangesNotifier.php 2 months ago TranslationUpdater.php 3 months ago TwigEnvironment.php 1 year ago TwigFileSystemCache.php 3 years ago Updater.php 1 week ago index.php 3 years ago
Populator.php
799 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Config;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Captcha\CaptchaConstants;
9 use MailPoet\Captcha\CaptchaRenderer;
10 use MailPoet\Cron\CronTrigger;
11 use MailPoet\Cron\Workers\AuthorizedSendingEmailsCheck;
12 use MailPoet\Cron\Workers\BackfillEngagementData;
13 use MailPoet\Cron\Workers\InactiveSubscribersMaintenance;
14 use MailPoet\Cron\Workers\Mixpanel;
15 use MailPoet\Cron\Workers\NewsletterTemplateThumbnails;
16 use MailPoet\Cron\Workers\StatsNotifications\Worker;
17 use MailPoet\Cron\Workers\SubscriberLinkTokens;
18 use MailPoet\Cron\Workers\SubscribersLastEngagement;
19 use MailPoet\Cron\Workers\SubscribersSegmentsCountSync;
20 use MailPoet\Cron\Workers\Tracks;
21 use MailPoet\Cron\Workers\UnsubscribeTokens;
22 use MailPoet\Doctrine\WPDB\Connection;
23 use MailPoet\Entities\NewsletterEntity;
24 use MailPoet\Entities\NewsletterOptionFieldEntity;
25 use MailPoet\Entities\NewsletterTemplateEntity;
26 use MailPoet\Entities\ScheduledTaskEntity;
27 use MailPoet\Entities\SegmentEntity;
28 use MailPoet\Entities\StatisticsFormEntity;
29 use MailPoet\Entities\SubscriberEntity;
30 use MailPoet\Entities\UserFlagEntity;
31 use MailPoet\Mailer\MailerLog;
32 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
33 use MailPoet\Referrals\ReferralDetector;
34 use MailPoet\Segments\SegmentsRepository;
35 use MailPoet\Segments\WP;
36 use MailPoet\Services\Bridge;
37 use MailPoet\Settings\Pages;
38 use MailPoet\Settings\SettingsController;
39 use MailPoet\Settings\UserFlagsRepository;
40 use MailPoet\Subscribers\NewSubscriberNotificationMailer;
41 use MailPoet\Subscribers\Source;
42 use MailPoet\WP\Functions as WPFunctions;
43 use MailPoetVendor\Carbon\Carbon;
44 use MailPoetVendor\Doctrine\ORM\EntityManager;
45
46 class Populator {
47 public $prefix;
48 public $templates;
49 /** @var SettingsController */
50 private $settings;
51 /** @var WPFunctions */
52 private $wp;
53 /** @var CaptchaRenderer */
54 private $captchaRenderer;
55 /** @var ReferralDetector */
56 private $referralDetector;
57 const TEMPLATES_NAMESPACE = '\MailPoet\Config\PopulatorData\Templates\\';
58 /** @var WP */
59 private $wpSegment;
60 /** @var EntityManager */
61 private $entityManager;
62 /** @var ScheduledTasksRepository */
63 private $scheduledTasksRepository;
64 /** @var SegmentsRepository */
65 private $segmentsRepository;
66
67 public function __construct(
68 SettingsController $settings,
69 WPFunctions $wp,
70 CaptchaRenderer $captchaRenderer,
71 ReferralDetector $referralDetector,
72 EntityManager $entityManager,
73 WP $wpSegment,
74 ScheduledTasksRepository $scheduledTasksRepository,
75 SegmentsRepository $segmentsRepository
76 ) {
77 $this->settings = $settings;
78 $this->wp = $wp;
79 $this->captchaRenderer = $captchaRenderer;
80 $this->wpSegment = $wpSegment;
81 $this->referralDetector = $referralDetector;
82 $this->prefix = Env::$dbPrefix;
83 $this->templates = [
84 'WelcomeBlank1Column',
85 'WelcomeBlank12Column',
86 'GiftWelcome',
87 'Minimal',
88 'Phone',
89 'Sunglasses',
90 'RealEstate',
91 'AppWelcome',
92 'FoodBox',
93 'Poet',
94 'PostNotificationsBlank1Column',
95 'ModularStyleStories',
96 'RssSimpleNews',
97 'NotSoMedium',
98 'WideStoryLayout',
99 'IndustryConference',
100 'ScienceWeekly',
101 'NewspaperTraditional',
102 'ClearNews',
103 'DogFood',
104 'KidsClothing',
105 'RockBand',
106 'WineCity',
107 'Fitness',
108 'Motor',
109 'Avocado',
110 'BookStoreWithCoupon',
111 'FlowersWithCoupon',
112 'NewsletterBlank1Column',
113 'NewsletterBlank12Column',
114 'NewsletterBlank121Column',
115 'NewsletterBlank13Column',
116 'SimpleText',
117 'TakeAHike',
118 'NewsDay',
119 'WorldCup',
120 'FestivalEvent',
121 'RetroComputingMagazine',
122 'Shoes',
123 'Music',
124 'Hotels',
125 'PieceOfCake',
126 'BuddhistTemple',
127 'Mosque',
128 'Synagogue',
129 'Faith',
130 'College',
131 'RenewableEnergy',
132 'PrimarySchool',
133 'ComputerRepair',
134 'YogaStudio',
135 'Retro',
136 'Charity',
137 'CityLocalNews',
138 'Coffee',
139 'Vlogger',
140 'Birds',
141 'Engineering',
142 'BrandingAgencyNews',
143 'WordPressTheme',
144 'Drone',
145 'FashionBlog',
146 'FashionStore',
147 'FashionBlogA',
148 'Photography',
149 'JazzClub',
150 'Guitarist',
151 'HealthyFoodBlog',
152 'Software',
153 'LifestyleBlogA',
154 'FashionShop',
155 'LifestyleBlogB',
156 'Painter',
157 'FarmersMarket',
158 'ConfirmInterestBeforeDeactivation',
159 'ConfirmInterestOrUnsubscribe',
160 'BirthdayCelebration',
161 ];
162 $this->entityManager = $entityManager;
163 $this->scheduledTasksRepository = $scheduledTasksRepository;
164 $this->segmentsRepository = $segmentsRepository;
165 }
166
167 public function up() {
168 $localizer = new Localizer();
169 $localizer->forceLoadWebsiteLocaleText();
170
171 $this->populateNewsletterOptionFields();
172 $this->populateNewsletterTemplates();
173
174 $this->createDefaultSegment();
175 $this->createDefaultSettings();
176 $this->createDefaultUsersFlags();
177 $this->createMailPoetPage();
178 $this->createSourceForSubscribers();
179 $this->scheduleInitialInactiveSubscribersCheck();
180 $this->scheduleAuthorizedSendingEmailsCheck();
181
182 $this->scheduleUnsubscribeTokens();
183 $this->scheduleSubscriberLinkTokens();
184 $this->detectReferral();
185 $this->scheduleSubscriberLastEngagementDetection();
186 $this->scheduleNewsletterTemplateThumbnails();
187 $this->scheduleBackfillEngagementData();
188 $this->scheduleSubscribersSegmentsCountSync();
189 $this->scheduleMixpanel();
190 $this->scheduleTracks();
191 }
192
193 private function createMailPoetPage() {
194 $page = Pages::getMailPoetPage(Pages::PAGE_SUBSCRIPTIONS);
195 if ($page === null) {
196 $mailpoetPageId = Pages::createMailPoetPage(Pages::PAGE_SUBSCRIPTIONS);
197 } else {
198 $mailpoetPageId = (int)$page->ID;
199 }
200
201 $subscription = $this->settings->get('subscription.pages', []);
202 if (empty($subscription)) {
203 $this->settings->set('subscription.pages', [
204 'unsubscribe' => $mailpoetPageId,
205 'manage' => $mailpoetPageId,
206 'confirmation' => $mailpoetPageId,
207 'confirm_unsubscribe' => $mailpoetPageId,
208 ]);
209 } else {
210 // For existing installations
211 $confirmUnsubPageSetting = empty($subscription['confirm_unsubscribe'])
212 ? $mailpoetPageId : $subscription['confirm_unsubscribe'];
213
214 $this->settings->set('subscription.pages', array_merge($subscription, [
215 'confirm_unsubscribe' => $confirmUnsubPageSetting,
216 ]));
217 }
218
219 $captchaPage = Pages::getMailPoetPage(Pages::PAGE_CAPTCHA);
220 if ($captchaPage === null) {
221 $captchaPageId = Pages::createMailPoetPage(Pages::PAGE_CAPTCHA);
222 } else {
223 $captchaPageId = $captchaPage->ID;
224 }
225
226 if (empty($this->settings->get('subscription.pages.captcha'))) {
227 $this->settings->set('subscription.pages.captcha', $captchaPageId);
228 }
229 }
230
231 private function createDefaultSettings() {
232 $settingsDbVersion = $this->settings->get('db_version');
233 $currentUser = $this->wp->wpGetCurrentUser();
234
235 // set cron trigger option to default method
236 if (!$this->settings->get(CronTrigger::SETTING_NAME)) {
237 $this->settings->set(CronTrigger::SETTING_NAME, [
238 'method' => CronTrigger::DEFAULT_METHOD,
239 ]);
240 }
241
242 // set default sender info based on current user
243 $currentUserName = $currentUser->display_name ?: ''; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
244 // parse current user name if an email is used
245 $senderName = explode('@', $currentUserName);
246 $senderName = reset($senderName);
247 // If current user is not set, default to admin email
248 $senderAddress = $currentUser->user_email ?: $this->wp->getOption('admin_email'); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
249 $defaultSender = [
250 'name' => $senderName,
251 'address' => $senderAddress ?: '',
252 ];
253 $savedSender = $this->settings->get('sender', []);
254
255 /**
256 * Set default from name & address
257 * In some cases ( like when the plugin is getting activated other than from WP Admin ) user data may not
258 * still be set at this stage, so setting the defaults for `sender` is postponed
259 */
260 if (empty($savedSender) || empty($savedSender['address'])) {
261 $this->settings->set('sender', $defaultSender);
262 }
263
264 // enable signup confirmation by default
265 if (!$this->settings->get('signup_confirmation')) {
266 $this->settings->set('signup_confirmation', [
267 'enabled' => true,
268 ]);
269 }
270
271 // set installation date
272 if (!$this->settings->get('installed_at')) {
273 $this->settings->set('installed_at', date("Y-m-d H:i:s"));
274 }
275
276 // set captcha settings
277 $captcha = $this->settings->get('captcha');
278 $reCaptcha = $this->settings->get('re_captcha');
279 if (empty($captcha)) {
280 $captchaType = CaptchaConstants::TYPE_DISABLED;
281 if (!empty($reCaptcha['enabled'])) {
282 $captchaType = CaptchaConstants::TYPE_RECAPTCHA;
283 } elseif ($this->captchaRenderer->isSupported()) {
284 $captchaType = CaptchaConstants::TYPE_BUILTIN;
285 }
286 $this->settings->set('captcha', [
287 'type' => $captchaType,
288 'recaptcha_site_token' => !empty($reCaptcha['site_token']) ? $reCaptcha['site_token'] : '',
289 'recaptcha_secret_token' => !empty($reCaptcha['secret_token']) ? $reCaptcha['secret_token'] : '',
290 'turnstile_site_token' => '',
291 'turnstile_secret_token' => '',
292 ]);
293 }
294
295 $subscriberEmailNotification = $this->settings->get(NewSubscriberNotificationMailer::SETTINGS_KEY);
296 if (empty($subscriberEmailNotification)) {
297 $sender = $this->settings->get('sender', []);
298 $this->settings->set('subscriber_email_notification', [
299 'enabled' => true,
300 'automated' => true,
301 'address' => isset($sender['address']) ? $sender['address'] : null,
302 ]);
303 }
304
305 $statsNotifications = $this->settings->get(Worker::SETTINGS_KEY);
306 if (empty($statsNotifications)) {
307 $sender = $this->settings->get('sender', []);
308 $this->settings->set(Worker::SETTINGS_KEY, [
309 'enabled' => true,
310 'address' => isset($sender['address']) ? $sender['address'] : null,
311 ]);
312 }
313
314 $woocommerceOptinOnCheckout = $this->settings->get('woocommerce.optin_on_checkout');
315 $legacyLabelText = _x('Yes, I would like to be added to your mailing list', "default email opt-in message displayed on checkout page for ecommerce websites", 'mailpoet');
316 $currentLabelText = _x('I would like to receive exclusive emails with discounts and product information', "default email opt-in message displayed on checkout page for ecommerce websites", 'mailpoet');
317 if (empty($woocommerceOptinOnCheckout)) {
318 $this->settings->set('woocommerce.optin_on_checkout', [
319 'enabled' => empty($settingsDbVersion), // enable on new installs only
320 'message' => $currentLabelText,
321 'position' => Hooks::DEFAULT_OPTIN_POSITION,
322 ]);
323 } elseif (isset($woocommerceOptinOnCheckout['message']) && $woocommerceOptinOnCheckout['message'] === $legacyLabelText) {
324 $this->settings->set('woocommerce.optin_on_checkout.message', $currentLabelText);
325 }
326 // reset mailer log
327 MailerLog::resetMailerLog();
328 }
329
330 private function createDefaultUsersFlags() {
331 $prefix = 'user_seen_editor_tutorial';
332 $prefixLength = strlen($prefix);
333 foreach ($this->settings->getAll() as $name => $value) {
334 if (substr($name, 0, $prefixLength) === $prefix) {
335 $userId = substr($name, $prefixLength);
336 $this->createOrUpdateUserFlag($userId, 'editor_tutorial_seen', $value);
337 $this->settings->delete($name);
338 }
339 }
340 }
341
342 private function createOrUpdateUserFlag($userId, $name, $value) {
343 $userFlagsRepository = \MailPoet\DI\ContainerWrapper::getInstance(WP_DEBUG)->get(UserFlagsRepository::class);
344 $flag = $userFlagsRepository->findOneBy([
345 'userId' => $userId,
346 'name' => $name,
347 ]);
348
349 if (!$flag) {
350 $flag = new UserFlagEntity();
351 $flag->setUserId($userId);
352 $flag->setName($name);
353 $userFlagsRepository->persist($flag);
354 }
355 $flag->setValue($value);
356 $userFlagsRepository->flush();
357 }
358
359 private function createDefaultSegment() {
360 // WP Users segment
361 $this->segmentsRepository->getWPUsersSegment();
362 // WooCommerce customers segment
363 $this->segmentsRepository->getWooCommerceSegment();
364
365 // Synchronize WP Users
366 $this->wpSegment->synchronizeUsers();
367
368 // Default segment
369 $defaultSegment = $this->segmentsRepository->findOneBy(
370 ['type' => 'default'],
371 ['id' => 'ASC']
372 );
373
374 if (!$defaultSegment instanceof SegmentEntity) {
375 $defaultSegment = new SegmentEntity(
376 __('Newsletter mailing list', 'mailpoet'),
377 SegmentEntity::TYPE_DEFAULT,
378 __('This list is automatically created when you install MailPoet.', 'mailpoet')
379 );
380 $this->segmentsRepository->persist($defaultSegment);
381 $this->segmentsRepository->flush();
382 }
383
384 return $defaultSegment;
385 }
386
387 private function populateNewsletterOptionFields() {
388 $optionFields = [
389 [
390 'name' => 'isScheduled',
391 'newsletter_type' => 'standard',
392 ],
393 [
394 'name' => 'scheduledAt',
395 'newsletter_type' => 'standard',
396 ],
397 [
398 'name' => 'event',
399 'newsletter_type' => 'welcome',
400 ],
401 [
402 'name' => 'segment',
403 'newsletter_type' => 'welcome',
404 ],
405 [
406 'name' => 'role',
407 'newsletter_type' => 'welcome',
408 ],
409 [
410 'name' => 'afterTimeNumber',
411 'newsletter_type' => 'welcome',
412 ],
413 [
414 'name' => 'afterTimeType',
415 'newsletter_type' => 'welcome',
416 ],
417 [
418 'name' => 'intervalType',
419 'newsletter_type' => 'notification',
420 ],
421 [
422 'name' => 'timeOfDay',
423 'newsletter_type' => 'notification',
424 ],
425 [
426 'name' => 'weekDay',
427 'newsletter_type' => 'notification',
428 ],
429 [
430 'name' => 'monthDay',
431 'newsletter_type' => 'notification',
432 ],
433 [
434 'name' => 'nthWeekDay',
435 'newsletter_type' => 'notification',
436 ],
437 [
438 'name' => 'schedule',
439 'newsletter_type' => 'notification',
440 ],
441 [
442 'name' => 'group',
443 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
444 ],
445 [
446 'name' => 'group',
447 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATION,
448 ],
449 [
450 'name' => 'group',
451 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL,
452 ],
453 [
454 'name' => 'event',
455 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
456 ],
457 [
458 'name' => 'event',
459 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATION,
460 ],
461 [
462 'name' => 'event',
463 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL,
464 ],
465 [
466 'name' => 'sendTo',
467 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
468 ],
469 [
470 'name' => 'segment',
471 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
472 ],
473 [
474 'name' => 'afterTimeNumber',
475 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
476 ],
477 [
478 'name' => 'afterTimeType',
479 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
480 ],
481 [
482 'name' => 'meta',
483 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATIC,
484 ],
485 [
486 'name' => 'afterTimeNumber',
487 'newsletter_type' => NewsletterEntity::TYPE_RE_ENGAGEMENT,
488 ],
489 [
490 'name' => 'afterTimeType',
491 'newsletter_type' => NewsletterEntity::TYPE_RE_ENGAGEMENT,
492 ],
493 [
494 'name' => 'automationId',
495 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATION,
496 ],
497 [
498 'name' => 'automationStepId',
499 'newsletter_type' => NewsletterEntity::TYPE_AUTOMATION,
500 ],
501 [
502 'name' => NewsletterOptionFieldEntity::NAME_FILTER_SEGMENT_ID,
503 'newsletter_type' => NewsletterEntity::TYPE_STANDARD,
504 ],
505 [
506 'name' => NewsletterOptionFieldEntity::NAME_SCHEDULE_MODE,
507 'newsletter_type' => NewsletterEntity::TYPE_STANDARD,
508 ],
509 [
510 'name' => NewsletterOptionFieldEntity::NAME_SCHEDULED_LOCAL_DATE,
511 'newsletter_type' => NewsletterEntity::TYPE_STANDARD,
512 ],
513 [
514 'name' => NewsletterOptionFieldEntity::NAME_SCHEDULED_LOCAL_TIME,
515 'newsletter_type' => NewsletterEntity::TYPE_STANDARD,
516 ],
517 [
518 'name' => NewsletterOptionFieldEntity::NAME_EXCLUDE_FROM_ARCHIVE,
519 'newsletter_type' => NewsletterEntity::TYPE_STANDARD,
520 ],
521 [
522 'name' => NewsletterOptionFieldEntity::NAME_FILTER_SEGMENT_ID,
523 'newsletter_type' => NewsletterEntity::TYPE_RE_ENGAGEMENT,
524 ],
525 [
526 'name' => NewsletterOptionFieldEntity::NAME_FILTER_SEGMENT_ID,
527 'newsletter_type' => NewsletterEntity::TYPE_NOTIFICATION,
528 ],
529 [
530 'name' => NewsletterOptionFieldEntity::NAME_SHARE_VISIBILITY,
531 'newsletter_type' => NewsletterEntity::TYPE_STANDARD,
532 ],
533 ];
534
535 // 1. Load all existing option fields from the database.
536 $tableName = $this->entityManager->getClassMetadata(NewsletterOptionFieldEntity::class)->getTableName();
537 $connection = $this->entityManager->getConnection();
538 $existingOptionFields = $connection->createQueryBuilder()
539 ->select('f.name, f.newsletter_type')
540 ->from($tableName, 'f')
541 ->executeQuery()
542 ->fetchAllAssociative();
543
544 // 2. Insert new option fields using a single query (good for first installs).
545 $inserts = array_udiff(
546 $optionFields,
547 $existingOptionFields,
548 fn($a, $b) => [$a['name'], $a['newsletter_type']] <=> [$b['name'], $b['newsletter_type']]
549 );
550 if ($inserts) {
551 $placeholders = implode(',', array_fill(0, count($inserts), '(?, ?)'));
552 $connection->executeStatement(
553 "INSERT INTO $tableName (name, newsletter_type) VALUES $placeholders",
554 array_merge(
555 ...array_map(
556 fn($of) => [$of['name'], $of['newsletter_type']],
557 $inserts
558 )
559 )
560 );
561 }
562 }
563
564 private function populateNewsletterTemplates(): void {
565 // 1. Load templates from the file system.
566 $templates = [];
567 foreach ($this->templates as $template) {
568 $template = self::TEMPLATES_NAMESPACE . $template;
569 $template = new $template(Env::$assetsUrl);
570 // @phpstan-ignore method.notFound (template classes live under PopulatorData/Templates which is excluded from analysis; each declares its own get())
571 $templates[] = $template->get();
572 }
573
574 // 2. Load existing corresponding (readonly) templates from the database.
575 $tableName = $this->entityManager->getClassMetadata(NewsletterTemplateEntity::class)->getTableName();
576 $connection = $this->entityManager->getConnection();
577 $existingTemplates = $connection->createQueryBuilder()
578 ->select('t.name, t.categories, t.readonly, t.thumbnail, t.body')
579 ->from($tableName, 't')
580 ->where('t.readonly = 1')
581 ->executeQuery()
582 ->fetchAllAssociativeIndexed();
583
584 // 3. Compare the existing and file system templates.
585 $inserts = [];
586 $updates = [];
587 foreach ($templates as $template) {
588 $existing = $existingTemplates[$template['name']] ?? null;
589 if (
590 $existing
591 && $existing['categories'] === $template['categories']
592 && $existing['body'] === $template['body']
593 && $existing['thumbnail'] === $template['thumbnail']
594 ) {
595 continue;
596 }
597
598 if ($existing) {
599 $updates[] = $template;
600 } else {
601 $inserts[] = $template;
602 }
603 }
604
605 // 4. Update existing templates.
606 foreach ($updates as $template) {
607 $connection->update($tableName, $template, ['name' => $template['name']]);
608 }
609
610 // 5. Insert new templates using a single query (good for first installs).
611 if ($inserts) {
612 $placeholders = implode(',', array_fill(0, count($inserts), '(?, ?, ?, ?, ?)'));
613 $connection->executeStatement(
614 "INSERT INTO $tableName (name, categories, readonly, thumbnail, body) VALUES $placeholders",
615 array_merge(
616 ...array_map(
617 fn($t) => [$t['name'], $t['categories'], $t['readonly'], $t['thumbnail'], $t['body']],
618 $inserts
619 )
620 )
621 );
622 }
623
624 // 6. Remove duplicates.
625 // SQLite doesn't support JOIN in DELETE queries, we need to use a subquery.
626 // MySQL doesn't support DELETE with subqueries reading from the same table.
627 if (Connection::isSQLite()) {
628 $connection->executeStatement("
629 DELETE FROM $tableName WHERE id IN (
630 SELECT t1.id
631 FROM $tableName t1
632 JOIN $tableName t2 ON t1.id < t2.id AND t1.name = t2.name
633 WHERE t1.readonly = 1
634 AND t2.readonly = 1
635 )
636 ");
637 } else {
638 $connection->executeStatement("
639 DELETE t1
640 FROM $tableName t1, $tableName t2
641 WHERE t1.id < t2.id AND t1.name = t2.name
642 AND t1.readonly = 1
643 AND t2.readonly = 1
644 ");
645 }
646 }
647
648 private function createSourceForSubscribers() {
649 $statisticsFormTable = $this->entityManager->getClassMetadata(StatisticsFormEntity::class)->getTableName();
650 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
651
652 // Temporarily skip the queries in WP Playground.
653 // UPDATE with JOIN is not yet supported by the SQLite integration.
654 if (Connection::isSQLite()) {
655 return;
656 }
657
658 $this->entityManager->getConnection()->executeStatement(
659 ' UPDATE LOW_PRIORITY `' . $subscriberTable . '` subscriber ' .
660 ' JOIN `' . $statisticsFormTable . '` stats ON stats.subscriber_id=subscriber.id ' .
661 " SET `source` = '" . Source::FORM . "'" .
662 " WHERE `source` = '" . Source::UNKNOWN . "'"
663 );
664
665 $this->entityManager->getConnection()->executeStatement(
666 'UPDATE LOW_PRIORITY `' . $subscriberTable . '`' .
667 " SET `source` = '" . Source::WORDPRESS_USER . "'" .
668 " WHERE `source` = '" . Source::UNKNOWN . "'" .
669 ' AND `wp_user_id` IS NOT NULL'
670 );
671
672 $this->entityManager->getConnection()->executeStatement(
673 'UPDATE LOW_PRIORITY `' . $subscriberTable . '`' .
674 " SET `source` = '" . Source::WOOCOMMERCE_USER . "'" .
675 " WHERE `source` = '" . Source::UNKNOWN . "'" .
676 ' AND `is_woocommerce_user` = 1'
677 );
678 }
679
680 private function scheduleInitialInactiveSubscribersCheck() {
681 $this->scheduleTask(
682 InactiveSubscribersMaintenance::TASK_TYPE,
683 Carbon::now()->millisecond(0)->addHour()
684 );
685 }
686
687 private function scheduleAuthorizedSendingEmailsCheck() {
688 if (!Bridge::isMPSendingServiceEnabled()) {
689 return;
690 }
691 $this->scheduleTask(
692 AuthorizedSendingEmailsCheck::TASK_TYPE,
693 Carbon::now()->millisecond(0)
694 );
695 }
696
697 private function scheduleUnsubscribeTokens() {
698 $this->scheduleTask(
699 UnsubscribeTokens::TASK_TYPE,
700 Carbon::now()->millisecond(0)
701 );
702 }
703
704 private function scheduleSubscriberLinkTokens() {
705 $this->scheduleTask(
706 SubscriberLinkTokens::TASK_TYPE,
707 Carbon::now()->millisecond(0)
708 );
709 }
710
711 private function scheduleMixpanel() {
712 $this->scheduleTask(Mixpanel::TASK_TYPE, Carbon::now()->millisecond(0));
713 }
714
715 private function scheduleTracks(): void {
716 $this->scheduleTask(Tracks::TASK_TYPE, Carbon::now()->millisecond(0));
717 }
718
719 private function scheduleTask($type, $datetime, $priority = null) {
720 $task = $this->scheduledTasksRepository->findOneBy(
721 [
722 'type' => $type,
723 'status' => [ScheduledTaskEntity::STATUS_SCHEDULED, null],
724 ]
725 );
726
727 if ($task) {
728 return true;
729 }
730
731 $task = new ScheduledTaskEntity();
732 $task->setType($type);
733 $task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
734 $task->setScheduledAt($datetime);
735
736 if ($priority !== null) {
737 $task->setPriority($priority);
738 }
739
740 $this->scheduledTasksRepository->persist($task);
741 $this->scheduledTasksRepository->flush();
742 }
743
744 private function detectReferral() {
745 $this->referralDetector->detect();
746 }
747
748 private function scheduleSubscriberLastEngagementDetection() {
749 if (version_compare((string)$this->settings->get('db_version', '3.72.1'), '3.72.0', '>')) {
750 return;
751 }
752 $this->scheduleTask(
753 SubscribersLastEngagement::TASK_TYPE,
754 Carbon::now()->millisecond(0)
755 );
756 }
757
758 private function scheduleNewsletterTemplateThumbnails() {
759 $this->scheduleTask(
760 NewsletterTemplateThumbnails::TASK_TYPE,
761 Carbon::now()->millisecond(0),
762 ScheduledTaskEntity::PRIORITY_LOW
763 );
764 }
765
766 private function scheduleBackfillEngagementData(): void {
767 $this->scheduleOneTimeBackfillTask(BackfillEngagementData::TASK_TYPE);
768 }
769
770 private function scheduleSubscribersSegmentsCountSync(): void {
771 $this->scheduleOneTimeBackfillTask(SubscribersSegmentsCountSync::TASK_TYPE);
772 }
773
774 /**
775 * Schedule a one-time backfill task unless a healthy instance already exists.
776 * Uses an allowlist of healthy statuses rather than finding any task, so a
777 * failed or cancelled task does not permanently block re-scheduling.
778 */
779 private function scheduleOneTimeBackfillTask(string $type): void {
780 $existingTask = $this->scheduledTasksRepository->findOneBy(
781 [
782 'type' => $type,
783 'status' => [
784 ScheduledTaskEntity::STATUS_SCHEDULED,
785 null, // running (stored as null for historical reasons)
786 ScheduledTaskEntity::STATUS_COMPLETED,
787 ScheduledTaskEntity::STATUS_PAUSED,
788 ScheduledTaskEntity::STATUS_CLI,
789 ],
790 ]
791 );
792
793 if ($existingTask) {
794 return;
795 }
796 $this->scheduleTask($type, Carbon::now()->millisecond(0));
797 }
798 }
799