This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,91 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="custom_fields")
*/
class CustomFieldEntity {
public const TYPE_DATE = 'date';
public const TYPE_TEXT = 'text';
public const TYPE_TEXTAREA = 'textarea';
public const TYPE_RADIO = 'radio';
public const TYPE_CHECKBOX = 'checkbox';
public const TYPE_SELECT = 'select';
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\Column(type="string", nullable=false, unique=true)
* @Assert\NotBlank()
* @var string
*/
private $name;
/**
* @ORM\Column(type="string", nullable=false)
* @Assert\NotBlank()
* @var string
*/
private $type;
/**
* @ORM\Column(type="array")
* @var array
*/
private $params;
/**
* @return string
*/
public function getName() {
return $this->name;
}
/**
* @return string
*/
public function getType() {
return $this->type;
}
/**
* @return array|null
*/
public function getParams() {
return $this->params;
}
/**
* @param string $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @param string $type
*/
public function setType($type) {
$this->type = $type;
}
/**
* @param array $params
*/
public function setParams(array $params) {
$this->params = $params;
}
}
@@ -0,0 +1,158 @@
<?php declare(strict_types = 1);
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\InvalidStateException;
use MailPoet\Segments\DynamicSegments\Filters\UserRole;
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Embeddable()
*/
class DynamicSegmentFilterData {
const TYPE_AUTOMATIONS = 'automations';
const TYPE_USER_ROLE = 'userRole';
const TYPE_EMAIL = 'email';
const TYPE_WOOCOMMERCE = 'woocommerce';
const TYPE_WOOCOMMERCE_MEMBERSHIP = 'woocommerceMembership';
const TYPE_WOOCOMMERCE_SUBSCRIPTION = 'woocommerceSubscription';
public const CONNECT_TYPE_AND = 'and';
public const CONNECT_TYPE_OR = 'or';
public const OPERATOR_ALL = 'all';
public const OPERATOR_ANY = 'any';
public const OPERATOR_NONE = 'none';
public const OPERATOR_STARTS_WITH = 'startsWith';
public const OPERATOR_NOT_ENDS_WITH = 'notEndsWith';
public const OPERATOR_IS = 'is';
public const OPERATOR_CONTAINS = 'contains';
public const OPERATOR_NOT_CONTAINS = 'notContains';
public const OPERATOR_NOT_STARTS_WITH = 'notStartsWith';
public const OPERATOR_IS_NOT = 'isNot';
public const OPERATOR_ENDS_WITH = 'endsWith';
public const TEXT_FIELD_OPERATORS = [
DynamicSegmentFilterData::OPERATOR_IS,
DynamicSegmentFilterData::OPERATOR_IS_NOT,
DynamicSegmentFilterData::OPERATOR_CONTAINS,
DynamicSegmentFilterData::OPERATOR_NOT_CONTAINS,
DynamicSegmentFilterData::OPERATOR_STARTS_WITH,
DynamicSegmentFilterData::OPERATOR_NOT_STARTS_WITH,
DynamicSegmentFilterData::OPERATOR_ENDS_WITH,
DynamicSegmentFilterData::OPERATOR_NOT_ENDS_WITH,
];
public const IS_NOT_BLANK = 'is_not_blank';
public const IS_BLANK = 'is_blank';
public const TIMEFRAME_ALL_TIME = 'allTime';
public const TIMEFRAME_IN_THE_LAST = 'inTheLast';
/**
* @ORM\Column(type="serialized_array")
* @var array|null
*/
private $filterData;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $filterType;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $action;
public function __construct(
string $filterType,
string $action,
array $filterData = []
) {
$this->filterType = $filterType;
$this->action = $action;
$this->filterData = $filterData;
}
public function getData(): ?array {
return $this->filterData;
}
/**
* @return mixed|null
*/
public function getParam(string $name) {
return $this->filterData[$name] ?? null;
}
public function getStringParam(string $name): string {
$value = $this->filterData[$name] ?? null;
if (!is_string($value)) {
throw new InvalidStateException("No string value found in filter data for param $name.");
}
return $value;
}
public function getIntParam(string $name): int {
$value = $this->filterData[$name] ?? null;
if (is_int($value)) {
return $value;
}
if (is_string($value)) {
return (int)($value);
}
throw new InvalidStateException("No compatible integer value found in filter data for param $name.");
}
public function getArrayParam(string $name): array {
$value = $this->getParam($name);
if (!is_array($value)) {
throw new InvalidStateException("No array value found in filter data for param $name.");
}
return $value;
}
public function getFilterType(): ?string {
if ($this->filterType) {
return $this->filterType;
}
// When a new column is empty, we try to get the value from serialized data
return $this->filterData['segmentType'] ?? null;
}
public function getAction(): ?string {
if ($this->action) {
return $this->action;
}
// When a new column is empty, we try to get the value from serialized data
// BC compatibility, the wordpress user role segment didn't have action
if ($this->getFilterType() === self::TYPE_USER_ROLE && !isset($this->filterData['action'])) {
return UserRole::TYPE;
}
return $this->filterData['action'] ?? null;
}
public function getOperator(): ?string {
$operator = $this->filterData['operator'] ?? null;
if (!$operator) {
return $this->getDefaultOperator();
}
return $operator;
}
private function getDefaultOperator(): ?string {
if ($this->getFilterType() === self::TYPE_WOOCOMMERCE && $this->getAction() === WooCommerceProduct::ACTION_PRODUCT) {
return self::OPERATOR_ANY;
}
return null;
}
}
@@ -0,0 +1,71 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="dynamic_segment_filters")
*/
class DynamicSegmentFilterEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SegmentEntity", inversedBy="filters")
* @var SegmentEntity|null
*/
private $segment;
/**
* @ORM\Embedded(class="MailPoet\Entities\DynamicSegmentFilterData", columnPrefix=false)
* @var DynamicSegmentFilterData
*/
private $filterData;
public function __construct(
SegmentEntity $segment,
DynamicSegmentFilterData $filterData
) {
$this->segment = $segment;
$this->filterData = $filterData;
}
public function __clone() {
$this->id = null;
$this->segment = null;
}
/**
* @return SegmentEntity|null
*/
public function getSegment() {
$this->safelyLoadToOneAssociation('segment');
return $this->segment;
}
/**
* @return DynamicSegmentFilterData
*/
public function getFilterData() {
return $this->filterData;
}
public function setSegment(SegmentEntity $segment) {
$this->segment = $segment;
}
public function setFilterData(DynamicSegmentFilterData $filterData) {
$this->filterData = $filterData;
}
}
@@ -0,0 +1,65 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="feature_flags", uniqueConstraints={@ORM\UniqueConstraint(name="name",columns={"name"})})
*/
class FeatureFlagEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\Column(type="string", nullable=false, unique=true)
* @var string
*/
private $name;
/**
* @ORM\Column(type="boolean", nullable=true)
* @var bool|null
*/
private $value;
/**
* @param string $name
* @param bool|null $value
*/
public function __construct(
$name,
$value = null
) {
$this->name = $name;
$this->value = $value;
}
/** @return string */
public function getName() {
return $this->name;
}
/** @param string $name */
public function setName($name) {
$this->name = $name;
}
/** @return bool|null */
public function getValue() {
return $this->value;
}
/** @param bool|null $value */
public function setValue($value) {
$this->value = $value;
}
}
@@ -0,0 +1,213 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="forms")
*/
class FormEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use DeletedAtTrait;
const DISPLAY_TYPE_BELOW_POST = 'below_posts';
const DISPLAY_TYPE_FIXED_BAR = 'fixed_bar';
const DISPLAY_TYPE_POPUP = 'popup';
const DISPLAY_TYPE_SLIDE_IN = 'slide_in';
const DISPLAY_TYPE_OTHERS = 'others';
const STATUS_ENABLED = 'enabled';
const STATUS_DISABLED = 'disabled';
const HTML_BLOCK_TYPE = 'html';
const HEADING_BLOCK_TYPE = 'heading';
const IMAGE_BLOCK_TYPE = 'image';
const PARAGRAPH_BLOCK_TYPE = 'paragraph';
const DIVIDER_BLOCK_TYPE = 'divider';
const CHECKBOX_BLOCK_TYPE = 'checkbox';
const RADIO_BLOCK_TYPE = 'radio';
const SEGMENT_SELECTION_BLOCK_TYPE = 'segment';
const DATE_BLOCK_TYPE = 'date';
const SELECT_BLOCK_TYPE = 'select';
const TEXT_BLOCK_TYPE = 'text';
const TEXTAREA_BLOCK_TYPE = 'textarea';
const SUBMIT_BLOCK_TYPE = 'submit';
const COLUMNS_BLOCK_TYPE = 'columns';
const COLUMN_BLOCK_TYPE = 'column';
public const FORM_FIELD_TYPES = [
self::CHECKBOX_BLOCK_TYPE,
self::RADIO_BLOCK_TYPE,
self::SEGMENT_SELECTION_BLOCK_TYPE,
self::DATE_BLOCK_TYPE,
self::SELECT_BLOCK_TYPE,
self::TEXT_BLOCK_TYPE,
self::TEXTAREA_BLOCK_TYPE,
];
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
/**
* @ORM\Column(type="serialized_array")
* @var array|null
*/
private $body;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status;
/**
* @ORM\Column(type="serialized_array")
* @var array|null
*/
private $settings;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $styles;
public function __construct(
$name
) {
$this->name = $name;
$this->status = self::STATUS_ENABLED;
}
/**
* @return string
*/
public function getName() {
return $this->name;
}
/**
* @return array|null
*/
public function getBody() {
return $this->body;
}
/**
* @return array|null
*/
public function getSettings() {
return $this->settings;
}
/**
* @return string|null
*/
public function getStyles() {
return $this->styles;
}
/**
* @param string $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @param array|null $body
*/
public function setBody($body) {
$this->body = $body;
}
/**
* @param array|null $settings
*/
public function setSettings($settings) {
$this->settings = $settings;
}
/**
* @param string|null $styles
*/
public function setStyles($styles) {
$this->styles = $styles;
}
/**
* @param string $status
*/
public function setStatus(string $status) {
$this->status = $status;
}
/**
* @return string
*/
public function getStatus(): string {
return $this->status;
}
public function toArray(): array {
return [
'id' => $this->getId(),
'name' => $this->getName(),
'body' => $this->getBody(),
'settings' => $this->getSettings(),
'styles' => $this->getStyles(),
'status' => $this->getStatus(),
'created_at' => $this->getCreatedAt(),
'updated_at' => $this->getUpdatedAt(),
'deleted_at' => $this->getDeletedAt(),
];
}
public function getBlocksByTypes(array $types, array $blocks = null): array {
$found = [];
if ($blocks === null) {
$blocks = $this->getBody() ?? [];
}
foreach ($blocks as $block) {
if (isset($block['type']) && in_array($block['type'], $types, true)) {
$found[] = $block;
}
if (isset($block['body']) && is_array($block['body']) && !empty($block['body'])) {
$found = array_merge($found, $this->getBlocksByTypes($types, $block['body']));
}
}
return $found;
}
public function getSegmentBlocksSegmentIds(): array {
$listSelectionBlocks = $this->getBlocksByTypes([FormEntity::SEGMENT_SELECTION_BLOCK_TYPE]);
$listSelection = [];
foreach ($listSelectionBlocks as $listSelectionBlock) {
$listSelection = array_unique(
array_merge(
$listSelection,
array_column($listSelectionBlock['params']['values'] ?? [], 'id')
)
);
}
return $listSelection;
}
public function getSettingsSegmentIds(): array {
return $this->settings['segments'] ?? [];
}
}
@@ -0,0 +1,107 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use DateTimeInterface;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="log")
*/
class LogEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $name;
/**
* @ORM\Column(type="integer", nullable=true)
* @var int|null
*/
private $level;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $message;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $rawMessage;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $context;
/**
* @return string|null
*/
public function getName(): ?string {
return $this->name;
}
/**
* @return int|null
*/
public function getLevel(): ?int {
return $this->level;
}
/**
* @return string|null
*/
public function getMessage(): ?string {
return $this->message;
}
public function getRawMessage(): ?string {
return $this->rawMessage;
}
public function getContext(): ?array {
return (array)json_decode($this->context ?? '{}', true);
}
public function setName(?string $name): void {
$this->name = $name;
}
public function setLevel(?int $level): void {
$this->level = $level;
}
public function setMessage(?string $message): void {
$this->message = $message;
}
public function setCreatedAt(DateTimeInterface $createdAt): void {
$this->createdAt = $createdAt;
}
public function setRawMessage(string $message): void {
$this->rawMessage = $message;
}
public function setContext(array $context): void {
$str = json_encode($context);
if ($str) {
$this->context = $str;
}
}
}
@@ -0,0 +1,658 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use DateTimeInterface;
use MailPoet\AutomaticEmails\WooCommerce\Events\AbandonedCart;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoet\Util\Helpers;
use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\Common\Collections\Collection;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletters")
*/
class NewsletterEntity {
// types
const TYPE_AUTOMATION = 'automation';
const TYPE_AUTOMATION_NOTIFICATION = 'automation_notification';
const TYPE_AUTOMATION_TRANSACTIONAL = 'automation_transactional';
const TYPE_STANDARD = 'standard';
const TYPE_NOTIFICATION = 'notification';
const TYPE_NOTIFICATION_HISTORY = 'notification_history';
const TYPE_RE_ENGAGEMENT = 're_engagement';
const TYPE_WC_TRANSACTIONAL_EMAIL = 'wc_transactional';
const TYPE_CONFIRMATION_EMAIL_CUSTOMIZER = 'confirmation_email';
// legacy types, replaced by automations
const TYPE_AUTOMATIC = 'automatic';
const TYPE_WELCOME = 'welcome';
// standard newsletters
const STATUS_DRAFT = 'draft';
const STATUS_SCHEDULED = 'scheduled';
const STATUS_SENDING = 'sending';
const STATUS_SENT = 'sent';
const STATUS_CORRUPT = 'corrupt';
/**
* Newsletters that their body HTML can get re-generated
* @see NewsletterSaveController::updateQueue
*/
const TYPES_WITH_RESETTABLE_BODY = [
NewsletterEntity::TYPE_STANDARD,
];
/**
* Newsletters that have additional restrictions for activation and sending
*/
const CAMPAIGN_TYPES = [
NewsletterEntity::TYPE_STANDARD,
NewsletterEntity::TYPE_NOTIFICATION,
NewsletterEntity::TYPE_NOTIFICATION_HISTORY,
NewsletterEntity::TYPE_RE_ENGAGEMENT,
];
// automatic newsletters status
const STATUS_ACTIVE = 'active';
/**
* Newsletters that use status "active"
*/
const ACTIVABLE_EMAILS = [
NewsletterEntity::TYPE_NOTIFICATION,
NewsletterEntity::TYPE_WELCOME,
NewsletterEntity::TYPE_AUTOMATIC,
NewsletterEntity::TYPE_AUTOMATION,
NewsletterEntity::TYPE_RE_ENGAGEMENT,
];
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use DeletedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $hash;
/**
* @ORM\Column(type="string")
* @var string
*/
private $subject;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $type;
/**
* @ORM\Column(type="string")
* @var string
*/
private $senderAddress = '';
/**
* @ORM\Column(type="string")
* @var string
*/
private $senderName = '';
/**
* @ORM\Column(type="string")
* @var string
*/
private $status = self::STATUS_DRAFT;
/**
* @ORM\Column(type="string")
* @var string
*/
private $replyToAddress = '';
/**
* @ORM\Column(type="string")
* @var string
*/
private $replyToName = '';
/**
* @ORM\Column(type="string")
* @var string
*/
private $preheader = '';
/**
* @ORM\Column(type="json", nullable=true)
* @var array|null
*/
private $body;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $sentAt;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $unsubscribeToken;
/**
* @ORM\Column(type="string")
* @var string
*/
private $gaCampaign = '';
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @var NewsletterEntity|null
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\NewsletterEntity", mappedBy="parent", fetch="EXTRA_LAZY")
* @var ArrayCollection<int, NewsletterEntity>
*/
private $children;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\NewsletterSegmentEntity", mappedBy="newsletter", orphanRemoval=true)
* @var ArrayCollection<int, NewsletterSegmentEntity>
*/
private $newsletterSegments;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\NewsletterOptionEntity", mappedBy="newsletter", orphanRemoval=true)
* @var ArrayCollection<int, NewsletterOptionEntity>
*/
private $options;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\SendingQueueEntity", mappedBy="newsletter")
* @var ArrayCollection<int, SendingQueueEntity>
*/
private $queues;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\WpPostEntity")
* @ORM\JoinColumn(name="wp_post_id", referencedColumnName="ID", nullable=true)
* @var WpPostEntity|null
*/
private $wpPost;
public function __construct() {
$this->children = new ArrayCollection();
$this->newsletterSegments = new ArrayCollection();
$this->options = new ArrayCollection();
$this->queues = new ArrayCollection();
}
/**
* @deprecated This is here only for backward compatibility with custom shortcodes https://kb.mailpoet.com/article/160-create-a-custom-shortcode
* This can be removed after 2026-01-01
*/
public function __get($key) {
$getterName = 'get' . Helpers::underscoreToCamelCase($key, $capitaliseFirstChar = true);
$callable = [$this, $getterName];
if (is_callable($callable)) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Intended for deprecation warnings
trigger_error(
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- if the function is callable, it's safe to output
"Direct access to \$newsletter->{$key} is deprecated and will be removed after 2026-01-01. Use \$newsletter->{$getterName}() instead.",
E_USER_DEPRECATED
);
return call_user_func($callable);
}
}
public function __clone() {
// reset ID
$this->id = null;
$this->newsletterSegments = new ArrayCollection();
$this->children = new ArrayCollection();
$this->options = new ArrayCollection();
$this->queues = new ArrayCollection();
}
/**
* @return string|null
*/
public function getHash() {
return $this->hash;
}
/**
* @param string|null $hash
*/
public function setHash($hash) {
$this->hash = $hash;
}
/**
* @return string
*/
public function getSubject() {
return $this->subject;
}
/**
* @param string $subject
*/
public function setSubject($subject) {
$this->subject = $subject;
}
/**
* @return string
*/
public function getType() {
return $this->type;
}
/**
* @param string $type
*/
public function setType($type) {
$this->type = $type;
}
/**
* @return string
*/
public function getSenderAddress() {
return $this->senderAddress;
}
/**
* @param string $senderAddress
*/
public function setSenderAddress($senderAddress) {
$this->senderAddress = $senderAddress;
}
/**
* @return string
*/
public function getSenderName() {
return $this->senderName;
}
/**
* @param string $senderName
*/
public function setSenderName($senderName) {
$this->senderName = $senderName;
}
/**
* @return string
*/
public function getStatus() {
return $this->status;
}
/**
* @param string $status
*/
public function setStatus($status) {
$this->status = $status;
// activate/deactivate unfinished tasks
$newTaskStatus = null;
if (($status === self::STATUS_DRAFT) && $this->canBeSetActive()) {
$newTaskStatus = ScheduledTaskEntity::STATUS_PAUSED;
}
if (($status === self::STATUS_ACTIVE) && $this->canBeSetActive()) {
$newTaskStatus = ScheduledTaskEntity::STATUS_SCHEDULED;
}
if (!$newTaskStatus) return;
$queues = $this->getUnfinishedQueues();
foreach ($queues as $queue) {
/** @var SendingQueueEntity $queue */
$task = $queue->getTask();
if ($task === null) continue;
$scheduled = new Carbon($task->getScheduledAt());
if ($scheduled < (new Carbon())->subDays(30)) continue;
if (($status === self::STATUS_DRAFT) && ($task->getStatus() !== ScheduledTaskEntity::STATUS_SCHEDULED)) continue;
if (($status === self::STATUS_ACTIVE) && ($task->getStatus() !== ScheduledTaskEntity::STATUS_PAUSED)) continue;
$task->setStatus($newTaskStatus);
}
}
/**
* @return string
*/
public function getReplyToAddress() {
return $this->replyToAddress;
}
/**
* @param string $replyToAddress
*/
public function setReplyToAddress($replyToAddress) {
$this->replyToAddress = $replyToAddress;
}
/**
* @return string
*/
public function getReplyToName() {
return $this->replyToName;
}
/**
* @param string $replyToName
*/
public function setReplyToName($replyToName) {
$this->replyToName = $replyToName;
}
/**
* @return string
*/
public function getPreheader() {
return $this->preheader;
}
/**
* @param string $preheader
*/
public function setPreheader($preheader) {
$this->preheader = $preheader;
}
/**
* @return array|null
*/
public function getBody() {
return $this->body;
}
/**
* @param array|null $body
*/
public function setBody($body) {
$this->body = $body;
}
/**
* @return DateTimeInterface|null
*/
public function getSentAt() {
return $this->sentAt;
}
/**
* @param DateTimeInterface|null $sentAt
*/
public function setSentAt($sentAt) {
$this->sentAt = $sentAt;
}
/**
* @return string|null
*/
public function getUnsubscribeToken() {
return $this->unsubscribeToken;
}
/**
* @return string
*/
public function getGaCampaign() {
return $this->gaCampaign;
}
/**
* @param string $gaCampaign
*/
public function setGaCampaign($gaCampaign) {
$this->gaCampaign = $gaCampaign;
}
/**
* @param string|null $unsubscribeToken
*/
public function setUnsubscribeToken($unsubscribeToken) {
$this->unsubscribeToken = $unsubscribeToken;
}
/**
* @return NewsletterEntity|null
*/
public function getParent() {
$this->safelyLoadToOneAssociation('parent');
return $this->parent;
}
/**
* @param NewsletterEntity|null $parent
*/
public function setParent($parent) {
$this->parent = $parent;
}
/**
* @return ArrayCollection<int, NewsletterEntity>
*/
public function getChildren() {
return $this->children;
}
/**
* @return ArrayCollection<int, NewsletterSegmentEntity>
*/
public function getNewsletterSegments() {
return $this->newsletterSegments;
}
/**
* @return int[]
*/
public function getSegmentIds() {
return array_filter($this->newsletterSegments->map(function(NewsletterSegmentEntity $newsletterSegment = null) {
if (!$newsletterSegment) return null;
$segment = $newsletterSegment->getSegment();
return $segment ? (int)$segment->getId() : null;
})->toArray());
}
/**
* @return ArrayCollection<int, NewsletterOptionEntity>
*/
public function getOptions() {
return $this->options;
}
public function getOption(string $name): ?NewsletterOptionEntity {
$option = $this->options->filter(function (NewsletterOptionEntity $option = null) use ($name): bool {
if (!$option) return false;
return ($field = $option->getOptionField()) ? $field->getName() === $name : false;
})->first();
return $option ?: null;
}
/**
* @return array<string, mixed> Associative array of newsletter option values with option names as keys
*/
public function getOptionsAsArray(): array {
$optionsArray = [];
foreach ($this->options as $option) {
$name = $option->getName();
if (!$name) {
continue;
}
$optionsArray[$name] = $option->getValue();
}
return $optionsArray;
}
public function getOptionValue(string $name) {
$option = $this->getOption($name);
return $option ? $option->getValue() : null;
}
public function getFilterSegmentId(): ?int {
$optionValue = $this->getOptionValue(NewsletterOptionFieldEntity::NAME_FILTER_SEGMENT_ID);
if ($optionValue) {
return (int)$optionValue;
}
$parentNewsletter = $this->getParent();
if ($parentNewsletter instanceof NewsletterEntity && $this->getId() !== $parentNewsletter->getId()) {
return $parentNewsletter->getFilterSegmentId();
}
return null;
}
/**
* @return ArrayCollection<int, SendingQueueEntity>
*/
public function getQueues() {
return $this->queues;
}
public function getLatestQueue(): ?SendingQueueEntity {
$criteria = new Criteria();
$criteria->orderBy(['id' => Criteria::DESC]);
$criteria->setMaxResults(1);
return $this->queues->matching($criteria)->first() ?: null;
}
public function getLastUpdatedQueue(): ?SendingQueueEntity {
$criteria = new Criteria();
$criteria->orderBy(['updatedAt' => Criteria::DESC]);
$criteria->setMaxResults(1);
return $this->queues->matching($criteria)->first() ?: null;
}
/**
* @return Collection<int, SendingQueueEntity>
*/
public function getUnfinishedQueues(): Collection {
$criteria = new Criteria();
$expr = Criteria::expr();
$criteria->where($expr->neq('countToProcess', 0));
return $this->queues->matching($criteria);
}
public function getGlobalStyle(string $category, string $style): ?string {
$body = $this->getBody();
if ($body === null) {
return null;
}
return $body['globalStyles'][$category][$style] ?? null;
}
public function setGlobalStyle(string $category, string $style, $value): void {
$body = $this->getBody();
if ($body === null) {
return;
}
$this->body['globalStyles'][$category][$style] = $value;
}
public function getProcessedAt(): ?DateTimeInterface {
$processedAt = null;
$queue = $this->getLatestQueue();
if ($queue instanceof SendingQueueEntity) {
$task = $queue->getTask();
if ($task instanceof ScheduledTaskEntity) {
$processedAt = $task->getProcessedAt();
}
}
return $processedAt;
}
public function getContent(): string {
$content = $this->getBody()['content'] ?? '';
return json_encode($content) ?: '';
}
/**
* Only some types of newsletters can be set as sent. Some others are just active or draft.
*/
public function canBeSetSent(): bool {
return in_array($this->getType(), [self::TYPE_NOTIFICATION_HISTORY, self::TYPE_STANDARD], true);
}
public function canBeSetActive(): bool {
return in_array($this->getType(), self::ACTIVABLE_EMAILS, true);
}
public function getWpPost(): ?WpPostEntity {
$this->safelyLoadToOneAssociation('wpPost');
return $this->wpPost;
}
public function setWpPost(?WpPostEntity $wpPostEntity): void {
$this->wpPost = $wpPostEntity;
}
public function getWpPostId(): ?int {
$wpPost = $this->wpPost;
return $wpPost ? $wpPost->getId() : null;
}
public function getCampaignName(): ?string {
$wpPost = $this->getWpPost();
if (!$wpPost) {
return null;
}
return $wpPost->getPostTitle();
}
/**
* Used for cases when we present newsletter by name.
* Newsletters created via legacy editor have only subjects.
*/
public function getCampaignNameOrSubject(): string {
$campaignName = $this->getCampaignName();
return $campaignName ?: $this->getSubject();
}
public function isTransactional(): bool {
// Legacy Abandoned Cart emails are transactional
if (
$this->getType() === NewsletterEntity::TYPE_AUTOMATIC
&& $this->getOptionValue(NewsletterOptionFieldEntity::NAME_EVENT) === AbandonedCart::SLUG
) {
return true;
}
return in_array($this->getType(), [
NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL,
NewsletterEntity::TYPE_WC_TRANSACTIONAL_EMAIL,
]);
}
}
@@ -0,0 +1,118 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletter_links")
*/
class NewsletterLinkEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
public const UNSUBSCRIBE_LINK_SHORT_CODE = '[link:subscription_unsubscribe_url]';
public const INSTANT_UNSUBSCRIBE_LINK_SHORT_CODE = '[link:subscription_instant_unsubscribe_url]';
public const UNSUBSCRIBE_LINK_SHORTCODES = [self::UNSUBSCRIBE_LINK_SHORT_CODE, self::INSTANT_UNSUBSCRIBE_LINK_SHORT_CODE];
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\Column(type="string")
* @var string
*/
private $url;
/**
* @ORM\Column(type="string")
* @var string
*/
private $hash;
/**
* Extra lazy is here for `getTotalClicksCount`.
* If we didn't specify extra lazy the function would load all clicks and count them. This way it uses a single count query.
* @ORM\OneToMany(targetEntity="MailPoet\Entities\StatisticsClickEntity", mappedBy="link", fetch="EXTRA_LAZY")
*
* @var ArrayCollection<int, StatisticsClickEntity>
*/
private $clicks;
public function __construct(
NewsletterEntity $newsletter,
SendingQueueEntity $queue,
string $url,
string $hash
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->url = $url;
$this->hash = $hash;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return SendingQueueEntity|null
*/
public function getQueue() {
$this->safelyLoadToOneAssociation('queue');
return $this->queue;
}
public function getUrl(): string {
return $this->url;
}
public function getHash(): string {
return $this->hash;
}
/**
* @return int
*/
public function getTotalClicksCount() {
return $this->clicks->count();
}
public function toArray(): array {
return [
'id' => $this->getId(),
'newsletter_id' => ($this->getNewsletter() instanceof NewsletterEntity) ? $this->getNewsletter()->getId() : null,
'queue_id' => ($this->getQueue() instanceof SendingQueueEntity) ? $this->getQueue()->getId() : null,
'url' => $this->getUrl(),
'hash' => $this->getHash(),
'created_at' => $this->getCreatedAt(),
'updated_at' => $this->getUpdatedAt(),
];
}
}
@@ -0,0 +1,91 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletter_option")
*/
class NewsletterOptionEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\Column(type="text", nullable=true)
* @var string|null
*/
private $value;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity", inversedBy="options")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterOptionFieldEntity")
* @var NewsletterOptionFieldEntity|null
*/
private $optionField;
public function __construct(
NewsletterEntity $newsletter,
NewsletterOptionFieldEntity $optionField
) {
$this->newsletter = $newsletter;
$this->optionField = $optionField;
}
/**
* @return string|null
*/
public function getValue() {
return $this->value;
}
/**
* @return string|null
*/
public function getName() {
$optionField = $this->getOptionField();
if ($optionField === null) {
return null;
}
return $optionField->getName();
}
/**
* @param string|null $value
*/
public function setValue($value) {
$this->value = $value;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return NewsletterOptionFieldEntity|null
*/
public function getOptionField() {
$this->safelyLoadToOneAssociation('optionField');
return $this->optionField;
}
}
@@ -0,0 +1,85 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletter_option_fields")
*/
class NewsletterOptionFieldEntity {
// names
public const NAME_AFTER_TIME_NUMBER = 'afterTimeNumber';
public const NAME_AFTER_TIME_TYPE = 'afterTimeType';
public const NAME_EVENT = 'event';
public const NAME_GROUP = 'group';
public const NAME_INTERVAL_TYPE = 'intervalType';
public const NAME_IS_SCHEDULED = 'isScheduled';
public const NAME_META = 'meta';
public const NAME_MONTH_DAY = 'monthDay';
public const NAME_NTH_WEEK_DAY = 'nthWeekDay';
public const NAME_ROLE = 'role';
public const NAME_SCHEDULE = 'schedule';
public const NAME_SCHEDULED_AT = 'scheduledAt';
public const NAME_SEGMENT = 'segment';
public const NAME_SEND_TO = 'sendTo';
public const NAME_TIME_OF_DAY = 'timeOfDay';
public const NAME_WEEK_DAY = 'weekDay';
public const NAME_AUTOMATION_ID = 'automationId';
public const NAME_AUTOMATION_STEP_ID = 'automationStepId';
public const NAME_FILTER_SEGMENT_ID = 'filterSegmentId';
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $name;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $newsletterType;
/**
* @return string
*/
public function getName() {
return $this->name;
}
/**
* @param string $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @return string
*/
public function getNewsletterType() {
return $this->newsletterType;
}
/**
* @param string $newsletterType
*/
public function setNewsletterType($newsletterType) {
$this->newsletterType = $newsletterType;
}
}
@@ -0,0 +1,58 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletter_posts")
*/
class NewsletterPostEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $postId;
public function __construct(
NewsletterEntity $newsletter,
int $postId
) {
$this->newsletter = $newsletter;
$this->postId = $postId;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return int
*/
public function getPostId(): int {
return $this->postId;
}
}
@@ -0,0 +1,59 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletter_segment")
*/
class NewsletterSegmentEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity", inversedBy="newsletterSegments")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SegmentEntity")
* @var SegmentEntity|null
*/
private $segment;
public function __construct(
NewsletterEntity $newsletter,
SegmentEntity $segment
) {
$this->newsletter = $newsletter;
$this->segment = $segment;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return SegmentEntity|null
*/
public function getSegment() {
$this->safelyLoadToOneAssociation('segment');
return $this->segment;
}
}
@@ -0,0 +1,149 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="newsletter_templates")
*/
class NewsletterTemplateEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $name;
/**
* @ORM\Column(type="string")
* @var string
*/
private $categories = '[]';
/**
* @ORM\Column(type="json", nullable=true)
* @Assert\NotBlank()
* @var array|null
*/
private $body;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $thumbnail;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $thumbnailData;
/**
* @ORM\Column(type="boolean")
* @var bool
*/
private $readonly = false;
public function __construct(
string $name
) {
$this->name = $name;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @param NewsletterEntity|null $newsletter
*/
public function setNewsletter($newsletter) {
$this->newsletter = $newsletter;
}
public function getName(): string {
return $this->name;
}
public function setName(string $name) {
$this->name = $name;
}
public function getCategories(): string {
return $this->categories;
}
public function setCategories(string $categories) {
$this->categories = $categories;
}
/**
* @return array|null
*/
public function getBody() {
return $this->body;
}
/**
* @param array|null $body
*/
public function setBody($body) {
$this->body = $body;
}
/**
* @return string|null
*/
public function getThumbnail() {
return $this->thumbnail;
}
/**
* @param string|null $thumbnail
*/
public function setThumbnail($thumbnail) {
$this->thumbnail = $thumbnail;
}
public function getThumbnailData(): ?string {
return $this->thumbnailData;
}
public function setThumbnailData(string $thumbnailData): void {
$this->thumbnailData = $thumbnailData;
}
public function getReadonly(): bool {
return $this->readonly;
}
public function setReadonly(bool $readonly) {
$this->readonly = $readonly;
}
}
@@ -0,0 +1,264 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use DateTimeInterface;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\Common\Collections\Collection;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="scheduled_tasks")
*/
class ScheduledTaskEntity {
const STATUS_COMPLETED = 'completed';
const STATUS_SCHEDULED = 'scheduled';
const STATUS_CANCELLED = 'cancelled';
const STATUS_PAUSED = 'paused';
const STATUS_INVALID = 'invalid';
const VIRTUAL_STATUS_RUNNING = 'running'; // For historical reasons this is stored as null in DB
const PRIORITY_HIGH = 1;
const PRIORITY_MEDIUM = 5;
const PRIORITY_LOW = 10;
const BASIC_RESCHEDULE_TIMEOUT = 5; // minutes
const MAX_RESCHEDULE_TIMEOUT = 1440; // minutes
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use DeletedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $type;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $status;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $priority = 0;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $scheduledAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $cancelledAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $processedAt;
/**
* @ORM\Column(type="json", nullable=true)
* @var array|null
*/
private $meta;
/**
* @ORM\Column(type="boolean", nullable=true)
* @var bool|null
*/
private $inProgress;
/**
* @ORM\Column(type="integer", options={"default" : 0})
* @var int
*/
private $rescheduleCount = 0;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\ScheduledTaskSubscriberEntity", mappedBy="task", fetch="EXTRA_LAZY")
* @var Collection<int, ScheduledTaskSubscriberEntity>
*/
private $subscribers;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\SendingQueueEntity", mappedBy="task", fetch="EAGER")
* @var SendingQueueEntity|null
*/
private $sendingQueue;
public function __construct() {
$this->subscribers = new ArrayCollection();
}
/**
* @return string|null
*/
public function getType() {
return $this->type;
}
/**
* @param string|null $type
*/
public function setType($type) {
$this->type = $type;
}
/**
* @return string|null
*/
public function getStatus() {
return $this->status;
}
/**
* @param string|null $status
*/
public function setStatus($status) {
if ($status === self::VIRTUAL_STATUS_RUNNING) {
$status = null;
}
$this->status = $status;
}
/**
* @return int
*/
public function getPriority() {
return $this->priority;
}
/**
* @param int $priority
*/
public function setPriority($priority) {
$this->priority = $priority;
}
/**
* @return DateTimeInterface|null
*/
public function getScheduledAt() {
return $this->scheduledAt;
}
/**
* @param DateTimeInterface|null $scheduledAt
*/
public function setScheduledAt($scheduledAt) {
$this->scheduledAt = $scheduledAt;
}
/**
* @return DateTimeInterface|null
*/
public function getCancelledAt() {
return $this->cancelledAt;
}
/**
* @param DateTimeInterface|null $cancelledAt
*/
public function setCancelledAt($cancelledAt) {
$this->cancelledAt = $cancelledAt;
}
/**
* @return DateTimeInterface|null
*/
public function getProcessedAt() {
return $this->processedAt;
}
/**
* @param DateTimeInterface|null $processedAt
*/
public function setProcessedAt($processedAt) {
$this->processedAt = $processedAt;
}
/**
* @return array|null
*/
public function getMeta() {
return $this->meta;
}
/**
* @param array|null $meta
*/
public function setMeta($meta) {
$this->meta = $meta;
}
/**
* @return bool|null
*/
public function getInProgress() {
return $this->inProgress;
}
/**
* @param bool|null $inProgress
*/
public function setInProgress($inProgress) {
$this->inProgress = $inProgress;
}
public function getRescheduleCount(): int {
return $this->rescheduleCount;
}
public function setRescheduleCount(int $rescheduleCount) {
$this->rescheduleCount = $rescheduleCount;
}
/**
* @return Collection<int, ScheduledTaskSubscriberEntity>
*/
public function getSubscribers(): Collection {
return $this->subscribers;
}
/**
* @param int $processed ScheduledTaskSubscriberEntity::PROCESSED_* constant
* @return SubscriberEntity[]
*/
public function getSubscribersByProcessed(int $processed): array {
$criteria = Criteria::create()
->where(Criteria::expr()->eq('processed', $processed));
$subscribers = $this->subscribers->matching($criteria)->map(function (ScheduledTaskSubscriberEntity $taskSubscriber = null): ?SubscriberEntity {
if (!$taskSubscriber) return null;
return $taskSubscriber->getSubscriber();
});
return array_filter($subscribers->toArray());
}
public function getSendingQueue(): ?SendingQueueEntity {
$this->safelyLoadToOneAssociation('sendingQueue');
return $this->sendingQueue;
}
public function setSendingQueue(SendingQueueEntity $sendingQueue): void {
$this->sendingQueue = $sendingQueue;
}
}
@@ -0,0 +1,150 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="scheduled_task_subscribers")
*/
class ScheduledTaskSubscriberEntity {
const STATUS_UNPROCESSED = 0;
const STATUS_PROCESSED = 1;
const FAIL_STATUS_OK = 0;
const FAIL_STATUS_FAILED = 1;
const SENDING_STATUS_SENT = 'sent';
const SENDING_STATUS_FAILED = 'failed';
const SENDING_STATUS_UNPROCESSED = 'unprocessed';
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $processed;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $failed;
/**
* @ORM\Column(type="text", nullable=true)
* @var string|null
*/
private $error;
/**
* @ORM\Id @ORM\ManyToOne(targetEntity="MailPoet\Entities\ScheduledTaskEntity", inversedBy="subscribers")
* @var ScheduledTaskEntity|null
*/
private $task;
/**
* @ORM\Id @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity", inversedBy="scheduledTaskSubscribers")
* @var SubscriberEntity|null
*/
private $subscriber;
public function __construct(
ScheduledTaskEntity $task,
SubscriberEntity $subscriber,
int $processed = 0,
int $failed = 0,
string $error = null
) {
$this->task = $task;
$this->subscriber = $subscriber;
$this->processed = $processed;
$this->failed = $failed;
$this->error = $error;
}
public function getProcessed(): int {
return $this->processed;
}
public function setProcessed(int $processed) {
$this->processed = $processed;
}
public function getFailed(): int {
return $this->failed;
}
public function setFailed(int $failed) {
$this->failed = $failed;
}
/**
* @return string|null
*/
public function getError() {
return $this->error;
}
/**
* @param string|null $error
*/
public function setError($error) {
$this->error = $error;
}
/**
* @return ScheduledTaskEntity|null
*/
public function getTask() {
$this->safelyLoadToOneAssociation('task');
return $this->task;
}
public function setTask(ScheduledTaskEntity $task) {
$this->task = $task;
}
/**
* @return SubscriberEntity|null
*/
public function getSubscriber() {
$this->safelyLoadToOneAssociation('subscriber');
return $this->subscriber;
}
/**
* Get the ID of the subscriber without querying wp_mailpoet_subscribers.
* $this->getSubscriber->getId() queries wp_mailpoet_subscribers because of
* the way the SafeToOneAssociationLoadTrait works.
*
* @return int|null
*/
public function getSubscriberId() {
if ($this->subscriber instanceof SubscriberEntity) {
return $this->subscriber->getId();
}
return null;
}
public function setSubscriber(SubscriberEntity $subscriber) {
$this->subscriber = $subscriber;
}
public function resetToUnprocessed() {
$this->setError(null);
$this->setProcessed(0);
$this->setFailed(0);
}
}
@@ -0,0 +1,196 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="segments")
*/
class SegmentEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use DeletedAtTrait;
const TYPE_WP_USERS = 'wp_users';
const TYPE_WC_USERS = 'woocommerce_users';
const TYPE_WC_MEMBERSHIPS = 'woocommerce_memberships';
const TYPE_DEFAULT = 'default';
const TYPE_DYNAMIC = 'dynamic';
const TYPE_WITHOUT_LIST = 'without-list';
const NON_WOO_RELATED_TYPES = [
self::TYPE_WP_USERS,
self::TYPE_DEFAULT,
self::TYPE_DYNAMIC,
self::TYPE_WITHOUT_LIST,
];
const SEGMENT_ENABLED = 'active';
const SEGMENT_DISABLED = 'disabled';
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $name;
/**
* @ORM\Column(type="string")
* @var string
*/
private $type;
/**
* @ORM\Column(type="string")
* @var string
*/
private $description;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\DynamicSegmentFilterEntity", mappedBy="segment")
* @var ArrayCollection<int, DynamicSegmentFilterEntity>
*/
private $dynamicFilters;
/**
* @ORM\Column(type="float", nullable=true)
* @var float|null
*/
private $averageEngagementScore;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var \DateTimeInterface|null
*/
private $averageEngagementScoreUpdatedAt;
/**
* @ORM\Column(type="boolean")
* @var bool
*/
private $displayInManageSubscriptionPage = false;
public function __construct(
string $name,
string $type,
string $description
) {
$this->name = $name;
$this->type = $type;
$this->description = $description;
$this->dynamicFilters = new ArrayCollection();
}
public function __clone() {
// reset ID
$this->id = null;
$this->dynamicFilters = new ArrayCollection();
}
/**
* @return string
*/
public function getName() {
return $this->name;
}
/**
* @param string $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @return string
*/
public function getType() {
return $this->type;
}
/**
* @param string $type
*/
public function setType($type) {
$this->type = $type;
}
/**
* @return string
*/
public function getDescription() {
return $this->description;
}
/**
* @param string $description
*/
public function setDescription($description) {
$this->description = $description;
}
/**
* @return ArrayCollection<int, DynamicSegmentFilterEntity>
*/
public function getDynamicFilters() {
return $this->dynamicFilters;
}
public function addDynamicFilter(DynamicSegmentFilterEntity $dynamicSegmentFilterEntity) {
$this->dynamicFilters->add($dynamicSegmentFilterEntity);
}
public function isStatic(): bool {
return in_array($this->getType(), [self::TYPE_DEFAULT, self::TYPE_WP_USERS, self::TYPE_WC_USERS, self::TYPE_WC_MEMBERSHIPS], true);
}
public function getAverageEngagementScore(): ?float {
return $this->averageEngagementScore;
}
public function setAverageEngagementScore(?float $averageEngagementScore): void {
$this->averageEngagementScore = $averageEngagementScore;
}
public function getAverageEngagementScoreUpdatedAt(): ?\DateTimeInterface {
return $this->averageEngagementScoreUpdatedAt;
}
public function setAverageEngagementScoreUpdatedAt(?\DateTimeInterface $averageEngagementScoreUpdatedAt): void {
$this->averageEngagementScoreUpdatedAt = $averageEngagementScoreUpdatedAt;
}
public function getDisplayInManageSubscriptionPage(): bool {
return $this->displayInManageSubscriptionPage;
}
public function setDisplayInManageSubscriptionPage(bool $state): void {
$this->displayInManageSubscriptionPage = $state;
}
/**
* Returns connect operand from the first filter, when doesn't exist, then returns a default value.
* @return string
*/
public function getFiltersConnectOperator(): string {
$firstFilter = $this->getDynamicFilters()->first();
$filterData = $firstFilter ? $firstFilter->getFilterData() : null;
if (!$firstFilter || !$filterData) {
return DynamicSegmentFilterData::CONNECT_TYPE_AND;
}
return $filterData->getParam('connect') ?: DynamicSegmentFilterData::CONNECT_TYPE_AND;
}
}
@@ -0,0 +1,215 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoet\Util\Helpers;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="sending_queues")
*/
class SendingQueueEntity {
const STATUS_COMPLETED = 'completed';
const STATUS_SCHEDULED = 'scheduled';
const STATUS_PAUSED = 'paused';
const PRIORITY_HIGH = 1;
const PRIORITY_MEDIUM = 5;
const PRIORITY_LOW = 10;
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use DeletedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\Column(type="json_or_serialized")
* @Assert\Type("array")
* @Assert\Collection(
* fields = {
* "html" = @Assert\NotBlank(),
* "text" = @Assert\NotBlank(),
* }
* )
* @var array|null
*/
private $newsletterRenderedBody;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $newsletterRenderedSubject;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $countTotal = 0;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $countProcessed = 0;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $countToProcess = 0;
/**
* @ORM\Column(type="json", nullable=true)
* @var array|null
*/
private $meta;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\ScheduledTaskEntity", fetch="EAGER")
* @var ScheduledTaskEntity|null
*/
private $task;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity", inversedBy="queues")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @deprecated This is here only for backward compatibility with custom shortcodes https://kb.mailpoet.com/article/160-create-a-custom-shortcode
* This can be removed after 2026-01-01
*/
public function __get($key) {
$getterName = 'get' . Helpers::underscoreToCamelCase($key, $capitaliseFirstChar = true);
$callable = [$this, $getterName];
if (is_callable($callable)) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Intended for deprecation warnings
trigger_error(
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- if the function is callable, it's safe to output
"Direct access to \$sendingQueue->{$key} is deprecated and will be removed after 2026-01-01. Use \$sendingQueue->{$getterName}() instead.",
E_USER_DEPRECATED
);
return call_user_func($callable);
}
}
/**
* @return array|null
*/
public function getNewsletterRenderedBody() {
return $this->newsletterRenderedBody;
}
/**
* @param array|null $newsletterRenderedBody
*/
public function setNewsletterRenderedBody($newsletterRenderedBody) {
$this->newsletterRenderedBody = $newsletterRenderedBody;
}
/**
* @return string|null
*/
public function getNewsletterRenderedSubject() {
return $this->newsletterRenderedSubject;
}
/**
* @param string|null $newsletterRenderedSubject
*/
public function setNewsletterRenderedSubject($newsletterRenderedSubject) {
$this->newsletterRenderedSubject = $newsletterRenderedSubject;
}
/**
* @return int
*/
public function getCountTotal() {
return $this->countTotal;
}
/**
* @param int $countTotal
*/
public function setCountTotal($countTotal) {
$this->countTotal = $countTotal;
}
/**
* @return int
*/
public function getCountProcessed() {
return $this->countProcessed;
}
/**
* @param int $countProcessed
*/
public function setCountProcessed($countProcessed) {
$this->countProcessed = $countProcessed;
}
/**
* @return int
*/
public function getCountToProcess() {
return $this->countToProcess;
}
/**
* @param int $countToProcess
*/
public function setCountToProcess($countToProcess) {
$this->countToProcess = $countToProcess;
}
/**
* @return array|null
*/
public function getMeta() {
return $this->meta;
}
/**
* @param array|null $meta
*/
public function setMeta($meta) {
$this->meta = $meta;
}
/**
* @return ScheduledTaskEntity|null
*/
public function getTask() {
$this->safelyLoadToOneAssociation('task');
return $this->task;
}
public function setTask(ScheduledTaskEntity $task) {
$this->task = $task;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
public function setNewsletter(NewsletterEntity $newsletter) {
$this->newsletter = $newsletter;
}
}
@@ -0,0 +1,60 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoet\Util\Helpers;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="settings")
*/
class SettingEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $name;
/**
* @ORM\Column(type="text", nullable=true)
* @var string|null
*/
private $value;
/** @return string */
public function getName() {
return $this->name;
}
/** @param string $name */
public function setName($name) {
$this->name = $name;
}
/** @return mixed */
public function getValue() {
return $this->value !== null && is_serialized($this->value) ? unserialize($this->value) : $this->value;
}
/** @param mixed $value */
public function setValue($value) {
$value = Helpers::recursiveTrim($value);
if (is_array($value)) {
$value = serialize($value);
}
$this->value = $value;
}
}
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_bounces")
*/
class StatisticsBounceEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
public function __construct(
NewsletterEntity $newsletter,
SendingQueueEntity $queue,
SubscriberEntity $subscriber
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->subscriber = $subscriber;
}
}
@@ -0,0 +1,183 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_clicks")
*/
class StatisticsClickEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterLinkEntity", inversedBy="clicks")
* @var NewsletterLinkEntity|null
*/
private $link;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\StatisticsWooCommercePurchaseEntity", mappedBy="click", fetch="EXTRA_LAZY")*
* @var ArrayCollection<int, StatisticsWooCommercePurchaseEntity>
*/
private $wooCommercePurchases;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\UserAgentEntity")
* @var UserAgentEntity|null
*/
private $userAgent;
/**
* @ORM\Column(type="smallint")
* @var int
*/
private $userAgentType = 0;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $count;
public function __construct(
NewsletterEntity $newsletter,
SendingQueueEntity $queue,
SubscriberEntity $subscriber,
NewsletterLinkEntity $link,
int $count
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->subscriber = $subscriber;
$this->link = $link;
$this->count = $count;
$this->wooCommercePurchases = new ArrayCollection();
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return SendingQueueEntity|null
*/
public function getQueue() {
$this->safelyLoadToOneAssociation('queue');
return $this->queue;
}
/**
* @return NewsletterLinkEntity|null
*/
public function getLink() {
$this->safelyLoadToOneAssociation('link');
return $this->link;
}
/**
* @param NewsletterEntity|null $newsletter
*/
public function setNewsletter($newsletter) {
$this->newsletter = $newsletter;
}
/**
* @param SendingQueueEntity|null $queue
*/
public function setQueue($queue) {
$this->queue = $queue;
}
/**
* @param SubscriberEntity|null $subscriber
*/
public function setSubscriber($subscriber) {
$this->subscriber = $subscriber;
}
/**
* @return SubscriberEntity|null
*/
public function getSubscriber(): ?SubscriberEntity {
return $this->subscriber;
}
/**
* @param NewsletterLinkEntity|null $link
*/
public function setLink($link) {
$this->link = $link;
}
/**
* @param int $count
*/
public function setCount(int $count) {
$this->count = $count;
}
/**
* @return ArrayCollection<int, StatisticsWooCommercePurchaseEntity>
*/
public function getWooCommercePurchases() {
return $this->wooCommercePurchases;
}
public function getCount(): int {
return $this->count;
}
public function getUserAgent(): ?UserAgentEntity {
return $this->userAgent;
}
public function setUserAgent(?UserAgentEntity $userAgent): void {
$this->userAgent = $userAgent;
}
public function getUserAgentType(): int {
return $this->userAgentType;
}
public function setUserAgentType(int $userAgentType): void {
$this->userAgentType = $userAgentType;
}
}
@@ -0,0 +1,53 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_forms")
*/
class StatisticsFormEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\FormEntity")
* @ORM\JoinColumn(name="form_id", referencedColumnName="id")
* @var FormEntity|null
*/
private $form;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
public function __construct(
FormEntity $form,
SubscriberEntity $subscriber
) {
$this->form = $form;
$this->subscriber = $subscriber;
}
public function getForm(): ?FormEntity {
$this->safelyLoadToOneAssociation('form');
return $this->form;
}
public function getSubscriber(): ?SubscriberEntity {
$this->safelyLoadToOneAssociation('form');
return $this->subscriber;
}
}
@@ -0,0 +1,96 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_newsletters")
*/
class StatisticsNewsletterEntity {
use AutoincrementedIdTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\Column(type="datetimetz", nullable=false)
* @var \DateTimeInterface
*/
private $sentAt;
public function __construct(
NewsletterEntity $newsletter,
SendingQueueEntity $queue,
SubscriberEntity $subscriber,
\DateTimeInterface $sentAt = null
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->subscriber = $subscriber;
$this->sentAt = $sentAt ?: new \DateTimeImmutable();
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return SendingQueueEntity|null
*/
public function getQueue() {
$this->safelyLoadToOneAssociation('queue');
return $this->queue;
}
/**
* @return SubscriberEntity|null
*/
public function getSubscriber() {
$this->safelyLoadToOneAssociation('subscriber');
return $this->subscriber;
}
/**
* @return \DateTimeInterface|null
*/
public function getSentAt() {
return $this->sentAt;
}
/**
* @param \DateTimeInterface $sentAt
*/
public function setSentAt(\DateTimeInterface $sentAt) {
$this->sentAt = $sentAt;
}
}
@@ -0,0 +1,117 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_opens")
*/
class StatisticsOpenEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\UserAgentEntity")
* @var UserAgentEntity|null
*/
private $userAgent;
/**
* @ORM\Column(type="smallint")
* @var int
*/
private $userAgentType = 0;
public function __construct(
NewsletterEntity $newsletter,
SendingQueueEntity $queue,
SubscriberEntity $subscriber
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->subscriber = $subscriber;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return SendingQueueEntity|null
*/
public function getQueue() {
$this->safelyLoadToOneAssociation('queue');
return $this->queue;
}
/**
* @param NewsletterEntity|null $newsletter
*/
public function setNewsletter($newsletter) {
$this->newsletter = $newsletter;
}
/**
* @param SendingQueueEntity|null $queue
*/
public function setQueue($queue) {
$this->queue = $queue;
}
/**
* @param SubscriberEntity|null $subscriber
*/
public function setSubscriber($subscriber) {
$this->subscriber = $subscriber;
}
public function getUserAgent(): ?UserAgentEntity {
return $this->userAgent;
}
public function setUserAgent(?UserAgentEntity $userAgent): void {
$this->userAgent = $userAgent;
}
public function getUserAgentType(): int {
return $this->userAgentType;
}
public function setUserAgentType(int $userAgentType): void {
$this->userAgentType = $userAgentType;
}
}
@@ -0,0 +1,133 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_unsubscribes")
*/
class StatisticsUnsubscribeEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use SafeToOneAssociationLoadTrait;
const SOURCE_NEWSLETTER = 'newsletter';
const SOURCE_MANAGE = 'manage';
const SOURCE_ADMINISTRATOR = 'admin';
const SOURCE_ORDER_CHECKOUT = 'order_checkout';
const SOURCE_AUTOMATION = 'automation';
const SOURCE_MP_API = 'mp_api';
const METHOD_LINK = 'link';
const METHOD_ONE_CLICK = 'one_click';
const METHOD_UNKNOWN = 'unknown';
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\Column(type="string")
* @var string
*/
private $source = 'unknown';
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $meta;
/**
* @ORM\Column(type="string", nullable=false)
* @var string
*/
private $method = self::METHOD_UNKNOWN;
public function __construct(
NewsletterEntity $newsletter = null,
SendingQueueEntity $queue = null,
SubscriberEntity $subscriber
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->subscriber = $subscriber;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return SendingQueueEntity|null
*/
public function getQueue() {
$this->safelyLoadToOneAssociation('queue');
return $this->queue;
}
/**
* @return string
*/
public function getSource(): string {
return $this->source;
}
/**
* @param string $source
*/
public function setSource(string $source) {
$this->source = $source;
}
/**
* @param string $meta
*/
public function setMeta(string $meta) {
$this->meta = $meta;
}
/**
* @return string|null
*/
public function getMeta() {
return $this->meta;
}
public function setMethod(string $method) {
$this->method = $method;
}
public function getMethod(): string {
return $this->method;
}
}
@@ -0,0 +1,145 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="statistics_woocommerce_purchases")
*/
class StatisticsWooCommercePurchaseEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @ORM\JoinColumn(name="newsletter_id", referencedColumnName="id", nullable=true)
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SendingQueueEntity")
* @ORM\JoinColumn(name="queue_id", referencedColumnName="id")
* @var SendingQueueEntity|null
*/
private $queue;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @ORM\JoinColumn(name="subscriber_id", referencedColumnName="id")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\StatisticsClickEntity", inversedBy="wooCommercePurchases")
* @ORM\JoinColumn(name="click_id", referencedColumnName="id")
* @var StatisticsClickEntity|null
*/
private $click;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $orderId;
/**
* @ORM\Column(type="string")
* @var string
*/
private $orderCurrency;
/**
* @ORM\Column(type="float")
* @var float
*/
private $orderPriceTotal;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status;
public function __construct(
NewsletterEntity $newsletter,
SendingQueueEntity $queue,
StatisticsClickEntity $click,
int $orderId,
string $orderCurrency,
float $orderPriceTotal,
string $status
) {
$this->newsletter = $newsletter;
$this->queue = $queue;
$this->click = $click;
$this->orderId = $orderId;
$this->orderCurrency = $orderCurrency;
$this->orderPriceTotal = $orderPriceTotal;
$this->status = $status;
}
public function getNewsletter(): ?NewsletterEntity {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
public function getQueue(): ?SendingQueueEntity {
$this->safelyLoadToOneAssociation('queue');
return $this->queue;
}
public function getSubscriber(): ?SubscriberEntity {
$this->safelyLoadToOneAssociation('subscriber');
return $this->subscriber;
}
public function getClick(): ?StatisticsClickEntity {
$this->safelyLoadToOneAssociation('click');
return $this->click;
}
public function getOrderId(): int {
return $this->orderId;
}
public function setSubscriber(?SubscriberEntity $subscriber) {
$this->subscriber = $subscriber;
}
public function getOrderCurrency(): string {
return $this->orderCurrency;
}
public function getOrderPriceTotal(): float {
return $this->orderPriceTotal;
}
public function setOrderCurrency(string $orderCurrency): void {
$this->orderCurrency = $orderCurrency;
}
public function setOrderPriceTotal(float $orderPriceTotal): void {
$this->orderPriceTotal = $orderPriceTotal;
}
public function getStatus(): string {
return $this->status;
}
public function setStatus(string $status): void {
$this->status = $status;
}
}
@@ -0,0 +1,59 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="stats_notifications")
*/
class StatsNotificationEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\NewsletterEntity")
* @var NewsletterEntity|null
*/
private $newsletter;
/**
* @ORM\OneToOne(targetEntity="MailPoet\Entities\ScheduledTaskEntity")
* @var ScheduledTaskEntity|null
*/
private $task;
public function __construct(
NewsletterEntity $newsletter,
ScheduledTaskEntity $task
) {
$this->newsletter = $newsletter;
$this->task = $task;
}
/**
* @return NewsletterEntity|null
*/
public function getNewsletter() {
$this->safelyLoadToOneAssociation('newsletter');
return $this->newsletter;
}
/**
* @return ScheduledTaskEntity|null
*/
public function getTask() {
$this->safelyLoadToOneAssociation('task');
return $this->task;
}
}
@@ -0,0 +1,122 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoet\InvalidStateException;
use MailPoet\Util\DateConverter;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="subscriber_custom_field")
*/
class SubscriberCustomFieldEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\CustomFieldEntity")
* @var CustomFieldEntity|null
*/
private $customField;
/**
* @ORM\Column(type="string")
* @var string
*/
private $value;
/**
* @param string|array|null $value
*/
public function __construct(
SubscriberEntity $subscriber,
CustomFieldEntity $customField,
$value
) {
$this->subscriber = $subscriber;
$this->customField = $customField;
$this->setValue($value);
}
/**
* @return SubscriberEntity|null
*/
public function getSubscriber() {
$this->safelyLoadToOneAssociation('subscriber');
return $this->subscriber;
}
public function getValue(): string {
return $this->value;
}
/**
* @return CustomFieldEntity|null
*/
public function getCustomField() {
return $this->customField;
}
/**
* @param string|array|null $value
*/
public function setValue($value): void {
$customField = $this->getCustomField();
if (!$customField instanceof CustomFieldEntity) {
throw new InvalidStateException('CustomField has to be set');
}
// format custom field data depending on type
if (is_array($value) && $customField->getType() === CustomFieldEntity::TYPE_DATE) {
$customFieldParams = $customField->getParams();
$dateFormat = $customFieldParams['date_format'] ?? null;
$dateType = isset($customFieldParams['date_type']) ? $customFieldParams['date_type'] : 'year_month_day';
switch ($dateType) {
case 'year_month_day':
$value = str_replace(['DD', 'MM', 'YYYY'], [$value['day'], $value['month'], $value['year']], $dateFormat);
break;
case 'year_month':
$value = str_replace(['MM', 'YYYY'], [$value['month'], $value['year']], $dateFormat);
break;
case 'month':
$value = (int)$value['month'] === 0 ? '' : sprintf('%s', $value['month']);
break;
case 'day':
$value = (int)$value['day'] === 0 ? '' : sprintf('%s', $value['day']);
break;
case 'year':
$value = (int)$value['year'] === 0 ? '' : sprintf('%04d', $value['year']);
break;
}
if (!empty($value) && is_string($value)) {
$value = (new DateConverter())->convertDateToDatetime($value, $dateFormat);
}
}
if (is_array($value)) {
throw new InvalidStateException('Final value has to be string');
}
$this->value = (string)$value;
}
}
@@ -0,0 +1,648 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use DateTimeInterface;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\DeletedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoet\Doctrine\EntityTraits\ValidationGroupsTrait;
use MailPoet\Util\Helpers;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\Common\Collections\Collection;
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="subscribers")
* @ORM\HasLifecycleCallbacks
* @ORM\EntityListeners({"\MailPoet\Doctrine\EventListeners\SubscriberListener"})
*/
class SubscriberEntity {
// hook names
public const HOOK_SUBSCRIBER_CREATED = 'mailpoet_subscriber_created';
public const HOOK_SUBSCRIBER_DELETED = 'mailpoet_subscriber_deleted';
public const HOOK_SUBSCRIBER_UPDATED = 'mailpoet_subscriber_updated';
public const HOOK_SUBSCRIBER_STATUS_CHANGED = 'mailpoet_subscriber_status_changed';
public const HOOK_MULTIPLE_SUBSCRIBERS_CREATED = 'mailpoet_multiple_subscribers_created';
public const HOOK_MULTIPLE_SUBSCRIBERS_DELETED = 'mailpoet_multiple_subscribers_deleted';
public const HOOK_MULTIPLE_SUBSCRIBERS_UPDATED = 'mailpoet_multiple_subscribers_updated';
// statuses
const STATUS_BOUNCED = 'bounced';
const STATUS_INACTIVE = 'inactive';
const STATUS_SUBSCRIBED = 'subscribed';
const STATUS_UNCONFIRMED = 'unconfirmed';
const STATUS_UNSUBSCRIBED = 'unsubscribed';
public const OBSOLETE_LINK_TOKEN_LENGTH = 6;
public const LINK_TOKEN_LENGTH = 32;
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use DeletedAtTrait;
use ValidationGroupsTrait;
/**
* @ORM\Column(type="bigint", nullable=true)
* @var string|null
*/
private $wpUserId;
/**
* @ORM\Column(type="boolean")
* @var bool
*/
private $isWoocommerceUser = false;
/**
* @ORM\Column(type="string")
* @var string
*/
private $firstName = '';
/**
* @ORM\Column(type="string")
* @var string
*/
private $lastName = '';
/**
* @ORM\Column(type="string")
* @Assert\Email(groups={"Saving"})
* @Assert\NotBlank()
* @var string
*/
private $email;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status = self::STATUS_UNCONFIRMED;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $subscribedIp;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $confirmedIp;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $confirmedAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastSubscribedAt;
/**
* @ORM\Column(type="text", nullable=true)
* @var string|null
*/
private $unconfirmedData;
/**
* @ORM\Column(type="string")
* @var string
*/
private $source = 'unknown';
/**
* @ORM\Column(type="integer")
* @var int
*/
private $countConfirmations = 0;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $unsubscribeToken;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $linkToken;
/**
* @ORM\Column(type="float", nullable=true)
* @var float|null
*/
private $engagementScore;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $engagementScoreUpdatedAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastEngagementAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastSendingAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastOpenAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastClickAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastPurchaseAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $lastPageViewAt;
/**
* @ORM\Column(type="datetimetz", nullable=true)
* @var DateTimeInterface|null
*/
private $woocommerceSyncedAt;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $emailCount = 0;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberSegmentEntity", mappedBy="subscriber", orphanRemoval=true)
* @var Collection<int, SubscriberSegmentEntity>
*/
private $subscriberSegments;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberCustomFieldEntity", mappedBy="subscriber", orphanRemoval=true)
* @var Collection<int, SubscriberCustomFieldEntity>
*/
private $subscriberCustomFields;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberTagEntity", mappedBy="subscriber", orphanRemoval=true)
* @var Collection<int, SubscriberTagEntity>
*/
private $subscriberTags;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\ScheduledTaskSubscriberEntity", mappedBy="subscriber", orphanRemoval=true)
* @var Collection<int, ScheduledTaskSubscriberEntity>
*/
private $scheduledTaskSubscribers;
public function __construct() {
$this->subscriberSegments = new ArrayCollection();
$this->subscriberCustomFields = new ArrayCollection();
$this->subscriberTags = new ArrayCollection();
$this->scheduledTaskSubscribers = new ArrayCollection();
}
/**
* @deprecated This is here only for backward compatibility with custom shortcodes https://kb.mailpoet.com/article/160-create-a-custom-shortcode
* This can be removed after 2026-01-01
*/
public function __get($key) {
$getterName = 'get' . Helpers::underscoreToCamelCase($key, $capitaliseFirstChar = true);
$callable = [$this, $getterName];
if (is_callable($callable)) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Intended for deprecation warnings
trigger_error(
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- if the function is callable, it's safe to output
"Direct access to \$subscriber->{$key} is deprecated and will be removed after 2026-01-01. Use \$subscriber->{$getterName}() instead.",
E_USER_DEPRECATED
);
return call_user_func($callable);
}
}
/**
* @return int|null
*/
public function getWpUserId() {
return $this->wpUserId ? (int)$this->wpUserId : null;
}
/**
* @param int|null $wpUserId
*/
public function setWpUserId($wpUserId) {
$this->wpUserId = $wpUserId ? (string)$wpUserId : null;
}
public function isWPUser(): bool {
return $this->getWpUserId() > 0;
}
/**
* @return bool
*/
public function getIsWoocommerceUser() {
return $this->isWoocommerceUser;
}
/**
* @param bool $isWoocommerceUser
*/
public function setIsWoocommerceUser($isWoocommerceUser) {
$this->isWoocommerceUser = $isWoocommerceUser;
}
/**
* @return string
*/
public function getFirstName() {
return $this->firstName;
}
/**
* @param string $firstName
*/
public function setFirstName($firstName) {
$this->firstName = $firstName;
}
/**
* @return string
*/
public function getLastName() {
return $this->lastName;
}
/**
* @param string $lastName
*/
public function setLastName($lastName) {
$this->lastName = $lastName;
}
/**
* @return string
*/
public function getEmail() {
return $this->email;
}
/**
* @param string $email
*/
public function setEmail($email) {
$this->email = $email;
}
/**
* @return string
*/
public function getStatus() {
return $this->status;
}
/**
* @param string $status
*/
public function setStatus($status) {
if (
!in_array($status, [
self::STATUS_BOUNCED,
self::STATUS_INACTIVE,
self::STATUS_SUBSCRIBED,
self::STATUS_UNCONFIRMED,
self::STATUS_UNSUBSCRIBED,
])
) {
throw new \InvalidArgumentException("Invalid status '{$status}' given to subscriber!");
}
$this->status = $status;
}
/**
* @return string|null
*/
public function getSubscribedIp() {
return $this->subscribedIp;
}
/**
* @param string $subscribedIp
*/
public function setSubscribedIp($subscribedIp) {
$this->subscribedIp = $subscribedIp;
}
/**
* @return string|null
*/
public function getConfirmedIp() {
return $this->confirmedIp;
}
/**
* @param string|null $confirmedIp
*/
public function setConfirmedIp($confirmedIp) {
$this->confirmedIp = $confirmedIp;
}
/**
* @return DateTimeInterface|null
*/
public function getConfirmedAt() {
return $this->confirmedAt;
}
/**
* @param DateTimeInterface|null $confirmedAt
*/
public function setConfirmedAt($confirmedAt) {
$this->confirmedAt = $confirmedAt;
}
/**
* @return DateTimeInterface|null
*/
public function getLastSubscribedAt() {
return $this->lastSubscribedAt;
}
/**
* @param DateTimeInterface|null $lastSubscribedAt
*/
public function setLastSubscribedAt($lastSubscribedAt) {
$this->lastSubscribedAt = $lastSubscribedAt;
}
/**
* @return string|null
*/
public function getUnconfirmedData() {
return $this->unconfirmedData;
}
/**
* @param string|null $unconfirmedData
*/
public function setUnconfirmedData($unconfirmedData) {
$this->unconfirmedData = $unconfirmedData;
}
/**
* @return string
*/
public function getSource() {
return $this->source;
}
/**
* @param string $source
*/
public function setSource($source) {
if (
!in_array($source, [
'api',
'form',
'unknown',
'imported',
'administrator',
'wordpress_user',
'woocommerce_user',
'woocommerce_checkout',
])
) {
throw new \InvalidArgumentException("Invalid source '{$source}' given to subscriber!");
}
$this->source = $source;
}
/**
* @return int
*/
public function getConfirmationsCount() {
return $this->countConfirmations;
}
/**
* @param int $countConfirmations
*/
public function setConfirmationsCount($countConfirmations) {
$this->countConfirmations = $countConfirmations;
}
/**
* @return string|null
*/
public function getUnsubscribeToken() {
return $this->unsubscribeToken;
}
/**
* @param string|null $unsubscribeToken
*/
public function setUnsubscribeToken($unsubscribeToken) {
$this->unsubscribeToken = $unsubscribeToken;
}
/**
* @return string|null
*/
public function getLinkToken() {
return $this->linkToken;
}
/**
* @param string|null $linkToken
*/
public function setLinkToken($linkToken) {
$this->linkToken = $linkToken;
}
/**
* @return Collection<int, SubscriberSegmentEntity>
*/
public function getSubscriberSegments(?string $status = null) {
if (!is_null($status)) {
$criteria = Criteria::create()
->where(Criteria::expr()->eq('status', $status));
$subscriberSegments = $this->subscriberSegments->matching($criteria);
} else {
$subscriberSegments = $this->subscriberSegments;
}
return $subscriberSegments;
}
/** * @return Collection<int, SegmentEntity> */
public function getSegments() {
return $this->subscriberSegments->map(function (SubscriberSegmentEntity $subscriberSegment = null) {
if (!$subscriberSegment) return null;
return $subscriberSegment->getSegment();
})->filter(function (?SegmentEntity $segment = null) {
return $segment !== null;
});
}
/**
* @return Collection<int, SubscriberCustomFieldEntity>
*/
public function getSubscriberCustomFields() {
return $this->subscriberCustomFields;
}
public function getSubscriberCustomField(CustomFieldEntity $customField): ?SubscriberCustomFieldEntity {
$criteria = Criteria::create()
->where(Criteria::expr()->eq('customField', $customField))
->setMaxResults(1);
return $this->getSubscriberCustomFields()->matching($criteria)->first() ?: null;
}
/**
* @return Collection<int, SubscriberTagEntity>
*/
public function getSubscriberTags() {
return $this->subscriberTags;
}
public function getSubscriberTag(TagEntity $tag): ?SubscriberTagEntity {
$criteria = Criteria::create()
->where(Criteria::expr()->eq('tag', $tag))
->setMaxResults(1);
return $this->getSubscriberTags()->matching($criteria)->first() ?: null;
}
/**
* @return float|null
*/
public function getEngagementScore(): ?float {
return $this->engagementScore;
}
/**
* @param float|null $engagementScore
*/
public function setEngagementScore(?float $engagementScore): void {
$this->engagementScore = $engagementScore;
}
/**
* @return DateTimeInterface|null
*/
public function getEngagementScoreUpdatedAt(): ?DateTimeInterface {
return $this->engagementScoreUpdatedAt;
}
/**
* @param DateTimeInterface|null $engagementScoreUpdatedAt
*/
public function setEngagementScoreUpdatedAt(?DateTimeInterface $engagementScoreUpdatedAt): void {
$this->engagementScoreUpdatedAt = $engagementScoreUpdatedAt;
}
public function getLastEngagementAt(): ?DateTimeInterface {
return $this->lastEngagementAt;
}
public function setLastEngagementAt(DateTimeInterface $lastEngagementAt): void {
$this->lastEngagementAt = $lastEngagementAt;
}
public function getLastSendingAt(): ?DateTimeInterface {
return $this->lastSendingAt;
}
public function setLastSendingAt(?DateTimeInterface $dateTime): void {
$this->lastSendingAt = $dateTime;
}
public function getLastOpenAt(): ?DateTimeInterface {
return $this->lastOpenAt;
}
public function setLastOpenAt(?DateTimeInterface $dateTime): void {
$this->lastOpenAt = $dateTime;
}
public function getLastClickAt(): ?DateTimeInterface {
return $this->lastClickAt;
}
public function setLastClickAt(?DateTimeInterface $dateTime): void {
$this->lastClickAt = $dateTime;
}
public function getLastPurchaseAt(): ?DateTimeInterface {
return $this->lastPurchaseAt;
}
public function setLastPurchaseAt(?DateTimeInterface $dateTime): void {
$this->lastPurchaseAt = $dateTime;
}
public function getLastPageViewAt(): ?DateTimeInterface {
return $this->lastPageViewAt;
}
public function setLastPageViewAt(?DateTimeInterface $dateTime): void {
$this->lastPageViewAt = $dateTime;
}
public function setWoocommerceSyncedAt(?DateTimeInterface $woocommerceSyncedAt): void {
$this->woocommerceSyncedAt = $woocommerceSyncedAt;
}
public function getWoocommerceSyncedAt(): ?DateTimeInterface {
return $this->woocommerceSyncedAt;
}
public function getEmailCount(): int {
return $this->emailCount;
}
public function setEmailCount(int $emailCount): void {
$this->emailCount = $emailCount;
}
/** @ORM\PreFlush */
public function cleanupSubscriberSegments(): void {
// Delete old orphan SubscriberSegments to avoid errors on update
$this->subscriberSegments->map(function (SubscriberSegmentEntity $subscriberSegment = null) {
if (!$subscriberSegment) return null;
if ($subscriberSegment->getSegment() === null) {
$this->subscriberSegments->removeElement($subscriberSegment);
}
});
}
}
@@ -0,0 +1,50 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use DateTimeInterface;
use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="subscriber_ips")
*/
class SubscriberIPEntity {
/**
* @ORM\Id
* @ORM\Column(type="string")
* @var string
*/
private $ip;
/**
* We have to use own type because createdAt is part of the primary key and basic datetimetz isn't supported in primary key
* @ORM\Id
* @ORM\Column(type="datetimetz_to_string")
* @var DateTimeInterface
*/
private $createdAt;
public function __construct(
string $ip
) {
$this->ip = $ip;
$this->createdAt = new Carbon();
}
public function getIP(): string {
return $this->ip;
}
public function getCreatedAt(): DateTimeInterface {
return $this->createdAt;
}
public function setCreatedAt(DateTimeInterface $createdAt): void {
$this->createdAt = $createdAt;
}
}
@@ -0,0 +1,81 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="subscriber_segment")
*/
class SubscriberSegmentEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SegmentEntity")
* @var SegmentEntity|null
*/
private $segment;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity", inversedBy="subscriberSegments")
* @var SubscriberEntity|null
*/
private $subscriber;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status;
public function __construct(
SegmentEntity $segment,
SubscriberEntity $subscriber,
string $status
) {
$this->segment = $segment;
$this->subscriber = $subscriber;
$this->status = $status;
}
/**
* @return SegmentEntity|null
*/
public function getSegment() {
$this->safelyLoadToOneAssociation('segment');
return $this->segment;
}
/**
* @return SubscriberEntity|null
*/
public function getSubscriber() {
$this->safelyLoadToOneAssociation('subscriber');
return $this->subscriber;
}
/**
* @return string
*/
public function getStatus(): string {
return $this->status;
}
/**
* @param string $status
*/
public function setStatus(string $status) {
$this->status = $status;
}
}
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\SafeToOneAssociationLoadTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="subscriber_tag")
*/
class SubscriberTagEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
use SafeToOneAssociationLoadTrait;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\TagEntity")
* @var TagEntity|null
*/
private $tag;
/**
* @ORM\ManyToOne(targetEntity="MailPoet\Entities\SubscriberEntity", inversedBy="subscriberTags")
* @var SubscriberEntity|null
*/
private $subscriber;
public function __construct(
TagEntity $tag,
SubscriberEntity $subscriber
) {
$this->tag = $tag;
$this->subscriber = $subscriber;
}
public function getTag(): ?TagEntity {
$this->safelyLoadToOneAssociation('tag');
return $this->tag;
}
public function getSubscriber(): ?SubscriberEntity {
$this->safelyLoadToOneAssociation('subscriber');
return $this->subscriber;
}
}
@@ -0,0 +1,67 @@
<?php declare(strict_types = 1);
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
use MailPoetVendor\Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity()
* @ORM\Table(name="tags")
*/
class TagEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\Column(type="string")
* @Assert\NotBlank()
* @var string
*/
private $name;
/**
* @ORM\Column(type="string")
* @var string
*/
private $description;
/**
* @ORM\OneToMany(targetEntity="MailPoet\Entities\SubscriberTagEntity", mappedBy="tag", fetch="EXTRA_LAZY")
* @var ArrayCollection<int, SubscriberTagEntity>
*/
private $subscriberTags;
public function __construct(
string $name,
string $description = ''
) {
$this->name = $name;
$this->description = $description;
$this->subscriberTags = new ArrayCollection();
}
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
public function getDescription(): string {
return $this->description;
}
public function setDescription(string $description): void {
$this->description = $description;
}
}
@@ -0,0 +1,66 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="user_agents")
*/
class UserAgentEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
public const USER_AGENT_TYPE_HUMAN = 0;
public const USER_AGENT_TYPE_MACHINE = 1;
public const MACHINE_USER_AGENTS = [
'Mozilla/5.0',
];
/**
* @ORM\Column(type="string")
* @var string
*/
private $hash;
/**
* @ORM\Column(type="string")
* @var string
*/
private $userAgent;
public function __construct(
string $userAgent
) {
$this->setUserAgent($userAgent);
}
public function getUserAgent(): string {
return $this->userAgent;
}
public function setUserAgent(string $userAgent): void {
$this->userAgent = $userAgent;
$this->hash = (string)crc32($userAgent);
}
public function getHash(): string {
return $this->hash;
}
public function getUserAgentType(): int {
if (in_array($this->getUserAgent(), self::MACHINE_USER_AGENTS, true)) {
return self::USER_AGENT_TYPE_MACHINE;
}
return self::USER_AGENT_TYPE_HUMAN;
}
}
@@ -0,0 +1,69 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\EntityTraits\AutoincrementedIdTrait;
use MailPoet\Doctrine\EntityTraits\CreatedAtTrait;
use MailPoet\Doctrine\EntityTraits\UpdatedAtTrait;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\Table(name="user_flags")
*/
class UserFlagEntity {
use AutoincrementedIdTrait;
use CreatedAtTrait;
use UpdatedAtTrait;
/**
* @ORM\Column(type="integer")
* @var int
*/
private $userId;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
/**
* @ORM\Column(type="string", nullable=true)
* @var string|null
*/
private $value;
/** @return int */
public function getUserId() {
return $this->userId;
}
/** @param int $userId */
public function setUserId($userId) {
$this->userId = $userId;
}
/** @return string */
public function getName() {
return $this->name;
}
/** @param string $name */
public function setName($name) {
$this->name = $name;
}
/** @return string|null */
public function getValue() {
return $this->value;
}
/** @param string|null $value */
public function setValue($value) {
$this->value = $value;
}
}
@@ -0,0 +1,50 @@
<?php declare(strict_types = 1);
namespace MailPoet\Entities;
if (!defined('ABSPATH')) exit;
use MailPoet\RuntimeException;
use MailPoetVendor\Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(readOnly=true)
* @ORM\Table(name="posts")
*/
class WpPostEntity {
/**
* @ORM\Column(type="integer", name="ID")
* @ORM\Id
* @ORM\GeneratedValue
* @var int
*/
private $id;
/**
* @ORM\Column(type="string", name="post_title")
* @var string
*/
private $postTitle;
public function __construct() {
throw new RuntimeException('WpPostEntity is read only and cannot be instantiated.');
}
public function getId(): int {
return $this->id;
}
public function getPostTitle(): string {
return $this->postTitle;
}
/**
* We don't use typehint for now because doctrine cache generator would fail as it doesn't know the class.
* @return \WP_Post|null
*/
public function getWpPostInstance() {
$post = \WP_Post::get_instance($this->id);
return $post ?: null;
}
}
@@ -0,0 +1 @@
<?php