PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | database/Migrations/TaxClassesMigrator.php +113 -0 1.3.21 → 1.6.5 View file →
@@ -2,9 +2,11 @@
2 2
3 3 namespace FluentCart\Database\Migrations;
4 4
5 5
6 +use FluentCart\App\Models\TaxClass;
6 7 use FluentCart\Framework\Database\Schema;
8 +use FluentCart\Framework\Support\Str;
7 9
8 10 class TaxClassesMigrator extends Migrator
9 11 {
10 12
@@ -27,8 +29,12 @@
27 29 static::renameCategoriesToMeta();
28 30 static::addMetaColumn();
29 31 static::addSlugColumn();
30 32 static::addDescriptionColumn();
33 + static::backfillNullSlugs();
34 + static::deduplicateSlugs();
35 + static::addSlugUniqueIndex();
36 + static::seedDefaultTaxClass();
31 37 }
32 38
33 39 public static function renameCategoriesToMeta()
34 40 {
@@ -57,6 +63,113 @@
57 63 public static function addDescriptionColumn()
58 64 {
59 65 // "ALTER TABLE %i ADD COLUMN `description` LONGTEXT NULL AFTER `slug`"
60 66 static::addColumnIfNotExists('description', 'LONGTEXT NULL', 'slug');
67 + }
68 +
69 + /**
70 + * Backfill NULL slugs for rows created before the slug column existed.
71 + */
72 + public static function backfillNullSlugs()
73 + {
74 + $rows = TaxClass::query()->whereNull('slug')->get();
75 +
76 + foreach ($rows as $row) {
77 + $base = Str::slug($row->title);
78 + if (!$base) {
79 + $base = 'tax-class';
80 + }
81 +
82 + $slug = $base;
83 + $suffix = 2;
84 +
85 + while (TaxClass::query()->where('slug', $slug)->where('id', '!=', $row->id)->exists()) {
86 + $slug = $base . '-' . $suffix;
87 + $suffix++;
88 + }
89 +
90 + TaxClass::query()->where('id', $row->id)->update(['slug' => $slug]);
91 + }
92 + }
93 +
94 + /**
95 + * Remove duplicate slug rows, keeping the one with the lowest ID.
96 + * Repoints `fct_tax_rates.class_id` references to the kept row first
97 + * to avoid orphaning tax rates.
98 + */
99 + public static function deduplicateSlugs()
100 + {
101 + global $wpdb;
102 + $table = $wpdb->prefix . static::$tableName;
103 + $ratesTable = $wpdb->prefix . 'fct_tax_rates';
104 +
105 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
106 + $duplicates = $wpdb->get_results(
107 + "SELECT `slug`, MIN(`id`) AS keep_id, COUNT(*) AS cnt FROM `{$table}` WHERE `slug` IS NOT NULL GROUP BY `slug` HAVING cnt > 1"
108 + );
109 +
110 + if (empty($duplicates)) {
111 + return;
112 + }
113 +
114 + foreach ($duplicates as $dup) {
115 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
116 + $deleteIds = $wpdb->get_col($wpdb->prepare(
117 + "SELECT `id` FROM `{$table}` WHERE `slug` = %s AND `id` != %d",
118 + $dup->slug,
119 + $dup->keep_id
120 + ));
121 +
122 + if (empty($deleteIds)) {
123 + continue;
124 + }
125 +
126 + $placeholders = implode(',', array_fill(0, count($deleteIds), '%d'));
127 +
128 + // Repoint tax rates to the kept class so rates aren't orphaned.
129 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
130 + $wpdb->query($wpdb->prepare(
131 + "UPDATE `{$ratesTable}` SET `class_id` = %d WHERE `class_id` IN ({$placeholders})",
132 + array_merge([$dup->keep_id], $deleteIds)
133 + ));
134 +
135 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
136 + $wpdb->query($wpdb->prepare(
137 + "DELETE FROM `{$table}` WHERE `id` IN ({$placeholders})",
138 + $deleteIds
139 + ));
140 + }
141 + }
142 +
143 + /**
144 + * Add a UNIQUE index on the slug column.
145 + */
146 + public static function addSlugUniqueIndex()
147 + {
148 + $indexName = static::getDbPrefix() . 'fct_tcl_slug_unq';
149 + static::addIndexIfNotExists($indexName, 'slug', true);
150 + }
151 +
152 + public static function seedDefaultTaxClass()
153 + {
154 + global $wpdb;
155 +
156 + if (TaxClass::query()->where('slug', 'standard')->exists()) {
157 + return;
158 + }
159 +
160 + $table = static::getTableName();
161 +
162 + // INSERT IGNORE so a concurrent migration run that inserted the row
163 + // between our exists() check and this insert can't raise a
164 + // duplicate-entry error on the unique slug index.
165 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
166 + $wpdb->query($wpdb->prepare(
167 + "INSERT IGNORE INTO %i (`slug`, `title`, `created_at`, `updated_at`) VALUES (%s, %s, %s, %s)",
168 + $table,
169 + 'standard',
170 + 'Standard',
171 + current_time('mysql', true),
172 + current_time('mysql', true)
173 + ));
61 174 }
62 175 }