PluginProbe
User Access Manager / trunk
User Access Manager vtrunk
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 trunk, at src/Setup/Database/DatabaseHandler.php

402 lines 12.8 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 alterTable(Table $table, string $alteration): bool
164 {
165 return $this->database->query("ALTER TABLE `{$table->getName()}` $alteration;") !== false;
166 }
167
168 /**
169 * @throws MissingColumnsException
170 */
171 public function repairDatabase(array $information = []): bool
172 {
173 $success = true;
174 $information = ($information === []) ? $this->getCorruptedDatabaseInformation() : $information;
175
176 foreach ($information[self::MISSING_TABLES] as $table) {
177 $this->addTable($table);
178 }
179
180 $alterationsByInformationKey = [
181 self::MISSING_COLUMNS => static fn(Column $column) => "ADD $column",
182 self::MODIFIED_COLUMNS => static fn(Column $column) => "MODIFY $column",
183 self::EXTRA_COLUMNS => static fn(Column $column) => "DROP `{$column->getName()}`"
184 ];
185
186 foreach ($alterationsByInformationKey as $informationKey => $buildAlteration) {
187 foreach ($information[$informationKey] as [$table, $column]) {
188 $success = $success && $this->alterTable($table, $buildAlteration($column));
189 }
190 }
191
192 return $success;
193 }
194
195 private function getActivePluginSites(): array
196 {
197 $activeSites = [];
198
199 foreach ($this->wordpress->getSites() as $site) {
200 $this->wordpress->switchToBlog($site->blog_id);
201 $plugins = (array) $this->wordpress->getOption('active_plugins', []);
202 $pluginsMap = array_flip($plugins);
203
204 if (isset($pluginsMap['user-access-manager/user-access-manager.php']) === true) {
205 $activeSites[$site->blog_id] = $site->blog_id;
206 }
207
208 $this->wordpress->restoreCurrentBlog();
209 }
210
211 return $activeSites;
212 }
213
214 private function hasSiteWithOutdatedDatabase(): bool
215 {
216 foreach ($this->getActivePluginSites() as $siteId) {
217 $table = $this->database->getBlogPrefix($siteId) . 'options';
218 $select = "SELECT `option_value` FROM `$table` WHERE `option_name` = '%s' LIMIT 1";
219 $select = $this->database->prepare($select, 'uam_db_version');
220 $currentDbVersion = $this->database->getVariable($select);
221
222 if ($currentDbVersion !== null
223 && version_compare((string) $currentDbVersion, UserAccessManager::DB_VERSION, '<') === true
224 ) {
225 return true;
226 }
227 }
228
229 return false;
230 }
231
232 /**
233 * @throws MissingColumnsException
234 */
235 public function isDatabaseUpdateNecessary(): bool
236 {
237 if ($this->wordpress->isSuperAdmin() === true && $this->hasSiteWithOutdatedDatabase() === true) {
238 return true;
239 }
240
241 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
242
243 if (empty($currentDbVersion) === true) {
244 $this->install();
245 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
246 }
247
248 return version_compare($currentDbVersion, UserAccessManager::DB_VERSION, '<');
249 }
250
251 /**
252 * @return string[]
253 */
254 private function getTableNames(): array
255 {
256 return [
257 $this->database->getUserGroupTable(),
258 $this->database->getUserGroupToObjectTable()
259 ];
260 }
261
262 public function backupDatabase(): bool
263 {
264 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
265
266 if (empty($currentDbVersion) === true
267 || version_compare($currentDbVersion, '1.2', '<') === true
268 ) {
269 return false;
270 }
271
272 $currentDbVersion = str_replace('.', '-', $currentDbVersion);
273 $success = true;
274
275 foreach ($this->getTableNames() as $table) {
276 $createQuery = "CREATE TABLE `{$table}_$currentDbVersion` LIKE `$table`";
277 $success = $success && ($this->database->query($createQuery) !== false);
278 $insertQuery = "INSERT `{$table}_$currentDbVersion` SELECT * FROM `$table`";
279 $success = $success && ($this->database->query($insertQuery) !== false);
280 }
281
282 return $success;
283 }
284
285 public function getBackups(): array
286 {
287 $versions = [];
288 $tables = $this->database->getColumn(
289 "SHOW TABLES LIKE '{$this->database->getPrefix()}uam_%'"
290 );
291
292 foreach ($tables as $table) {
293 if (preg_match('/.*_([0-9\-]+)/', $table, $matches) === 1) {
294 $version = str_replace('-', '.', $matches[1]);
295 $versions[$version] = $version;
296 }
297 }
298
299 return $versions;
300 }
301
302 private function getBackupTables(string $version): array
303 {
304 $backupTables = [];
305 $versionForDb = str_replace('.', '-', $version);
306
307 foreach ($this->getTableNames() as $table) {
308 $backupTable = (string) $this->database->getVariable(
309 "SHOW TABLES LIKE '{$table}_$versionForDb'"
310 );
311
312 if ($backupTable !== '') {
313 $backupTables[$table] = $backupTable;
314 }
315 }
316
317 return $backupTables;
318 }
319
320 public function revertDatabase(string $version): bool
321 {
322 $success = true;
323 $tables = $this->getBackupTables($version);
324
325 foreach ($tables as $table => $backupTable) {
326 $dropQuery = "DROP TABLE IF EXISTS `$table`";
327 $success = $success && ($this->database->query($dropQuery) !== false);
328 $renameQuery = "RENAME TABLE `$backupTable` TO `$table`";
329 $success = $success && ($this->database->query($renameQuery) !== false);
330 }
331
332 if ($success === true) {
333 $this->wordpress->updateOption('uam_db_version', $version);
334 }
335
336 return $success;
337 }
338
339 public function deleteBackup(string $version): bool
340 {
341 $success = true;
342 $tables = $this->getBackupTables($version);
343
344 foreach ($tables as $backupTable) {
345 $dropQuery = "DROP TABLE IF EXISTS `$backupTable`";
346 $success = $success && ($this->database->query($dropQuery) !== false);
347 }
348
349 return $success;
350 }
351
352 /**
353 * @return UpdateInterface[]
354 */
355 private function getOrderedDatabaseUpdates(): array
356 {
357 $rawUpdates = $this->updateFactory->getDatabaseUpdates();
358 $updates = [];
359
360 foreach ($rawUpdates as $rawUpdate) {
361 $updates[$rawUpdate->getVersion()] = $rawUpdate;
362 }
363
364 uksort($updates, 'version_compare');
365 return $updates;
366 }
367
368 public function updateDatabase(): bool
369 {
370 $currentDbVersion = (string) $this->wordpress->getOption('uam_db_version');
371
372 if (empty($currentDbVersion) === true) {
373 return false;
374 }
375
376 $success = true;
377
378 foreach ($this->getOrderedDatabaseUpdates() as $orderedUpdate) {
379 if (version_compare($currentDbVersion, $orderedUpdate->getVersion(), '<') === true) {
380 $success = $success && $orderedUpdate->update();
381 }
382 }
383
384 if ($success === true) {
385 $this->wordpress->updateOption('uam_db_version', UserAccessManager::DB_VERSION);
386 }
387
388 return $success;
389 }
390
391 /**
392 * @throws MissingColumnsException
393 */
394 public function removeTables(): void
395 {
396 foreach ($this->getTables() as $table) {
397 $dropQuery = "DROP TABLE IF EXISTS `{$table->getName()}`";
398 $this->database->query($dropQuery);
399 }
400 }
401 }
402