PluginProbe
User Access Manager / 2.3.13
User Access Manager v2.3.13
2.3.20 2.3.19 2.3.18 2.3.17 2.3.16 2.3.15 2.3.14 2.3.13 trunk 0.6 0.6.1 0.6.2 0.7 0.7 Beta 0.7.0.1 0.8 0.8.0.1 0.8.0.2 0.9 0.9.1 0.9.1.1 0.9.1.2 0.9.1.3 0.9.1.4 1.0 All 136 releases
user-access-manager / src / Setup / Database / DatabaseHandler.php

DatabaseHandler.php in User Access Manager 2.3.13, at src/Setup/Database/DatabaseHandler.php

404 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace UserAccessManager\Setup\Database;
6
7 use UserAccessManager\Database\Database;
8 use UserAccessManager\Setup\Update\UpdateFactory;
9 use UserAccessManager\Setup\Update\UpdateInterface;
10 use UserAccessManager\UserAccessManager;
11 use UserAccessManager\Wrapper\Wordpress;
12
13 class DatabaseHandler
14 {
15 public const MISSING_TABLES = 'MISSING_TABLE';
16 public const MISSING_COLUMNS = 'MISSING_COLUMNS';
17 public const MODIFIED_COLUMNS = 'MODIFIED_COLUMNS';
18 public const EXTRA_COLUMNS = 'EXTRA_COLUMNS';
19
20 public function __construct(
21 private Wordpress $wordpress,
22 private Database $database,
23 private DatabaseObjectFactory $databaseObjectFactory,
24 private UpdateFactory $updateFactory
25 ) {
26 }
27
28 private function tableExists(string $table): bool
29 {
30 $dbTable = $this->database->getVariable("SHOW TABLES LIKE '$table'");
31
32 return ($table === $dbTable);
33 }
34
35 private function addTable(Table $table): void
36 {
37 $this->database->dbDelta((string) $table);
38 }
39
40 /**
41 * @return Table[]
42 * @throws MissingColumnsException
43 */
44 private function getTables(): array
45 {
46 $charsetCollate = $this->database->getCharset();
47 $tables = [];
48
49 $tables[] = $this->databaseObjectFactory->createTable(
50 $this->database->getUserGroupTable(),
51 $charsetCollate,
52 [
53 $this->databaseObjectFactory->createColumn('ID', 'INT', false, null, true, true),
54 $this->databaseObjectFactory->createColumn('groupname', 'TINYTEXT'),
55 $this->databaseObjectFactory->createColumn('groupdesc', 'TEXT'),
56 $this->databaseObjectFactory->createColumn('read_access', 'TINYTEXT'),
57 $this->databaseObjectFactory->createColumn('write_access', 'TINYTEXT'),
58 $this->databaseObjectFactory->createColumn('ip_range', 'MEDIUMTEXT', true)
59 ]
60 );
61
62 $tables[] = $this->databaseObjectFactory->createTable(
63 $this->database->getUserGroupToObjectTable(),
64 $charsetCollate,
65 [
66 $this->databaseObjectFactory->createColumn('object_id', 'VARCHAR(32)', false, null, true),
67 $this->databaseObjectFactory->createColumn('general_object_type', 'VARCHAR(64)'),
68 $this->databaseObjectFactory->createColumn('object_type', 'VARCHAR(32)', false, null, true),
69 $this->databaseObjectFactory->createColumn('group_id', 'VARCHAR(32)', false, null, true),
70 $this->databaseObjectFactory->createColumn('group_type', 'VARCHAR(32)', false, null, true),
71 $this->databaseObjectFactory->createColumn('from_date', 'DATETIME', true),
72 $this->databaseObjectFactory->createColumn('to_date', 'DATETIME', true)
73 ]
74 );
75
76 return $tables;
77 }
78
79 /**
80 * @throws MissingColumnsException
81 */
82 public function install(): void
83 {
84 foreach ($this->getTables() as $table) {
85 if ($this->tableExists($table->getName()) === false) {
86 $this->addTable($table);
87 }
88 }
89
90 $this->wordpress->addOption('uam_db_version', UserAccessManager::DB_VERSION);
91 }
92
93 /**
94 * @return Column[]
95 */
96 private function getExistingColumns(Table $table): array
97 {
98 $query = "SHOW COLUMNS FROM `{$table->getName()}`;";
99 $existingRawColumns = (array) $this->database->getResults($query);
100 $existingColumns = [];
101
102 foreach ($existingRawColumns as $existingRawColumn) {
103 $existingColumns[$existingRawColumn->Field] = $this->databaseObjectFactory->createColumn(
104 $existingRawColumn->Field,
105 strtoupper($existingRawColumn->Type),
106 $existingRawColumn->Null === 'YES',
107 $existingRawColumn->Default,
108 $existingRawColumn->Key === 'PRI',
109 $existingRawColumn->Extra === 'auto_increment'
110 );
111 }
112
113 return $existingColumns;
114 }
115
116 private function addCorruptedRows(Table $table, array &$information): void
117 {
118 $existingColumns = $this->getExistingColumns($table);
119
120 foreach ($table->getColumns() as $column) {
121 if (isset($existingColumns[$column->getName()]) === false) {
122 $information[self::MISSING_COLUMNS][] = [$table, $column];
123 continue;
124 }
125
126 $existingColumn = $existingColumns[$column->getName()];
127 unset($existingColumns[$column->getName()]);
128
129 if ((string) $column !== (string) $existingColumn) {
130 $information[self::MODIFIED_COLUMNS][] = [$table, $column];
131 }
132 }
133
134 foreach ($existingColumns as $existingColumn) {
135 $information[self::EXTRA_COLUMNS][] = [$table, $existingColumn];
136 }
137 }
138
139 /**
140 * @throws MissingColumnsException
141 */
142 public function getCorruptedDatabaseInformation(): array
143 {
144 $information = [
145 self::MISSING_TABLES => [],
146 self::MISSING_COLUMNS => [],
147 self::MODIFIED_COLUMNS => [],
148 self::EXTRA_COLUMNS => []
149 ];
150
151 foreach ($this->getTables() as $table) {
152 if ($this->tableExists($table->getName()) === false) {
153 $information[self::MISSING_TABLES][] = $table;
154 continue;
155 }
156
157 $this->addCorruptedRows($table, $information);
158 }
159
160 return $information;
161 }
162
163 private function addColumn(Table $table, Column $column): bool
164 {
165 return $this->database->query("ALTER TABLE `{$table->getName()}` ADD $column;") !== false;
166 }
167
168 private function modifyColumn(Table $table, Column $column): bool
169 {
170 return $this->database->query("ALTER TABLE `{$table->getName()}` MODIFY $column;") !== false;
171 }
172
173 private function dropColumn(Table $table, Column $column): bool
174 {
175 return $this->database->query("ALTER TABLE `{$table->getName()}` DROP `{$column->getName()}`;") !== false;
176 }
177
178 /**
179 * @throws MissingColumnsException
180 */
181 public function repairDatabase(array $information = []): bool
182 {
183 $success = true;
184 $information = ($information === []) ? $this->getCorruptedDatabaseInformation() : $information;
185
186 foreach ($information[self::MISSING_TABLES] as $table) {
187 $this->addTable($table);
188 }
189
190 foreach ($information[self::MISSING_COLUMNS] as $columnInformation) {
191 $success = $success && $this->addColumn($columnInformation[0], $columnInformation[1]);
192 }
193
194 foreach ($information[self::MODIFIED_COLUMNS] as $columnInformation) {
195 $success = $success && $this->modifyColumn($columnInformation[0], $columnInformation[1]);
196 }
197
198 foreach ($information[self::EXTRA_COLUMNS] as $columnInformation) {
199 $success = $success && $this->dropColumn($columnInformation[0], $columnInformation[1]);
200 }
201
202 return $success;
203 }
204
205 private function getActivePluginSites(): array
206 {
207 $activeSites = [];
208
209 foreach ($this->wordpress->getSites() as $site) {
210 $this->wordpress->switchToBlog($site->blog_id);
211 $plugins = (array) $this->wordpress->getOption('active_plugins', []);
212 $pluginsMap = array_flip($plugins);
213
214 if (isset($pluginsMap['user-access-manager/user-access-manager.php']) === true) {
215 $activeSites[$site->blog_id] = $site->blog_id;
216 }
217
218 $this->wordpress->restoreCurrentBlog();
219 }
220
221 return $activeSites;
222 }
223
224 /**
225 * @throws MissingColumnsException
226 */
227 public function isDatabaseUpdateNecessary(): bool
228 {
229 if ($this->wordpress->isSuperAdmin() === true) {
230 foreach ($this->getActivePluginSites() as $siteId) {
231 $table = $this->database->getBlogPrefix($siteId) . 'options';
232 $select = "SELECT option_value FROM $table WHERE option_name = '%s' LIMIT 1";
233 $select = $this->database->prepare($select, 'uam_db_version');
234 $currentDbVersion = $this->database->getVariable($select);
235
236 if ($currentDbVersion !== null
237 && version_compare((string) $currentDbVersion, UserAccessManager::DB_VERSION, '<') === true
238 ) {
239 return true;
240 }
241 }
242 }
243
244 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
245
246 if (empty($currentDbVersion) === true) {
247 $this->install();
248 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
249 }
250
251 return version_compare($currentDbVersion, UserAccessManager::DB_VERSION, '<');
252 }
253
254 public function backupDatabase(): bool
255 {
256 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
257
258 if (empty($currentDbVersion) === true
259 || version_compare($currentDbVersion, '1.2', '<') === true
260 ) {
261 return false;
262 }
263
264 $tables = [
265 $this->database->getUserGroupTable(),
266 $this->database->getUserGroupToObjectTable()
267 ];
268
269 $currentDbVersion = str_replace('.', '-', $currentDbVersion);
270 $success = true;
271
272 foreach ($tables as $table) {
273 $createQuery = "CREATE TABLE `{$table}_$currentDbVersion` LIKE `$table`";
274 $success = $success && ($this->database->query($createQuery) !== false);
275 $insertQuery = "INSERT `{$table}_$currentDbVersion` SELECT * FROM `$table`";
276 $success = $success && ($this->database->query($insertQuery) !== false);
277 }
278
279 return $success;
280 }
281
282 public function getBackups(): array
283 {
284 $versions = [];
285 $tables = $this->database->getColumn(
286 "SHOW TABLES LIKE '{$this->database->getPrefix()}uam_%'"
287 );
288
289 foreach ($tables as $table) {
290 if (preg_match('/.*_([0-9\-]+)/i', $table, $matches) === 1) {
291 $version = str_replace('-', '.', $matches[1]);
292 $versions[$version] = $version;
293 }
294 }
295
296 return $versions;
297 }
298
299 private function getBackupTables(string $version): array
300 {
301 $backupTables = [];
302 $tables = [
303 $this->database->getUserGroupTable(),
304 $this->database->getUserGroupToObjectTable()
305 ];
306
307 $versionForDb = str_replace('.', '-', $version);
308
309 foreach ($tables as $table) {
310 $backupTable = (string) $this->database->getVariable(
311 "SHOW TABLES LIKE '{$table}_$versionForDb'"
312 );
313
314 if ($backupTable !== '') {
315 $backupTables[$table] = $backupTable;
316 }
317 }
318
319 return $backupTables;
320 }
321
322 public function revertDatabase(string $version): bool
323 {
324 $success = true;
325 $tables = $this->getBackupTables($version);
326
327 foreach ($tables as $table => $backupTable) {
328 $dropQuery = "DROP TABLE IF EXISTS `$table`";
329 $success = $success && ($this->database->query($dropQuery) !== false);
330 $renameQuery = "RENAME TABLE `$backupTable` TO `$table`";
331 $success = $success && ($this->database->query($renameQuery) !== false);
332 }
333
334 if ($success === true) {
335 $this->wordpress->updateOption('uam_db_version', $version);
336 }
337
338 return $success;
339 }
340
341 public function deleteBackup(string $version): bool
342 {
343 $success = true;
344 $tables = $this->getBackupTables($version);
345
346 foreach ($tables as $backupTable) {
347 $dropQuery = "DROP TABLE IF EXISTS `$backupTable`";
348 $success = $success && ($this->database->query($dropQuery) !== false);
349 }
350
351 return $success;
352 }
353
354 /**
355 * @return UpdateInterface[]
356 */
357 private function getOrderedDatabaseUpdates(): array
358 {
359 $rawUpdates = $this->updateFactory->getDatabaseUpdates();
360 $updates = [];
361
362 foreach ($rawUpdates as $rawUpdate) {
363 $updates[$rawUpdate->getVersion()] = $rawUpdate;
364 }
365
366 uksort($updates, 'version_compare');
367 return $updates;
368 }
369
370 public function updateDatabase(): bool
371 {
372 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
373
374 if (empty($currentDbVersion) === true) {
375 return false;
376 }
377
378 $success = true;
379
380 foreach ($this->getOrderedDatabaseUpdates() as $orderedUpdate) {
381 if (version_compare($currentDbVersion, $orderedUpdate->getVersion(), '<') === true) {
382 $success = $success && $orderedUpdate->update();
383 }
384 }
385
386 if ($success === true) {
387 $this->wordpress->updateOption('uam_db_version', UserAccessManager::DB_VERSION);
388 }
389
390 return $success;
391 }
392
393 /**
394 * @throws MissingColumnsException
395 */
396 public function removeTables(): void
397 {
398 foreach ($this->getTables() as $table) {
399 $dropQuery = "DROP TABLE IF EXISTS `{$table->getName()}`";
400 $this->database->query($dropQuery);
401 }
402 }
403 }
404