Initial commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Subscribers.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/SubscriberMeta.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/SubscriberPivot.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Campaigns.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/CampaignEmails.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/CampaignUrlMetrics.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Lists.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Tags.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Meta.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/SubscriberNotes.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/UrlStores.php');
|
||||
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Funnels.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/FunnelSequences.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/FunnelSubscribers.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/FunnelMetrics.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/Terms.php');
|
||||
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/TermRelations.php');
|
||||
|
||||
|
||||
class FluentCRMDBMigrator
|
||||
{
|
||||
public static function run($network_wide = false)
|
||||
{
|
||||
global $wpdb;
|
||||
if ($network_wide) {
|
||||
// Retrieve all site IDs from this network (WordPress >= 4.6 provides easy to use functions for that).
|
||||
if (function_exists('get_sites') && function_exists('get_current_network_id')) {
|
||||
$site_ids = get_sites(array('fields' => 'ids', 'network_id' => get_current_network_id()));
|
||||
} else {
|
||||
$site_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs WHERE site_id = $wpdb->siteid;");
|
||||
}
|
||||
// Install the plugin for all these sites.
|
||||
foreach ($site_ids as $site_id) {
|
||||
switch_to_blog($site_id);
|
||||
self::migrate();
|
||||
restore_current_blog();
|
||||
}
|
||||
} else {
|
||||
self::migrate();
|
||||
}
|
||||
}
|
||||
|
||||
public static function migrate()
|
||||
{
|
||||
\FluentCrmMigrations\Subscribers::migrate();
|
||||
\FluentCrmMigrations\SubscriberMeta::migrate();
|
||||
\FluentCrmMigrations\SubscriberPivot::migrate();
|
||||
\FluentCrmMigrations\Campaigns::migrate();
|
||||
\FluentCrmMigrations\CampaignEmails::migrate();
|
||||
\FluentCrmMigrations\CampaignUrlMetrics::migrate();
|
||||
\FluentCrmMigrations\Lists::migrate();
|
||||
\FluentCrmMigrations\Tags::migrate();
|
||||
\FluentCrmMigrations\Meta::migrate();
|
||||
\FluentCrmMigrations\SubscriberNotes::migrate();
|
||||
\FluentCrmMigrations\UrlStores::migrate();
|
||||
|
||||
\FluentCrmMigrations\Funnels::migrate();
|
||||
\FluentCrmMigrations\FunnelSequences::migrate();
|
||||
\FluentCrmMigrations\FunnelSubscribers::migrate();
|
||||
\FluentCrmMigrations\FunnelMetrics::migrate();
|
||||
|
||||
\FluentCrmMigrations\Terms::migrate();
|
||||
\FluentCrmMigrations\TermRelations::migrate();
|
||||
|
||||
update_option('_fluentcrm_db_version', FLUENTCRM_DB_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($network_wide)) {
|
||||
$network_wide = false;
|
||||
}
|
||||
|
||||
FluentCRMDBMigrator::run($network_wide);
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
// Silence is golden.
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class ActivityLogsMigrator
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix . 'fc_activity_logs';
|
||||
|
||||
$indexPrefix = $wpdb->prefix . 'fc_al_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`object_type` VARCHAR(192) NOT NULL,
|
||||
`object_id` BIGINT NULL,
|
||||
`action` VARCHAR(192) NOT NULL,
|
||||
`source` VARCHAR(50) DEFAULT 'wp_admin',
|
||||
`description` TEXT NULL,
|
||||
`activity_by` BIGINT UNSIGNED NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_u_action_idx` (`action`),
|
||||
INDEX `{$indexPrefix}_u_src_idx` (`source`)
|
||||
) $charsetCollate;";
|
||||
|
||||
if(!function_exists('dbDelta')) {
|
||||
require_once(ABSPATH.'wp-admin/includes/upgrade.php');
|
||||
}
|
||||
|
||||
dbDelta($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class CampaignEmails
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix . 'fc_campaign_emails';
|
||||
|
||||
$indexPrefix = $wpdb->prefix . 'fc_cam_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`campaign_id` BIGINT UNSIGNED NULL,
|
||||
`email_type` VARCHAR(50) NULL DEFAULT 'campaign',
|
||||
`subscriber_id` BIGINT UNSIGNED NULL, /*Nullable because ondelete set null*/
|
||||
`email_subject_id` BIGINT UNSIGNED NULL, /*FK subjects.id*/
|
||||
`email_address` VARCHAR(192) NOT NULL,
|
||||
`email_subject` VARCHAR(192) NULL,
|
||||
`email_body` LONGTEXT NULL,
|
||||
`email_headers` TEXT NULL,
|
||||
`is_open` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`is_parsed` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`click_counter` INT NULL,
|
||||
`status` VARCHAR(50) NOT NULL DEFAULT 'draft', /*sent, scheduled, pending, bounced,failed*/
|
||||
`note` TEXT NULL, /* To keep the failed message */
|
||||
`scheduled_at` TIMESTAMP NULL, /*for scheduled email (check status)*/
|
||||
`email_hash` VARCHAR(192) NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_sid_idx` (`subscriber_id` ASC),
|
||||
INDEX `{$indexPrefix}_et_idx` (`email_type` ASC),
|
||||
INDEX `{$indexPrefix}_emtidx` (`email_hash` ASC),
|
||||
INDEX `{$indexPrefix}_updated_at` (`updated_at`),
|
||||
INDEX `{$indexPrefix}status_sc_at` (`status`, `scheduled_at`),
|
||||
INDEX `{$indexPrefix}cid_status` (`campaign_id`, `status`)
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
$indexNames = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
$indexNames[] = $index->Key_name;
|
||||
}
|
||||
|
||||
if(!in_array('scheduled_at', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} ADD INDEX `scheduled_at` (`scheduled_at`);");
|
||||
}
|
||||
|
||||
if(!in_array('updated_at', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} ADD INDEX {$indexPrefix}_updated_at (`updated_at`);");
|
||||
}
|
||||
|
||||
if(!in_array('email_hash', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} ADD INDEX `{$indexPrefix}_emtidx` (`email_hash`);");
|
||||
}
|
||||
|
||||
// Composite index for batch email queries: WHERE status IN (...) AND scheduled_at <= ? ORDER BY scheduled_at
|
||||
if (!in_array($indexPrefix . 'status_sc_at', $indexNames)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} ADD INDEX `{$indexPrefix}status_sc_at` (`status`, `scheduled_at`);");
|
||||
}
|
||||
|
||||
// Composite index for campaign-scoped queries: WHERE campaign_id = ? AND status = ?
|
||||
// Owned by DbPerformanceService so the migration and the runtime index
|
||||
// health-check / repair path share one definition.
|
||||
\FluentCrm\App\Services\DbPerformanceService::ensureCriticalIndex($indexPrefix . 'cid_status');
|
||||
|
||||
// Drop redundant single-column indexes now covered by composite indexes
|
||||
$redundantIndexes = [
|
||||
$indexPrefix . '_estidx', // status — covered by (status, scheduled_at)
|
||||
$indexPrefix . '_scheduled_at', // scheduled_at (prefixed) — covered by (status, scheduled_at)
|
||||
'scheduled_at', // scheduled_at (unprefixed, from earlier migration) — covered by (status, scheduled_at)
|
||||
$indexPrefix . '_cid_idx', // campaign_id — covered by (campaign_id, status)
|
||||
$indexPrefix . 'sc_at_status', // (scheduled_at, status) — wrong column order
|
||||
];
|
||||
|
||||
foreach ($redundantIndexes as $indexName) {
|
||||
if (in_array($indexName, $indexNames)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} DROP INDEX `{$indexName}`");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class CampaignUrlMetrics
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_campaign_url_metrics';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`url_id` BIGINT UNSIGNED NULL,
|
||||
`campaign_id` BIGINT UNSIGNED NULL,
|
||||
`subscriber_id` BIGINT UNSIGNED NULL,
|
||||
`type` VARCHAR(50) NULL DEFAULT 'click', /*view/click*/
|
||||
`ip_address` VARCHAR(30) NULL,
|
||||
`country` VARCHAR(40) NULL,
|
||||
`city` VARCHAR(40) NULL,
|
||||
`counter` INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
KEY `url_id` (`url_id`),
|
||||
KEY `campaign_id` (`campaign_id`),
|
||||
KEY `subscriber_id` (`subscriber_id`),
|
||||
KEY `type` (`type`)
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('subscriber_id', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexSql = "ALTER TABLE {$table} ADD INDEX `url_id` (`url_id`),
|
||||
ADD INDEX `campaign_id` (`campaign_id`),
|
||||
ADD INDEX `subscriber_id` (`subscriber_id`),
|
||||
ADD INDEX `type` (`type`);";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($indexSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Campaigns
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_campaigns';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`parent_id` BIGINT UNSIGNED NULL,
|
||||
`type` VARCHAR(50) NOT NULL DEFAULT 'campaign',
|
||||
`title` VARCHAR(192) NOT NULL,
|
||||
`available_urls` TEXT NULL,
|
||||
`slug` VARCHAR(192) NOT NULL,
|
||||
`status` VARCHAR(50) NOT NULL,
|
||||
`template_id` BIGINT(20) UNSIGNED NULL,
|
||||
`email_subject` VARCHAR(192),
|
||||
`email_pre_header` VARCHAR(192),
|
||||
`email_body` LONGTEXT NOT NULL,
|
||||
`recipients_count` INT NOT NULL DEFAULT 0,
|
||||
`delay` INT(11) NULL DEFAULT 0,
|
||||
`utm_status` TINYINT(1) NULL DEFAULT 0,
|
||||
`utm_source` VARCHAR(192) NULL,
|
||||
`utm_medium` VARCHAR(192) NULL,
|
||||
`utm_campaign` VARCHAR(192) NULL,
|
||||
`utm_term` VARCHAR(192) NULL,
|
||||
`utm_content` VARCHAR(192) NULL,
|
||||
`design_template` VARCHAR(192) NULL,
|
||||
`scheduled_at` TIMESTAMP NULL,
|
||||
`settings` LONGTEXT null,
|
||||
`created_by` BIGINT UNSIGNED NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
KEY `type` (`type`),
|
||||
KEY `status` (`status`),
|
||||
KEY `parent_id` (`parent_id`)
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('status', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$indexSql = "ALTER TABLE {$table} ADD INDEX `type` (`type`),
|
||||
ADD INDEX `status` (`status`),
|
||||
ADD INDEX `parent_id` (`parent_id`);";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($indexSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class CompaniesMigrator
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_companies';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_index_';
|
||||
|
||||
if(!function_exists('dbDelta')) {
|
||||
require_once(ABSPATH.'wp-admin/includes/upgrade.php');
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`hash` VARCHAR(90) NULL,
|
||||
`owner_id` BIGINT UNSIGNED NULL,
|
||||
`name` VARCHAR(192) NULL,
|
||||
`industry` VARCHAR(192) NULL,
|
||||
`email` VARCHAR(190) NULL,
|
||||
`timezone` VARCHAR(192) NULL,
|
||||
`address_line_1` VARCHAR(192) NULL,
|
||||
`address_line_2` VARCHAR(192) NULL,
|
||||
`postal_code` VARCHAR(192) NULL,
|
||||
`city` VARCHAR(192) NULL,
|
||||
`state` VARCHAR(192) NULL,
|
||||
`country` VARCHAR(192) NULL,
|
||||
`employees_number` INT UNSIGNED NULL DEFAULT 0,
|
||||
`description` LONGTEXT NULL,
|
||||
`phone` VARCHAR(50) NULL,
|
||||
`type` VARCHAR(50) DEFAULT '', /*active, archived*/
|
||||
`logo` VARCHAR(192) NULL,
|
||||
`website` VARCHAR(192) NULL,
|
||||
`linkedin_url` VARCHAR(192) NULL,
|
||||
`facebook_url` VARCHAR(192) NULL,
|
||||
`twitter_url` VARCHAR(192) NULL,
|
||||
`date_of_start` DATE NULL,
|
||||
`meta` LONGTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_com_owner_id_idx` (`owner_id`),
|
||||
INDEX `{$indexPrefix}_com_industry_idx` (`industry`),
|
||||
INDEX `{$indexPrefix}_com_type_idx` (`type`),
|
||||
INDEX `{$indexPrefix}_com_name_idx` (`name`)
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// check if meta is available in the column
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$columns = $wpdb->get_results("SHOW COLUMNS FROM $table", ARRAY_A);
|
||||
$allColumns = array_column($columns, 'Field');
|
||||
|
||||
if (!in_array('meta', $allColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN `meta` LONGTEXT NULL AFTER `linkedin_url`");
|
||||
}
|
||||
|
||||
if (!in_array('date_of_start', $allColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN `date_of_start` DATE NULL AFTER `linkedin_url`");
|
||||
}
|
||||
|
||||
if (!in_array('facebook_url', $allColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN `facebook_url` VARCHAR(192) NULL AFTER `linkedin_url`");
|
||||
}
|
||||
|
||||
if(!in_array('twitter_url', $allColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN `twitter_url` VARCHAR(192) NULL AFTER `linkedin_url`");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class FunnelMetrics
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @param bool $isForced
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix . 'fc_funnel_metrics';
|
||||
|
||||
$indexPrefix = $wpdb->prefix . 'fc_fmx_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`funnel_id` BIGINT UNSIGNED NULL,
|
||||
`sequence_id` BIGINT UNSIGNED NULL,
|
||||
`subscriber_id` BIGINT UNSIGNED NULL,
|
||||
`benchmark_value` BIGINT UNSIGNED DEFAULT 0,
|
||||
`benchmark_currency` VARCHAR(10) DEFAULT 'USD',
|
||||
`status` VARCHAR(50) DEFAULT 'completed',
|
||||
`notes` TEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_m_idx` (`funnel_id` ASC),
|
||||
INDEX `{$indexPrefix}_ms__idx` (`subscriber_id` ASC),
|
||||
KEY `sequence_id` (`sequence_id`),
|
||||
KEY `status` (`status`),
|
||||
UNIQUE KEY `funnel_seq_subscriber_unique` (`funnel_id`, `sequence_id`, `subscriber_id`)
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('sequence_id', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexSql = "ALTER TABLE {$table} ADD INDEX `sequence_id` (`sequence_id`),
|
||||
ADD INDEX `status` (`status`);";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($indexSql);
|
||||
}
|
||||
|
||||
// Composite unique index for idempotency enforcement. The sweep +
|
||||
// dedupe + ADD UNIQUE KEY convergence lives in one place —
|
||||
// DbPerformanceService, shared with the runtime index health-check /
|
||||
// repair path — so the two can never drift.
|
||||
\FluentCrm\App\Services\DbPerformanceService::ensureCriticalIndex('funnel_seq_subscriber_unique');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class FunnelSequences
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @param bool $isForced
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix . 'fc_funnel_sequences';
|
||||
|
||||
$indexPrefix = $wpdb->prefix . 'fc_fq_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`funnel_id` BIGINT UNSIGNED NULL,
|
||||
`parent_id` BIGINT UNSIGNED DEFAULT 0,
|
||||
`action_name` VARCHAR(192) NULL,
|
||||
`condition_type` VARCHAR(192) NULL,
|
||||
`type` VARCHAR(50) DEFAULT 'sequence',
|
||||
`title` VARCHAR(192) NULL,
|
||||
`description` VARCHAR(192) NULL,
|
||||
`status` VARCHAR(50) NULL DEFAULT 'draft',
|
||||
`conditions` TEXT,
|
||||
`settings` TEXT,
|
||||
`note` TEXT,
|
||||
`delay` INT UNSIGNED NULL,
|
||||
`c_delay` INT UNSIGNED NULL,
|
||||
`sequence` INT UNSIGNED NULL,
|
||||
`created_by` BIGINT UNSIGNED NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_fs_idx` (`status` ASC),
|
||||
INDEX `{$indexPrefix}_fid_idx` (`funnel_id` ASC),
|
||||
KEY `c_delay` (`c_delay`),
|
||||
KEY `sequence` (`sequence`),
|
||||
KEY `action_name` (`action_name`),
|
||||
KEY `type_action_name_idx` (`type`, `action_name`)
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
$sequenceTable = $wpdb->prefix.'fc_funnel_sequences';
|
||||
$isMigrated = $wpdb->get_col($wpdb->prepare("SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND COLUMN_NAME='parent_id' AND TABLE_NAME=%s", $sequenceTable));
|
||||
if(!$isMigrated) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$sequenceTable} ADD COLUMN `parent_id` bigint NOT NULL DEFAULT '0', ADD `condition_type` varchar(192) NULL AFTER `parent_id`");
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('action_name', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexSql = "ALTER TABLE {$table} ADD INDEX `c_delay` (`c_delay`),
|
||||
ADD INDEX `sequence` (`sequence`),
|
||||
ADD INDEX `action_name` (`action_name`);";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($indexSql);
|
||||
}
|
||||
|
||||
// Composite index for benchmark lookups (type + action_name). Created
|
||||
// via DbPerformanceService so the migration and the runtime index
|
||||
// health-check / repair path share one definition.
|
||||
\FluentCrm\App\Services\DbPerformanceService::ensureCriticalIndex('type_action_name_idx');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class FunnelSubscribers
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @param bool $isForced
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_funnel_subscribers';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_fsx_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`funnel_id` BIGINT UNSIGNED NULL,
|
||||
`starting_sequence_id` BIGINT UNSIGNED NULL,
|
||||
`next_sequence` BIGINT UNSIGNED NULL,
|
||||
`subscriber_id` BIGINT UNSIGNED NULL,
|
||||
`last_sequence_id` BIGINT UNSIGNED NULL,
|
||||
`next_sequence_id` BIGINT UNSIGNED NULL,
|
||||
`last_sequence_status` VARCHAR(50) DEFAULT 'pending',
|
||||
`status` VARCHAR(50) DEFAULT 'active',
|
||||
`type` VARCHAR(50) DEFAULT 'funnel',
|
||||
`last_executed_time` TIMESTAMP NULL,
|
||||
`next_execution_time` TIMESTAMP NULL,
|
||||
`notes` TEXT NULL,
|
||||
`source_trigger_name` VARCHAR(192) NULL,
|
||||
`source_ref_id` BIGINT UNSIGNED NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_fidx` (`funnel_id` ASC),
|
||||
INDEX `{$indexPrefix}_fsq_idx` (`subscriber_id` ASC),
|
||||
KEY `status` (`status`),
|
||||
KEY `type` (`type`),
|
||||
KEY `next_execution_time` (`next_execution_time`),
|
||||
KEY `next_sequence` (`next_sequence`),
|
||||
UNIQUE KEY `funnel_subscriber_idx` (`funnel_id`, `subscriber_id`),
|
||||
KEY `status_next_exec_idx` (`status`, `next_execution_time`)
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('status', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$sql = "ALTER TABLE {$table} ADD INDEX `status` (`status`),
|
||||
ADD INDEX `type` (`type`),
|
||||
ADD INDEX `next_execution_time` (`next_execution_time`),
|
||||
ADD INDEX `next_sequence` (`next_sequence`);";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
}
|
||||
|
||||
// Two critical indexes on this table are owned by DbPerformanceService
|
||||
// so the migration and the runtime index health-check / repair path
|
||||
// share one definition and can never drift:
|
||||
// - funnel_subscriber_idx: UNIQUE (funnel_id, subscriber_id). Its
|
||||
// sweep + progress-aware dedupe + drop/re-add convergence (an old
|
||||
// non-unique index of the same name OR a missing key both resolve
|
||||
// to "unique key present") lives in the service.
|
||||
// - status_next_exec_idx: composite index for the cron heartbeat
|
||||
// query (runs every 60 seconds).
|
||||
\FluentCrm\App\Services\DbPerformanceService::ensureCriticalIndex('funnel_subscriber_idx');
|
||||
\FluentCrm\App\Services\DbPerformanceService::ensureCriticalIndex('status_next_exec_idx');
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Funnels
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @param bool $isForced
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_funnels';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_fn_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`type` VARCHAR(50) NOT NULL DEFAULT 'funnel',
|
||||
`title` VARCHAR(192) NOT NULL,
|
||||
`trigger_name` VARCHAR(150) NULL,
|
||||
`status` VARCHAR(50) NULL DEFAULT 'draft',
|
||||
`conditions` TEXT,
|
||||
`settings` TEXT,
|
||||
`created_by` BIGINT UNSIGNED NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_f_idx` (`status` ASC),
|
||||
INDEX `{$indexPrefix}_ft_idx` (`trigger_name` ASC),
|
||||
KEY `type` (`type`)
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('type', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "ALTER TABLE {$table} ADD INDEX `type` (`type`);";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Lists
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_lists';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`title` VARCHAR(192) NOT NULL,
|
||||
`slug` VARCHAR(192) NOT NULL,
|
||||
`description` TINYTEXT NULL,
|
||||
`is_public` TINYINT(1) DEFAULT 0,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Meta
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_meta';
|
||||
$indexPrefix = $wpdb->prefix .'fc_mt_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`object_type` VARCHAR(50) NOT NULL,
|
||||
`object_id` BIGINT NULL,
|
||||
`key` VARCHAR(192) NOT NULL,
|
||||
`value` LONGTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_mt_idx` (`object_type` ASC),
|
||||
INDEX `{$indexPrefix}_mto_id_idx` (`object_id` ASC),
|
||||
INDEX `{$indexPrefix}_mto_id_key` (`key` )
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('key', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} ADD INDEX `{$indexPrefix}_mto_id_key` (`key`);");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class SubscriberEventTracking
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix . 'fc_event_tracking';
|
||||
|
||||
$indexPrefix = $wpdb->prefix . 'fc_et_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`subscriber_id` BIGINT UNSIGNED NOT NULL,
|
||||
`counter` INT(11) UNSIGNED NULL DEFAULT 1,
|
||||
`created_by` BIGINT UNSIGNED NULL,
|
||||
`provider` VARCHAR(50) DEFAULT 'custom',
|
||||
`event_key` VARCHAR(192) NOT NULL,
|
||||
`title` VARCHAR(192) NOT NULL,
|
||||
`value` TEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_s_id_idx` (`subscriber_id`),
|
||||
INDEX `{$indexPrefix}_s_idx` (`event_key`),
|
||||
INDEX `{$indexPrefix}_s_idx_title` (`title`)
|
||||
) $charsetCollate;";
|
||||
|
||||
if(!function_exists('dbDelta')) {
|
||||
require_once(ABSPATH.'wp-admin/includes/upgrade.php');
|
||||
}
|
||||
|
||||
dbDelta($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class SubscriberMeta
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_subscriber_meta';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_index_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`subscriber_id` BIGINT UNSIGNED NOT NULL,
|
||||
`created_by` BIGINT UNSIGNED NOT NULL,
|
||||
`object_type` VARCHAR(50) DEFAULT 'option',
|
||||
`key` VARCHAR(192) NOT NULL,
|
||||
`value` LONGTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_s_meta_id_idx` (`subscriber_id` ASC),
|
||||
INDEX `{$indexPrefix}_s_ot_idx` (`object_type` ASC)
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
} else{
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('object_type', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "ALTER TABLE {$table} ADD INDEX `object_type` (`object_type`);";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class SubscriberNotes
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_subscriber_notes';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_sn_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`subscriber_id` BIGINT UNSIGNED NOT NULL,
|
||||
`parent_id` BIGINT UNSIGNED NULL,
|
||||
`created_by` BIGINT UNSIGNED NULL,
|
||||
`status` VARCHAR(50) DEFAULT 'open',
|
||||
`type` VARCHAR(50) DEFAULT 'note',
|
||||
`is_private` TINYINT DEFAULT 1,
|
||||
`title` VARCHAR(192) NULL,
|
||||
`description` LONGTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_s_id_idx` (`subscriber_id` ASC),
|
||||
INDEX `{$indexPrefix}_s_idx` (`status` ASC),
|
||||
KEY `type` (`type`)
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
$charsetCollate = $wpdb->collate;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "ALTER TABLE {$table} CHANGE `description` `description` longtext COLLATE '".$charsetCollate."' NULL AFTER `title`;";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('type', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexSql = "ALTER TABLE `{$table}` ADD INDEX `type` (`type`);";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($indexSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class SubscriberPivot
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* This table will maintain many-to-many relationships
|
||||
* between subscriber & lists and subscriber & tags.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_subscriber_pivot';
|
||||
|
||||
$subscriberTable = $wpdb->prefix .'fc_subscribers';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_srp_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`subscriber_id` BIGINT UNSIGNED NOT NULL,
|
||||
`object_id` BIGINT UNSIGNED NOT NULL, /*list_id or tag_id*/
|
||||
`object_type` VARCHAR(50) NOT NULL, /*list or tag*/
|
||||
`status` VARCHAR(50) NULL,
|
||||
`is_public` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_sp_id_idx` (`subscriber_id` ASC),
|
||||
INDEX `{$indexPrefix}_sp_o_id_idx` (`object_id` ASC),
|
||||
INDEX `{$indexPrefix}_sp_t_id_idx` (`object_type` ASC),
|
||||
UNIQUE KEY `subscriber_object_type_unique` (`subscriber_id`, `object_id`, `object_type`(50))
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
return;
|
||||
}
|
||||
|
||||
// Existing table — ensure the composite unique key is present so that
|
||||
// attachTags/attachLists can rely on INSERT IGNORE for race-safe upsert.
|
||||
// The unique key prevents two concurrent attach paths (e.g. WooCommerce
|
||||
// + WP Fusion + LearnDash hooks firing on the same enrollment) from
|
||||
// both inserting a duplicate (subscriber_id, object_id, object_type)
|
||||
// row and both firing the corresponding contact_added_to_* action.
|
||||
//
|
||||
// The sweep + dedupe + online ADD UNIQUE KEY convergence lives in one
|
||||
// place — DbPerformanceService, which is also what the runtime index
|
||||
// health-check / repair path uses — so the two can never drift.
|
||||
\FluentCrm\App\Services\DbPerformanceService::ensureCriticalIndex('subscriber_object_type_unique');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Subscribers
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_subscribers';
|
||||
|
||||
$indexPrefix = $wpdb->prefix .'fc_index_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT UNSIGNED NULL,
|
||||
`hash` VARCHAR(90) NULL,
|
||||
`contact_owner` BIGINT UNSIGNED NULL,
|
||||
`company_id` BIGINT UNSIGNED NULL,
|
||||
`prefix` VARCHAR(192) NULL,
|
||||
`first_name` VARCHAR(192) NULL,
|
||||
`last_name` VARCHAR(192) NULL,
|
||||
`email` VARCHAR(190) NOT NULL UNIQUE,
|
||||
`timezone` VARCHAR(192) NULL,
|
||||
`address_line_1` VARCHAR(192) NULL,
|
||||
`address_line_2` VARCHAR(192) NULL,
|
||||
`postal_code` VARCHAR(192) NULL,
|
||||
`city` VARCHAR(192) NULL,
|
||||
`state` VARCHAR(192) NULL,
|
||||
`country` VARCHAR(192) NULL,
|
||||
`ip` VARCHAR(40) NULL,
|
||||
`latitude` DECIMAL(10, 8) NULL,
|
||||
`longitude` DECIMAL(10, 8) NULL,
|
||||
`total_points` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`life_time_value` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`phone` VARCHAR(50) NULL,
|
||||
`status` VARCHAR(50) NOT NULL DEFAULT 'subscribed', /*subscribed, unsubscribed, pending, bounced*/
|
||||
`contact_type` VARCHAR(50) DEFAULT 'lead', /*lead, customer*/
|
||||
`source` VARCHAR(50) NULL,
|
||||
`avatar` VARCHAR(192) NULL,
|
||||
`date_of_birth` DATE NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`last_activity` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_subscriber_user_id_idx` (`user_id` ASC),
|
||||
INDEX `{$indexPrefix}_subscriber_status_idx` (`status` ASC)
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// change ip column from 20 to 40 to support ipv6
|
||||
$column_name = 'ip';
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "ALTER TABLE {$table} MODIFY {$column_name} VARCHAR(40) NULL;";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Tags
|
||||
{
|
||||
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_tags';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`title` VARCHAR(192) NOT NULL,
|
||||
`slug` VARCHAR(192) NOT NULL,
|
||||
`description` TINYTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL
|
||||
) $charsetCollate;";
|
||||
|
||||
dbDelta($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class TermRelations
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_term_relations';
|
||||
$indexPrefix = $wpdb->prefix .'fc_tmr_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`term_id` BIGINT UNSIGNED NULL,
|
||||
`object_type` VARCHAR(192) NOT NULL,
|
||||
`object_id` BIGINT NULL,
|
||||
`settings` LONGTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_tm_idx` (`term_id` ASC),
|
||||
INDEX `{$indexPrefix}_tm_id_type` (`object_type` ASC),
|
||||
INDEX `{$indexPrefix}_tm_id_idx` (`object_id`)
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class Terms
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_terms';
|
||||
$indexPrefix = $wpdb->prefix .'fc_tms_';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`parent_id` BIGINT UNSIGNED NULL,
|
||||
`taxonomy_name` VARCHAR(50) NOT NULL,
|
||||
`slug` VARCHAR(100) NOT NULL,
|
||||
`title` TEXT NULL,
|
||||
`position` DECIMAL(10,2) DEFAULT 1.00 NOT NULL,
|
||||
`description` LONGTEXT NULL,
|
||||
`settings` LONGTEXT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
INDEX `{$indexPrefix}_tm_idx` (`taxonomy_name` ASC),
|
||||
INDEX `{$indexPrefix}_tm_id_slug` (`slug` ASC),
|
||||
INDEX `{$indexPrefix}_tm_id_pid` (`parent_id` )
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace FluentCrmMigrations;
|
||||
|
||||
class UrlStores
|
||||
{
|
||||
/**
|
||||
* Migrate the table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrate()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$charsetCollate = $wpdb->get_charset_collate();
|
||||
|
||||
$table = $wpdb->prefix .'fc_url_stores';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table'") != $table) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = "CREATE TABLE $table (
|
||||
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`url` TEXT NOT NULL,
|
||||
`short` VARCHAR(50) NOT NULL,
|
||||
`created_at` TIMESTAMP NULL,
|
||||
`updated_at` TIMESTAMP NULL,
|
||||
UNIQUE KEY `short` (`short`),
|
||||
KEY `url` (`url`(191))
|
||||
) $charsetCollate;";
|
||||
dbDelta($sql);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table");
|
||||
$indexedColumns = [];
|
||||
foreach ($indexes as $index) {
|
||||
$indexedColumns[] = $index->Column_name;
|
||||
}
|
||||
|
||||
if(!in_array('short', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$sql = "ALTER TABLE {$table} ADD UNIQUE INDEX `short` (`short`);";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
} else {
|
||||
// Upgrade existing non-unique index to unique
|
||||
$isUnique = false;
|
||||
foreach ($indexes as $index) {
|
||||
if ($index->Column_name === 'short' && $index->Non_unique == 0) {
|
||||
$isUnique = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$isUnique) {
|
||||
// Check if duplicates actually exist before running heavy queries
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$hasDuplicates = $wpdb->get_var("SELECT COUNT(*) FROM (SELECT `short` FROM {$table} GROUP BY `short` HAVING COUNT(*) > 1) AS dupes");
|
||||
|
||||
if ($hasDuplicates) {
|
||||
$metricsTable = $wpdb->prefix . 'fc_campaign_url_metrics';
|
||||
// Re-point metrics from duplicate rows to the kept (highest id) row before cleanup
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$repointed = $wpdb->query("UPDATE {$metricsTable} m INNER JOIN {$table} t1 ON m.url_id = t1.id INNER JOIN (SELECT `short`, MAX(id) AS max_id FROM {$table} GROUP BY `short` HAVING COUNT(*) > 1) dups ON t1.`short` = dups.`short` AND t1.id < dups.max_id SET m.url_id = dups.max_id");
|
||||
|
||||
// Only delete duplicates if re-point succeeded (false = query error)
|
||||
if ($repointed === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up duplicate short values (keep highest id)
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$cleaned = $wpdb->query("DELETE t1 FROM {$table} t1 INNER JOIN {$table} t2 WHERE t1.`short` = t2.`short` AND t1.id < t2.id");
|
||||
|
||||
if ($cleaned === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} DROP INDEX `short`, ADD UNIQUE INDEX `short` (`short`);");
|
||||
}
|
||||
}
|
||||
|
||||
if(!in_array('url', $indexedColumns)) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query("ALTER TABLE {$table} ADD INDEX `url` (`url`(191));");
|
||||
}
|
||||
|
||||
// change column type from tinytext to text - for already installed sites
|
||||
$column_name = 'url';
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$dataType = $wpdb->get_row("describe {$table} {$column_name}");
|
||||
if($dataType->Type == 'tinytext') {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$sql = "ALTER TABLE {$table} MODIFY {$column_name} TEXT NOT NULL;";
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
// Silence is golden
|
||||
Reference in New Issue
Block a user