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