PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / Entities / SubscriberEntity.php
mailpoet / lib / Entities Last commit date
CustomFieldEntity.php 2 months ago DynamicSegmentFilterData.php 2 months ago DynamicSegmentFilterEntity.php 3 years ago FeatureFlagEntity.php 3 years ago FormEntity.php 1 week ago LogEntity.php 3 years ago NewsletterEntity.php 2 months ago NewsletterLinkEntity.php 3 years ago NewsletterOptionEntity.php 3 years ago NewsletterOptionFieldEntity.php 1 month ago NewsletterPostEntity.php 3 years ago NewsletterSegmentEntity.php 3 years ago NewsletterTemplateEntity.php 3 years ago ScheduledTaskEntity.php 1 month ago ScheduledTaskSubscriberEntity.php 1 year ago SegmentEntity.php 2 months ago SendingQueueEntity.php 2 months ago SettingEntity.php 3 years ago StatisticsBounceEntity.php 3 years ago StatisticsClickEntity.php 3 years ago StatisticsFormEntity.php 3 years ago StatisticsNewsletterEntity.php 1 year ago StatisticsOpenEntity.php 3 years ago StatisticsUnsubscribeEntity.php 2 months ago StatisticsWooCommercePurchaseEntity.php 2 years ago StatsNotificationEntity.php 3 years ago SubscriberCustomFieldEntity.php 3 years ago SubscriberEntity.php 4 days ago SubscriberIPEntity.php 3 years ago SubscriberSegmentEntity.php 3 years ago SubscriberTagEntity.php 4 years ago TagEntity.php 3 years ago UserAgentEntity.php 3 years ago UserFlagEntity.php 3 years ago WpPostEntity.php 2 years ago index.php 3 years ago
SubscriberEntity.php
852 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Entities;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use DateTimeInterface;
9 use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
10 use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
11 use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
12 use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
13 use MailPoet\Doctrine\EntityTraits\ValidationGroupsTrait;
14 use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
15 use MailPoetVendor\Doctrine\Common\Collections\Collection;
16 use MailPoetVendor\Doctrine\Common\Collections\Criteria;
17 use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
18 use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
19
20 /**
21 * @ORM\Entity()
22 * @ORM\Table(name="subscribers")
23 * @ORM\HasLifecycleCallbacks
24 * @ORM\EntityListeners({"\MailPoet\Doctrine\EventListeners\SubscriberListener"})
25 */
26 class SubscriberEntity {
27 // hook names
28 public const HOOK_SUBSCRIBER_CREATED = 'mailpoet_subscriber_created';
29 public const HOOK_SUBSCRIBER_DELETED = 'mailpoet_subscriber_deleted';
30 public const HOOK_SUBSCRIBER_UPDATED = 'mailpoet_subscriber_updated';
31 public const HOOK_SUBSCRIBER_STATUS_CHANGED = 'mailpoet_subscriber_status_changed';
32 public const HOOK_MULTIPLE_SUBSCRIBERS_CREATED = 'mailpoet_multiple_subscribers_created';
33 public const HOOK_MULTIPLE_SUBSCRIBERS_DELETED = 'mailpoet_multiple_subscribers_deleted';
34 public const HOOK_MULTIPLE_SUBSCRIBERS_UPDATED = 'mailpoet_multiple_subscribers_updated';
35 public const HOOK_SUBSCRIBERS_COUNT_CHANGED = 'mailpoet_subscribers_count_changed';
36
37 // statuses
38 const STATUS_BOUNCED = 'bounced';
39 const STATUS_INACTIVE = 'inactive';
40 const STATUS_SUBSCRIBED = 'subscribed';
41 const STATUS_UNCONFIRMED = 'unconfirmed';
42 const STATUS_UNSUBSCRIBED = 'unsubscribed';
43
44 // tracking consent
45 const TRACKING_CONSENT_UNKNOWN = 'unknown';
46 const TRACKING_CONSENT_GRANTED = 'granted';
47 const TRACKING_CONSENT_DENIED = 'denied';
48
49 const TRACKING_CONSENT_METHOD_FOOTER_LINK = 'footer_link';
50 const TRACKING_CONSENT_METHOD_MANAGE_PAGE = 'manage_page';
51 const TRACKING_CONSENT_METHOD_FORM = 'form';
52 const TRACKING_CONSENT_METHOD_ADMIN = 'admin';
53 const TRACKING_CONSENT_METHOD_IMPORT = 'import';
54
55 public const OBSOLETE_LINK_TOKEN_LENGTH = 6;
56 public const LINK_TOKEN_LENGTH = 32;
57 public const TIME_ZONE_FIELD_NAME = 'mailpoet_subscriber_timezone';
58 public const TIME_ZONE_SOURCE_FORM = 'form';
59 public const TIME_ZONE_SOURCE_MANUAL = 'manual';
60 public const TIME_ZONE_SOURCE_SITE_FALLBACK = 'site_fallback';
61 public const TIME_ZONE_CONFIDENCE_BROWSER = 90;
62 public const TIME_ZONE_CONFIDENCE_MANUAL = 100;
63
64 /** @var array<string,bool>|null */
65 private static $validTimeZones = null;
66
67 use AutoincrementedIdTrait;
68 use CreatedAtTrait;
69 use UpdatedAtTrait;
70 use DeletedAtTrait;
71 use ValidationGroupsTrait;
72
73 /**
74 * @ORM\Column(type="bigint", nullable=true)
75 * @var string|null
76 */
77 private $wpUserId;
78
79 /**
80 * @ORM\Column(type="boolean")
81 * @var bool
82 */
83 private $isWoocommerceUser = false;
84
85 /**
86 * CNIL/Garante: three states are legally distinct. `unknown` means we never
87 * asked — it is NOT consent, and under the opt-in regime it must not be
88 * treated as consent. How `unknown` is handled is a site setting; see
89 * TrackingConsentController.
90 *
91 * @ORM\Column(type="string", length=20)
92 * @Assert\Choice({"unknown", "granted", "denied"})
93 * @var string
94 */
95 private $trackingConsent = self::TRACKING_CONSENT_UNKNOWN;
96
97 /**
98 * @ORM\Column(type="datetimetz", nullable=true)
99 * @var DateTimeInterface|null
100 */
101 private $trackingConsentUpdatedAt;
102
103 /**
104 * @ORM\Column(type="string", length=40, nullable=true)
105 * @var string|null
106 */
107 private $trackingConsentMethod;
108
109 /**
110 * The exact wording shown when the choice was made. Required for proof of
111 * consent (CNIL §6: a record of each person's consent "as well as the
112 * conditions under which that consent was obtained").
113 *
114 * @ORM\Column(type="text", nullable=true)
115 * @var string|null
116 */
117 private $trackingConsentCopy;
118
119 /**
120 * @ORM\Column(type="string")
121 * @var string
122 */
123 private $firstName = '';
124
125 /**
126 * @ORM\Column(type="string")
127 * @var string
128 */
129 private $lastName = '';
130
131 /**
132 * @ORM\Column(type="string")
133 * @Assert\Email(groups={"Saving"})
134 * @Assert\NotBlank()
135 * @var string
136 */
137 private $email;
138
139 /**
140 * @ORM\Column(type="string")
141 * @var string
142 */
143 private $status = self::STATUS_UNCONFIRMED;
144
145 /**
146 * @ORM\Column(type="string", nullable=true)
147 * @var string|null
148 */
149 private $subscribedIp;
150
151 /**
152 * @ORM\Column(type="string", nullable=true)
153 * @var string|null
154 */
155 private $confirmedIp;
156
157 /**
158 * @ORM\Column(type="string", nullable=true)
159 * @var string|null
160 */
161 private $timeZone;
162
163 /**
164 * @ORM\Column(type="string", nullable=true)
165 * @var string|null
166 */
167 private $timeZoneSource;
168
169 /**
170 * @ORM\Column(type="integer", nullable=true)
171 * @var int|null
172 */
173 private $timeZoneConfidence;
174
175 /**
176 * @ORM\Column(type="datetimetz", nullable=true)
177 * @var DateTimeInterface|null
178 */
179 private $timeZoneUpdatedAt;
180
181 /**
182 * @ORM\Column(type="datetimetz", nullable=true)
183 * @var DateTimeInterface|null
184 */
185 private $confirmedAt;
186
187 /**
188 * @ORM\Column(type="datetimetz", nullable=true)
189 * @var DateTimeInterface|null
190 */
191 private $lastSubscribedAt;
192
193 /**
194 * @ORM\Column(type="datetimetz", nullable=true)
195 * @var DateTimeInterface|null
196 */
197 private $lastConfirmationEmailSentAt;
198
199 /**
200 * @ORM\Column(type="text", nullable=true)
201 * @var string|null
202 */
203 private $unconfirmedData;
204
205 /**
206 * @ORM\Column(type="string")
207 * @var string
208 */
209 private $source = 'unknown';
210
211 /**
212 * @ORM\Column(type="integer")
213 * @var int
214 */
215 private $countConfirmations = 0;
216
217 /**
218 * Denormalized number of subscribed memberships in non-deleted segments.
219 * Maintained by SegmentsCountRecalculator; used to quickly find subscribers
220 * without a list (segments_count = 0).
221 * @ORM\Column(type="integer", options={"unsigned":true})
222 * @var int
223 */
224 private $segmentsCount = 0;
225
226 /**
227 * @ORM\Column(type="string", nullable=true)
228 * @var string|null
229 */
230 private $unsubscribeToken;
231
232 /**
233 * @ORM\Column(type="string", nullable=true)
234 * @var string|null
235 */
236 private $linkToken;
237
238 /**
239 * @ORM\Column(type="float", nullable=true)
240 * @var float|null
241 */
242 private $engagementScore;
243
244 /**
245 * @ORM\Column(type="datetimetz", nullable=true)
246 * @var DateTimeInterface|null
247 */
248 private $engagementScoreUpdatedAt;
249
250 /**
251 * @ORM\Column(type="datetimetz", nullable=true)
252 * @var DateTimeInterface|null
253 */
254 private $lastEngagementAt;
255
256 /**
257 * @ORM\Column(type="datetimetz", nullable=true)
258 * @var DateTimeInterface|null
259 */
260 private $lastSendingAt;
261
262 /**
263 * @ORM\Column(type="datetimetz", nullable=true)
264 * @var DateTimeInterface|null
265 */
266 private $lastOpenAt;
267
268 /**
269 * @ORM\Column(type="datetimetz", nullable=true)
270 * @var DateTimeInterface|null
271 */
272 private $lastClickAt;
273
274 /**
275 * @ORM\Column(type="datetimetz", nullable=true)
276 * @var DateTimeInterface|null
277 */
278 private $lastPurchaseAt;
279
280 /**
281 * @ORM\Column(type="datetimetz", nullable=true)
282 * @var DateTimeInterface|null
283 */
284 private $lastPageViewAt;
285
286 /**
287 * @ORM\Column(type="datetimetz", nullable=true)
288 * @var DateTimeInterface|null
289 */
290 private $woocommerceSyncedAt;
291
292 /**
293 * @ORM\Column(type="integer")
294 * @var int
295 */
296 private $emailCount = 0;
297
298 /**
299 * @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberSegmentEntity", mappedBy="subscriber", orphanRemoval=true)
300 * @var Collection<int, SubscriberSegmentEntity>
301 */
302 private $subscriberSegments;
303
304 /**
305 * @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberCustomFieldEntity", mappedBy="subscriber", orphanRemoval=true)
306 * @var Collection<int, SubscriberCustomFieldEntity>
307 */
308 private $subscriberCustomFields;
309
310 /**
311 * @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberTagEntity", mappedBy="subscriber", orphanRemoval=true)
312 * @var Collection<int, SubscriberTagEntity>
313 */
314 private $subscriberTags;
315
316 /**
317 * @ORM\OneToMany(targetEntity="MailPoet\Entities\ScheduledTaskSubscriberEntity", mappedBy="subscriber", orphanRemoval=true)
318 * @var Collection<int, ScheduledTaskSubscriberEntity>
319 */
320 private $scheduledTaskSubscribers;
321
322 public function __construct() {
323 $this->subscriberSegments = new ArrayCollection();
324 $this->subscriberCustomFields = new ArrayCollection();
325 $this->subscriberTags = new ArrayCollection();
326 $this->scheduledTaskSubscribers = new ArrayCollection();
327 }
328
329 /**
330 * @return int|null
331 */
332 public function getWpUserId() {
333 return $this->wpUserId ? (int)$this->wpUserId : null;
334 }
335
336 /**
337 * @param int|null $wpUserId
338 */
339 public function setWpUserId($wpUserId) {
340 $this->wpUserId = $wpUserId ? (string)$wpUserId : null;
341 }
342
343 public function isWPUser(): bool {
344 return $this->getWpUserId() > 0;
345 }
346
347 /**
348 * @return bool
349 */
350 public function getIsWoocommerceUser() {
351 return $this->isWoocommerceUser;
352 }
353
354 /**
355 * @param bool $isWoocommerceUser
356 */
357 public function setIsWoocommerceUser($isWoocommerceUser) {
358 $this->isWoocommerceUser = $isWoocommerceUser;
359 }
360
361 public function getTrackingConsent(): string {
362 return $this->trackingConsent;
363 }
364
365 /**
366 * Setting the state also stamps when, how, and against what wording it
367 * changed (CNIL/Garante record-keeping).
368 */
369 public function setTrackingConsent(string $consent, ?string $method = null, ?string $copy = null): void {
370 if ($this->trackingConsent === $consent) {
371 return;
372 }
373 $this->trackingConsent = $consent;
374 $this->trackingConsentUpdatedAt = new \DateTimeImmutable();
375 $this->trackingConsentMethod = $method;
376 $this->trackingConsentCopy = $copy;
377 }
378
379 public function getTrackingConsentUpdatedAt(): ?DateTimeInterface {
380 return $this->trackingConsentUpdatedAt;
381 }
382
383 public function getTrackingConsentMethod(): ?string {
384 return $this->trackingConsentMethod;
385 }
386
387 public function getTrackingConsentCopy(): ?string {
388 return $this->trackingConsentCopy;
389 }
390
391 /**
392 * @return string
393 */
394 public function getFirstName() {
395 return $this->firstName;
396 }
397
398 /**
399 * @param string $firstName
400 */
401 public function setFirstName($firstName) {
402 $this->firstName = $firstName;
403 }
404
405 /**
406 * @return string
407 */
408 public function getLastName() {
409 return $this->lastName;
410 }
411
412 /**
413 * @param string $lastName
414 */
415 public function setLastName($lastName) {
416 $this->lastName = $lastName;
417 }
418
419 /**
420 * @return string
421 */
422 public function getEmail() {
423 return $this->email;
424 }
425
426 /**
427 * @param string $email
428 */
429 public function setEmail($email) {
430 $this->email = $email;
431 }
432
433 /**
434 * @return string
435 */
436 public function getStatus() {
437 return $this->status;
438 }
439
440 /**
441 * @param string $status
442 */
443 public function setStatus($status) {
444 if (
445 !in_array($status, [
446 self::STATUS_BOUNCED,
447 self::STATUS_INACTIVE,
448 self::STATUS_SUBSCRIBED,
449 self::STATUS_UNCONFIRMED,
450 self::STATUS_UNSUBSCRIBED,
451 ])
452 ) {
453 throw new \InvalidArgumentException("Invalid status '{$status}' given to subscriber!");
454 }
455 $this->status = $status;
456 }
457
458 /**
459 * @return string|null
460 */
461 public function getSubscribedIp() {
462 return $this->subscribedIp;
463 }
464
465 /**
466 * @param string $subscribedIp
467 */
468 public function setSubscribedIp($subscribedIp) {
469 $this->subscribedIp = $subscribedIp;
470 }
471
472 /**
473 * @return string|null
474 */
475 public function getConfirmedIp() {
476 return $this->confirmedIp;
477 }
478
479 /**
480 * @param string|null $confirmedIp
481 */
482 public function setConfirmedIp($confirmedIp) {
483 $this->confirmedIp = $confirmedIp;
484 }
485
486 public function getTimeZone(): ?string {
487 return $this->timeZone;
488 }
489
490 public function setTimeZone(?string $timeZone): void {
491 $this->timeZone = $timeZone;
492 }
493
494 public function getTimeZoneSource(): ?string {
495 return $this->timeZoneSource;
496 }
497
498 public function setTimeZoneSource(?string $timeZoneSource): void {
499 $this->timeZoneSource = $timeZoneSource;
500 }
501
502 public function getTimeZoneConfidence(): ?int {
503 return $this->timeZoneConfidence;
504 }
505
506 public function setTimeZoneConfidence(?int $timeZoneConfidence): void {
507 $this->timeZoneConfidence = $timeZoneConfidence;
508 }
509
510 public function getTimeZoneUpdatedAt(): ?DateTimeInterface {
511 return $this->timeZoneUpdatedAt;
512 }
513
514 public function setTimeZoneUpdatedAt(?DateTimeInterface $timeZoneUpdatedAt): void {
515 $this->timeZoneUpdatedAt = $timeZoneUpdatedAt;
516 }
517
518 /**
519 * @param mixed $timeZone
520 */
521 public static function sanitizeTimeZone($timeZone): ?string {
522 if (!is_string($timeZone)) {
523 return null;
524 }
525 $timeZone = trim($timeZone);
526 if ($timeZone === '' || strlen($timeZone) > 64) {
527 return null;
528 }
529 return self::isValidTimeZone($timeZone) ? $timeZone : null;
530 }
531
532 /**
533 * @param mixed $timeZone
534 */
535 public static function isValidTimeZone($timeZone): bool {
536 if (!is_string($timeZone) || $timeZone === '') {
537 return false;
538 }
539 if (self::$validTimeZones === null) {
540 self::$validTimeZones = array_fill_keys(\DateTimeZone::listIdentifiers(), true);
541 }
542 return isset(self::$validTimeZones[$timeZone]);
543 }
544
545 /**
546 * @return DateTimeInterface|null
547 */
548 public function getConfirmedAt() {
549 return $this->confirmedAt;
550 }
551
552 /**
553 * @param DateTimeInterface|null $confirmedAt
554 */
555 public function setConfirmedAt($confirmedAt) {
556 $this->confirmedAt = $confirmedAt;
557 }
558
559 /**
560 * @return DateTimeInterface|null
561 */
562 public function getLastSubscribedAt() {
563 return $this->lastSubscribedAt;
564 }
565
566 /**
567 * @param DateTimeInterface|null $lastSubscribedAt
568 */
569 public function setLastSubscribedAt($lastSubscribedAt) {
570 $this->lastSubscribedAt = $lastSubscribedAt;
571 }
572
573 /**
574 * @return DateTimeInterface|null
575 */
576 public function getLastConfirmationEmailSentAt() {
577 return $this->lastConfirmationEmailSentAt;
578 }
579
580 /**
581 * @param DateTimeInterface|null $lastConfirmationEmailSentAt
582 */
583 public function setLastConfirmationEmailSentAt($lastConfirmationEmailSentAt) {
584 $this->lastConfirmationEmailSentAt = $lastConfirmationEmailSentAt;
585 }
586
587 /**
588 * @return string|null
589 */
590 public function getUnconfirmedData() {
591 return $this->unconfirmedData;
592 }
593
594 /**
595 * @param string|null $unconfirmedData
596 */
597 public function setUnconfirmedData($unconfirmedData) {
598 $this->unconfirmedData = $unconfirmedData;
599 }
600
601 /**
602 * @return string
603 */
604 public function getSource() {
605 return $this->source;
606 }
607
608 /**
609 * @param string $source
610 */
611 public function setSource($source) {
612 if (
613 !in_array($source, [
614 'api',
615 'form',
616 'unknown',
617 'imported',
618 'administrator',
619 'wordpress_user',
620 'wordpress_user_deleted',
621 'woocommerce_user',
622 'woocommerce_checkout',
623 ])
624 ) {
625 throw new \InvalidArgumentException("Invalid source '{$source}' given to subscriber!");
626 }
627 $this->source = $source;
628 }
629
630 /**
631 * @return int
632 */
633 public function getConfirmationsCount() {
634 return $this->countConfirmations;
635 }
636
637 /**
638 * @param int $countConfirmations
639 */
640 public function setConfirmationsCount($countConfirmations) {
641 $this->countConfirmations = $countConfirmations;
642 }
643
644 public function getSegmentsCount(): int {
645 return $this->segmentsCount;
646 }
647
648 public function setSegmentsCount(int $segmentsCount): void {
649 $this->segmentsCount = $segmentsCount;
650 }
651
652 /**
653 * @return string|null
654 */
655 public function getUnsubscribeToken() {
656 return $this->unsubscribeToken;
657 }
658
659 /**
660 * @param string|null $unsubscribeToken
661 */
662 public function setUnsubscribeToken($unsubscribeToken) {
663 $this->unsubscribeToken = $unsubscribeToken;
664 }
665
666 /**
667 * @return string|null
668 */
669 public function getLinkToken() {
670 return $this->linkToken;
671 }
672
673 /**
674 * @param string|null $linkToken
675 */
676 public function setLinkToken($linkToken) {
677 $this->linkToken = $linkToken;
678 }
679
680 /**
681 * @return Collection<int, SubscriberSegmentEntity>
682 */
683 public function getSubscriberSegments(?string $status = null) {
684 if (!is_null($status)) {
685 $criteria = Criteria::create()
686 ->where(Criteria::expr()->eq('status', $status));
687 $subscriberSegments = $this->subscriberSegments->matching($criteria);
688 } else {
689 $subscriberSegments = $this->subscriberSegments;
690 }
691
692 return $subscriberSegments;
693 }
694
695 /** * @return Collection<int, SegmentEntity> */
696 public function getSegments() {
697 return $this->subscriberSegments->map(function (?SubscriberSegmentEntity $subscriberSegment = null) {
698 if (!$subscriberSegment) return null;
699 return $subscriberSegment->getSegment();
700 })->filter(function (?SegmentEntity $segment = null) {
701 return $segment !== null;
702 });
703 }
704
705 /**
706 * @return Collection<int, SubscriberCustomFieldEntity>
707 */
708 public function getSubscriberCustomFields() {
709 return $this->subscriberCustomFields;
710 }
711
712 public function getSubscriberCustomField(CustomFieldEntity $customField): ?SubscriberCustomFieldEntity {
713 $criteria = Criteria::create()
714 ->where(Criteria::expr()->eq('customField', $customField))
715 ->setMaxResults(1);
716 return $this->getSubscriberCustomFields()->matching($criteria)->first() ?: null;
717 }
718
719 /**
720 * @return Collection<int, SubscriberTagEntity>
721 */
722 public function getSubscriberTags() {
723 return $this->subscriberTags;
724 }
725
726 public function getSubscriberTag(TagEntity $tag): ?SubscriberTagEntity {
727 $criteria = Criteria::create()
728 ->where(Criteria::expr()->eq('tag', $tag))
729 ->setMaxResults(1);
730 return $this->getSubscriberTags()->matching($criteria)->first() ?: null;
731 }
732
733 /**
734 * @return float|null
735 */
736 public function getEngagementScore(): ?float {
737 return $this->engagementScore;
738 }
739
740 /**
741 * @param float|null $engagementScore
742 */
743 public function setEngagementScore(?float $engagementScore): void {
744 $this->engagementScore = $engagementScore;
745 }
746
747 /**
748 * @return DateTimeInterface|null
749 */
750 public function getEngagementScoreUpdatedAt(): ?DateTimeInterface {
751 return $this->engagementScoreUpdatedAt;
752 }
753
754 /**
755 * @param DateTimeInterface|null $engagementScoreUpdatedAt
756 */
757 public function setEngagementScoreUpdatedAt(?DateTimeInterface $engagementScoreUpdatedAt): void {
758 $this->engagementScoreUpdatedAt = $engagementScoreUpdatedAt;
759 }
760
761 public function getLastEngagementAt(): ?DateTimeInterface {
762 return $this->lastEngagementAt;
763 }
764
765 /**
766 * Sets the raw engagement timestamp without touching status. Prefer markEngaged() when
767 * recording a real engagement event (open, click, purchase, page view) so inactive
768 * subscribers are reactivated immediately instead of waiting for the maintenance cron.
769 */
770 public function setLastEngagementAt(DateTimeInterface $lastEngagementAt): void {
771 $this->lastEngagementAt = $lastEngagementAt;
772 }
773
774 /**
775 * Records engagement and immediately reactivates the subscriber if they were inactive,
776 * so they don't wait for the InactiveSubscribersMaintenance cron to be reactivated.
777 */
778 public function markEngaged(DateTimeInterface $engagedAt): void {
779 $this->setLastEngagementAt($engagedAt);
780 if ($this->getStatus() === self::STATUS_INACTIVE) {
781 $this->setStatus(self::STATUS_SUBSCRIBED);
782 }
783 }
784
785 public function getLastSendingAt(): ?DateTimeInterface {
786 return $this->lastSendingAt;
787 }
788
789 public function setLastSendingAt(?DateTimeInterface $dateTime): void {
790 $this->lastSendingAt = $dateTime;
791 }
792
793 public function getLastOpenAt(): ?DateTimeInterface {
794 return $this->lastOpenAt;
795 }
796
797 public function setLastOpenAt(?DateTimeInterface $dateTime): void {
798 $this->lastOpenAt = $dateTime;
799 }
800
801 public function getLastClickAt(): ?DateTimeInterface {
802 return $this->lastClickAt;
803 }
804
805 public function setLastClickAt(?DateTimeInterface $dateTime): void {
806 $this->lastClickAt = $dateTime;
807 }
808
809 public function getLastPurchaseAt(): ?DateTimeInterface {
810 return $this->lastPurchaseAt;
811 }
812
813 public function setLastPurchaseAt(?DateTimeInterface $dateTime): void {
814 $this->lastPurchaseAt = $dateTime;
815 }
816
817 public function getLastPageViewAt(): ?DateTimeInterface {
818 return $this->lastPageViewAt;
819 }
820
821 public function setLastPageViewAt(?DateTimeInterface $dateTime): void {
822 $this->lastPageViewAt = $dateTime;
823 }
824
825 public function setWoocommerceSyncedAt(?DateTimeInterface $woocommerceSyncedAt): void {
826 $this->woocommerceSyncedAt = $woocommerceSyncedAt;
827 }
828
829 public function getWoocommerceSyncedAt(): ?DateTimeInterface {
830 return $this->woocommerceSyncedAt;
831 }
832
833 public function getEmailCount(): int {
834 return $this->emailCount;
835 }
836
837 public function setEmailCount(int $emailCount): void {
838 $this->emailCount = $emailCount;
839 }
840
841 /** @ORM\PreFlush */
842 public function cleanupSubscriberSegments(): void {
843 // Delete old orphan SubscriberSegments to avoid errors on update
844 $this->subscriberSegments->map(function (?SubscriberSegmentEntity $subscriberSegment = null) {
845 if (!$subscriberSegment) return null;
846 if ($subscriberSegment->getSegment() === null) {
847 $this->subscriberSegments->removeElement($subscriberSegment);
848 }
849 });
850 }
851 }
852