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

522 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * DatabaseHandler.php
4 *
5 * The DatabaseHandler class file.
6 *
7 * PHP versions 5
8 *
9 * @author Alexander Schneider <alexanderschneider85@gmail.com>
10 * @copyright 2008-2017 Alexander Schneider
11 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2
12 * @version SVN: $id$
13 * @link http://wordpress.org/extend/plugins/user-access-manager/
14 */
15
16 declare(strict_types=1);
17
18 namespace UserAccessManager\Setup\Database;
19
20 use UserAccessManager\Database\Database;
21 use UserAccessManager\Setup\Update\UpdateFactory;
22 use UserAccessManager\Setup\Update\UpdateInterface;
23 use UserAccessManager\UserAccessManager;
24 use UserAccessManager\Wrapper\Wordpress;
25
26 /**
27 * Class DatabaseHandler
28 *
29 * @package UserAccessManager\Setup\Database
30 */
31 class DatabaseHandler
32 {
33 const MISSING_TABLES = 'MISSING_TABLE';
34 const MISSING_COLUMNS = 'MISSING_COLUMNS';
35 const MODIFIED_COLUMNS = 'MODIFIED_COLUMNS';
36 const EXTRA_COLUMNS = 'EXTRA_COLUMNS';
37
38 /**
39 * @var Wordpress
40 */
41 private $wordpress;
42
43 /**
44 * @var Database
45 */
46 private $database;
47
48 /**
49 * @var DatabaseObjectFactory
50 */
51 private $databaseObjectFactory;
52
53 /**
54 * @var UpdateFactory
55 */
56 private $updateFactory;
57
58 /**
59 * DatabaseHandler constructor.
60 * @param Wordpress $wordpress
61 * @param Database $database
62 * @param DatabaseObjectFactory $databaseObjectFactory
63 * @param UpdateFactory $updateFactory
64 */
65 public function __construct(
66 Wordpress $wordpress,
67 Database $database,
68 DatabaseObjectFactory $databaseObjectFactory,
69 UpdateFactory $updateFactory
70 ) {
71 $this->wordpress = $wordpress;
72 $this->database = $database;
73 $this->databaseObjectFactory = $databaseObjectFactory;
74 $this->updateFactory = $updateFactory;
75 }
76
77 /**
78 * Checks if the table exists.
79 * @param string $table
80 * @return bool
81 */
82 private function tableExists(string $table): bool
83 {
84 $dbTable = $this->database->getVariable("SHOW TABLES LIKE '{$table}'");
85
86 return ($table === $dbTable);
87 }
88
89 /**
90 * Adds a table.
91 * @param Table $table
92 */
93 private function addTable(Table $table)
94 {
95 $this->database->dbDelta((string) $table);
96 }
97
98 /**
99 * Returns all tables.
100 * @return Table[]
101 * @throws MissingColumnsException
102 */
103 private function getTables(): array
104 {
105 $charsetCollate = $this->database->getCharset();
106 $tables = [];
107
108 $tables[] = $this->databaseObjectFactory->createTable(
109 $this->database->getUserGroupTable(),
110 $charsetCollate,
111 [
112 $this->databaseObjectFactory->createColumn('ID', 'INT', false, null, true, true),
113 $this->databaseObjectFactory->createColumn('groupname', 'TINYTEXT'),
114 $this->databaseObjectFactory->createColumn('groupdesc', 'TEXT'),
115 $this->databaseObjectFactory->createColumn('read_access', 'TINYTEXT'),
116 $this->databaseObjectFactory->createColumn('write_access', 'TINYTEXT'),
117 $this->databaseObjectFactory->createColumn('ip_range', 'MEDIUMTEXT', true)
118 ]
119 );
120
121 $tables[] = $this->databaseObjectFactory->createTable(
122 $this->database->getUserGroupToObjectTable(),
123 $charsetCollate,
124 [
125 $this->databaseObjectFactory->createColumn('object_id', 'VARCHAR(32)', false, null, true),
126 $this->databaseObjectFactory->createColumn('general_object_type', 'VARCHAR(64)'),
127 $this->databaseObjectFactory->createColumn('object_type', 'VARCHAR(32)', false, null, true),
128 $this->databaseObjectFactory->createColumn('group_id', 'VARCHAR(32)', false, null, true),
129 $this->databaseObjectFactory->createColumn('group_type', 'VARCHAR(32)', false, null, true),
130 $this->databaseObjectFactory->createColumn('from_date', 'DATETIME', true),
131 $this->databaseObjectFactory->createColumn('to_date', 'DATETIME', true)
132 ]
133 );
134
135 return $tables;
136 }
137
138 /**
139 * Adds the tables to the database.
140 * @throws MissingColumnsException
141 */
142 public function install()
143 {
144 foreach ($this->getTables() as $table) {
145 if ($this->tableExists($table->getName()) === false) {
146 $this->addTable($table);
147 }
148 }
149
150 $this->wordpress->addOption('uam_db_version', UserAccessManager::DB_VERSION);
151 }
152
153 /**
154 * Returns the existing columns for a table.
155 * @param Table $table
156 * @return Column[]
157 */
158 private function getExistingColumns(Table $table): array
159 {
160 $query = "SHOW COLUMNS FROM `{$table->getName()}`;";
161 $existingRawColumns = (array) $this->database->getResults($query);
162 $existingColumns = [];
163
164 foreach ($existingRawColumns as $existingRawColumn) {
165 $existingColumns[$existingRawColumn->Field] = $this->databaseObjectFactory->createColumn(
166 $existingRawColumn->Field,
167 strtoupper($existingRawColumn->Type),
168 $existingRawColumn->Null === 'YES',
169 $existingRawColumn->Default,
170 $existingRawColumn->Key === 'PRI',
171 $existingRawColumn->Extra === 'auto_increment'
172 );
173 }
174
175 return $existingColumns;
176 }
177
178 /**
179 * Add corrupted columns to the information array if the are some.
180 * @param Table $table
181 * @param array $information
182 */
183 private function addCorruptedRows(Table $table, array &$information)
184 {
185 $existingColumns = $this->getExistingColumns($table);
186
187 foreach ($table->getColumns() as $column) {
188 if (isset($existingColumns[$column->getName()]) === false) {
189 $information[self::MISSING_COLUMNS][] = [$table, $column];
190 continue;
191 }
192
193 $existingColumn = $existingColumns[$column->getName()];
194 unset($existingColumns[$column->getName()]);
195
196 if ((string) $column !== (string) $existingColumn) {
197 $information[self::MODIFIED_COLUMNS][] = [$table, $column];
198 }
199 }
200
201 foreach ($existingColumns as $existingColumn) {
202 $information[self::EXTRA_COLUMNS][] = [$table, $existingColumn];
203 }
204 }
205
206 /**
207 * Returns corrupted database information.
208 * @return array
209 * @throws MissingColumnsException
210 */
211 public function getCorruptedDatabaseInformation(): array
212 {
213 $information = [
214 self::MISSING_TABLES => [],
215 self::MISSING_COLUMNS => [],
216 self::MODIFIED_COLUMNS => [],
217 self::EXTRA_COLUMNS => []
218 ];
219
220 foreach ($this->getTables() as $table) {
221 if ($this->tableExists($table->getName()) === false) {
222 $information[self::MISSING_TABLES][] = $table;
223 continue;
224 }
225
226 $this->addCorruptedRows($table, $information);
227 }
228
229 return $information;
230 }
231
232 /**
233 * Adds a new column.
234 * @param Table $table
235 * @param Column $column
236 * @return bool
237 */
238 private function addColumn(Table $table, Column $column): bool
239 {
240 return $this->database->query("ALTER TABLE `{$table->getName()}` ADD $column;") !== false;
241 }
242
243 /**
244 * Modify an existing column.
245 * @param Table $table
246 * @param Column $column
247 * @return bool
248 */
249 private function modifyColumn(Table $table, Column $column): bool
250 {
251 return $this->database->query("ALTER TABLE `{$table->getName()}` MODIFY $column;") !== false;
252 }
253
254 /**
255 * Drops an existing column.
256 * @param Table $table
257 * @param Column $column
258 * @return bool
259 */
260 private function dropColumn(Table $table, Column $column): bool
261 {
262 return $this->database->query("ALTER TABLE `{$table->getName()}` DROP `{$column->getName()}`;") !== false;
263 }
264
265 /**
266 * Repairs a corrupt database.
267 * @param array $information
268 * @return bool
269 * @throws MissingColumnsException
270 */
271 public function repairDatabase(array $information = []): bool
272 {
273 $success = true;
274 $information = ($information === []) ? $this->getCorruptedDatabaseInformation() : $information;
275
276 foreach ($information[self::MISSING_TABLES] as $table) {
277 $this->addTable($table);
278 }
279
280 foreach ($information[self::MISSING_COLUMNS] as $columnInformation) {
281 $success = $success && $this->addColumn($columnInformation[0], $columnInformation[1]);
282 }
283
284 foreach ($information[self::MODIFIED_COLUMNS] as $columnInformation) {
285 $success = $success && $this->modifyColumn($columnInformation[0], $columnInformation[1]);
286 }
287
288 foreach ($information[self::EXTRA_COLUMNS] as $columnInformation) {
289 $success = $success && $this->dropColumn($columnInformation[0], $columnInformation[1]);
290 }
291
292 return $success;
293 }
294
295 /**
296 * Returns all sites where the user access manager is active.
297 * @return array
298 */
299 private function getActivePluginSites(): array
300 {
301 $activeSites = [];
302
303 foreach ($this->wordpress->getSites() as $site) {
304 $this->wordpress->switchToBlog($site->blog_id);
305 $plugins = (array) $this->wordpress->getOption('active_plugins', []);
306 $pluginsMap = array_flip($plugins);
307
308 if (isset($pluginsMap['user-access-manager/user-access-manager.php']) === true) {
309 $activeSites[$site->blog_id] = $site->blog_id;
310 }
311
312 $this->wordpress->restoreCurrentBlog();
313 }
314
315 return $activeSites;
316 }
317
318 /**
319 * Checks if a database update is necessary.
320 * @return bool
321 */
322 public function isDatabaseUpdateNecessary(): bool
323 {
324 if ($this->wordpress->isSuperAdmin() === true) {
325 foreach ($this->getActivePluginSites() as $siteId) {
326 $table = $this->database->getBlogPrefix($siteId) . 'options';
327 $select = "SELECT option_value FROM {$table} WHERE option_name = '%s' LIMIT 1";
328 $select = $this->database->prepare($select, 'uam_db_version');
329 $currentDbVersion = $this->database->getVariable($select);
330
331 if ($currentDbVersion !== null
332 && version_compare($currentDbVersion, UserAccessManager::DB_VERSION, '<') === true
333 ) {
334 return true;
335 }
336 }
337 }
338
339 $currentDbVersion = $this->wordpress->getOption('uam_db_version');
340 return version_compare($currentDbVersion, UserAccessManager::DB_VERSION, '<');
341 }
342
343 /**
344 * Creates a database backup.
345 * @return bool
346 */
347 public function backupDatabase(): bool
348 {
349 $currentDbVersion = $this->wordpress->getOption('uam_db_version');
350
351 if (empty($currentDbVersion) === true
352 || version_compare($currentDbVersion, '1.2', '<') === true
353 ) {
354 return false;
355 }
356
357 $tables = [
358 $this->database->getUserGroupTable(),
359 $this->database->getUserGroupToObjectTable()
360 ];
361
362 $currentDbVersion = str_replace('.', '-', $currentDbVersion);
363 $success = true;
364
365 foreach ($tables as $table) {
366 $createQuery = "CREATE TABLE `{$table}_{$currentDbVersion}` LIKE `{$table}`";
367 $success = $success && ($this->database->query($createQuery) !== false);
368 $insertQuery = "INSERT `{$table}_{$currentDbVersion}` SELECT * FROM `{$table}`";
369 $success = $success && ($this->database->query($insertQuery) !== false);
370 }
371
372 return $success;
373 }
374
375 /**
376 * Returns the version for which a backup was created.
377 * @return array
378 */
379 public function getBackups(): array
380 {
381 $versions = [];
382 $tables = (array) $this->database->getColumn(
383 "SHOW TABLES LIKE '{$this->database->getPrefix()}uam_%'"
384 );
385
386 foreach ($tables as $table) {
387 if (preg_match('/.*_([0-9\-]+)/i', $table, $matches) === 1) {
388 $version = str_replace('-', '.', $matches[1]);
389 $versions[$version] = $version;
390 }
391 }
392
393 return $versions;
394 }
395
396 /**
397 * Returns the backup tables for the given version.
398 * @param string $version
399 * @return array
400 */
401 private function getBackupTables(string $version): array
402 {
403 $backupTables = [];
404 $tables = [
405 $this->database->getUserGroupTable(),
406 $this->database->getUserGroupToObjectTable()
407 ];
408
409 $versionForDb = str_replace('.', '-', $version);
410
411 foreach ($tables as $table) {
412 $backupTable = (string) $this->database->getVariable(
413 "SHOW TABLES LIKE '{$table}_{$versionForDb}'"
414 );
415
416 if ($backupTable !== '') {
417 $backupTables[$table] = $backupTable;
418 }
419 }
420
421 return $backupTables;
422 }
423
424 /**
425 * Reverts the database to the given version.
426 * @param string $version
427 * @return bool
428 */
429 public function revertDatabase(string $version): bool
430 {
431 $success = true;
432 $tables = $this->getBackupTables($version);
433
434 foreach ($tables as $table => $backupTable) {
435 $dropQuery = "DROP TABLE IF EXISTS `{$table}`";
436 $success = $success && ($this->database->query($dropQuery) !== false);
437 $renameQuery = "RENAME TABLE `{$backupTable}` TO `{$table}`";
438 $success = $success && ($this->database->query($renameQuery) !== false);
439 }
440
441 if ($success === true) {
442 $this->wordpress->updateOption('uam_db_version', $version);
443 }
444
445 return $success;
446 }
447
448 /**
449 * Deletes the given database backup.
450 * @param string $version
451 * @return bool
452 */
453 public function deleteBackup(string $version): bool
454 {
455 $success = true;
456 $tables = $this->getBackupTables($version);
457
458 foreach ($tables as $table => $backupTable) {
459 $dropQuery = "DROP TABLE IF EXISTS `{$backupTable}`";
460 $success = $success && ($this->database->query($dropQuery) !== false);
461 }
462
463 return $success;
464 }
465
466 /**
467 * Returns the ordered updates.
468 * @return UpdateInterface[]
469 */
470 private function getOrderedDatabaseUpdates(): array
471 {
472 $rawUpdates = $this->updateFactory->getDatabaseUpdates();
473 $updates = [];
474
475 foreach ($rawUpdates as $rawUpdate) {
476 $updates[$rawUpdate->getVersion()] = $rawUpdate;
477 }
478
479 uksort($updates, 'version_compare');
480 return $updates;
481 }
482
483 /**
484 * Updates the database.
485 * @return bool
486 */
487 public function updateDatabase(): bool
488 {
489 $currentDbVersion = $this->wordpress->getOption('uam_db_version');
490
491 if (empty($currentDbVersion) === true) {
492 return false;
493 }
494
495 $success = true;
496
497 foreach ($this->getOrderedDatabaseUpdates() as $orderedUpdate) {
498 if (version_compare($currentDbVersion, $orderedUpdate->getVersion(), '<') === true) {
499 $success = $success && $orderedUpdate->update();
500 }
501 }
502
503 if ($success === true) {
504 $this->wordpress->updateOption('uam_db_version', UserAccessManager::DB_VERSION);
505 }
506
507 return $success;
508 }
509
510 /**
511 * Removes the tables.
512 * @throws MissingColumnsException
513 */
514 public function removeTables()
515 {
516 foreach ($this->getTables() as $table) {
517 $dropQuery = "DROP TABLE IF EXISTS `{$table->getName()}`";
518 $this->database->query($dropQuery);
519 }
520 }
521 }
522