| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- <?php
- namespace MailPoet\Models;
- if (!defined('ABSPATH')) exit;
- use MailPoet\DI\ContainerWrapper;
- use MailPoet\Entities\NewsletterEntity;
- use MailPoet\Newsletter\NewslettersRepository;
- use MailPoet\Settings\SettingsController;
- use MailPoet\Tasks\Sending as SendingTask;
- use MailPoet\Util\Helpers;
- use MailPoet\Util\Security;
- use MailPoet\WP\Functions as WPFunctions;
- /**
- * @property int $id
- * @property int $parentId
- * @property string $type
- * @property object|array|bool $queue
- * @property string $hash
- * @property string $senderName
- * @property string $senderAddress
- * @property string $replyToName
- * @property string $replyToAddress
- * @property string $status
- * @property string|object $meta
- * @property array $options
- * @property bool|array $statistics
- * @property string $sentAt
- * @property string $deletedAt
- * @property int $totalSent
- * @property int $totalScheduled
- * @property array $segments
- * @property string $subject
- * @property string $preheader
- * @property string|array|null $body
- * @property string|null $schedule
- * @property bool|null $isScheduled
- * @property string|null $scheduledAt
- * @property string $gaCampaign
- * @property string $event
- * @property string $unsubscribeToken
- */
- class Newsletter extends Model {
- public static $_table = MP_NEWSLETTERS_TABLE; // phpcs:ignore PSR2.Classes.PropertyDeclaration
- const TYPE_AUTOMATIC = NewsletterEntity::TYPE_AUTOMATIC;
- const TYPE_STANDARD = NewsletterEntity::TYPE_STANDARD;
- const TYPE_WELCOME = NewsletterEntity::TYPE_WELCOME;
- const TYPE_NOTIFICATION = NewsletterEntity::TYPE_NOTIFICATION;
- const TYPE_NOTIFICATION_HISTORY = NewsletterEntity::TYPE_NOTIFICATION_HISTORY;
- const TYPE_WC_TRANSACTIONAL_EMAIL = NewsletterEntity::TYPE_WC_TRANSACTIONAL_EMAIL;
- // standard newsletters
- const STATUS_DRAFT = NewsletterEntity::STATUS_DRAFT;
- const STATUS_SCHEDULED = NewsletterEntity::STATUS_SCHEDULED;
- const STATUS_SENDING = NewsletterEntity::STATUS_SENDING;
- const STATUS_SENT = NewsletterEntity::STATUS_SENT;
- // automatic newsletters status
- const STATUS_ACTIVE = NewsletterEntity::STATUS_ACTIVE;
- public function __construct() {
- parent::__construct();
- $this->addValidations('type', [
- 'required' => WPFunctions::get()->__('Please specify a type.', 'mailpoet'),
- ]);
- }
- public function queue() {
- return $this->hasOne(__NAMESPACE__ . '\SendingQueue', 'newsletter_id', 'id');
- }
- public function children() {
- return $this->hasMany(
- __NAMESPACE__ . '\Newsletter',
- 'parent_id',
- 'id'
- );
- }
- public function parent() {
- return $this->hasOne(
- __NAMESPACE__ . '\Newsletter',
- 'id',
- 'parent_id'
- );
- }
- public function segments() {
- return $this->hasManyThrough(
- __NAMESPACE__ . '\Segment',
- __NAMESPACE__ . '\NewsletterSegment',
- 'newsletter_id',
- 'segment_id'
- );
- }
- public function segmentRelations() {
- return $this->hasMany(
- __NAMESPACE__ . '\NewsletterSegment',
- 'newsletter_id',
- 'id'
- );
- }
- public function options() {
- return $this->hasManyThrough(
- __NAMESPACE__ . '\NewsletterOptionField',
- __NAMESPACE__ . '\NewsletterOption',
- 'newsletter_id',
- 'option_field_id'
- )->select_expr(MP_NEWSLETTER_OPTION_TABLE . '.value');
- }
- public function save() {
- if (is_string($this->deletedAt) && strlen(trim($this->deletedAt)) === 0) {
- $this->set_expr('deleted_at', 'NULL');
- }
- if (isset($this->body) && ($this->body !== false)) {
- $this->body = $this->getBodyString();
- $this->set(
- 'body',
- $this->body
- );
- }
- $this->set('hash',
- ($this->hash)
- ? $this->hash
- : Security::generateHash()
- );
- return parent::save();
- }
- public function trash() {
- $this->save();
- trigger_error('Calling Newsletter::trash() is deprecated and will be removed. Use \MailPoet\Newsletter\NewslettersRepository instead.', E_USER_DEPRECATED);
- ContainerWrapper::getInstance()->get(NewslettersRepository::class)->bulkTrash([$this->id]);
- return $this;
- }
- public function restore() {
- $this->save();
- trigger_error('Calling Newsletter::restore() is deprecated and will be removed. Use \MailPoet\Newsletter\NewslettersRepository instead.', E_USER_DEPRECATED);
- ContainerWrapper::getInstance()->get(NewslettersRepository::class)->bulkRestore([$this->id]);
- return $this;
- }
- public function delete() {
- trigger_error('Calling Newsletter::delete() is deprecated and will be removed. Use \MailPoet\Newsletter\NewslettersRepository instead.', E_USER_DEPRECATED);
- ContainerWrapper::getInstance()->get(NewslettersRepository::class)->bulkDelete([$this->id]);
- return null;
- }
- public function setStatus($status = null) {
- if ($status === self::STATUS_ACTIVE) {
- if (!$this->body || empty(json_decode($this->getBodyString()))) {
- $this->setError(
- Helpers::replaceLinkTags(
- __('This is an empty email without any content and it cannot be sent. Please update [link]the email[/link].'),
- 'admin.php?page=mailpoet-newsletter-editor&id=' . $this->id
- )
- );
- return $this;
- }
- }
- if (in_array($status, [
- self::STATUS_DRAFT,
- self::STATUS_SCHEDULED,
- self::STATUS_SENDING,
- self::STATUS_SENT,
- self::STATUS_ACTIVE,
- ])) {
- $this->set('status', $status);
- $this->save();
- }
- $typesWithActivation = [self::TYPE_NOTIFICATION, self::TYPE_WELCOME, self::TYPE_AUTOMATIC];
- if (($status === self::STATUS_DRAFT) && in_array($this->type, $typesWithActivation)) {
- ScheduledTask::pauseAllByNewsletter($this);
- }
- if (($status === self::STATUS_ACTIVE) && in_array($this->type, $typesWithActivation)) {
- ScheduledTask::setScheduledAllByNewsletter($this);
- }
- return $this;
- }
- public function duplicate($data = []) {
- $newsletterData = $this->asArray();
- // remove id so that it creates a new record
- unset($newsletterData['id']);
- // merge data with newsletter data (allows override)
- $data['unsubscribe_token'] = Security::generateUnsubscribeToken(self::class);
- $data = array_merge($newsletterData, $data);
- $duplicate = self::create();
- $duplicate->hydrate($data);
- // reset timestamps
- $duplicate->set_expr('created_at', 'NOW()');
- $duplicate->set_expr('updated_at', 'NOW()');
- $duplicate->set_expr('deleted_at', 'NULL');
- // reset status
- $duplicate->set('status', self::STATUS_DRAFT);
- // reset hash
- $duplicate->set('hash', null);
- // reset sent at date
- $duplicate->set('sent_at', null);
- $duplicate->save();
- if ($duplicate->getErrors() === false) {
- // create relationships between duplicate and segments
- $segments = $this->segments()->findMany();
- if (!empty($segments)) {
- foreach ($segments as $segment) {
- $relation = NewsletterSegment::create();
- $relation->segmentId = $segment->id;
- $relation->newsletterId = $duplicate->id;
- $relation->save();
- }
- }
- // duplicate options
- $options = NewsletterOption::where('newsletter_id', $this->id)
- ->findMany();
- $ignoredOptionFieldIds = Helpers::flattenArray(
- NewsletterOptionField::whereIn('name', ['isScheduled', 'scheduledAt'])
- ->select('id')
- ->findArray()
- );
- if (!empty($options)) {
- foreach ($options as $option) {
- if (in_array($option->optionFieldId, $ignoredOptionFieldIds)) {
- continue;
- }
- $relation = NewsletterOption::create();
- $relation->newsletterId = $duplicate->id;
- $relation->optionFieldId = $option->optionFieldId;
- $relation->value = $option->value;
- $relation->save();
- }
- }
- }
- return $duplicate;
- }
- public function createNotificationHistory() {
- $newsletterData = $this->asArray();
- // remove id so that it creates a new record
- unset($newsletterData['id']);
- $data = array_merge(
- $newsletterData,
- [
- 'parent_id' => $this->id,
- 'type' => self::TYPE_NOTIFICATION_HISTORY,
- 'status' => self::STATUS_SENDING,
- 'unsubscribe_token' => Security::generateUnsubscribeToken(self::class),
- ]
- );
- $notificationHistory = self::create();
- $notificationHistory->hydrate($data);
- // reset timestamps
- $notificationHistory->set_expr('created_at', 'NOW()');
- $notificationHistory->set_expr('updated_at', 'NOW()');
- $notificationHistory->set_expr('deleted_at', 'NULL');
- // reset hash
- $notificationHistory->set('hash', null);
- $notificationHistory->save();
- if ($notificationHistory->getErrors() === false) {
- // create relationships between notification history and segments
- $segments = $this->segments()->findMany();
- if (!empty($segments)) {
- foreach ($segments as $segment) {
- $relation = NewsletterSegment::create();
- $relation->segmentId = $segment->id;
- $relation->newsletterId = $notificationHistory->id;
- $relation->save();
- }
- }
- }
- return $notificationHistory;
- }
- public function asArray() {
- $model = parent::asArray();
- if (isset($model['body'])) {
- $model['body'] = json_decode($model['body'], true);
- }
- return $model;
- }
- public function withSegments($inclDeleted = false) {
- $this->segments = $this->segments()->findArray();
- if ($inclDeleted) {
- $this->withDeletedSegments();
- }
- return $this;
- }
- public function withDeletedSegments() {
- if (!empty($this->segments)) {
- $segmentIds = array_column($this->segments, 'id');
- $links = $this->segmentRelations()
- ->whereNotIn('segment_id', $segmentIds)->findArray();
- $deletedSegments = [];
- foreach ($links as $link) {
- $deletedSegments[] = [
- 'id' => $link['segment_id'],
- 'name' => WPFunctions::get()->__('Deleted list', 'mailpoet'),
- ];
- }
- $this->segments = array_merge($this->segments, $deletedSegments);
- }
- return $this;
- }
- public function getQueue($columns = '*') {
- return SendingTask::getByNewsletterId($this->id);
- }
- public function getBodyString(): string {
- if (is_array($this->body)) {
- return (string)json_encode($this->body);
- }
- if ($this->body === null) {
- return '';
- }
- return $this->body;
- }
- public function withSendingQueue() {
- $queue = $this->getQueue();
- if ($queue === false) {
- $this->queue = false;
- } else {
- $this->queue = $queue->asArray();
- }
- return $this;
- }
- public function withOptions() {
- $options = $this->options()->findArray();
- if (empty($options)) {
- $this->options = [];
- } else {
- $this->options = array_column($options, 'value', 'name');
- }
- return $this;
- }
- public static function filterWithOptions($orm, $type) {
- $orm = $orm->select(MP_NEWSLETTERS_TABLE . '.*');
- $optionFields = NewsletterOptionField::findArray();
- foreach ($optionFields as $optionField) {
- if ($optionField['newsletter_type'] !== $type) {
- continue;
- }
- $orm = $orm->select_expr(
- 'IFNULL(GROUP_CONCAT(CASE WHEN ' .
- MP_NEWSLETTER_OPTION_FIELDS_TABLE . '.id=' . $optionField['id'] . ' THEN ' .
- MP_NEWSLETTER_OPTION_TABLE . '.value END), NULL) as "' . $optionField['name'] . '"');
- }
- $orm = $orm
- ->left_outer_join(
- MP_NEWSLETTER_OPTION_TABLE,
- [
- MP_NEWSLETTERS_TABLE . '.id',
- '=',
- MP_NEWSLETTER_OPTION_TABLE . '.newsletter_id',
- ]
- )
- ->left_outer_join(
- MP_NEWSLETTER_OPTION_FIELDS_TABLE,
- [
- MP_NEWSLETTER_OPTION_FIELDS_TABLE . '.id',
- '=',
- MP_NEWSLETTER_OPTION_TABLE . '.option_field_id',
- ]
- )
- ->group_by(MP_NEWSLETTERS_TABLE . '.id');
- return $orm;
- }
- public static function filterStatus($orm, $status = false) {
- if (in_array($status, [
- self::STATUS_DRAFT,
- self::STATUS_SCHEDULED,
- self::STATUS_SENDING,
- self::STATUS_SENT,
- self::STATUS_ACTIVE,
- ])) {
- $orm->where('status', $status);
- }
- return $orm;
- }
- public static function filterType($orm, $type = false, $group = false) {
- if (in_array($type, [
- self::TYPE_STANDARD,
- self::TYPE_WELCOME,
- self::TYPE_AUTOMATIC,
- self::TYPE_NOTIFICATION,
- self::TYPE_NOTIFICATION_HISTORY,
- ])) {
- if ($type === self::TYPE_AUTOMATIC && $group) {
- $orm = $orm->join(
- NewsletterOptionField::$_table,
- [
- 'option_fields.newsletter_type', '=', self::$_table . '.type',
- ],
- 'option_fields'
- )
- ->join(
- NewsletterOption::$_table,
- [
- 'options.newsletter_id', '=', self::$_table . '.id',
- ],
- 'options'
- )
- ->whereRaw('`options`.`option_field_id` = `option_fields`.`id`')
- ->where('options.value', $group);
- }
- $orm = $orm->where(self::$_table . '.type', $type);
- }
- return $orm;
- }
- public static function createOrUpdate($data = []) {
- $data['unsubscribe_token'] = Security::generateUnsubscribeToken(self::class);
- return parent::_createOrUpdate($data, false, function($data) {
- $settings = SettingsController::getInstance();
- // set default sender based on settings
- if (empty($data['sender'])) {
- $sender = $settings->get('sender', []);
- $data['sender_name'] = (
- !empty($sender['name'])
- ? $sender['name']
- : ''
- );
- $data['sender_address'] = (
- !empty($sender['address'])
- ? $sender['address']
- : ''
- );
- }
- // set default reply_to based on settings
- if (empty($data['reply_to'])) {
- $replyTo = $settings->get('reply_to', []);
- $data['reply_to_name'] = (
- !empty($replyTo['name'])
- ? $replyTo['name']
- : ''
- );
- $data['reply_to_address'] = (
- !empty($replyTo['address'])
- ? $replyTo['address']
- : ''
- );
- }
- return $data;
- });
- }
- public static function getWelcomeNotificationsForSegments($segments) {
- return NewsletterOption::tableAlias('options')
- ->select('options.newsletter_id')
- ->select('options.value', 'segment_id')
- ->join(
- self::$_table,
- 'newsletters.id = options.newsletter_id',
- 'newsletters'
- )
- ->join(
- MP_NEWSLETTER_OPTION_FIELDS_TABLE,
- 'option_fields.id = options.option_field_id',
- 'option_fields'
- )
- ->whereNull('newsletters.deleted_at')
- ->where('newsletters.type', 'welcome')
- ->where('option_fields.name', 'segment')
- ->whereIn('options.value', $segments)
- ->findMany();
- }
- public static function getByHash($hash) {
- return parent::where('hash', $hash)
- ->findOne();
- }
- public function getMeta() {
- if (!$this->meta) return;
- return (Helpers::isJson($this->meta) && is_string($this->meta)) ? json_decode($this->meta, true) : $this->meta;
- }
- public static function findOneWithOptions($id) {
- $newsletter = self::findOne($id);
- if (!$newsletter instanceof self) {
- return false;
- }
- return self::filter('filterWithOptions', $newsletter->type)->findOne($id);
- }
- }
|