PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.20
ووسلام – همگام سازی ووکامرس و باسلام v1.10.20
1.10.19 1.10.20 1.10.18 1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 1.8.8 1.8.5 1.8.6 All 53 releases
← All changes | includes/Migrations/MigratorService.php +108 -4 1.8.61.10.20 View file →
@@ -2,8 +2,9 @@
2 2
3 3 namespace SyncBasalam\Migrations;
4 4
5 5 use SyncBasalam\Admin\Settings\SettingsConfig;
6 +use SyncBasalam\Services\Products\ProductConnection;
6 7 use SyncBasalam\Services\WebhookService;
7 8 use SyncBasalam\Utilities\ProductMetaKey;
8 9
9 10 defined('ABSPATH') || exit;
@@ -22,8 +23,9 @@
22 23 $source = $this->wpdb->prefix . 'bsl_payments';
23 24 $target = $this->wpdb->prefix . 'sync_basalam_payments';
24 25
25 26 if ($this->tableExists($source) && $this->tableExists($target)) {
27 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
26 28 $this->wpdb->query("INSERT INTO $target (payment_id, invoice_id, user_id, city_id, province_id, order_id)
27 29 SELECT payment_id, invoice_id, user_id, city_id, province_id, order_id FROM $source");
28 30 }
29 31 }
@@ -33,8 +35,9 @@
33 35 $source = $this->wpdb->prefix . 'bsl_map_options';
34 36 $target = $this->wpdb->prefix . 'sync_basalam_map_options';
35 37
36 38 if ($this->tableExists($source) && $this->tableExists($target)) {
39 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
37 40 $this->wpdb->query("INSERT INTO $target (woo_name, sync_basalam_name)
38 41 SELECT woo_name, bslm_name FROM $source");
39 42 }
40 43 }
@@ -41,18 +44,24 @@
41 44
42 45 public function migrateUploadedPhotos()
43 46 {
44 47 $source = $this->wpdb->prefix . 'bsl_uploaded_photo';
45 - $target = $this->wpdb->prefix . 'sync_basalam_uploaded_photo';
48 + $target = $this->wpdb->prefix . 'sync_basalam_uploaded_media';
46 49
47 50 if ($this->tableExists($source) && $this->tableExists($target)) {
48 - $this->wpdb->query("INSERT INTO $target (woo_photo_id, sync_basalam_photo_id, sync_basalam_photo_url)
49 - SELECT woo_photo_id, bslm_photo_id, bslm_photo_url FROM $source");
51 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
52 + $this->wpdb->query(
53 + "INSERT IGNORE INTO {$target} (type, source_identity, media_id, media_url, created_at)
54 + SELECT 'photo', CONCAT('attachment:', woo_photo_id), bslm_photo_id, bslm_photo_url, NOW()
55 + FROM {$source}
56 + WHERE woo_photo_id IS NOT NULL"
57 + );
50 58 }
51 59 }
52 60
53 61 public function migratePostMeta()
54 62 {
63 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
55 64 $this->wpdb->query(
56 65 "UPDATE {$this->wpdb->postmeta}
57 66 SET meta_key = REPLACE(meta_key, 'bslm_', 'sync_basalam_')
58 67 WHERE meta_key LIKE '%bslm_%'"
@@ -57,8 +66,9 @@
57 66 SET meta_key = REPLACE(meta_key, 'bslm_', 'sync_basalam_')
58 67 WHERE meta_key LIKE '%bslm_%'"
59 68 );
60 69
70 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
61 71 $this->wpdb->query(
62 72 "UPDATE {$this->wpdb->postmeta}
63 73 SET meta_key = REPLACE(meta_key, 'bsl_', 'sync_basalam_')
64 74 WHERE meta_key LIKE '%bsl_%'"
@@ -66,8 +76,9 @@
66 76 }
67 77
68 78 public function migrateOptionsRows()
69 79 {
80 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
70 81 $this->wpdb->query(
71 82 "UPDATE {$this->wpdb->options}
72 83 SET option_name = REPLACE(option_name, 'bslm_', 'sync_basalam_')
73 84 WHERE option_name LIKE '%bslm_%'"
@@ -72,8 +83,9 @@
72 83 SET option_name = REPLACE(option_name, 'bslm_', 'sync_basalam_')
73 84 WHERE option_name LIKE '%bslm_%'"
74 85 );
75 86
87 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix / core $wpdb properties, not user input.
76 88 $this->wpdb->query(
77 89 "UPDATE {$this->wpdb->options}
78 90 SET option_name = REPLACE(option_name, 'bsl_', 'sync_basalam_')
79 91 WHERE option_name LIKE '%bsl_%'"
@@ -83,8 +95,9 @@
83 95 public function migrateActions()
84 96 {
85 97 $source = $this->wpdb->prefix . 'actionscheduler_actions';
86 98
99 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from $wpdb->prefix, not user input; values are prepared.
87 100 $this->wpdb->query(
88 101 $this->wpdb->prepare(
89 102 "UPDATE $source
90 103 SET hook = REPLACE(hook, 'bslm_', 'sync_basalam_')
@@ -93,8 +106,9 @@
93 106 'pending'
94 107 )
95 108 );
96 109
110 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from $wpdb->prefix, not user input; values are prepared.
97 111 $this->wpdb->query(
98 112 $this->wpdb->prepare(
99 113 "UPDATE $source
100 114 SET hook = REPLACE(hook, 'bsl_', 'sync_basalam_')
@@ -108,8 +122,9 @@
108 122 public function migrateSettings()
109 123 {
110 124 $settingsRowName = 'sync_basalam_settings';
111 125
126 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from core $wpdb property, not user input; value is prepared.
112 127 $settingsSerialized = $this->wpdb->get_var(
113 128 $this->wpdb->prepare(
114 129 "SELECT option_value FROM {$this->wpdb->options} WHERE option_name = %s",
115 130 $settingsRowName
@@ -128,9 +143,9 @@
128 143 'basalam_token' => 'token',
129 144 'basalam_refresh_token' => 'refresh_token',
130 145 'basalam_vendor_id' => 'vendor_id',
131 146 'basalam_is_vendor' => 'is_vendor',
132 - 'basalam_increase' => 'increase_price_value',
147 + 'basalam_increase' => 'price_change_value',
133 148 'basalam_round' => 'round_price',
134 149 'basalam_webhook_token' => 'webhook_header_token',
135 150 'basalam_auto_confirm_order' => 'auto_confirm_order',
136 151 'basalam_product_wholesale' => 'all_products_wholesale',
@@ -153,8 +168,9 @@
153 168 ];
154 169
155 170 foreach ($tables as $table) {
156 171 if ($this->tableExists($table)) {
172 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; table identifier from $wpdb->prefix, not user input.
157 173 $this->wpdb->query("RENAME TABLE $table TO {$table}_old");
158 174 }
159 175 }
160 176 }
@@ -161,8 +177,9 @@
161 177
162 178 private function tableExists($table)
163 179 {
164 180
181 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Legacy-data migration; no object cache for these one-time operational queries.
165 182 return $this->wpdb->get_var($this->wpdb->prepare("SHOW TABLES LIKE %s", $table)) === $table;
166 183 }
167 184
168 185 private function columnExists($table, $column)
@@ -167,8 +184,9 @@
167 184
168 185 private function columnExists($table, $column)
169 186 {
170 187
188 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from $wpdb->prefix, not user input; value is prepared.
171 189 $result = $this->wpdb->get_results($this->wpdb->prepare(
172 190 "SHOW COLUMNS FROM `$table` LIKE %s",
173 191 $column
174 192 ));
@@ -181,8 +199,9 @@
181 199
182 200 $table = $this->wpdb->prefix . 'sync_basalam_uploaded_photo';
183 201
184 202 if ($this->tableExists($table) && !$this->columnExists($table, 'created_at')) {
203 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; table identifier from $wpdb->prefix, not user input.
185 204 $this->wpdb->query("ALTER TABLE $table ADD COLUMN created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP");
186 205 }
187 206 }
188 207
@@ -207,8 +226,9 @@
207 226 ];
208 227
209 228 foreach ($columns as $columnName => $sql) {
210 229 if (!$this->columnExists($table, $columnName)) {
230 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; ALTER statement built from static SQL with a $wpdb->prefix table identifier, not user input.
211 231 $this->wpdb->query($sql);
212 232 }
213 233 }
214 234 }
@@ -220,8 +240,9 @@
220 240 if (!$this->tableExists($table)) {
221 241 return;
222 242 }
223 243
244 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Legacy-data migration; no object cache for these one-time operational queries; values are prepared.
224 245 $existingIndex = $this->wpdb->get_var(
225 246 $this->wpdb->prepare(
226 247 "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS
227 248 WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND INDEX_NAME = %s LIMIT 1",
@@ -236,8 +257,9 @@
236 257
237 258 $this->removeDuplicateInvoicePayments($table);
238 259
239 260 $this->wpdb->hide_errors();
261 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; table identifier from $wpdb->prefix, not user input.
240 262 $this->wpdb->query("ALTER TABLE {$table} ADD UNIQUE KEY unique_invoice_id (invoice_id)");
241 263 $this->wpdb->show_errors();
242 264 }
243 265
@@ -242,8 +264,9 @@
242 264 }
243 265
244 266 private function removeDuplicateInvoicePayments($table)
245 267 {
268 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from $wpdb->prefix, not user input.
246 269 $duplicates = $this->wpdb->get_results(
247 270 "SELECT invoice_id, MIN(id) AS keep_id
248 271 FROM {$table}
249 272 WHERE invoice_id IS NOT NULL AND invoice_id > 0
@@ -258,8 +281,9 @@
258 281 foreach ($duplicates as $duplicate) {
259 282 $invoiceId = (int) $duplicate->invoice_id;
260 283 $keepId = (int) $duplicate->keep_id;
261 284
285 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from $wpdb->prefix, not user input; values are prepared.
262 286 $rowsToDelete = $this->wpdb->get_results(
263 287 $this->wpdb->prepare(
264 288 "SELECT id, order_id FROM {$table} WHERE invoice_id = %d AND id <> %d",
265 289 $invoiceId,
@@ -281,8 +305,9 @@
281 305 }
282 306 }
283 307 }
284 308
309 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Legacy-data migration; no object cache for these one-time operational queries.
285 310 $this->wpdb->delete($table, ['id' => (int) $row->id], ['%d']);
286 311 }
287 312
288 313 \SyncBasalam\Logger\Logger::debug(
@@ -294,14 +319,17 @@
294 319 public function addRetryAfterColumnToJobManager()
295 320 {
296 321 $tableName = $this->wpdb->prefix . 'sync_basalam_job_manager';
297 322
323 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from $wpdb->prefix, not user input; value is prepared.
298 324 $columnExists = $this->wpdb->get_var(
299 325 $this->wpdb->prepare("SHOW COLUMNS FROM `{$tableName}` LIKE %s", 'retry_after')
300 326 );
301 327
302 328 if (!$columnExists) {
329 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; table identifier from $wpdb->prefix, not user input.
303 330 $this->wpdb->query("ALTER TABLE {$tableName} ADD COLUMN retry_after BIGINT(20) NULL DEFAULT NULL");
331 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; table identifier from $wpdb->prefix, not user input.
304 332 $this->wpdb->query("ALTER TABLE {$tableName} ADD INDEX idx_retry_after (retry_after)");
305 333 }
306 334 }
307 335
@@ -325,8 +353,9 @@
325 353
326 354 foreach ($metaKeyMap as $oldMetaKey => $newMetaKey) {
327 355 if ($oldMetaKey === $newMetaKey) continue;
328 356
357 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from core $wpdb property, not user input; values are prepared.
329 358 $this->wpdb->query(
330 359 $this->wpdb->prepare(
331 360 "INSERT INTO {$postmetaTable} (post_id, meta_key, meta_value)
332 361 SELECT old_meta.post_id, %s, old_meta.meta_value
@@ -341,8 +370,9 @@
341 370 $oldMetaKey
342 371 )
343 372 );
344 373
374 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifier from core $wpdb property, not user input; value is prepared.
345 375 $this->wpdb->query(
346 376 $this->wpdb->prepare(
347 377 "DELETE FROM {$postmetaTable} WHERE meta_key = %s",
348 378 $oldMetaKey
@@ -360,5 +390,79 @@
360 390 $settings[SettingsConfig::REFRESH_TOKEN] = null;
361 391
362 392 update_option('sync_basalam_settings', $settings);
363 393 }
394 +
395 + /**
396 + * Variant price/stock used to follow the product price/stock checkboxes of the custom sync mode.
397 + * Copy the old values so variable products keep syncing after the new checkboxes are introduced.
398 + */
399 + public function backfillVariantSyncFields()
400 + {
401 + $settings = get_option('sync_basalam_settings', []);
402 + if (!is_array($settings)) return;
403 +
404 + // The new keys may already have been filled with their "0" default by a settings read.
405 + if (empty($settings[SettingsConfig::SYNC_PRODUCT_FIELD_VARIANT_PRICE])) {
406 + $settings[SettingsConfig::SYNC_PRODUCT_FIELD_VARIANT_PRICE] = $settings[SettingsConfig::SYNC_PRODUCT_FIELD_PRICE] ?? 0;
407 + }
408 +
409 + if (empty($settings[SettingsConfig::SYNC_PRODUCT_FIELD_VARIANT_STOCK])) {
410 + $settings[SettingsConfig::SYNC_PRODUCT_FIELD_VARIANT_STOCK] = $settings[SettingsConfig::SYNC_PRODUCT_FIELD_STOCK] ?? 0;
411 + }
412 +
413 + update_option('sync_basalam_settings', $settings);
414 + }
415 +
416 + public function createUploadedMediaTable()
417 + {
418 + require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
419 +
420 + $tableName = $this->wpdb->prefix . 'sync_basalam_uploaded_media';
421 + $charsetCollate = $this->wpdb->get_charset_collate();
422 +
423 + $sql = "CREATE TABLE $tableName (
424 + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
425 + type VARCHAR(16) NOT NULL,
426 + source_identity VARCHAR(191) NOT NULL,
427 + media_id BIGINT UNSIGNED NULL,
428 + media_url VARCHAR(2083) NULL,
429 + created_at DATETIME NOT NULL,
430 + PRIMARY KEY (id),
431 + UNIQUE KEY unique_type_source (type, source_identity),
432 + KEY idx_type_created (type, created_at),
433 + KEY idx_created_at (created_at)
434 + ) $charsetCollate;";
435 +
436 + dbDelta($sql);
437 +
438 + $this->migrateLegacyUploadedPhotos($tableName);
439 + }
440 +
441 + /**
442 + * اصلاح محصولاتی که به دلیل کپی شدن، به یک محصول باسلام اتصال مشترک دارند.
443 + * قدیمی‌ترین محصول (محصول اصلی) متصل می‌ماند و اتصال نسخه‌های کپی حذف می‌شود.
444 + */
445 + public function repairDuplicateProductConnections(): void
446 + {
447 + ProductConnection::repairDuplicateConnections();
448 + }
449 +
450 + private function migrateLegacyUploadedPhotos(string $targetTable): void
451 + {
452 + $legacyTable = $this->wpdb->prefix . 'sync_basalam_uploaded_photo';
453 +
454 + if (!$this->tableExists($legacyTable)) return;
455 +
456 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Legacy-data migration; table identifiers from $wpdb->prefix, not user input.
457 + $this->wpdb->query(
458 + "INSERT IGNORE INTO {$targetTable} (type, source_identity, media_id, media_url, created_at)
459 + SELECT 'photo', CONCAT('attachment:', woo_photo_id), sync_basalam_photo_id, sync_basalam_photo_url, COALESCE(created_at, NOW())
460 + FROM {$legacyTable}
461 + WHERE woo_photo_id IS NOT NULL"
462 + );
463 +
464 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- One-time schema migration; table identifier from $wpdb->prefix, not user input.
465 + $this->wpdb->query("DROP TABLE IF EXISTS {$legacyTable}");
466 + }
467 +
364 468 }