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

523 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(11)', 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 continue;
199 }
200 }
201
202 foreach ($existingColumns as $existingColumn) {
203 $information[self::EXTRA_COLUMNS][] = [$table, $existingColumn];
204 }
205 }
206
207 /**
208 * Returns corrupted database information.
209 * @return array
210 * @throws MissingColumnsException
211 */
212 public function getCorruptedDatabaseInformation(): array
213 {
214 $information = [
215 self::MISSING_TABLES => [],
216 self::MISSING_COLUMNS => [],
217 self::MODIFIED_COLUMNS => [],
218 self::EXTRA_COLUMNS => []
219 ];
220
221 foreach ($this->getTables() as $table) {
222 if ($this->tableExists($table->getName()) === false) {
223 $information[self::MISSING_TABLES][] = $table;
224 continue;
225 }
226
227 $this->addCorruptedRows($table, $information);
228 }
229
230 return $information;
231 }
232
233 /**
234 * Adds a new column.
235 * @param Table $table
236 * @param Column $column
237 * @return bool
238 */
239 private function addColumn(Table $table, Column $column): bool
240 {
241 return $this->database->query("ALTER TABLE `{$table->getName()}` ADD $column;") !== false;
242 }
243
244 /**
245 * Modify an existing column.
246 * @param Table $table
247 * @param Column $column
248 * @return bool
249 */
250 private function modifyColumn(Table $table, Column $column): bool
251 {
252 return $this->database->query("ALTER TABLE `{$table->getName()}` MODIFY $column;") !== false;
253 }
254
255 /**
256 * Drops an existing column.
257 * @param Table $table
258 * @param Column $column
259 * @return bool
260 */
261 private function dropColumn(Table $table, Column $column): bool
262 {
263 return $this->database->query("ALTER TABLE `{$table->getName()}` DROP `{$column->getName()}`;") !== false;
264 }
265
266 /**
267 * Repairs a corrupt database.
268 * @param array $information
269 * @return bool
270 * @throws MissingColumnsException
271 */
272 public function repairDatabase(array $information = []): bool
273 {
274 $success = true;
275 $information = ($information === []) ? $this->getCorruptedDatabaseInformation() : $information;
276
277 foreach ($information[self::MISSING_TABLES] as $table) {
278 $this->addTable($table);
279 }
280
281 foreach ($information[self::MISSING_COLUMNS] as $columnInformation) {
282 $success = $success && $this->addColumn($columnInformation[0], $columnInformation[1]);
283 }
284
285 foreach ($information[self::MODIFIED_COLUMNS] as $columnInformation) {
286 $success = $success && $this->modifyColumn($columnInformation[0], $columnInformation[1]);
287 }
288
289 foreach ($information[self::EXTRA_COLUMNS] as $columnInformation) {
290 $success = $success && $this->dropColumn($columnInformation[0], $columnInformation[1]);
291 }
292
293 return $success;
294 }
295
296 /**
297 * Returns all sites where the user access manager is active.
298 * @return array
299 */
300 private function getActivePluginSites(): array
301 {
302 $activeSites = [];
303
304 foreach ($this->wordpress->getSites() as $site) {
305 $this->wordpress->switchToBlog($site->blog_id);
306 $plugins = (array) $this->wordpress->getOption('active_plugins', []);
307 $pluginsMap = array_flip($plugins);
308
309 if (isset($pluginsMap['user-access-manager/user-access-manager.php']) === true) {
310 $activeSites[$site->blog_id] = $site->blog_id;
311 }
312
313 $this->wordpress->restoreCurrentBlog();
314 }
315
316 return $activeSites;
317 }
318
319 /**
320 * Checks if a database update is necessary.
321 * @return bool
322 */
323 public function isDatabaseUpdateNecessary(): bool
324 {
325 if ($this->wordpress->isSuperAdmin() === true) {
326 foreach ($this->getActivePluginSites() as $siteId) {
327 $table = $this->database->getBlogPrefix($siteId) . 'options';
328 $select = "SELECT option_value FROM {$table} WHERE option_name = '%s' LIMIT 1";
329 $select = $this->database->prepare($select, 'uam_db_version');
330 $currentDbVersion = $this->database->getVariable($select);
331
332 if ($currentDbVersion !== null
333 && version_compare($currentDbVersion, UserAccessManager::DB_VERSION, '<') === true
334 ) {
335 return true;
336 }
337 }
338 }
339
340 $currentDbVersion = $this->wordpress->getOption('uam_db_version');
341 return version_compare($currentDbVersion, UserAccessManager::DB_VERSION, '<');
342 }
343
344 /**
345 * Creates a database backup.
346 * @return bool
347 */
348 public function backupDatabase(): bool
349 {
350 $currentDbVersion = $this->wordpress->getOption('uam_db_version');
351
352 if (empty($currentDbVersion) === true
353 || version_compare($currentDbVersion, '1.2', '<') === true
354 ) {
355 return false;
356 }
357
358 $tables = [
359 $this->database->getUserGroupTable(),
360 $this->database->getUserGroupToObjectTable()
361 ];
362
363 $currentDbVersion = str_replace('.', '-', $currentDbVersion);
364 $success = true;
365
366 foreach ($tables as $table) {
367 $createQuery = "CREATE TABLE `{$table}_{$currentDbVersion}` LIKE `{$table}`";
368 $success = $success && ($this->database->query($createQuery) !== false);
369 $insertQuery = "INSERT `{$table}_{$currentDbVersion}` SELECT * FROM `{$table}`";
370 $success = $success && ($this->database->query($insertQuery) !== false);
371 }
372
373 return $success;
374 }
375
376 /**
377 * Returns the version for which a backup was created.
378 * @return array
379 */
380 public function getBackups(): array
381 {
382 $versions = [];
383 $tables = (array) $this->database->getColumn(
384 "SHOW TABLES LIKE '{$this->database->getPrefix()}uam_%'"
385 );
386
387 foreach ($tables as $table) {
388 if (preg_match('/.*_([0-9\-]+)/i', $table, $matches) === 1) {
389 $version = str_replace('-', '.', $matches[1]);
390 $versions[$version] = $version;
391 }
392 }
393
394 return $versions;
395 }
396
397 /**
398 * Returns the backup tables for the given version.
399 * @param string $version
400 * @return array
401 */
402 private function getBackupTables(string $version): array
403 {
404 $backupTables = [];
405 $tables = [
406 $this->database->getUserGroupTable(),
407 $this->database->getUserGroupToObjectTable()
408 ];
409
410 $versionForDb = str_replace('.', '-', $version);
411
412 foreach ($tables as $table) {
413 $backupTable = (string) $this->database->getVariable(
414 "SHOW TABLES LIKE '{$table}_{$versionForDb}'"
415 );
416
417 if ($backupTable !== '') {
418 $backupTables[$table] = $backupTable;
419 }
420 }
421
422 return $backupTables;
423 }
424
425 /**
426 * Reverts the database to the given version.
427 * @param string $version
428 * @return bool
429 */
430 public function revertDatabase(string $version): bool
431 {
432 $success = true;
433 $tables = $this->getBackupTables($version);
434
435 foreach ($tables as $table => $backupTable) {
436 $dropQuery = "DROP TABLE IF EXISTS `{$table}`";
437 $success = $success && ($this->database->query($dropQuery) !== false);
438 $renameQuery = "RENAME TABLE `{$backupTable}` TO `{$table}`";
439 $success = $success && ($this->database->query($renameQuery) !== false);
440 }
441
442 if ($success === true) {
443 $this->wordpress->updateOption('uam_db_version', $version);
444 }
445
446 return $success;
447 }
448
449 /**
450 * Deletes the given database backup.
451 * @param string $version
452 * @return bool
453 */
454 public function deleteBackup(string $version): bool
455 {
456 $success = true;
457 $tables = $this->getBackupTables($version);
458
459 foreach ($tables as $table => $backupTable) {
460 $dropQuery = "DROP TABLE IF EXISTS `{$backupTable}`";
461 $success = $success && ($this->database->query($dropQuery) !== false);
462 }
463
464 return $success;
465 }
466
467 /**
468 * Returns the ordered updates.
469 * @return UpdateInterface[]
470 */
471 private function getOrderedDatabaseUpdates(): array
472 {
473 $rawUpdates = $this->updateFactory->getDatabaseUpdates();
474 $updates = [];
475
476 foreach ($rawUpdates as $rawUpdate) {
477 $updates[$rawUpdate->getVersion()] = $rawUpdate;
478 }
479
480 uksort($updates, 'version_compare');
481 return $updates;
482 }
483
484 /**
485 * Updates the database.
486 * @return bool
487 */
488 public function updateDatabase(): bool
489 {
490 $currentDbVersion = $this->wordpress->getOption('uam_db_version');
491
492 if (empty($currentDbVersion) === true) {
493 return false;
494 }
495
496 $success = true;
497
498 foreach ($this->getOrderedDatabaseUpdates() as $orderedUpdate) {
499 if (version_compare($currentDbVersion, $orderedUpdate->getVersion(), '<') === true) {
500 $success = $success && $orderedUpdate->update();
501 }
502 }
503
504 if ($success === true) {
505 $this->wordpress->updateOption('uam_db_version', UserAccessManager::DB_VERSION);
506 }
507
508 return $success;
509 }
510
511 /**
512 * Removes the tables.
513 * @throws MissingColumnsException
514 */
515 public function removeTables()
516 {
517 foreach ($this->getTables() as $table) {
518 $dropQuery = "DROP TABLE IF EXISTS `{$table->getName()}`";
519 $this->database->query($dropQuery);
520 }
521 }
522 }
523