PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / InstallActions / Migrations / EnsureUniquePaymentTransactionId.php

EnsureUniquePaymentTransactionId.php in The Innovative Form Builder – IvyForms 1.5, at backend/src/Services/InstallActions/Migrations/EnsureUniquePaymentTransactionId.php

119 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace IvyForms\Services\InstallActions\Migrations;
4
5 use IvyForms\Common\Exceptions\InvalidArgumentException;
6 use IvyForms\Services\InstallActions\DB\Payment\PaymentsTable;
7
8 /**
9 * Enforces UNIQUE(transactionId) on existing payments tables.
10 */
11 class EnsureUniquePaymentTransactionId
12 {
13 /**
14 * @return bool
15 */
16 public static function run(): bool
17 {
18 global $wpdb;
19
20 try {
21 $tableName = PaymentsTable::getTableName();
22 } catch (InvalidArgumentException $e) {
23 return false;
24 }
25
26 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
27 if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $tableName)) !== $tableName) {
28 return true;
29 }
30
31 $indexes = self::getTransactionIdIndexes($tableName);
32 if ($indexes === null) {
33 return false;
34 }
35
36 if (self::hasUniqueTransactionIdIndex($indexes)) {
37 return true;
38 }
39
40 return self::enforceUniqueTransactionId($tableName, $indexes);
41 }
42
43 /**
44 * @return array<int, array<string, mixed>>|null
45 */
46 private static function getTransactionIdIndexes(string $tableName): ?array
47 {
48 global $wpdb;
49
50 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
51 $indexes = $wpdb->get_results("SHOW INDEX FROM `{$tableName}` WHERE Column_name = 'transactionId'", ARRAY_A);
52
53 return is_array($indexes) ? $indexes : null;
54 }
55
56 /**
57 * @param array<int, array<string, mixed>> $indexes
58 */
59 private static function hasUniqueTransactionIdIndex(array $indexes): bool
60 {
61 foreach ($indexes as $index) {
62 if (isset($index['Non_unique']) && (int) $index['Non_unique'] === 0) {
63 return true;
64 }
65 }
66
67 return false;
68 }
69
70 /**
71 * @param array<int, array<string, mixed>> $indexes
72 */
73 private static function enforceUniqueTransactionId(string $tableName, array $indexes): bool
74 {
75 global $wpdb;
76
77 // Keep the earliest row per transactionId so UNIQUE can be applied safely.
78 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
79 $wpdb->query(
80 "DELETE p1 FROM `{$tableName}` p1
81 INNER JOIN `{$tableName}` p2
82 ON p1.transactionId = p2.transactionId AND p1.id > p2.id"
83 );
84
85 self::dropNonPrimaryIndexes($tableName, $indexes);
86
87 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
88 $result = $wpdb->query(
89 "ALTER TABLE `{$tableName}` ADD UNIQUE KEY `transactionId` (`transactionId`)"
90 );
91
92 if ($result === false) {
93 error_log('IvyForms migration error: failed to add UNIQUE transactionId on payments table');
94
95 return false;
96 }
97
98 return true;
99 }
100
101 /**
102 * @param array<int, array<string, mixed>> $indexes
103 */
104 private static function dropNonPrimaryIndexes(string $tableName, array $indexes): void
105 {
106 global $wpdb;
107
108 foreach ($indexes as $index) {
109 $keyName = (string) ($index['Key_name'] ?? '');
110 if ($keyName === '' || $keyName === 'PRIMARY') {
111 continue;
112 }
113
114 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
115 $wpdb->query("ALTER TABLE `{$tableName}` DROP INDEX `{$keyName}`");
116 }
117 }
118 }
119