init
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\Worker;
|
||||
use MailPoet\Entities\FormEntity;
|
||||
use MailPoet\Form\FormsRepository;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Settings\SettingsChangeHandler;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Util\Notices\ChangedTrackingNotice;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
/**
|
||||
* Extracted from original Migration_20221028_105818.php when separating Db and App migrations.
|
||||
*/
|
||||
class Migration_20221028_105818_App extends AppMigration {
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var SettingsChangeHandler */
|
||||
private $settingsChangeHandler;
|
||||
|
||||
/** @var FormsRepository */
|
||||
private $formsRepository;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function run(): void {
|
||||
$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);
|
||||
|
||||
$this->updateDefaultInactiveSubscriberTimeRange();
|
||||
$this->setDefaultValueForLoadingThirdPartyLibrariesForExistingInstalls();
|
||||
$this->disableMailPoetCronTrigger();
|
||||
|
||||
// POPULATOR
|
||||
$this->enableStatsNotificationsForAutomatedEmails();
|
||||
$this->addPlacementStatusToForms();
|
||||
$this->migrateFormPlacement();
|
||||
$this->updateToUnifiedTrackingSettings();
|
||||
}
|
||||
|
||||
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 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 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 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 disableMailPoetCronTrigger() {
|
||||
$method = $this->settings->get(CronTrigger::SETTING_NAME . '.method');
|
||||
if ($method !== 'MailPoet') {
|
||||
return;
|
||||
}
|
||||
$this->settings->set(CronTrigger::SETTING_NAME . '.method', CronTrigger::METHOD_WORDPRESS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
|
||||
class Migration_20230109_144830 extends AppMigration {
|
||||
/**
|
||||
* Due to a bug https://mailpoet.atlassian.net/browse/MAILPOET-4940 some users may have
|
||||
* paused sending without having the error message and they have no way to resume sending.
|
||||
* This migration will unpause sending for all users who have paused sending and have no error message.
|
||||
*/
|
||||
public function run(): void {
|
||||
$mailerLog = MailerLog::getMailerLog();
|
||||
if (isset($mailerLog['status']) && $mailerLog['status'] === MailerLog::STATUS_PAUSED && !isset($mailerLog['error'])) {
|
||||
$mailerLog['status'] = null;
|
||||
MailerLog::updateMailerLog($mailerLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Homepage\HomepageDataController;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
|
||||
class Migration_20230131_121621 extends AppMigration {
|
||||
/**
|
||||
* This migration detect whether we should display Task List and Product Discovery sections
|
||||
* on the homepage for the old users.
|
||||
*/
|
||||
public function run(): void {
|
||||
// Hide task list for users who installed the plugin more than 2 weeks ago
|
||||
$settings = $this->container->get(SettingsController::class);
|
||||
$installedAt = strtotime($settings->get('installed_at', date('Y-m-d H:i:s')));
|
||||
$twoWeeksAgo = strtotime('-2 weeks');
|
||||
if ($installedAt < $twoWeeksAgo) {
|
||||
$settings->set('homepage.task_list_dismissed', true);
|
||||
}
|
||||
|
||||
// Hide product discovery for users who completed all tasks
|
||||
$homepageDataController = $this->container->get(HomepageDataController::class);
|
||||
$wooCommerceHelper = $this->container->get(Helper::class);
|
||||
$homepageData = $homepageDataController->getPageData();
|
||||
$productDiscoveryStatus = $homepageData['productDiscoveryStatus'];
|
||||
if ($wooCommerceHelper->isWooCommerceActive()) {
|
||||
$productDiscoveryIsComplete = $productDiscoveryStatus['addSubscriptionForm'] &&
|
||||
$productDiscoveryStatus['setUpWelcomeCampaign'] &&
|
||||
$productDiscoveryStatus['setUpAbandonedCartEmail'] &&
|
||||
$productDiscoveryStatus['brandWooEmails'];
|
||||
} else {
|
||||
$productDiscoveryIsComplete = $productDiscoveryStatus['addSubscriptionForm'] &&
|
||||
$productDiscoveryStatus['setUpWelcomeCampaign'] &&
|
||||
$productDiscoveryStatus['sendFirstNewsletter'];
|
||||
}
|
||||
$settings->set('homepage.product_discovery_dismissed', $productDiscoveryIsComplete);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Scheduler\PostNotificationScheduler;
|
||||
|
||||
class Migration_20230419_080000 extends AppMigration {
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var PostNotificationScheduler */
|
||||
private $postNotificationScheduler;
|
||||
|
||||
public function run(): void {
|
||||
$this->newslettersRepository = $this->container->get(NewslettersRepository::class);
|
||||
$this->postNotificationScheduler = $this->container->get(PostNotificationScheduler::class);
|
||||
$this->fixPostNotificationScheduleTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Because we released PostNotificationScheduler that didn't schedule notifications with the minute resolution,
|
||||
* which was added in version 4.10.0, we need to fix the scheduled time for all notifications.
|
||||
*
|
||||
* Ticket with bug: https://mailpoet.atlassian.net/browse/MAILPOET-5244
|
||||
* Ticket with adding minute resolution: https://mailpoet.atlassian.net/browse/MAILPOET-4602
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function fixPostNotificationScheduleTime() {
|
||||
$newsletters = $this->newslettersRepository->findBy(['type' => NewsletterEntity::TYPE_NOTIFICATION]);
|
||||
foreach ($newsletters as $newsletter) {
|
||||
$this->postNotificationScheduler->processPostNotificationSchedule($newsletter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
|
||||
class Migration_20230425_211517 extends AppMigration {
|
||||
public function run(): void {
|
||||
$settingsController = $this->container->get(SettingsController::class);
|
||||
$possibleKeys = [
|
||||
'subscribe.on_register.label',
|
||||
'subscribe.on_comment.label',
|
||||
];
|
||||
$default = __('Yes, add me to your mailing list', 'mailpoet');
|
||||
foreach ($possibleKeys as $key) {
|
||||
$currentValue = $settingsController->get($key);
|
||||
if ($currentValue === 'TRANSLATION "yesAddMe" NOT FOUND') {
|
||||
$settingsController->set($key, $default);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Segments\DynamicSegments\DynamicSegmentFilterRepository;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceAverageSpent;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceNumberOfOrders;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceSingleOrderValue;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceTotalSpent;
|
||||
|
||||
class Migration_20230712_180341 extends AppMigration {
|
||||
public function run(): void {
|
||||
$dynamicSegmentFilterRepository = $this->container->get(DynamicSegmentFilterRepository::class);
|
||||
$filters = $dynamicSegmentFilterRepository->findBy(
|
||||
[
|
||||
'filterData.action' => [
|
||||
WooCommerceNumberOfOrders::ACTION_NUMBER_OF_ORDERS,
|
||||
WooCommerceTotalSpent::ACTION_TOTAL_SPENT,
|
||||
WooCommerceSingleOrderValue::ACTION_SINGLE_ORDER_VALUE,
|
||||
WooCommerceAverageSpent::ACTION,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
$filterData = $filter->getFilterData();
|
||||
$data = $filter->getFilterData()->getData();
|
||||
|
||||
if (isset($data['number_of_orders_days'])) {
|
||||
$days = $data['number_of_orders_days'];
|
||||
} else if (isset($data['total_spent_days'])) {
|
||||
$days = $data['total_spent_days'];
|
||||
} else if (isset($data['single_order_value_days'])) {
|
||||
$days = $data['single_order_value_days'];
|
||||
} else if (isset($data['average_spent_days'])) {
|
||||
$days = $data['average_spent_days'];
|
||||
}
|
||||
|
||||
$filterType = $filterData->getFilterType();
|
||||
$filterAction = $filterData->getAction();
|
||||
|
||||
if (isset($days) && is_string($filterType) && is_string($filterAction)) {
|
||||
$data['days'] = $days;
|
||||
$newFilterData = new DynamicSegmentFilterData($filterType, $filterAction, $data);
|
||||
$filter->setFilterData($newFilterData);
|
||||
$this->entityManager->persist($filter);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\DynamicSegmentFilterData;
|
||||
use MailPoet\Entities\DynamicSegmentFilterEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Segments\DynamicSegments\DynamicSegmentFilterRepository;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceUsedPaymentMethod;
|
||||
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceUsedShippingMethod;
|
||||
|
||||
class Migration_20230803_200413_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$dynamicSegmentFilterRepository = $this->container->get(DynamicSegmentFilterRepository::class);
|
||||
$filters = $dynamicSegmentFilterRepository->findBy(
|
||||
[
|
||||
'filterData.action' => [
|
||||
WooCommerceUsedPaymentMethod::ACTION,
|
||||
WooCommerceUsedShippingMethod::ACTION,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
/** @var DynamicSegmentFilterEntity $filter */
|
||||
foreach ($filters as $filter) {
|
||||
$filterData = $filter->getFilterData();
|
||||
$data = $filter->getFilterData()->getData();
|
||||
|
||||
if (isset($data['used_payment_method_days'])) {
|
||||
$days = $data['used_payment_method_days'];
|
||||
} elseif (isset($data['used_shipping_method_days'])) {
|
||||
$days = $data['used_shipping_method_days'];
|
||||
}
|
||||
|
||||
$filterType = $filterData->getFilterType();
|
||||
$filterAction = $filterData->getAction();
|
||||
|
||||
if (isset($days) && is_string($filterType) && is_string($filterAction)) {
|
||||
$data['days'] = $days;
|
||||
$data['timeframe'] = DynamicSegmentFilterData::TIMEFRAME_IN_THE_LAST;
|
||||
$newFilterData = new DynamicSegmentFilterData($filterType, $filterAction, $data);
|
||||
$filter->setFilterData($newFilterData);
|
||||
$this->entityManager->persist($filter);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\WPDB\Connection;
|
||||
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
||||
use MailPoet\Migrations\Db\Migration_20230824_054259_Db;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
|
||||
class Migration_20230825_093531_App extends AppMigration {
|
||||
const DEFAULT_STATUS = Migration_20230824_054259_Db::DEFAULT_STATUS;
|
||||
|
||||
public function run(): void {
|
||||
|
||||
$wooCommerceHelper = $this->container->get(Helper::class);
|
||||
|
||||
// If Woo is not active and the table doesn't exist, we can skip this migration
|
||||
if (!$wooCommerceHelper->isWooCommerceActive() && !$this->tableExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporarily skip the queries in WP Playground.
|
||||
// The SQLite integration doesn't seem to support them yet.
|
||||
if (Connection::isSQLite()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wooCommerceHelper->isWooCommerceCustomOrdersTableEnabled() ?
|
||||
$this->populateStatusColumnUsingHpos() : $this->populateStatusColumnUsingPost();
|
||||
}
|
||||
|
||||
private function populateStatusColumnUsingHpos(): void {
|
||||
global $wpdb;
|
||||
|
||||
$revenueTable = esc_sql($this->getTableName());
|
||||
$ordersTable = esc_sql($wpdb->prefix . 'wc_orders');
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE %i AS rev, %i AS wc SET rev.status=TRIM(Leading 'wc-' FROM wc.status) WHERE wc.id = rev.order_id AND rev.status= %s",
|
||||
$revenueTable,
|
||||
$ordersTable,
|
||||
self::DEFAULT_STATUS
|
||||
));
|
||||
}
|
||||
|
||||
private function populateStatusColumnUsingPost(): void {
|
||||
global $wpdb;
|
||||
|
||||
$revenueTable = esc_sql($this->getTableName());
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE %i AS rev, %i AS wc SET rev.status=TRIM(Leading 'wc-' FROM wc.post_status) WHERE wc.id = rev.order_id AND rev.status= %s",
|
||||
$revenueTable,
|
||||
$wpdb->posts,
|
||||
self::DEFAULT_STATUS
|
||||
));
|
||||
}
|
||||
|
||||
private function getTableName(): string {
|
||||
return $this->entityManager->getClassMetadata(StatisticsWooCommercePurchaseEntity::class)->getTableName();
|
||||
}
|
||||
|
||||
private function tableExists(): bool {
|
||||
global $wpdb;
|
||||
|
||||
$revenueTable = $this->getTableName();
|
||||
return $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($revenueTable))) === $revenueTable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\WPDB\Connection as WPDBConnection;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
|
||||
use MailPoetVendor\Doctrine\DBAL\Connection;
|
||||
|
||||
/**
|
||||
* Due to a bug https://mailpoet.atlassian.net/browse/MAILPOET-5719 we need to fix already existing data.
|
||||
* The performance optimization we changed the method for updating counts in the sending queue after finishing the scheduled task.
|
||||
* This change affected counts in automatic emails, because the value of processed emails has min and max value calculated from the total count.
|
||||
*/
|
||||
class Migration_20231128_120355_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$wooCommerceHelper = $this->container->get(Helper::class);
|
||||
|
||||
// If Woo is not active and the table doesn't exist, we can skip this migration
|
||||
if (!$wooCommerceHelper->isWooCommerceActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporarily skip the queries in WP Playground.
|
||||
// UPDATE with JOIN is not yet supported by the SQLite integration.
|
||||
if (WPDBConnection::isSQLite()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $this->container->get(Connection::class);
|
||||
|
||||
// Fix data for completed tasks
|
||||
$sendingQueuesTable = $this->getTableName(SendingQueueEntity::class);
|
||||
$scheduledTasksTable = $this->getTableName(ScheduledTaskEntity::class);
|
||||
$newslettersTable = $this->getTableName(NewsletterEntity::class);
|
||||
$newsletterTypes = [NewsletterEntity::TYPE_AUTOMATIC, NewsletterEntity::TYPE_WELCOME];
|
||||
$statusCompleted = ScheduledTaskEntity::STATUS_COMPLETED;
|
||||
$connection->executeStatement("
|
||||
UPDATE {$sendingQueuesTable}
|
||||
JOIN {$scheduledTasksTable} ON {$scheduledTasksTable}.id = {$sendingQueuesTable}.task_id
|
||||
JOIN {$newslettersTable} ON {$newslettersTable}.id = {$sendingQueuesTable}.newsletter_id
|
||||
SET {$sendingQueuesTable}.count_total = 1,
|
||||
{$sendingQueuesTable}.count_processed = 1,
|
||||
{$sendingQueuesTable}.count_to_process = 0
|
||||
WHERE {$newslettersTable}.type IN (:newsletterTypes)
|
||||
AND {$scheduledTasksTable}.status = :taskStatus
|
||||
", [
|
||||
'newsletterTypes' => $newsletterTypes,
|
||||
'taskStatus' => $statusCompleted,
|
||||
], [
|
||||
'newsletterTypes' => ArrayParameterType::STRING,
|
||||
]);
|
||||
|
||||
// Fix data for scheduled tasks
|
||||
$statusScheduled = ScheduledTaskEntity::STATUS_SCHEDULED;
|
||||
$connection->executeStatement("
|
||||
UPDATE {$sendingQueuesTable}
|
||||
JOIN {$scheduledTasksTable} ON {$scheduledTasksTable}.id = {$sendingQueuesTable}.task_id
|
||||
JOIN {$newslettersTable} ON {$newslettersTable}.id = {$sendingQueuesTable}.newsletter_id
|
||||
SET {$sendingQueuesTable}.count_total = 1,
|
||||
{$sendingQueuesTable}.count_processed = 0,
|
||||
{$sendingQueuesTable}.count_to_process = 1
|
||||
WHERE {$newslettersTable}.type IN (:newsletterTypes)
|
||||
AND {$scheduledTasksTable}.status = :taskStatus
|
||||
", [
|
||||
'newsletterTypes' => $newsletterTypes,
|
||||
'taskStatus' => $statusScheduled,
|
||||
], [
|
||||
'newsletterTypes' => ArrayParameterType::STRING,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $entityClassName
|
||||
*/
|
||||
private function getTableName(string $entityClassName): string {
|
||||
return $this->entityManager->getClassMetadata($entityClassName)->getTableName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
|
||||
/**
|
||||
* Due to a bug https://mailpoet.atlassian.net/browse/MAILPOET-5886
|
||||
* The status of newsletters was not updated to sent when the task was completed
|
||||
* In this migration we find newsletters with status sending and their tasks are completed and update their status to sent
|
||||
*/
|
||||
class Migration_20240202_130053_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$affectedNewsletterIds = $this->entityManager->createQueryBuilder()
|
||||
->select('n.id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->join('n.queues', 'q')
|
||||
->join('q.task', 't')
|
||||
->where('n.status = :status_sending')
|
||||
->andWhere('t.status = :status_completed')
|
||||
->setParameter('status_sending', NewsletterEntity::STATUS_SENDING)
|
||||
->setParameter('status_completed', ScheduledTaskEntity::STATUS_COMPLETED)
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
$affectedNewsletterIds = array_column($affectedNewsletterIds, 'id');
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(NewsletterEntity::class, 'n')
|
||||
->set('n.status', ':status_sent')
|
||||
->where('n.id IN (:ids)')
|
||||
->setParameter('status_sent', NewsletterEntity::STATUS_SENT)
|
||||
->setParameter('ids', $affectedNewsletterIds)
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\WPDB\Connection;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Entities\StatisticsNewsletterEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
|
||||
|
||||
/**
|
||||
* We've had a set of bugs where campaign type newsletters (see NewsletterEntity::CAMPAIGN_TYPES),
|
||||
* such as post notifications, were getting stuck in the following state:
|
||||
* - The newsletter was in the "sending" state.
|
||||
* - The task failed to complete and ended up in the "invalid" state.
|
||||
*
|
||||
* This migration completes tasks that sent out all emails
|
||||
* and pauses those that have unprocessed subscribers.
|
||||
*/
|
||||
class Migration_20240207_105912_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$this->pauseInvalidTasksWithUnprocessedSubscribers();
|
||||
$this->completeInvalidTasksWithAllSubscribersProcessed();
|
||||
$this->backfillMissingDataForMigratedNewsletters();
|
||||
}
|
||||
|
||||
private function pauseInvalidTasksWithUnprocessedSubscribers(): void {
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('DISTINCT t.id')
|
||||
->from(ScheduledTaskEntity::class, 't')
|
||||
->join('t.subscribers', 's', 'WITH', 's.processed = :unprocessed')
|
||||
->join('t.sendingQueue', 'q')
|
||||
->join('q.newsletter', 'n')
|
||||
->where('t.deletedAt IS NULL')
|
||||
->andWhere('t.status = :invalid')
|
||||
->andWhere('n.deletedAt IS NULL')
|
||||
->andWhere('n.status = :sending')
|
||||
->andWhere('n.type IN (:campaignTypes)')
|
||||
->setParameter('unprocessed', ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)
|
||||
->setParameter('invalid', ScheduledTaskEntity::STATUS_INVALID)
|
||||
->setParameter('sending', NewsletterEntity::STATUS_SENDING)
|
||||
->setParameter('campaignTypes', NewsletterEntity::CAMPAIGN_TYPES)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(ScheduledTaskEntity::class, 't')
|
||||
->set('t.status', ':paused')
|
||||
->where('t.id IN (:ids)')
|
||||
->setParameter('paused', ScheduledTaskEntity::STATUS_PAUSED)
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
|
||||
private function completeInvalidTasksWithAllSubscribersProcessed(): void {
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('DISTINCT t.id, n.id AS nid, t.updatedAt')
|
||||
->from(ScheduledTaskEntity::class, 't')
|
||||
->leftJoin('t.subscribers', 's', 'WITH', 's.processed = :unprocessed')
|
||||
->join('t.sendingQueue', 'q')
|
||||
->join('q.newsletter', 'n')
|
||||
->where('t.deletedAt IS NULL')
|
||||
->andWhere('t.status = :invalid')
|
||||
->andWhere('s.task IS NULL')
|
||||
->andWhere('n.deletedAt IS NULL')
|
||||
->andWhere('n.status = :sending')
|
||||
->andWhere('n.type IN (:campaignTypes)')
|
||||
->setParameter('unprocessed', ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)
|
||||
->setParameter('invalid', ScheduledTaskEntity::STATUS_INVALID)
|
||||
->setParameter('sending', NewsletterEntity::STATUS_SENDING)
|
||||
->setParameter('campaignTypes', NewsletterEntity::CAMPAIGN_TYPES)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
|
||||
// update sending queue counts
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(SendingQueueEntity::class, 'q')
|
||||
->set('q.countProcessed', 'q.countTotal')
|
||||
->set('q.countToProcess', 0)
|
||||
->where('q.task IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// complete the invalid tasks
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->update(ScheduledTaskEntity::class, 't')
|
||||
->set('t.status', ':completed')
|
||||
->where('t.id IN (:ids)')
|
||||
->setParameter('completed', ScheduledTaskEntity::STATUS_COMPLETED)
|
||||
->setParameter('ids', $ids)
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// mark newsletters as sent, update "sentAt" (DBAL needed to be able to use JOIN)
|
||||
$newslettersTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
|
||||
$scheduledTasksTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
|
||||
$scheduledTaskSubscribersTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
|
||||
$sendingQueuesTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
|
||||
|
||||
// Temporarily skip the query in WP Playground.
|
||||
// UPDATE with JOIN is not yet supported by the SQLite integration.
|
||||
if (Connection::isSQLite()) {
|
||||
return;
|
||||
}
|
||||
$this->entityManager->getConnection()->executeStatement(
|
||||
"
|
||||
UPDATE $newslettersTable n
|
||||
JOIN $sendingQueuesTable q ON n.id = q.newsletter_id
|
||||
JOIN $scheduledTasksTable t ON q.task_id = t.id
|
||||
SET
|
||||
n.status = :sent,
|
||||
n.sent_at = COALESCE(
|
||||
(
|
||||
-- use 'updated_at' of processed subscriber with the highest ID ('MAX(subscriber_id)' can use index)
|
||||
SELECT updated_at FROM $scheduledTaskSubscribersTable WHERE task_id = t.id AND subscriber_id = (
|
||||
SELECT MAX(subscriber_id) FROM $scheduledTaskSubscribersTable WHERE task_id = t.id
|
||||
)
|
||||
),
|
||||
t.updated_at
|
||||
)
|
||||
WHERE t.id IN (:ids)
|
||||
",
|
||||
['sent' => NewsletterEntity::STATUS_SENT, 'ids' => $ids],
|
||||
['ids' => ArrayParameterType::INTEGER]
|
||||
);
|
||||
}
|
||||
|
||||
private function backfillMissingDataForMigratedNewsletters(): void {
|
||||
// In https://mailpoet.atlassian.net/browse/MAILPOET-5886 we fixed missing "sent" status
|
||||
// by https://github.com/mailpoet/mailpoet/pull/5416, but didn't backfill missing data.
|
||||
|
||||
// get affected newsletter IDs
|
||||
$ids = $this->entityManager->createQueryBuilder()
|
||||
->select('n.id')
|
||||
->from(NewsletterEntity::class, 'n')
|
||||
->where('n.status = :sent')
|
||||
->andWhere('n.sentAt IS NULL')
|
||||
->setParameter('sent', NewsletterEntity::STATUS_SENT)
|
||||
->getQuery()
|
||||
->getSingleColumnResult();
|
||||
|
||||
// get missing newsletter statistics IDs
|
||||
$data = $this->entityManager->createQueryBuilder()
|
||||
->select('IDENTITY(q.newsletter) AS nid, q.id AS qid, IDENTITY(s.subscriber) AS sid, s.updatedAt AS sentAt')
|
||||
->from(SendingQueueEntity::class, 'q')
|
||||
->join('q.task', 't')
|
||||
->join('t.subscribers', 's')
|
||||
->leftJoin(StatisticsNewsletterEntity::class, 'ns', 'WITH', 'ns.queue = q AND ns.subscriber = s.subscriber')
|
||||
->where('q.newsletter IN (:ids)')
|
||||
->andWhere('ns.id IS NULL')
|
||||
->andWhere('s.processed = :processed')
|
||||
->setParameter('ids', $ids)
|
||||
->setParameter('processed', ScheduledTaskSubscriberEntity::STATUS_PROCESSED)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// insert missing newsletter statistics
|
||||
$newsletterStatisticsTable = $this->entityManager->getClassMetadata(StatisticsNewsletterEntity::class)->getTableName();
|
||||
foreach ($data as $row) {
|
||||
$this->entityManager->getConnection()->executeStatement("
|
||||
INSERT IGNORE INTO $newsletterStatisticsTable (newsletter_id, queue_id, subscriber_id, sent_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
", [$row['nid'], $row['qid'], $row['sid'], $row['sentAt']->format('Y-m-d H:i:s')]);
|
||||
}
|
||||
|
||||
// add missing "sentAt" (DBAL needed to be able to use JOIN)
|
||||
$newslettersTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
|
||||
$scheduledTasksTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
|
||||
$scheduledTaskSubscribersTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
|
||||
$sendingQueuesTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
|
||||
|
||||
// Temporarily skip the query in WP Playground.
|
||||
// UPDATE with JOIN is not yet supported by the SQLite integration.
|
||||
if (Connection::isSQLite()) {
|
||||
return;
|
||||
}
|
||||
$this->entityManager->getConnection()->executeStatement(
|
||||
"
|
||||
UPDATE $newslettersTable n
|
||||
JOIN $sendingQueuesTable q ON n.id = q.newsletter_id
|
||||
JOIN $scheduledTasksTable t ON q.task_id = t.id
|
||||
SET n.sent_at = COALESCE(
|
||||
(
|
||||
-- use 'updated_at' of processed subscriber with the highest ID ('MAX(subscriber_id)' can use index)
|
||||
SELECT updated_at FROM $scheduledTaskSubscribersTable WHERE task_id = t.id AND subscriber_id = (
|
||||
SELECT MAX(subscriber_id) FROM $scheduledTaskSubscribersTable WHERE task_id = t.id
|
||||
)
|
||||
),
|
||||
t.updated_at
|
||||
)
|
||||
WHERE q.newsletter_id IN (:ids)
|
||||
",
|
||||
['ids' => $ids],
|
||||
['ids' => ArrayParameterType::INTEGER]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\WooCommerce\Helper;
|
||||
use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
|
||||
|
||||
class Migration_20240322_110443_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$wooCommerceHelper = $this->container->get(Helper::class);
|
||||
|
||||
// If Woo is not active and the table doesn't exist, we can skip this migration
|
||||
if (!$wooCommerceHelper->isWooCommerceActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$purchaseStatisticsTable = $this->getTableName(StatisticsWooCommercePurchaseEntity::class);
|
||||
$purchaseStatistics = $this->entityManager->getConnection()->fetchAllAssociative("
|
||||
SELECT order_id
|
||||
FROM {$purchaseStatisticsTable}
|
||||
");
|
||||
|
||||
global $wpdb;
|
||||
if ($wooCommerceHelper->isWooCommerceCustomOrdersTableEnabled()) {
|
||||
$ordersTable = $wooCommerceHelper->getOrdersTableName();
|
||||
$query = "
|
||||
SELECT id AS order_id, status AS status
|
||||
FROM `{$ordersTable}`
|
||||
WHERE type = 'shop_order' AND id in (:orderIds)
|
||||
";
|
||||
} else {
|
||||
$query = "
|
||||
SELECT wpp.id AS order_id, wpp.post_status AS status
|
||||
FROM `{$wpdb->posts}` wpp
|
||||
WHERE wpp.post_type = 'shop_order'
|
||||
AND wpp.ID in (:orderIds)
|
||||
";
|
||||
}
|
||||
|
||||
foreach (array_chunk($purchaseStatistics, 2) as $chunk) {
|
||||
$orderIds = array_column($chunk, 'order_id');
|
||||
|
||||
/** @var array{order_id: int, status: string}[] $orders */
|
||||
$orders = $this->entityManager->getConnection()->executeQuery(
|
||||
$query,
|
||||
['orderIds' => $orderIds],
|
||||
['orderIds' => ArrayParameterType::INTEGER],
|
||||
)->fetchAllAssociative();
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$this->entityManager->getConnection()->executeStatement("
|
||||
UPDATE {$purchaseStatisticsTable}
|
||||
SET status = :status
|
||||
WHERE order_id = :orderId
|
||||
", [
|
||||
'orderId' => $order['order_id'],
|
||||
'status' => str_replace('wc-', '', $order['status']), // WC order status in DB is prefixed with 'wc-'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $entityClassName
|
||||
*/
|
||||
private function getTableName(string $entityClassName): string {
|
||||
return $this->entityManager->getClassMetadata($entityClassName)->getTableName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\Hooks;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WooCommerce\Subscription;
|
||||
|
||||
class Migration_20240730_212419_App extends AppMigration {
|
||||
private SettingsController $settings;
|
||||
|
||||
public function run(): void {
|
||||
$this->settings = $this->container->get(SettingsController::class);
|
||||
|
||||
// Skip if the opt-in checkbox is not enabled or the position is already set.
|
||||
// When enabling, the user chooses the position of the opt-in checkbox
|
||||
// on the same settings page.
|
||||
$optInEnabled = $this->settings->get(Subscription::OPTIN_ENABLED_SETTING_NAME, false);
|
||||
$optInPosition = $this->settings->get(Subscription::OPTIN_POSITION_SETTING_NAME, null);
|
||||
if (!$optInEnabled || $optInPosition) {
|
||||
return;
|
||||
}
|
||||
// Set previous default value for existing installations
|
||||
$this->settings->set(Subscription::OPTIN_POSITION_SETTING_NAME, Hooks::OPTIN_POSITION_BEFORE_TERMS_AND_CONDITIONS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\ConfirmationEmailCustomizer;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
/**
|
||||
* Fixes confirmation emails with missing hash.
|
||||
* These emails were created by plugin before we fixed [MAILPOET-6273]
|
||||
*/
|
||||
class Migration_20241015_105511_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$settings = $this->container->get(SettingsController::class);
|
||||
$confirmationEmailTemplateId = (int)$settings->get(ConfirmationEmailCustomizer::SETTING_EMAIL_ID, null);
|
||||
if (!$confirmationEmailTemplateId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repository = $this->container->get(NewslettersRepository::class);
|
||||
$confirmationEmail = $repository->findOneById($confirmationEmailTemplateId);
|
||||
if (!$confirmationEmail instanceof NewsletterEntity || $confirmationEmail->getHash()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$confirmationEmail->setHash(Security::generateHash());
|
||||
$repository->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\ScheduledTaskEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Some newsletters might have an incorrect status due to a bug where we set the status 'sending'
|
||||
* to automation emails.
|
||||
*
|
||||
* See https://mailpoet.atlassian.net/browse/MAILPOET-6241
|
||||
*/
|
||||
class Migration_20241128_114257_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$newsletterRepository = $this->container->get(NewslettersRepository::class);
|
||||
$newsletters = $newsletterRepository->findBy([
|
||||
'type' => NewsletterEntity::ACTIVABLE_EMAILS,
|
||||
'status' => NewsletterEntity::STATUS_SENDING,
|
||||
]);
|
||||
|
||||
foreach ($newsletters as $newsletter) {
|
||||
$newsletter->setStatus(NewsletterEntity::STATUS_ACTIVE);
|
||||
// As a consequence of the bug, some tasks might be paused, we need to unpause them
|
||||
$this->updateTasks($newsletter);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateTasks(NewsletterEntity $newsletter): void {
|
||||
$oldTaskThreshold = (new Carbon())->subDays(30);
|
||||
$queues = $newsletter->getUnfinishedQueues();
|
||||
foreach ($queues as $queue) {
|
||||
$task = $queue->getTask();
|
||||
|
||||
// Switch relatively new paused tasks to scheduled
|
||||
if ($task && ($task->getScheduledAt() > $oldTaskThreshold) && $task->getStatus() === ScheduledTaskEntity::STATUS_PAUSED) {
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
|
||||
$this->entityManager->flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Switch old paused tasks to completed and mark scheduled task subscribers as failed
|
||||
// This will prevent sending outdated automatic emails. When marked as failed, the user still can resend them in Sending Status screen.
|
||||
if ($task && ($task->getScheduledAt() <= $oldTaskThreshold) && $task->getStatus() === ScheduledTaskEntity::STATUS_PAUSED) {
|
||||
$task->setStatus(ScheduledTaskEntity::STATUS_COMPLETED);
|
||||
$task->setProcessedAt(new Carbon());
|
||||
$this->entityManager->flush();
|
||||
$scheduledTaskSubscribersTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeQuery(
|
||||
"UPDATE $scheduledTaskSubscribersTable
|
||||
SET `processed` = :processed, `failed` = :failed, `error` = :error
|
||||
WHERE task_id = :task_id",
|
||||
[
|
||||
'processed' => ScheduledTaskSubscriberEntity::STATUS_PROCESSED,
|
||||
'failed' => ScheduledTaskSubscriberEntity::FAIL_STATUS_FAILED,
|
||||
'error' => 'Sending timed out for being paused too long.',
|
||||
'task_id' => $task->getId(),
|
||||
]
|
||||
);
|
||||
$this->entityManager->refresh($task);
|
||||
}
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Migrations\App;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
use MailPoet\Entities\SendingQueueEntity;
|
||||
use MailPoet\Migrator\AppMigration;
|
||||
|
||||
/**
|
||||
* The plugin from the version 5.5.2 to 5.6.1 contained a bug when we stored links containing & and in some cases also links with `&amp;` in the database.
|
||||
* This migration fixes the issue by replacing `&amp;` with `& and then & with &`.
|
||||
*
|
||||
* See https://mailpoet.atlassian.net/browse/MAILPOET-6433
|
||||
*/
|
||||
class Migration_20250120_094614_App extends AppMigration {
|
||||
public function run(): void {
|
||||
$sendingQueueId = $this->getSendingQueueId();
|
||||
if ($sendingQueueId) {
|
||||
$linksTable = $this->entityManager->getClassMetadata(NewsletterLinkEntity::class)->getTableName();
|
||||
$this->entityManager->getConnection()->executeQuery("
|
||||
UPDATE {$linksTable}
|
||||
SET url = REPLACE( REPLACE(url, '&amp;', '&'), '&', '&')
|
||||
WHERE queue_id >= :queue_id;
|
||||
", ['queue_id' => $sendingQueueId]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSendingQueueId(): ?int {
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
/** @var array{id: number}|null $result */
|
||||
$result = $qb->select('sq.id AS id')
|
||||
->from(SendingQueueEntity::class, 'sq')
|
||||
->where(
|
||||
$qb->expr()->gt('sq.createdAt', ':date')
|
||||
)
|
||||
->orderBy('sq.id', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->setParameter('date', '2024-12-24:00:00:00')
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
return $result ? (int)$result['id'] : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user