PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Database / CustomTable.php
wp-staging / Framework / Database Last commit date
Exporter 1 day ago QueryBuilder 1 day ago CustomTable.php 1 day ago DbInfo.php 1 day ago ExcludedTables.php 1 day ago ExternalDatabaseConfiguration.php 1 day ago OptionPreservationHandler.php 1 day ago SearchReplace.php 1 day ago SelectedTables.php 1 day ago TableDto.php 1 day ago TableService.php 1 day ago TablesRenamer.php 1 day ago WpDbInfo.php 1 day ago WpOptionsInfo.php 1 day ago iDbInfo.php 2 years ago
CustomTable.php
370 lines
1 <?php
2
3 namespace WPStaging\Framework\Database;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Framework\Adapter\Database as DatabaseAdapter;
7 use WPStaging\Framework\Adapter\Database\InterfaceDatabaseClient as Database;
8
9 use function WPStaging\functions\debug_log;
10
11
12
13
14
15
16
17
18 abstract class CustomTable
19 {
20 const TABLE_NOT_EXIST = -1;
21 const TABLE_EXISTS = 0;
22 const TABLE_CREATED = 1;
23
24
25
26
27 protected $database;
28
29
30
31
32 protected $tableState;
33
34
35
36
37 public function __construct($database = null)
38 {
39 $this->database = $database ?: WPStaging::getInstance()->getContainer()->make(DatabaseAdapter::class)->getClient();
40 }
41
42
43
44
45
46
47 abstract protected function getTableName();
48
49
50
51
52
53
54 abstract protected function getTableVersionKey();
55
56
57
58
59
60
61 abstract protected function getTableVersion();
62
63
64
65
66
67
68 abstract protected function getCreateTableSql();
69
70
71
72
73
74 abstract public function invalidateCache();
75
76
77
78
79
80
81 public function getFullTableName()
82 {
83 global $wpdb;
84 return $wpdb->prefix . $this->getTableName();
85 }
86
87
88
89
90
91 public function ensureTable()
92 {
93 if ($this->tableState === null) {
94 $this->checkTable(true);
95 }
96 }
97
98
99
100
101
102
103
104 public function checkTable($force = false)
105 {
106 if (!$force && $this->tableState !== null) {
107 return $this->tableState;
108 }
109
110 $currentVersion = get_option($this->getTableVersionKey(), '0.0.0');
111 $exists = $this->tableExists();
112 $schemaValid = $exists && $this->hasExpectedSchema();
113 $requiresUpgrade = version_compare($currentVersion, $this->getTableVersion(), '<');
114
115 if ($exists && $schemaValid && !$requiresUpgrade) {
116 $this->tableState = self::TABLE_EXISTS;
117 return $this->tableState;
118 }
119
120 if ($this->updateTable() === self::TABLE_EXISTS) {
121 $this->tableState = self::TABLE_EXISTS;
122 return self::TABLE_CREATED;
123 }
124
125 $this->tableState = self::TABLE_NOT_EXIST;
126
127 return $this->tableState;
128 }
129
130
131
132
133
134
135
136
137
138 protected function updateTable()
139 {
140 $tableSql = $this->getCreateTableSql();
141
142 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
143
144 dbDelta($tableSql);
145
146 if (!$this->hasExpectedSchema()) {
147
148 if ($this->database->query($tableSql) === false || !$this->hasExpectedSchema()) {
149 debug_log($this->getTableName() . ' Table Upgrade Error: ' . $this->database->error());
150 return self::TABLE_NOT_EXIST;
151 }
152 }
153
154 update_option($this->getTableVersionKey(), $this->getTableVersion());
155
156 return self::TABLE_EXISTS;
157 }
158
159
160
161
162 public function tableExists()
163 {
164 global $wpdb;
165
166 $tableName = $this->getFullTableName();
167
168 if ($wpdb instanceof \wpdb) {
169 $query = $wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($tableName));
170 $result = $wpdb->get_var($query);
171
172 return is_string($result) && $result === $tableName;
173 }
174
175 $escapedTableName = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $tableName);
176 $escapedTableName = $this->database->escape($escapedTableName);
177
178 $result = $this->database->query("SHOW TABLES LIKE '{$escapedTableName}' ESCAPE '\\\\'");
179 if ($result === false) {
180 return false;
181 }
182
183 $row = $this->database->fetchRow($result);
184
185 return !empty($row[0]) && (string)$row[0] === $tableName;
186 }
187
188
189
190
191
192
193 protected function hasExpectedSchema()
194 {
195 if (!$this->tableExists()) {
196 return false;
197 }
198
199 $expectedSchema = $this->parseExpectedSchema($this->getCreateTableSql());
200
201 if (empty($expectedSchema['columns']) && empty($expectedSchema['indexes'])) {
202 return true;
203 }
204
205 $actualColumns = $this->getActualColumnNames();
206 foreach ($expectedSchema['columns'] as $columnName) {
207 if (!in_array($columnName, $actualColumns, true)) {
208 return false;
209 }
210 }
211
212 $actualIndexes = $this->getActualIndexNames();
213 foreach ($expectedSchema['indexes'] as $indexName) {
214 if (!in_array($indexName, $actualIndexes, true)) {
215 return false;
216 }
217 }
218
219 return true;
220 }
221
222
223
224
225
226
227
228
229
230 public function dropTable()
231 {
232 $tableName = $this->getFullTableName();
233 $result = $this->executeDropTableQuery($tableName);
234
235 delete_option($this->getTableVersionKey());
236 $this->invalidateCache();
237
238 $cleanupResult = $this->executeDropTableQuery($tableName);
239
240 $this->tableState = null;
241
242 return $result !== false && $cleanupResult !== false;
243 }
244
245
246
247
248 private function getActualColumnNames()
249 {
250 global $wpdb;
251
252 $tableName = $this->getFullTableName();
253
254 if ($wpdb instanceof \wpdb) {
255 $columns = $wpdb->get_col("SHOW COLUMNS FROM `{$tableName}`", 0);
256 return is_array($columns) ? array_values($columns) : [];
257 }
258
259 $result = $this->database->query("SHOW COLUMNS FROM `{$tableName}`");
260 if ($result === false) {
261 return [];
262 }
263
264 $columns = [];
265 while ($row = $this->database->fetchAssoc($result)) {
266 if (!empty($row['Field'])) {
267 $columns[] = (string)$row['Field'];
268 }
269 }
270
271 return $columns;
272 }
273
274
275
276
277 private function getActualIndexNames()
278 {
279 global $wpdb;
280
281 $tableName = $this->getFullTableName();
282
283 if ($wpdb instanceof \wpdb) {
284 $indexes = $wpdb->get_col("SHOW INDEX FROM `{$tableName}`", 2);
285 return is_array($indexes) ? array_values(array_unique(array_map('strval', $indexes))) : [];
286 }
287
288 $result = $this->database->query("SHOW INDEX FROM `{$tableName}`");
289 if ($result === false) {
290 return [];
291 }
292
293 $indexes = [];
294 while ($row = $this->database->fetchAssoc($result)) {
295 if (!empty($row['Key_name'])) {
296 $indexes[] = (string)$row['Key_name'];
297 }
298 }
299
300 return array_values(array_unique($indexes));
301 }
302
303
304
305
306
307 private function parseExpectedSchema($createTableSql)
308 {
309 $schema = [
310 'columns' => [],
311 'indexes' => [],
312 ];
313
314 $createTableSql = trim($createTableSql);
315 $openingParen = strpos($createTableSql, '(');
316 $closingParen = strrpos($createTableSql, ')');
317
318 if ($openingParen === false || $closingParen === false || $closingParen <= $openingParen) {
319 return $schema;
320 }
321
322 $definitions = preg_split('/\r?\n/', substr($createTableSql, $openingParen + 1, $closingParen - $openingParen - 1));
323 if (!is_array($definitions)) {
324 return $schema;
325 }
326
327 foreach ($definitions as $definition) {
328 $definition = trim($definition, " \t\n\r\0\x0B,");
329
330 if ($definition === '') {
331 continue;
332 }
333
334 if (preg_match('/^PRIMARY\s+KEY/i', $definition)) {
335 $schema['indexes'][] = 'PRIMARY';
336 continue;
337 }
338
339 if (preg_match('/^(?:UNIQUE\s+KEY|KEY|INDEX)\s+`?([A-Za-z0-9_]+)`?/i', $definition, $indexMatches)) {
340 $schema['indexes'][] = $indexMatches[1];
341 continue;
342 }
343
344 if (preg_match('/^`?([A-Za-z0-9_]+)`?\s+/i', $definition, $columnMatches)) {
345 $schema['columns'][] = $columnMatches[1];
346 }
347 }
348
349 $schema['columns'] = array_values(array_unique($schema['columns']));
350 $schema['indexes'] = array_values(array_unique($schema['indexes']));
351
352 return $schema;
353 }
354
355
356
357
358
359 private function executeDropTableQuery($tableName)
360 {
361 global $wpdb;
362
363 if ($wpdb instanceof \wpdb) {
364 return $wpdb->query("DROP TABLE IF EXISTS `{$tableName}`");
365 }
366
367 return $this->database->query("DROP TABLE IF EXISTS `{$tableName}`");
368 }
369 }
370