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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Migrator\DbMigration;
/**
* 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".
*
* Additionally, having multiple timestamp columns with "NOT NULL" seems to produce the
* following error in some SQL modes:
* SQLSTATE[42000]: Syntax error or access violation: 1067 Invalid default value for 'updated_at'"
*/
class Migration_20221110_151621 extends DbMigration {
public function run(): void {
$this->createTable('automations', [
'id int(11) unsigned NOT NULL AUTO_INCREMENT',
'name varchar(191) NOT NULL',
'author bigint NOT NULL',
'status varchar(191) 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',
'activated_at timestamp NULL',
'deleted_at timestamp NULL',
'PRIMARY KEY (id)',
]);
$this->createTable('automation_versions', [
'id int(11) unsigned NOT NULL AUTO_INCREMENT',
'automation_id int(11) unsigned NOT NULL',
'steps 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)',
'INDEX (automation_id)',
]);
$this->createTable('automation_triggers', [
'automation_id int(11) unsigned NOT NULL',
'trigger_key varchar(191)',
'PRIMARY KEY (automation_id, trigger_key)',
]);
$this->createTable('automation_runs', [
'id int(11) unsigned NOT NULL AUTO_INCREMENT',
'automation_id int(11) unsigned NOT NULL',
'version_id int(11) unsigned NOT NULL',
'trigger_key varchar(191) NOT NULL',
'status varchar(191) 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',
'subjects longtext',
'next_step_id varchar(191)',
'PRIMARY KEY (id)',
'INDEX (automation_id, status)',
]);
$this->createTable('automation_run_logs', [
'id int(11) unsigned NOT NULL AUTO_INCREMENT',
'automation_run_id int(11) unsigned NOT NULL',
'step_id varchar(191) NOT NULL',
'status varchar(191) NOT NULL',
'started_at timestamp NOT NULL',
'completed_at timestamp NULL DEFAULT NULL',
'error longtext',
'data longtext',
'PRIMARY KEY (id)',
'INDEX (automation_run_id)',
]);
}
}
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SettingEntity;
use MailPoet\Migrator\DbMigration;
use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
class Migration_20230111_120000 extends DbMigration {
public function run(): void {
$segmentsTable = $this->getTableName(SegmentEntity::class);
$settingsTable = $this->getTableName(SettingEntity::class);
$columnName = 'display_in_manage_subscription_page';
if ($this->columnExists($segmentsTable, $columnName)) {
return;
}
$this->connection->executeStatement("
ALTER TABLE {$segmentsTable}
ADD {$columnName} tinyint(1) NOT NULL DEFAULT 0
");
$subscriptionSetting = $this->connection->fetchOne("
SELECT value
FROM {$settingsTable}
WHERE name = ?", ['subscription']);
$subscriptionSetting = is_string($subscriptionSetting) ? unserialize($subscriptionSetting) : [];
$subscriptionSetting = is_array($subscriptionSetting) ? $subscriptionSetting : [];
$segmentIds = $subscriptionSetting['segments'] ?? [];
if ($segmentIds) {
// display only segments from settings.subscription.segments
$this->connection->executeStatement("
UPDATE {$segmentsTable}
SET {$columnName} = 1
WHERE id IN (?)
", [$segmentIds], [ArrayParameterType::INTEGER]);
$subscriptionSetting['segments'] = [];
$this->connection->executeStatement(
"
UPDATE {$settingsTable}
SET value = ?
WHERE name = ?",
[
serialize($subscriptionSetting),
'subscription',
]
);
} else {
$this->connection->executeStatement("
UPDATE {$segmentsTable}
SET {$columnName} = 1
WHERE type = ?
", [SegmentEntity::TYPE_DEFAULT]);
}
}
}
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\StatisticsUnsubscribeEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230111_130000 extends DbMigration {
public function run(): void {
$tableName = $this->getTableName(StatisticsUnsubscribeEntity::class);
if (!$this->columnExists($tableName, 'method')) {
$this->connection->executeStatement("
ALTER TABLE {$tableName}
ADD method varchar(40) NOT NULL DEFAULT 'unknown'
");
}
}
}
@@ -0,0 +1,85 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Migrator\DbMigration;
class Migration_20230215_050813 extends DbMigration {
public function run(): void {
$this->subjectsMigration();
$this->addMetaColumnToAutomations();
}
private function addMetaColumnToAutomations(): void {
global $wpdb;
$tableName = esc_sql($wpdb->prefix . 'mailpoet_automations');
if ($this->columnExists($tableName, 'meta')) {
return;
}
$this->connection->executeQuery("ALTER TABLE $tableName ADD COLUMN `meta` LONGTEXT DEFAULT NULL AFTER `status`");
$this->connection->executeQuery("UPDATE $tableName SET `meta` = '{\"mailpoet:run-once-per-subscriber\":true}'");
}
private function subjectsMigration(): void {
$this->createTable('automation_run_subjects', [
'`id` int(11) unsigned NOT NULL AUTO_INCREMENT',
'`automation_run_id` int(11) unsigned NOT NULL',
'`key` varchar(191)',
'`args` longtext',
'`hash` varchar(191)',
'PRIMARY KEY (id)',
'index (automation_run_id)',
'index (hash)',
]);
$this->moveSubjectData();
$this->dropSubjectColumn();
}
private function moveSubjectData(): void {
global $wpdb;
$runTable = $wpdb->prefix . 'mailpoet_automation_runs';
$subjectTable = $wpdb->prefix . 'mailpoet_automation_run_subjects';
if (!$this->columnExists($runTable, 'subjects')) {
return;
}
$results = $wpdb->get_results($wpdb->prepare("SELECT id,subjects FROM %i", $runTable), ARRAY_A);
if (!is_array($results) || !$results) {
return;
}
foreach ($results as $result) {
$subjects = $result['subjects'];
if (!$subjects) {
continue;
}
$subjects = json_decode($subjects, true);
if (!is_array($subjects) || !$subjects) {
continue;
}
$values = [];
foreach ($subjects as $subject) {
$values[] = (string)$wpdb->prepare("(%d,%s,%s)", $result['id'], $subject['key'], json_encode($subject['args']));
}
if ($wpdb->query($wpdb->prepare("INSERT INTO %i (`automation_run_id`, `key`, `args`) VALUES %s", $subjectTable, implode(',', $values))) === false) {
continue;
}
$wpdb->query($wpdb->prepare("UPDATE %i SET subjects = NULL WHERE id = %d", $runTable, $result['id']));
}
}
private function dropSubjectColumn(): void {
global $wpdb;
$tableName = esc_sql($wpdb->prefix . 'mailpoet_automation_runs');
if (!$this->columnExists($tableName, 'subjects')) {
return;
}
$wpdb->query($wpdb->prepare("ALTER TABLE %i DROP COLUMN subjects", $tableName));
}
}
@@ -0,0 +1,44 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\LogEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230221_200520 extends DbMigration {
public function run(): void {
$this->addRawMessagesToLogs();
$this->addContextToLogs();
}
private function addRawMessagesToLogs() {
$logsTable = $this->getTableName(LogEntity::class);
$columnName = 'raw_message';
if ($this->columnExists($logsTable, $columnName)) {
return;
}
$this->connection->executeStatement("
ALTER TABLE {$logsTable}
ADD {$columnName} longtext DEFAULT NULL
");
}
private function addContextToLogs() {
$logsTable = $this->getTableName(LogEntity::class);
$columnName = 'context';
if ($this->columnExists($logsTable, $columnName)) {
return;
}
$this->connection->executeStatement("
ALTER TABLE {$logsTable}
ADD {$columnName} longtext DEFAULT NULL
");
}
}
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230421_135915 extends DbMigration {
public function run(): void {
$newslettersTable = $this->getTableName(NewsletterEntity::class);
$this->connection->executeQuery("
ALTER TABLE $newslettersTable
CHANGE type type varchar(150) NOT NULL DEFAULT 'standard'
");
$this->connection->executeQuery("
UPDATE $newslettersTable
SET type = 'automation_transactional'
WHERE type = 'transactional'
");
}
}
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230503_210945 extends DbMigration {
public function run(): void {
$subscribersTable = $this->getTableName(SubscriberEntity::class);
if (!$this->indexExists($subscribersTable, 'first_name')) {
$this->connection->executeQuery(
"ALTER TABLE `{$subscribersTable}`
ADD INDEX `first_name` (`first_name`(10))"
);
}
if (!$this->indexExists($subscribersTable, 'last_name')) {
$this->connection->executeQuery(
"ALTER TABLE `{$subscribersTable}`
ADD INDEX `last_name` (`last_name`(10))"
);
}
}
}
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230605_174836 extends DbMigration {
public function run(): void {
$subscribersTable = $this->getTableName(SubscriberEntity::class);
$newColumns = [
'last_sending_at',
'last_open_at',
'last_click_at',
'last_purchase_at',
'last_page_view_at',
];
foreach ($newColumns as $column) {
if ($this->columnExists($subscribersTable, $column)) {
continue;
}
$this->connection->executeQuery(
"ALTER TABLE `{$subscribersTable}`
ADD COLUMN `{$column}` TIMESTAMP NULL DEFAULT NULL,
ADD INDEX `{$column}` (`{$column}`)"
);
}
}
}
@@ -0,0 +1,45 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Migrator\DbMigration;
class Migration_20230703_105957 extends DbMigration {
public function run(): void {
$this->migrateLogTable();
$this->migrateRunTable();
}
public function migrateLogTable(): void {
global $wpdb;
$table = $wpdb->prefix . 'mailpoet_automation_run_logs';
if (!$this->indexExists($table, 'status')) {
$this->connection->executeStatement("ALTER TABLE $table ADD INDEX `status` (`status`)");
}
if (!$this->indexExists($table, 'step_id')) {
$this->connection->executeStatement("ALTER TABLE $table ADD INDEX `step_id` (`step_id`)");
}
}
public function migrateRunTable(): void {
global $wpdb;
$table = $wpdb->prefix . 'mailpoet_automation_runs';
if (!$this->indexExists($table, 'created_at')) {
$this->connection->executeStatement("ALTER TABLE $table ADD INDEX `created_at` (`created_at`)");
}
if (!$this->indexExists($table, 'version_id')) {
$this->connection->executeStatement("ALTER TABLE $table ADD INDEX `version_id` (`version_id`)");
}
if (!$this->indexExists($table, 'status')) {
$this->connection->executeStatement("ALTER TABLE $table ADD INDEX `status` (`status`)");
}
if (!$this->indexExists($table, 'next_step_id')) {
$this->connection->executeStatement("ALTER TABLE $table ADD INDEX `next_step_id` (`next_step_id`)");
}
}
}
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Migrator\DbMigration;
/**
* This migration was created on 2023/06/16 but needs to be renamed to precede 20230419 which fails when the column is missing.
*/
class Migration_20230716_130221_Db extends DbMigration {
public function run(): void {
$tableName = $this->getTableName(NewsletterEntity::class);
if (!$this->columnExists($tableName, 'wp_post_id')) {
$this->connection->executeStatement("
ALTER TABLE {$tableName}
ADD wp_post_id int NULL
");
}
if (!$this->indexExists($tableName, 'wp_post_id')) {
$this->connection->executeQuery(
"ALTER TABLE `{$tableName}`
ADD INDEX `wp_post_id` (`wp_post_id`)"
);
}
}
}
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\DI\ContainerWrapper;
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230824_054259_Db extends DbMigration {
public const DEFAULT_STATUS = 'unknown';
public function __construct(
ContainerWrapper $container
) {
parent::__construct($container);
}
public function run(): void {
$this->createStatusColumn();
}
private function createStatusColumn(): void {
$revenueTable = $this->getTableName(StatisticsWooCommercePurchaseEntity::class);
if (!$this->tableExists($revenueTable) || $this->columnExists($revenueTable, 'status')) {
return;
}
$this->connection->executeQuery(
"ALTER TABLE `" . $revenueTable . "`
ADD COLUMN `status` VARCHAR(40) NOT NULL DEFAULT '" . self::DEFAULT_STATUS . "',
ADD INDEX `status` (`status`)"
);
}
}
@@ -0,0 +1,23 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\StatisticsNewsletterEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20230831_124214_Db extends DbMigration {
public function run(): void {
global $wpdb;
$migrationsTable = $wpdb->prefix . 'mailpoet_migrations';
$automationRunLogs = $wpdb->prefix . 'mailpoet_automation_run_logs';
$statisticsNewslettersTable = $this->getTableName(StatisticsNewsletterEntity::class);
// fix unintended "ON UPDATE current_timestamp()" on some timestamp columns
$this->connection->executeStatement("ALTER TABLE $migrationsTable CHANGE `started_at` `started_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP");
$this->connection->executeStatement("ALTER TABLE $automationRunLogs CHANGE `started_at` `started_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP");
$this->connection->executeStatement("ALTER TABLE $statisticsNewslettersTable CHANGE `sent_at` `sent_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP");
}
}
@@ -0,0 +1,141 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\WPDB\Connection;
use MailPoet\Migrator\DbMigration;
class Migration_20230831_143755_Db extends DbMigration {
public function run(): void {
global $wpdb;
$logsTable = $wpdb->prefix . 'mailpoet_automation_run_logs';
// add "updated_at" column
if (!$this->columnExists($logsTable, 'updated_at')) {
$this->connection->executeStatement("ALTER TABLE $logsTable CHANGE `started_at` `started_at` timestamp NULL"); // prevent ER_TOO_MUCH_AUTO_TIMESTAMP_COLS
$this->connection->executeStatement("ALTER TABLE $logsTable ADD COLUMN updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `started_at`");
}
// add "step_type" column
if (!$this->columnExists($logsTable, 'step_type')) {
$this->connection->executeStatement("ALTER TABLE $logsTable ADD COLUMN step_type VARCHAR(255) NOT NULL DEFAULT 'action' AFTER `step_id`");
// Temporarily use a full column definition to drop default in WP Playground.
// DROP DEFAULT is not yet supported by the SQLite integration.
if (Connection::isSQLite()) {
$this->connection->executeStatement("ALTER TABLE $logsTable CHANGE COLUMN step_type step_type VARCHAR(255) NOT NULL");
} else {
$this->connection->executeStatement("ALTER TABLE $logsTable ALTER COLUMN step_type DROP DEFAULT");
}
}
// add "step_key" column
if (!$this->columnExists($logsTable, 'step_key')) {
$this->connection->executeStatement("ALTER TABLE $logsTable ADD COLUMN step_key VARCHAR(255) NOT NULL DEFAULT '' AFTER `step_type`");
// Temporarily use a full column definition to drop default in WP Playground.
// DROP DEFAULT is not yet supported by the SQLite integration.
if (Connection::isSQLite()) {
$this->connection->executeStatement("ALTER TABLE $logsTable CHANGE COLUMN step_key step_key VARCHAR(255) NOT NULL");
} else {
$this->connection->executeStatement("ALTER TABLE $logsTable ALTER COLUMN step_key DROP DEFAULT");
}
}
// add "run_number" column
if (!$this->columnExists($logsTable, 'run_number')) {
$this->connection->executeStatement("ALTER TABLE $logsTable ADD COLUMN run_number INT NOT NULL DEFAULT 1 AFTER `updated_at`");
// Temporarily use a full column definition to drop default in WP Playground.
// DROP DEFAULT is not yet supported by the SQLite integration.
if (Connection::isSQLite()) {
$this->connection->executeStatement("ALTER TABLE $logsTable CHANGE COLUMN run_number run_number INT NOT NULL");
} else {
$this->connection->executeStatement("ALTER TABLE $logsTable ALTER COLUMN run_number DROP DEFAULT");
}
}
// go through automation data and backfill step keys and trigger logs
$this->backfillStepKeysAndTriggers();
// fix mix of 'complete' and 'completed' statuses
$this->connection->executeStatement("UPDATE $logsTable SET status = 'complete' WHERE status = 'completed'");
// fix empty values for errors and data
$this->connection->executeStatement("ALTER TABLE $logsTable CHANGE `data` `data` longtext NOT NULL AFTER run_number");
$this->connection->executeStatement("UPDATE $logsTable SET data = '{}' WHERE data = '[]' OR data IS NULL");
$this->connection->executeStatement("UPDATE $logsTable SET error = NULL WHERE error = '[]' OR error IS NULL");
// remove "completed_at" column (with "updated_at" it's no longer needed), backfill "updated_at"
if ($this->columnExists($logsTable, 'completed_at')) {
$this->connection->executeStatement("UPDATE $logsTable SET updated_at = COALESCE(completed_at, started_at)");
$this->connection->executeStatement("ALTER TABLE $logsTable DROP COLUMN completed_at");
}
// add unique index, remove no longer needed index
if (!$this->indexExists($logsTable, 'automation_run_id_step_id')) {
$this->connection->executeStatement(
"DELETE t1 FROM $logsTable as t1, $logsTable as t2 WHERE t1.id < t2.id AND t1.automation_run_id = t2.automation_run_id AND t1.step_id=t2.step_id"
);
$this->connection->executeStatement("ALTER TABLE $logsTable ADD UNIQUE automation_run_id_step_id (automation_run_id, step_id)");
}
if ($this->indexExists($logsTable, 'automation_run_id')) {
$this->connection->executeStatement("ALTER TABLE $logsTable DROP INDEX automation_run_id");
}
}
private function backfillStepKeysAndTriggers(): void {
global $wpdb;
$logsTable = $wpdb->prefix . 'mailpoet_automation_run_logs';
$runsTable = $wpdb->prefix . 'mailpoet_automation_runs';
$versionsTable = $wpdb->prefix . 'mailpoet_automation_versions';
$triggerAddedMap = [];
while (true) {
$data = $this->connection->executeQuery("
SELECT rl.id, rl.automation_run_id, rl.step_id, rl.started_at, v.steps
FROM {$logsTable} rl
JOIN {$runsTable} r ON r.id = rl.automation_run_id
JOIN {$versionsTable} v ON v.id = r.version_id
WHERE rl.step_key = ''
ORDER BY rl.id ASC
LIMIT 50
")->fetchAllAssociative();
if (count($data) === 0) {
break;
}
$queries = [];
/** @var array<int, array{id:int, automation_run_id:int, step_id:int, started_at:string, steps:string}> $data */
foreach ($data as $item) {
/** @var array $steps */
$steps = json_decode(strval($item['steps']), true);
$id = intval($item['id']);
$stepId = strval($item['step_id']);
$stepKey = strval($steps[$stepId]['key'] ?? 'unknown');
$triggerId = $steps['root']['next_steps'][0]['id'];
$triggerKey = $steps['root']['next_steps'][0]['key'];
$queries[] = "UPDATE {$logsTable} SET step_key = '{$stepKey}' WHERE id = {$id}";
// backfill triggers
$runId = intval($item['automation_run_id']);
if (!isset($triggerAddedMap[$runId])) {
$startedAt = strval($item['started_at']);
$date = "DATE_SUB('$startedAt', INTERVAL 1 SECOND)";
$queries[] = "
INSERT INTO {$logsTable} (automation_run_id, step_id, step_type, step_key, status, started_at, updated_at, run_number, data)
VALUES ($runId, '$triggerId', 'trigger', '$triggerKey', 'complete', $date, $date, 1, '{}')
";
$triggerAddedMap[$runId] = true;
}
}
$this->connection->executeStatement(implode(';', $queries));
}
}
}
@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20240119_113943_Db extends DbMigration {
public function run(): void {
$table = $this->getTableName(StatisticsWooCommercePurchaseEntity::class);
if (!$this->tableExists($table)) {
return;
}
// make "newsletter_id" nullable
$this->connection->executeStatement("ALTER TABLE $table CHANGE newsletter_id newsletter_id int(11) unsigned NULL");
// update data
$this->connection->executeStatement("UPDATE $table SET newsletter_id = NULL WHERE newsletter_id = 0");
}
}
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Migrator\DbMigration;
class Migration_20240617_122847_Db extends DbMigration {
public function run(): void {
$scheduledTasksTable = $this->getTableName(ScheduledTaskEntity::class);
$newColumn = 'cancelled_at';
if ($this->columnExists($scheduledTasksTable, $newColumn)) {
return;
}
$this->connection->executeQuery(
"ALTER TABLE `{$scheduledTasksTable}`
ADD COLUMN `{$newColumn}` TIMESTAMP NULL DEFAULT NULL"
);
}
}
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Migrator\DbMigration;
class Migration_20240725_182318_Db extends DbMigration {
public function run(): void {
global $wpdb;
$automationRunsTable = esc_sql($wpdb->prefix . 'mailpoet_automation_runs');
$automationRunLogsTable = esc_sql($wpdb->prefix . 'mailpoet_automation_run_logs');
if (!$this->tableExists($automationRunsTable) || !$this->tableExists($automationRunLogsTable)) {
return;
}
// update failed automation runs that should be complete, but keep updated_at column unchanged
$this->connection->executeStatement(
"UPDATE $automationRunsTable
SET
`status` = 'complete',
`updated_at` = `updated_at`
WHERE id IN (
SELECT `automation_run_id`
FROM $automationRunLogsTable
WHERE `status` = 'failed'
AND `error` LIKE '%nextStepNotScheduled%'
AND `step_key` = 'core:if-else'
)"
);
// update failed automation run logs that should be complete, but keep updated_at column unchanged
$this->connection->executeStatement(
"UPDATE $automationRunLogsTable
SET
`status` = 'complete',
`error` = NULL,
`updated_at` = `updated_at`
WHERE `status` = 'failed'
AND `error` LIKE '%nextStepNotScheduled%'
AND `step_key` = 'core:if-else'"
);
}
}
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Migrator\DbMigration;
/**
* In https://github.com/mailpoet/mailpoet/pull/5628 we removed Google+ social icons
* but they were still used in templates. This migration removes them from the templates.
*/
class Migration_20241007_170437_Db extends DbMigration {
public function run(): void {
global $wpdb;
$templatesTable = esc_sql($wpdb->prefix . 'mailpoet_newsletter_templates');
if (!$this->tableExists($templatesTable)) {
return;
}
$templatesWithGooglePlus = $this->connection->fetchAllAssociative(
"SELECT id, body FROM $templatesTable WHERE body LIKE '%\"iconType\":\"google-plus\"%'"
);
foreach ($templatesWithGooglePlus as $template) {
if (!is_string($template['body'])) {
continue;
}
$body = json_decode($template['body'], true);
$error = json_last_error();
if ($error || !is_array($body)) {
continue;
}
$content = &$body['content'];
$this->removeGooglePlusIcons($content);
$updatedBody = json_encode($body);
if ($updatedBody === false) {
continue;
}
$this->connection->update(
$templatesTable,
['body' => $updatedBody],
['id' => $template['id']]
);
}
}
private function removeGooglePlusIcons(&$array) {
if (!isset($array['blocks']) || !is_array($array['blocks'])) {
return;
}
foreach ($array['blocks'] as &$block) {
$this->removeGooglePlusIcons($block);
if (!isset($block['type']) || $block['type'] !== 'social') {
continue;
}
$filteredIcons = array_filter($block['icons'], function($icon) {
return $icon['iconType'] !== 'google-plus';
});
$block['icons'] = array_values($filteredIcons);
}
}
}
@@ -0,0 +1,98 @@
<?php declare(strict_types = 1);
namespace MailPoet\Migrations\Db;
if (!defined('ABSPATH')) exit;
use MailPoet\Automation\Engine\Utils\Json;
use MailPoet\Migrator\DbMigration;
/**
* Fixes WooCommerce Subscriptions status filter in automations (e.g. trigger filters or if/else conditions)
* - Changes `field_type` from `enum_array` to `enum`
* - Changes condition from `matches-*` to `is-*`
* - Strips `wc-` prefix from values
*/
class Migration_20241108_103249_Db extends DbMigration {
public function run(): void {
global $wpdb;
$automationVersionsTable = esc_sql($wpdb->prefix . 'mailpoet_automation_versions');
$wooSubscriptionAutomations = $wpdb->get_results(
$wpdb->prepare(
"SELECT `id`, `steps` FROM %i WHERE `steps` LIKE %s OR `steps` LIKE %s",
$automationVersionsTable,
'%woocommerce-subscriptions:subscription:status%',
'%woocommerce-subscriptions:subscription:billing-period%'
),
ARRAY_A
);
foreach ($wooSubscriptionAutomations as $automation) {
// Parse JSON encoded automation steps
try {
$jsonData = Json::decode($automation['steps']);
} catch (\Exception $e) {
continue;
}
// Iterate over each automation step
foreach ($jsonData as $key => &$item) {
if (!isset($item['filters']['groups']) || !is_array($item['filters']['groups'])) {
continue;
}
// Iterate over each step filter group
// This can be e.g. a trigger filter, or if/else condition
foreach ($item['filters']['groups'] as &$group) {
if (!isset($group['filters']) || !is_array($group['filters'])) {
continue;
}
foreach ($group['filters'] as &$filter_item) {
$this->fixWooSubscriptionFilters($filter_item);
}
}
}
$updatedJson = json_encode($jsonData);
$wpdb->update($automationVersionsTable, ['steps' => $updatedJson], ['id' => $automation['id']]);
}
}
private function fixWooSubscriptionFilters(&$filter_item): void {
// Only fix WooCommerce Subscriptions status and billing period filters
if (!isset($filter_item['field_key'])) {
return;
}
$isStatusField = $filter_item['field_key'] === 'woocommerce-subscriptions:subscription:status';
$isBillingPeriodField = $filter_item['field_key'] === 'woocommerce-subscriptions:subscription:billing-period';
if (!$isStatusField && !$isBillingPeriodField) {
return;
}
// Fix field_type from enum_array to enum
if (isset($filter_item['field_type']) && $filter_item['field_type'] === 'enum_array') {
$filter_item['field_type'] = 'enum';
}
// Fix condition
if (isset($filter_item['condition'])) {
$conditionMap = [
'matches-any-of' => 'is-any-of',
'matches-all-of' => 'is-any-of',
'matches-none-of' => 'is-none-of',
];
$filter_item['condition'] = $conditionMap[$filter_item['condition']] ?? $filter_item['condition'];
}
// Strip "wc-" prefix from values but only in status field
if (!$isStatusField) {
return;
}
if (isset($filter_item['args']['value']) && is_array($filter_item['args']['value'])) {
$filter_item['args']['value'] = array_map(function($value) {
return preg_replace('/^wc-/', '', $value);
}, $filter_item['args']['value']);
}
}
}
@@ -0,0 +1 @@
<?php