Your IP : 216.73.216.95


Current Path : /var/www/html/newsite/wp-content/plugins/mailpoet/lib/Migrations/
Upload File :
Current File : /var/www/html/newsite/wp-content/plugins/mailpoet/lib/Migrations/Migration_20221028_105818.php

<?php declare(strict_types = 1);

namespace MailPoet\Migrations;

if (!defined('ABSPATH')) exit;


use MailPoet\Config\Env;
use MailPoet\Cron\CronTrigger;
use MailPoet\Cron\Workers\StatsNotifications\Worker;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\FormEntity;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterLinkEntity;
use MailPoet\Entities\NewsletterTemplateEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Form\FormsRepository;
use MailPoet\Migrator\Migration;
use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
use MailPoet\Segments\DynamicSegments\Filters\UserRole;
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory;
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSubscription;
use MailPoet\Settings\SettingsChangeHandler;
use MailPoet\Settings\SettingsController;
use MailPoet\Settings\TrackingConfig;
use MailPoet\Util\Helpers;
use MailPoet\Util\Notices\ChangedTrackingNotice;
use MailPoet\WP\Functions as WPFunctions;

/**
 * Moved from MailPoet\Config\Migrator.
 *
 * The "created_at" column must be NULL in some tables to avoid "there can be only one
 * TIMESTAMP column with CURRENT_TIMESTAMP" error on MySQL version < 5.6.5 that occurs
 * even when other timestamp is simply "NOT NULL".
 */
class Migration_20221028_105818 extends Migration {
  /** @var string */
  private $prefix;

  /** @var string */
  private $charsetCollate;

  /** @var string[] */
  private $models = [
    'segments',
    'settings',
    'custom_fields',
    'scheduled_tasks',
    'stats_notifications',
    'scheduled_task_subscribers',
    'sending_queues',
    'subscribers',
    'subscriber_segment',
    'subscriber_custom_field',
    'subscriber_ips',
    'newsletters',
    'newsletter_templates',
    'newsletter_option_fields',
    'newsletter_option',
    'newsletter_segment',
    'newsletter_links',
    'newsletter_posts',
    'forms',
    'statistics_newsletters',
    'statistics_clicks',
    'statistics_bounces',
    'statistics_opens',
    'statistics_unsubscribes',
    'statistics_forms',
    'statistics_woocommerce_purchases',
    'log',
    'user_flags',
    'feature_flags',
    'dynamic_segment_filters',
    'user_agents',
    'tags',
    'subscriber_tag',
  ];

  /** @var SettingsController */
  private $settings;

  /** @var SettingsChangeHandler */
  private $settingsChangeHandler;

  /** @var FormsRepository */
  private $formsRepository;

  /** @var WPFunctions */
  private $wp;

  public function run(): void {
    $this->prefix = Env::$dbPrefix;
    $this->charsetCollate = Env::$dbCharsetCollate;
    $this->settings = $this->container->get(SettingsController::class);
    $this->settingsChangeHandler = $this->container->get(SettingsChangeHandler::class);
    $this->formsRepository = $this->container->get(FormsRepository::class);
    $this->wp = $this->container->get(WPFunctions::class);

    // Ensure dbDelta function
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    $output = [];
    foreach ($this->models as $model) {
      $modelMethod = Helpers::underscoreToCamelCase($model);
      $output = array_merge(dbDelta($this->$modelMethod()), $output);
    }
    $this->updateNullInUnsubscribeStats();
    $this->fixScheduledTasksSubscribersTimestampColumns();
    $this->removeDeprecatedStatisticsIndexes();
    $this->migrateSerializedFilterDataToNewColumns();
    $this->migratePurchasedProductDynamicFilters();
    $this->migrateWooSubscriptionsDynamicFilters();
    $this->migratePurchasedInCategoryDynamicFilters();
    $this->migrateEmailActionsFilters();
    $this->updateDefaultInactiveSubscriberTimeRange();
    $this->setDefaultValueForLoadingThirdPartyLibrariesForExistingInstalls();
    $this->disableMailPoetCronTrigger();

    // POPULATOR
    $this->updateMetaFields();
    $this->updateLastSubscribedAt();
    $this->enableStatsNotificationsForAutomatedEmails();
    $this->updateSentUnsubscribeLinksToInstantUnsubscribeLinks();
    $this->pauseTasksForPausedNewsletters();
    $this->moveGoogleAnalyticsFromPremium();
    $this->addPlacementStatusToForms();
    $this->migrateFormPlacement();
    $this->moveNewsletterTemplatesThumbnailData();
    $this->updateToUnifiedTrackingSettings();
    $this->fixNotificationHistoryRecordsStuckAtSending();
  }

  private function segments() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(90) NOT NULL,',
      'type varchar(90) NOT NULL DEFAULT "default",',
      'description varchar(250) NOT NULL DEFAULT "",',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'deleted_at timestamp NULL,',
      'average_engagement_score FLOAT unsigned NULL,',
      'average_engagement_score_updated_at timestamp NULL,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY name (name),',
      'KEY average_engagement_score_updated_at (average_engagement_score_updated_at)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function settings() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(50) NOT NULL,',
      'value longtext,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY name (name)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function customFields() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(90) NOT NULL,',
      'type varchar(90) NOT NULL,',
      'params longtext NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY name (name)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function scheduledTasks() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'type varchar(90) NULL DEFAULT NULL,',
      'status varchar(12) NULL DEFAULT NULL,',
      'priority mediumint(9) NOT NULL DEFAULT 0,',
      'scheduled_at timestamp NULL,',
      'processed_at timestamp NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'deleted_at timestamp NULL,',
      'in_progress int(1),',
      'reschedule_count int(11) NOT NULL DEFAULT 0,',
      'meta longtext,',
      'PRIMARY KEY  (id),',
      'KEY type (type),',
      'KEY status (status)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statsNotifications() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'task_id int(11) unsigned NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id),',
      'UNIQUE KEY newsletter_id_task_id (newsletter_id, task_id),',
      'KEY task_id (task_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function disableMailPoetCronTrigger() {
    $method = $this->settings->get(CronTrigger::SETTING_NAME . '.method');
    if ($method !== 'MailPoet') {
      return;
    }
    $this->settings->set(CronTrigger::SETTING_NAME . '.method', CronTrigger::METHOD_WORDPRESS);
  }

  private function scheduledTaskSubscribers() {
    $attributes = [
      'task_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'processed int(1) NOT NULL,',
      'failed smallint(1) NOT NULL DEFAULT 0,',
      'error text NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (task_id, subscriber_id),',
      'KEY subscriber_id (subscriber_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function sendingQueues() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'task_id int(11) unsigned NOT NULL,',
      'newsletter_id int(11) unsigned NULL,',
      'newsletter_rendered_body longtext,',
      'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
      'subscribers longtext,',
      'count_total int(11) unsigned NOT NULL DEFAULT 0,',
      'count_processed int(11) unsigned NOT NULL DEFAULT 0,',
      'count_to_process int(11) unsigned NOT NULL DEFAULT 0,',
      'meta longtext,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'deleted_at timestamp NULL,',
      'PRIMARY KEY  (id),',
      'KEY task_id (task_id),',
      'KEY newsletter_id (newsletter_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function subscribers() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'wp_user_id bigint(20) NULL,',
      'is_woocommerce_user int(1) NOT NULL DEFAULT 0,',
      'first_name varchar(255) NOT NULL DEFAULT "",',
      'last_name varchar(255) NOT NULL DEFAULT "",',
      'email varchar(150) NOT NULL,',
      'status varchar(12) NOT NULL DEFAULT "' . SubscriberEntity::STATUS_UNCONFIRMED . '",',
      'subscribed_ip varchar(45) NULL,',
      'confirmed_ip varchar(45) NULL,',
      'confirmed_at timestamp NULL,',
      'last_subscribed_at timestamp NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'deleted_at timestamp NULL,',
      'unconfirmed_data longtext,',
      "source enum('form','imported','administrator','api','wordpress_user','woocommerce_user','woocommerce_checkout','unknown') DEFAULT 'unknown',",
      'count_confirmations int(11) unsigned NOT NULL DEFAULT 0,',
      'unsubscribe_token char(15) NULL,',
      'link_token char(32) NULL,',
      'engagement_score FLOAT unsigned NULL,',
      'engagement_score_updated_at timestamp NULL,',
      'last_engagement_at timestamp NULL,',
      'woocommerce_synced_at timestamp NULL,',
      'email_count int(11) unsigned NOT NULL DEFAULT 0, ',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY email (email),',
      'UNIQUE KEY unsubscribe_token (unsubscribe_token),',
      'KEY wp_user_id (wp_user_id),',
      'KEY updated_at (updated_at),',
      'KEY status_deleted_at (status,deleted_at),',
      'KEY last_subscribed_at (last_subscribed_at),',
      'KEY engagement_score_updated_at (engagement_score_updated_at),',
      'KEY link_token (link_token)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function subscriberSegment() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'segment_id int(11) unsigned NOT NULL,',
      'status varchar(12) NOT NULL DEFAULT "' . SubscriberEntity::STATUS_SUBSCRIBED . '",',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY subscriber_segment (subscriber_id,segment_id),',
      'KEY segment_id (segment_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function subscriberCustomField() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'custom_field_id int(11) unsigned NOT NULL,',
      'value text NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function subscriberIps() {
    $attributes = [
      'ip varchar(45) NOT NULL,',
      'created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (created_at, ip),',
      'KEY ip (ip)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletters() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'hash varchar(150) NULL DEFAULT NULL,',
      'parent_id int(11) unsigned NULL,',
      'subject varchar(250) NOT NULL DEFAULT "",',
      'type varchar(20) NOT NULL DEFAULT "standard",',
      'sender_address varchar(150) NOT NULL DEFAULT "",',
      'sender_name varchar(150) NOT NULL DEFAULT "",',
      'status varchar(20) NOT NULL DEFAULT "' . NewsletterEntity::STATUS_DRAFT . '",',
      'reply_to_address varchar(150) NOT NULL DEFAULT "",',
      'reply_to_name varchar(150) NOT NULL DEFAULT "",',
      'preheader varchar(250) NOT NULL DEFAULT "",',
      'body longtext,',
      'sent_at timestamp NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'deleted_at timestamp NULL,',
      'unsubscribe_token char(15) NULL,',
      'ga_campaign varchar(250) NOT NULL DEFAULT "",',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY unsubscribe_token (unsubscribe_token),',
      'KEY type_status (type,status)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletterTemplates() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) NULL DEFAULT 0,',
      'name varchar(250) NOT NULL,',
      'categories varchar(250) NOT NULL DEFAULT "[]",',
      'description varchar(255) NOT NULL DEFAULT "",',
      'body longtext,',
      'thumbnail longtext,',
      'thumbnail_data longtext,',
      'readonly tinyint(1) DEFAULT 0,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletterOptionFields() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(90) NOT NULL,',
      'newsletter_type varchar(90) NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY name_newsletter_type (newsletter_type,name)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletterOption() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'option_field_id int(11) unsigned NOT NULL,',
      'value longtext,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletterSegment() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'segment_id int(11) unsigned NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletterLinks() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NOT NULL,',
      'url varchar(2083) NOT NULL,',
      'hash varchar(20) NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'KEY newsletter_id (newsletter_id),',
      'KEY queue_id (queue_id),',
      'KEY url (url(100))',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function newsletterPosts() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'post_id int(11) unsigned NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'KEY newsletter_id (newsletter_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function forms() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(90) NOT NULL,', // should be null but db_delta can't handle this change
      'status varchar(20) NOT NULL DEFAULT "' . FormEntity::STATUS_ENABLED . '",',
      'body longtext,',
      'settings longtext,',
      'styles longtext,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'deleted_at timestamp NULL,',
      'PRIMARY KEY  (id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsNewsletters() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NOT NULL,',
      'sent_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'KEY newsletter_id (newsletter_id),',
      'KEY subscriber_id (subscriber_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsBounces() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NOT NULL,',
      'created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsClicks() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NOT NULL,',
      'link_id int(11) unsigned NOT NULL,',
      'user_agent_id int(11) unsigned NULL,',
      'user_agent_type tinyint(1) NOT NULL DEFAULT 0,',
      'count int(11) unsigned NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'KEY newsletter_id_subscriber_id_user_agent_type (newsletter_id, subscriber_id, user_agent_type),',
      'KEY queue_id (queue_id),',
      'KEY subscriber_id (subscriber_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsOpens() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NOT NULL,',
      'user_agent_id int(11) unsigned NULL,',
      'user_agent_type tinyint(1) NOT NULL DEFAULT 0,',
      'created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'KEY newsletter_id_subscriber_id_user_agent_type (newsletter_id, subscriber_id, user_agent_type),',
      'KEY queue_id (queue_id),',
      'KEY subscriber_id (subscriber_id),',
      'KEY created_at (created_at),',
      'KEY subscriber_id_created_at (subscriber_id, created_at)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsUnsubscribes() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NULL,',
      'created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,',
      "source varchar(255) DEFAULT 'unknown',",
      'meta varchar(255) NULL,',
      'PRIMARY KEY  (id),',
      'KEY newsletter_id_subscriber_id (newsletter_id, subscriber_id),',
      'KEY queue_id (queue_id),',
      'KEY subscriber_id (subscriber_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsForms() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'form_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,',
      'PRIMARY KEY  (id),',
      'UNIQUE KEY form_subscriber (form_id,subscriber_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function statisticsWoocommercePurchases() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'newsletter_id int(11) unsigned NOT NULL,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'queue_id int(11) unsigned NOT NULL,',
      'click_id int(11) unsigned NOT NULL,',
      'order_id bigint(20) unsigned NOT NULL,',
      'order_currency char(3) NOT NULL,',
      'order_price_total float NOT NULL COMMENT "With shipping and taxes in order_currency",',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id),',
      'KEY newsletter_id (newsletter_id),',
      'KEY queue_id (queue_id),',
      'KEY subscriber_id (subscriber_id),',
      'UNIQUE KEY click_id_order_id (click_id, order_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function log() {
    $attributes = [
      'id bigint(20) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(255),',
      'level int(11),',
      'message longtext,',
      'created_at timestamp DEFAULT CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function userFlags() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'user_id bigint(20) NOT NULL,',
      'name varchar(50) NOT NULL,',
      'value varchar(255),',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id),',
      'UNIQUE KEY user_id_name (user_id, name)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function featureFlags() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(100) NOT NULL,',
      'value tinyint(1),',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id),',
      'UNIQUE KEY name (name)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function dynamicSegmentFilters() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'segment_id int(11) unsigned NOT NULL,',
      'created_at timestamp NULL,',
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'filter_data longblob,',
      'filter_type varchar(255) NULL,',
      'action varchar(255) NULL,',
      'PRIMARY KEY (id),',
      'KEY segment_id (segment_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function userAgents() {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'hash varchar(32) UNIQUE NOT NULL, ',
      'user_agent text NOT NULL, ',
      'created_at timestamp NULL,',
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function tags(): string {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'name varchar(191) NOT NULL,',
      'description text NOT NULL DEFAULT "",',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id),',
      'UNIQUE KEY name (name)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function subscriberTag(): string {
    $attributes = [
      'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
      'subscriber_id int(11) unsigned NOT NULL,',
      'tag_id int(11) unsigned NOT NULL,',
      'created_at timestamp NULL,', // must be NULL, see comment at the top
      'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
      'PRIMARY KEY (id),',
      'UNIQUE KEY subscriber_tag (subscriber_id, tag_id),',
      'KEY tag_id (tag_id)',
    ];
    return $this->sqlify(__FUNCTION__, $attributes);
  }

  private function sqlify($model, $attributes) {
    $table = $this->prefix . Helpers::camelCaseToUnderscore($model);

    $sql = [];
    $sql[] = "CREATE TABLE " . $table . " (";
    $sql = array_merge($sql, $attributes);
    $sql[] = ") " . $this->charsetCollate . ";";

    return implode("\n", $sql);
  }

  private function updateNullInUnsubscribeStats() {
    global $wpdb;
    // perform once for versions below or equal to 3.47.6
    if (version_compare((string)$this->settings->get('db_version', '3.47.6'), '3.47.6', '>')) {
      return false;
    }
    $table = esc_sql("{$this->prefix}statistics_unsubscribes");
    $query = "
    ALTER TABLE `{$table}`
      CHANGE `newsletter_id` `newsletter_id` int(11) unsigned NULL,
      CHANGE `queue_id` `queue_id` int(11) unsigned NULL;
    ";
    $wpdb->query($query);
    return true;
  }

  /**
   * This method adds updated_at column to scheduled_task_subscribers for users with old MySQL..
   * Updated_at was added after created_at column and created_at used to have default CURRENT_TIMESTAMP.
   * Since MySQL versions below 5.6.5 allow only one column with CURRENT_TIMESTAMP as default per table
   * and db_delta doesn't remove default values we need to perform this change manually..
   * @return bool
   */
  private function fixScheduledTasksSubscribersTimestampColumns() {
    // skip the migration if the DB version is higher than 3.63.0 or is not set (a new install)
    if (version_compare((string)$this->settings->get('db_version', '3.63.1'), '3.63.0', '>')) {
      return false;
    }

    global $wpdb;
    $scheduledTasksSubscribersTable = esc_sql("{$this->prefix}scheduled_task_subscribers");
    // Remove default CURRENT_TIMESTAMP from created_at
    $updateCreatedAtQuery = "
      ALTER TABLE `$scheduledTasksSubscribersTable`
      CHANGE `created_at` `created_at` timestamp NULL;
    ";
    $wpdb->query($updateCreatedAtQuery);

    // Add updated_at column in case it doesn't exist
    $updatedAtColumnExists = $wpdb->get_results($wpdb->prepare("
      SELECT COLUMN_NAME
      FROM INFORMATION_SCHEMA.COLUMNS
      WHERE table_name = %s AND column_name = 'updated_at';
     ", $scheduledTasksSubscribersTable));
    if (empty($updatedAtColumnExists)) {
      $addUpdatedAtQuery = "
        ALTER TABLE `$scheduledTasksSubscribersTable`
        ADD `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
      ";
      $wpdb->query($addUpdatedAtQuery);
    }
    return true;
  }

  private function removeDeprecatedStatisticsIndexes(): bool {
    global $wpdb;
    // skip the migration if the DB version is higher than 3.67.1 or is not set (a new install)
    if (version_compare((string)$this->settings->get('db_version', '3.67.1'), '3.67.1', '>')) {
      return false;
    }

    $dbName = Env::$dbName;
    $statisticsTables = [
      esc_sql("{$this->prefix}statistics_clicks"),
      esc_sql("{$this->prefix}statistics_opens"),
    ];
    foreach ($statisticsTables as $statisticsTable) {
      $oldStatisticsIndexExists = $wpdb->get_results($wpdb->prepare("
      SELECT DISTINCT INDEX_NAME
      FROM INFORMATION_SCHEMA.STATISTICS
      WHERE TABLE_SCHEMA = %s
        AND TABLE_NAME = %s
        AND INDEX_NAME='newsletter_id_subscriber_id'
     ", $dbName, $statisticsTable));
      if (!empty($oldStatisticsIndexExists)) {
        $dropIndexQuery = "
        ALTER TABLE `{$statisticsTable}`
          DROP INDEX `newsletter_id_subscriber_id`
      ";
        $wpdb->query($dropIndexQuery);
      }
    }

    return true;
  }

  private function migrateSerializedFilterDataToNewColumns(): bool {
    global $wpdb;
    // skip the migration if the DB version is higher than 3.73.1 or is not set (a new install)
    if (version_compare((string)$this->settings->get('db_version', '3.73.1'), '3.73.0', '>')) {
      return false;
    }

    $dynamicSegmentFiltersTable = esc_sql("{$this->prefix}dynamic_segment_filters");
    $dynamicSegmentFilters = $wpdb->get_results("
      SELECT id, filter_data, filter_type, `action`
      FROM {$dynamicSegmentFiltersTable}
    ", ARRAY_A);
    foreach ($dynamicSegmentFilters as $dynamicSegmentFilter) {
      if ($dynamicSegmentFilter['filter_type'] && $dynamicSegmentFilter['action']) {
        continue;
      }
      /** @var array $filterData */
      $filterData = unserialize($dynamicSegmentFilter['filter_data']);
      // bc compatibility fix, the filter with the segmentType userRole didn't have filled action
      if ($filterData['segmentType'] === DynamicSegmentFilterData::TYPE_USER_ROLE && empty($filterData['action'])) {
        $filterData['action'] = UserRole::TYPE;
      }
      $wpdb->update($dynamicSegmentFiltersTable, [
        'action' => $filterData['action'] ?? null,
        'filter_type' => $filterData['segmentType'] ?? null,
      ], ['id' => $dynamicSegmentFilter['id']]);
    }

    return true;
  }

  private function migratePurchasedProductDynamicFilters(): bool {
    global $wpdb;
    // skip the migration if the DB version is higher than 3.74.3 or is not set (a new install)
    if (version_compare((string)$this->settings->get('db_version', '3.74.3'), '3.74.2', '>')) {
      return false;
    }

    $dynamicSegmentFiltersTable = esc_sql("{$this->prefix}dynamic_segment_filters");
    $filterType = DynamicSegmentFilterData::TYPE_WOOCOMMERCE;
    $action = WooCommerceProduct::ACTION_PRODUCT;
    $dynamicSegmentFilters = $wpdb->get_results("
      SELECT `id`, `filter_data`, `filter_type`, `action`
      FROM {$dynamicSegmentFiltersTable}
      WHERE `filter_type` = '{$filterType}'
        AND `action` = '{$action}'
    ", ARRAY_A);

    foreach ($dynamicSegmentFilters as $dynamicSegmentFilter) {
      /** @var array $filterData */
      $filterData = unserialize($dynamicSegmentFilter['filter_data']);
      if (!isset($filterData['product_ids'])) {
        $filterData['product_ids'] = [];
      }

      if (isset($filterData['product_id']) && !in_array($filterData['product_id'], $filterData['product_ids'])) {
        $filterData['product_ids'][] = $filterData['product_id'];
        unset($filterData['product_id']);
      }

      if (!isset($filterData['operator'])) {
        $filterData['operator'] = DynamicSegmentFilterData::OPERATOR_ANY;
      }

      $wpdb->update($dynamicSegmentFiltersTable, [
        'filter_data' => serialize($filterData),
      ], ['id' => $dynamicSegmentFilter['id']]);
    }

    return true;
  }

  private function migratePurchasedInCategoryDynamicFilters(): bool {
    global $wpdb;
    // skip the migration if the DB version is higher than 3.75.1 or is not set (a new install)
    if (version_compare((string)$this->settings->get('db_version', '3.76.0'), '3.75.1', '>')) {
      return false;
    }

    $dynamicSegmentFiltersTable = esc_sql("{$this->prefix}dynamic_segment_filters");
    $filterType = DynamicSegmentFilterData::TYPE_WOOCOMMERCE;
    $action = WooCommerceCategory::ACTION_CATEGORY;
    $dynamicSegmentFilters = $wpdb->get_results($wpdb->prepare("
      SELECT `id`, `filter_data`, `filter_type`, `action`
      FROM {$dynamicSegmentFiltersTable}
      WHERE `filter_type` = %s
        AND `action` = %s
    ", $filterType, $action), ARRAY_A);

    foreach ($dynamicSegmentFilters as $dynamicSegmentFilter) {
      /** @var array $filterData */
      $filterData = unserialize($dynamicSegmentFilter['filter_data']);
      if (!isset($filterData['category_ids'])) {
        $filterData['category_ids'] = [];
      }

      if (isset($filterData['category_id']) && !in_array($filterData['category_id'], $filterData['category_ids'])) {
        $filterData['category_ids'][] = $filterData['category_id'];
        unset($filterData['category_id']);
      }

      if (!isset($filterData['operator'])) {
        $filterData['operator'] = DynamicSegmentFilterData::OPERATOR_ANY;
      }

      $wpdb->update($dynamicSegmentFiltersTable, [
        'filter_data' => serialize($filterData),
      ], ['id' => $dynamicSegmentFilter['id']]);
    }

    return true;
  }

  private function migrateWooSubscriptionsDynamicFilters(): bool {
    global $wpdb;
    // skip the migration if the DB version is higher than 3.75.1 or is not set (a new installation)
    if (version_compare((string)$this->settings->get('db_version', '3.76.0'), '3.75.1', '>')) {
      return false;
    }

    $dynamicSegmentFiltersTable = esc_sql("{$this->prefix}dynamic_segment_filters");
    $filterType = DynamicSegmentFilterData::TYPE_WOOCOMMERCE_SUBSCRIPTION;
    $action = WooCommerceSubscription::ACTION_HAS_ACTIVE;
    $dynamicSegmentFilters = $wpdb->get_results($wpdb->prepare("
      SELECT `id`, `filter_data`, `filter_type`, `action`
      FROM {$dynamicSegmentFiltersTable}
      WHERE `filter_type` = %s
        AND `action` = %s
    ", $filterType, $action), ARRAY_A);

    foreach ($dynamicSegmentFilters as $dynamicSegmentFilter) {
      /** @var array $filterData */
      $filterData = unserialize($dynamicSegmentFilter['filter_data']);
      if (!isset($filterData['product_ids'])) {
        $filterData['product_ids'] = [];
      }

      if (isset($filterData['product_id']) && !in_array($filterData['product_id'], $filterData['product_ids'])) {
        $filterData['product_ids'][] = $filterData['product_id'];
        unset($filterData['product_id']);
      }

      if (!isset($filterData['operator'])) {
        $filterData['operator'] = DynamicSegmentFilterData::OPERATOR_ANY;
      }

      $wpdb->update($dynamicSegmentFiltersTable, [
        'filter_data' => serialize($filterData),
      ], ['id' => $dynamicSegmentFilter['id']]);
    }
    return true;
  }

  private function migrateEmailActionsFilters(): bool {
    global $wpdb;
    // skip the migration if the DB version is higher than 3.77.1 or is not set (a new installation)
    if (version_compare($this->settings->get('db_version', '3.77.2'), '3.77.1', '>')) {
      return false;
    }

    $dynamicSegmentFiltersTable = esc_sql("{$this->prefix}dynamic_segment_filters");
    $filterType = DynamicSegmentFilterData::TYPE_EMAIL;
    $dynamicSegmentFilters = $wpdb->get_results("
      SELECT `id`, `filter_data`, `filter_type`, `action`
      FROM {$dynamicSegmentFiltersTable}
      WHERE `filter_type` = '{$filterType}'
    ", ARRAY_A);

    foreach ($dynamicSegmentFilters as $dynamicSegmentFilter) {
      if (!is_array($dynamicSegmentFilter)) {
        continue;
      }
      $filterData = unserialize($dynamicSegmentFilter['filter_data']);
      if (!is_array($filterData)) {
        continue;
      }
      $action = $dynamicSegmentFilter['action'];

      // Not clicked filter is no longer used and was replaced by clicked with none of operator
      if ($action === EmailAction::ACTION_NOT_CLICKED) {
        $action = EmailAction::ACTION_CLICKED;
        $filterData['operator'] = DynamicSegmentFilterData::OPERATOR_NONE;
      }

      // Clicked link filter is refactored to work with multiple link ids
      if ($action === EmailAction::ACTION_CLICKED) {
        if (!isset($filterData['link_ids'])) {
          $filterData['link_ids'] = [];
        }

        if (isset($filterData['link_id']) && !in_array($filterData['link_id'], $filterData['link_ids'])) {
          $filterData['link_ids'][] = (int)$filterData['link_id'];
          unset($filterData['link_id']);
        }
      }

      // Not opened filter is no longer used and was replaced by opened with none of operand
      if ($action === EmailAction::ACTION_NOT_OPENED) {
        $action = EmailAction::ACTION_OPENED;
        $filterData['operator'] = DynamicSegmentFilterData::OPERATOR_NONE;
      }

      // Opened and Machine opened filters are refactored to work with multiple newsletters
      if (($action === EmailAction::ACTION_OPENED) || ($action === EmailAction::ACTION_MACHINE_OPENED)) {
        if (!isset($filterData['newsletters'])) {
          $filterData['newsletters'] = [];
        }

        if (isset($filterData['newsletter_id']) && !in_array($filterData['newsletter_id'], $filterData['newsletters'])) {
          $filterData['newsletters'][] = (int)$filterData['newsletter_id'];
          unset($filterData['newsletter_id']);
        }
      }

      // Ensure default operator
      if (!isset($filterData['operator'])) {
        $filterData['operator'] = DynamicSegmentFilterData::OPERATOR_ANY;
      }

      $wpdb->update($dynamicSegmentFiltersTable, [
        'filter_data' => serialize($filterData),
        'action' => $action,
      ], ['id' => $dynamicSegmentFilter['id']]);
    }
    return true;
  }

  private function updateDefaultInactiveSubscriberTimeRange(): bool {
    // Skip if the installed version is newer than the release that preceded this migration, or if it's a fresh install
    $currentlyInstalledVersion = (string)$this->settings->get('db_version', '3.78.1');
    if (version_compare($currentlyInstalledVersion, '3.78.0', '>')) {
      return false;
    }

    $currentValue = (int)$this->settings->get('deactivate_subscriber_after_inactive_days');
    if ($currentValue === 180) {
      $this->settings->set('deactivate_subscriber_after_inactive_days', 365);
      $this->settingsChangeHandler->onInactiveSubscribersIntervalChange();
    }

    return true;
  }

  private function setDefaultValueForLoadingThirdPartyLibrariesForExistingInstalls(): bool {
    // skip the migration if the DB version is higher than 3.91.1 or is not set (a new installation)
    if (version_compare($this->settings->get('db_version', '3.91.2'), '3.91.1', '>')) {
      return false;
    }

    $thirdPartyScriptsEnabled = $this->settings->get('3rd_party_libs');
    if (is_null($thirdPartyScriptsEnabled)) {
      // keep loading 3rd party libraries for existing users so the functionality is not broken
      $this->settings->set('3rd_party_libs.enabled', '1');
    }

    return true;
  }

  private function updateMetaFields() {
    global $wpdb;
    // perform once for versions below or equal to 3.26.0
    if (version_compare((string)$this->settings->get('db_version', '3.26.1'), '3.26.0', '>')) {
      return false;
    }
    $scheduledTaskTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
    $sendingQueueTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
    $tables = [$scheduledTaskTable, $sendingQueueTable];
    foreach ($tables as $table) {
      $wpdb->query("UPDATE `" . esc_sql($table) . "` SET meta = NULL WHERE meta = 'null'");
    }
    return true;
  }

  private function enableStatsNotificationsForAutomatedEmails() {
    if (version_compare((string)$this->settings->get('db_version', '3.31.2'), '3.31.1', '>')) {
      return;
    }
    $settings = $this->settings->get(Worker::SETTINGS_KEY);
    $settings['automated'] = true;
    $this->settings->set(Worker::SETTINGS_KEY, $settings);
  }

  private function updateSentUnsubscribeLinksToInstantUnsubscribeLinks() {
    if (version_compare((string)$this->settings->get('db_version', '3.46.14'), '3.46.13', '>')) {
      return;
    }
    global $wpdb;
    $table = esc_sql($this->entityManager->getClassMetadata(NewsletterLinkEntity::class)->getTableName());
    $wpdb->query($wpdb->prepare(
      "UPDATE `$table` SET `url` = %s WHERE `url` = %s;",
      NewsletterLinkEntity::INSTANT_UNSUBSCRIBE_LINK_SHORT_CODE,
      NewsletterLinkEntity::UNSUBSCRIBE_LINK_SHORT_CODE
    ));
  }

  private function pauseTasksForPausedNewsletters() {
    if (version_compare((string)$this->settings->get('db_version', '3.60.5'), '3.60.4', '>')) {
      return;
    }

    $scheduledTaskTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
    $sendingQueueTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
    $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();

    $query = "
      UPDATE $scheduledTaskTable as t
        JOIN $sendingQueueTable as q ON t.id = q.task_id
        JOIN $newsletterTable as n ON n.id = q.newsletter_id
        SET t.status = :tStatusPaused
        WHERE
          t.status = :tStatusScheduled
          AND n.status = :nStatusDraft
    ";
    $this->entityManager->getConnection()->executeStatement(
      $query,
      [
        'tStatusPaused' => ScheduledTaskEntity::STATUS_PAUSED,
        'tStatusScheduled' => ScheduledTaskEntity::STATUS_SCHEDULED,
        'nStatusDraft' => NewsletterEntity::STATUS_DRAFT,
      ]
    );
  }

  private function addPlacementStatusToForms() {
    if (version_compare((string)$this->settings->get('db_version', '3.49.0'), '3.48.1', '>')) {
      return;
    }
    $forms = $this->formsRepository->findAll();
    foreach ($forms as $form) {
      $settings = $form->getSettings();
      if (
        (isset($settings['place_form_bellow_all_posts']) && $settings['place_form_bellow_all_posts'] === '1')
        || (isset($settings['place_form_bellow_all_pages']) && $settings['place_form_bellow_all_pages'] === '1')
      ) {
        $settings['form_placement_bellow_posts_enabled'] = '1';
      } else {
        $settings['form_placement_bellow_posts_enabled'] = '';
      }
      if (
        (isset($settings['place_popup_form_on_all_posts']) && $settings['place_popup_form_on_all_posts'] === '1')
        || (isset($settings['place_popup_form_on_all_pages']) && $settings['place_popup_form_on_all_pages'] === '1')
      ) {
        $settings['form_placement_popup_enabled'] = '1';
      } else {
        $settings['form_placement_popup_enabled'] = '';
      }
      if (
        (isset($settings['place_fixed_bar_form_on_all_posts']) && $settings['place_fixed_bar_form_on_all_posts'] === '1')
        || (isset($settings['place_fixed_bar_form_on_all_pages']) && $settings['place_fixed_bar_form_on_all_pages'] === '1')
      ) {
        $settings['form_placement_fixed_bar_enabled'] = '1';
      } else {
        $settings['form_placement_fixed_bar_enabled'] = '';
      }
      if (
        (isset($settings['place_slide_in_form_on_all_posts']) && $settings['place_slide_in_form_on_all_posts'] === '1')
        || (isset($settings['place_slide_in_form_on_all_pages']) && $settings['place_slide_in_form_on_all_pages'] === '1')
      ) {
        $settings['form_placement_slide_in_enabled'] = '1';
      } else {
        $settings['form_placement_slide_in_enabled'] = '';
      }
      $form->setSettings($settings);
    }
    $this->formsRepository->flush();
  }

  private function migrateFormPlacement() {
    if (version_compare((string)$this->settings->get('db_version', '3.50.0'), '3.49.1', '>')) {
      return;
    }
    $forms = $this->formsRepository->findAll();
    foreach ($forms as $form) {
      $settings = $form->getSettings();
      if (!is_array($settings)) continue;
      $settings['form_placement'] = [
        FormEntity::DISPLAY_TYPE_POPUP => [
          'enabled' => $settings['form_placement_popup_enabled'],
          'delay' => $settings['popup_form_delay'] ?? 0,
          'styles' => $settings['popup_styles'] ?? [],
          'posts' => [
            'all' => $settings['place_popup_form_on_all_posts'] ?? '',
          ],
          'pages' => [
            'all' => $settings['place_popup_form_on_all_pages'] ?? '',
          ],
        ],
        FormEntity::DISPLAY_TYPE_FIXED_BAR => [
          'enabled' => $settings['form_placement_fixed_bar_enabled'],
          'delay' => $settings['fixed_bar_form_delay'] ?? 0,
          'styles' => $settings['fixed_bar_styles'] ?? [],
          'position' => $settings['fixed_bar_form_position'] ?? 'top',
          'posts' => [
            'all' => $settings['place_fixed_bar_form_on_all_posts'] ?? '',
          ],
          'pages' => [
            'all' => $settings['place_fixed_bar_form_on_all_pages'] ?? '',
          ],
        ],
        FormEntity::DISPLAY_TYPE_BELOW_POST => [
          'enabled' => $settings['form_placement_bellow_posts_enabled'],
          'styles' => $settings['below_post_styles'] ?? [],
          'posts' => [
            'all' => $settings['place_form_bellow_all_posts'] ?? '',
          ],
          'pages' => [
            'all' => $settings['place_form_bellow_all_pages'] ?? '',
          ],
        ],
        FormEntity::DISPLAY_TYPE_SLIDE_IN => [
          'enabled' => $settings['form_placement_slide_in_enabled'],
          'delay' => $settings['slide_in_form_delay'] ?? 0,
          'position' => $settings['slide_in_form_position'] ?? 'right',
          'styles' => $settings['slide_in_styles'] ?? [],
          'posts' => [
            'all' => $settings['place_slide_in_form_on_all_posts'] ?? '',
          ],
          'pages' => [
            'all' => $settings['place_slide_in_form_on_all_pages'] ?? '',
          ],
        ],
        FormEntity::DISPLAY_TYPE_OTHERS => [
          'styles' => $settings['other_styles'] ?? [],
        ],
      ];
      if (isset($settings['form_placement_slide_in_enabled'])) unset($settings['form_placement_slide_in_enabled']);
      if (isset($settings['form_placement_fixed_bar_enabled'])) unset($settings['form_placement_fixed_bar_enabled']);
      if (isset($settings['form_placement_popup_enabled'])) unset($settings['form_placement_popup_enabled']);
      if (isset($settings['form_placement_bellow_posts_enabled'])) unset($settings['form_placement_bellow_posts_enabled']);
      if (isset($settings['place_form_bellow_all_pages'])) unset($settings['place_form_bellow_all_pages']);
      if (isset($settings['place_form_bellow_all_posts'])) unset($settings['place_form_bellow_all_posts']);
      if (isset($settings['place_popup_form_on_all_pages'])) unset($settings['place_popup_form_on_all_pages']);
      if (isset($settings['place_popup_form_on_all_posts'])) unset($settings['place_popup_form_on_all_posts']);
      if (isset($settings['popup_form_delay'])) unset($settings['popup_form_delay']);
      if (isset($settings['place_fixed_bar_form_on_all_pages'])) unset($settings['place_fixed_bar_form_on_all_pages']);
      if (isset($settings['place_fixed_bar_form_on_all_posts'])) unset($settings['place_fixed_bar_form_on_all_posts']);
      if (isset($settings['fixed_bar_form_delay'])) unset($settings['fixed_bar_form_delay']);
      if (isset($settings['fixed_bar_form_position'])) unset($settings['fixed_bar_form_position']);
      if (isset($settings['place_slide_in_form_on_all_pages'])) unset($settings['place_slide_in_form_on_all_pages']);
      if (isset($settings['place_slide_in_form_on_all_posts'])) unset($settings['place_slide_in_form_on_all_posts']);
      if (isset($settings['slide_in_form_delay'])) unset($settings['slide_in_form_delay']);
      if (isset($settings['slide_in_form_position'])) unset($settings['slide_in_form_position']);
      if (isset($settings['other_styles'])) unset($settings['other_styles']);
      if (isset($settings['slide_in_styles'])) unset($settings['slide_in_styles']);
      if (isset($settings['below_post_styles'])) unset($settings['below_post_styles']);
      if (isset($settings['fixed_bar_styles'])) unset($settings['fixed_bar_styles']);
      if (isset($settings['popup_styles'])) unset($settings['popup_styles']);
      $form->setSettings($settings);
    }
    $this->formsRepository->flush();
  }

  private function moveGoogleAnalyticsFromPremium() {
    global $wpdb;
    if (version_compare((string)$this->settings->get('db_version', '3.38.2'), '3.38.1', '>')) {
      return;
    }
    $premiumTableName = $wpdb->prefix . 'mailpoet_premium_newsletter_extra_data';
    $premiumTableExists = (int)$wpdb->get_var(
      $wpdb->prepare(
        "SELECT COUNT(1) FROM information_schema.tables WHERE table_schema=%s AND table_name=%s;",
        $wpdb->dbname,
        $premiumTableName
      )
    );
    if ($premiumTableExists) {
      $table = esc_sql($this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName());
      $query = "
        UPDATE
          `{$table}` as n
        JOIN `$premiumTableName` as ped ON n.id=ped.newsletter_id
          SET n.ga_campaign = ped.ga_campaign
      ";
      $wpdb->query($query);
    }
    return true;
  }

  private function updateLastSubscribedAt() {
    global $wpdb;
    // perform once for versions below or equal to 3.42.0
    if (version_compare((string)$this->settings->get('db_version', '3.42.1'), '3.42.0', '>')) {
      return false;
    }
    $table = esc_sql($this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName());
    $query = $wpdb->prepare(
      "UPDATE `{$table}` SET last_subscribed_at = GREATEST(COALESCE(confirmed_at, 0), COALESCE(created_at, 0)) WHERE status != %s AND last_subscribed_at IS NULL;",
      SubscriberEntity::STATUS_UNCONFIRMED
    );
    $wpdb->query($query);
    return true;
  }

  private function moveNewsletterTemplatesThumbnailData() {
    if (version_compare((string)$this->settings->get('db_version', '3.73.3'), '3.73.2', '>')) {
      return;
    }
    $newsletterTemplatesTable = $this->entityManager->getClassMetadata(NewsletterTemplateEntity::class)->getTableName();
    $this->entityManager->getConnection()->executeQuery("
      UPDATE " . $newsletterTemplatesTable . "
      SET thumbnail_data = thumbnail, thumbnail = NULL
      WHERE thumbnail LIKE 'data:image%';"
    );
  }

  private function updateToUnifiedTrackingSettings() {
    if (version_compare((string)$this->settings->get('db_version', '3.74.3'), '3.74.2', '>')) {
      return;
    }
    $emailTracking = $this->settings->get('tracking.enabled', true);
    $wooTrackingCookie = $this->settings->get('woocommerce.accept_cookie_revenue_tracking.enabled');
    if ($wooTrackingCookie === null) { // No setting for WooCommerce Cookie Tracking - WooCommerce was not active
      $trackingLevel = $emailTracking ? TrackingConfig::LEVEL_FULL : TrackingConfig::LEVEL_BASIC;
    } elseif ($wooTrackingCookie) { // WooCommerce Cookie Tracking enabled
      $trackingLevel = TrackingConfig::LEVEL_FULL;
      // Cookie was enabled but tracking disabled and we are switching to full.
      // So we activate an admin notice to let the user know that we activated tracking
      if (!$emailTracking) {
        $this->wp->setTransient(ChangedTrackingNotice::OPTION_NAME, true);
      }
    } else { // WooCommerce Tracking Cookie Disabled
      $trackingLevel = $emailTracking ? TrackingConfig::LEVEL_PARTIAL : TrackingConfig::LEVEL_BASIC;
    }
    $this->settings->set('tracking.level', $trackingLevel);
  }

  private function fixNotificationHistoryRecordsStuckAtSending() {
    // perform once for versions below or equal to 3.99.0
    if (version_compare((string)$this->settings->get('db_version', '3.99.1'), '3.99.0', '>')) {
      return false;
    }

    $newsletters = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
    $queues = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
    $tasks = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();

    $this->entityManager->getConnection()->executeStatement("
      UPDATE {$newsletters} n
      JOIN {$queues} q ON n.id = q.newsletter_id
      JOIN {$tasks} t ON q.task_id = t.id
      SET n.status = :sentStatus
      WHERE n.type = :type
      AND n.status = :sendingStatus
      AND t.status = :taskStatus
    ", [
      'type' => NewsletterEntity::TYPE_NOTIFICATION_HISTORY,
      'sendingStatus' => NewsletterEntity::STATUS_SENDING,
      'sentStatus' => NewsletterEntity::STATUS_SENT,
      'taskStatus' => ScheduledTaskEntity::STATUS_COMPLETED,
    ]);

    return true;
  }
}