| @@ -10,8 +10,10 @@ | ||
| 10 | 10 | private array $messagesNoHtml = []; |
| 11 | 11 | |
| 12 | 12 | public function checkDBAjax(): void |
| 13 | 13 | { |
| 14 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 15 | + | |
| 14 | 16 | $this->checkDB(); |
| 15 | 17 | |
| 16 | 18 | if (empty($this->messagesNoHtml)) { |
| 17 | 19 | echo '<i class="acymicon-check-circle acym__color__green"></i>'; |
| @@ -17,9 +19,9 @@ | ||
| 17 | 19 | echo '<i class="acymicon-check-circle acym__color__green"></i>'; |
| 18 | 20 | } else { |
| 19 | 21 | $nbMessages = count($this->messagesNoHtml); |
| 20 | 22 | foreach ($this->messagesNoHtml as $i => $oneMsg) { |
| 21 | - echo '<span style="color:'.$oneMsg['color'].'">'.$oneMsg['msg'].'</span>'; | |
| 23 | + echo '<span style="color:'.esc_attr($oneMsg['color']).'">'.esc_html($oneMsg['msg']).'</span>'; | |
| 22 | 24 | |
| 23 | 25 | if ($i < $nbMessages) { |
| 24 | 26 | echo '<br />'; |
| 25 | 27 | } |
| @@ -28,20 +30,31 @@ | ||
| 28 | 30 | |
| 29 | 31 | exit; |
| 30 | 32 | } |
| 31 | 33 | |
| 34 | + /** | |
| 35 | + * Check database integrity button in the security tab | |
| 36 | + */ | |
| 32 | 37 | public function checkDB(bool $fromConfiguration = true): array |
| 33 | 38 | { |
| 39 | + // Get the structure that the AcyMailing tables should have in the database | |
| 34 | 40 | $correctTablesStructure = $this->getCorrectTablesStructure(); |
| 41 | + // Get the current structure of the AcyMailing tables and tries to repair/create them if needed | |
| 35 | 42 | $currentTablesStructure = $this->getCurrentTablesStructure($correctTablesStructure); |
| 43 | + // Adds missing columns in AcyMailing tables and missing indexes / primary keys / constraints on the tables | |
| 36 | 44 | $this->fixCurrentStructure($correctTablesStructure, $currentTablesStructure); |
| 37 | 45 | |
| 46 | + // Clean the duplicates in the acym_url table, caused by a bug before the 12/04/19 | |
| 38 | 47 | $this->cleanDuplicatedUrls($fromConfiguration); |
| 48 | + // Fills the key column in the users table when missing | |
| 39 | 49 | $this->addMissingUserKeys(); |
| 40 | 50 | |
| 41 | 51 | return $this->messagesNoHtml; |
| 42 | 52 | } |
| 43 | 53 | |
| 54 | + /** | |
| 55 | + * Returns the structure that the AcyMailing tables should have in the database | |
| 56 | + */ | |
| 44 | 57 | private function getCorrectTablesStructure(): array |
| 45 | 58 | { |
| 46 | 59 | $correctTablesStructure = [ |
| 47 | 60 | 'structure' => [], |
| @@ -52,8 +65,9 @@ | ||
| 52 | 65 | |
| 53 | 66 | $queries = file_get_contents(ACYM_BACK.'tables.sql'); |
| 54 | 67 | $tables = explode('CREATE TABLE IF NOT EXISTS ', $queries); |
| 55 | 68 | |
| 69 | + // For each table, get its name, its column names and its indexes / primary key | |
| 56 | 70 | foreach ($tables as $oneTable) { |
| 57 | 71 | if (strpos($oneTable, '`#__') !== 0) { |
| 58 | 72 | continue; |
| 59 | 73 | } |
| @@ -69,8 +83,9 @@ | ||
| 69 | 83 | continue; |
| 70 | 84 | } |
| 71 | 85 | $oneField = rtrim(trim($oneField), ','); |
| 72 | 86 | |
| 87 | + // Find the column names and remember them | |
| 73 | 88 | if (substr($oneField, 0, 1) === '`') { |
| 74 | 89 | $columnName = substr($oneField, 1, strpos($oneField, '`', 1) - 1); |
| 75 | 90 | $correctTablesStructure['structure'][$tableName][$columnName] = trim($oneField, ','); |
| 76 | 91 | continue; |
| @@ -75,8 +90,9 @@ | ||
| 75 | 90 | $correctTablesStructure['structure'][$tableName][$columnName] = trim($oneField, ','); |
| 76 | 91 | continue; |
| 77 | 92 | } |
| 78 | 93 | |
| 94 | + // Remember the primary key and indexes of the table | |
| 79 | 95 | if (strpos($oneField, 'PRIMARY KEY') === 0) { |
| 80 | 96 | $correctTablesStructure['indexes'][$tableName]['PRIMARY'] = $oneField; |
| 81 | 97 | } elseif (strpos($oneField, 'INDEX') === 0) { |
| 82 | 98 | $firstBackquotePos = strpos($oneField, '`'); |
| @@ -102,8 +118,11 @@ | ||
| 102 | 118 | |
| 103 | 119 | return $correctTablesStructure; |
| 104 | 120 | } |
| 105 | 121 | |
| 122 | + /** | |
| 123 | + * Returns the current structure of the AcyMailing tables and tries to repair/create them if needed | |
| 124 | + */ | |
| 106 | 125 | private function getCurrentTablesStructure(array $correctTablesStructure): array |
| 107 | 126 | { |
| 108 | 127 | $currentTablesStructure = []; |
| 109 | 128 | |
| @@ -120,9 +139,10 @@ | ||
| 120 | 139 | } |
| 121 | 140 | continue; |
| 122 | 141 | } |
| 123 | 142 | |
| 124 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 143 | + // We didn't get the columns, the table crashed or doesn't exist | |
| 144 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 125 | 145 | $this->messagesNoHtml[] = [ |
| 126 | 146 | 'error' => false, |
| 127 | 147 | 'color' => 'blue', |
| 128 | 148 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_LOAD_COLUMNS_ERROR', $oneTableName, $errorMessage), |
| @@ -135,9 +155,9 @@ | ||
| 135 | 155 | $isError = null; |
| 136 | 156 | } |
| 137 | 157 | |
| 138 | 158 | if ($isError === null) { |
| 139 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 159 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 140 | 160 | $this->messagesNoHtml[] = [ |
| 141 | 161 | 'error' => true, |
| 142 | 162 | 'color' => 'red', |
| 143 | 163 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_REPAIR_TABLE_ERROR', $oneTableName, $errorMessage), |
| @@ -151,8 +171,9 @@ | ||
| 151 | 171 | } |
| 152 | 172 | continue; |
| 153 | 173 | } else { |
| 154 | 174 | try { |
| 175 | + // Create missing table | |
| 155 | 176 | $isError = acym_query($correctTablesStructure['createTable'][$oneTableName]); |
| 156 | 177 | } catch (\Exception $e) { |
| 157 | 178 | $isError = null; |
| 158 | 179 | } |
| @@ -157,9 +178,9 @@ | ||
| 157 | 178 | $isError = null; |
| 158 | 179 | } |
| 159 | 180 | |
| 160 | 181 | if ($isError === null) { |
| 161 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 182 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 162 | 183 | $this->messagesNoHtml[] = [ |
| 163 | 184 | 'error' => true, |
| 164 | 185 | 'color' => 'red', |
| 165 | 186 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_CREATE_TABLE_ERROR', $oneTableName, $errorMessage), |
| @@ -176,8 +197,11 @@ | ||
| 176 | 197 | |
| 177 | 198 | return $currentTablesStructure; |
| 178 | 199 | } |
| 179 | 200 | |
| 201 | + /** | |
| 202 | + * Adds missing columns in AcyMailing tables and missing indexes / primary keys on the tables | |
| 203 | + */ | |
| 180 | 204 | private function fixCurrentStructure(array $correctTablesStructure, array $currentTablesStructure): void |
| 181 | 205 | { |
| 182 | 206 | foreach ($correctTablesStructure['tableNames'] as $oneTableName) { |
| 183 | 207 | if (empty($currentTablesStructure[$oneTableName])) { |
| @@ -191,8 +215,11 @@ | ||
| 191 | 215 | $this->addMissingTableConstraints($correctTablesStructure['constraints'][$oneTableName], $oneTableName); |
| 192 | 216 | } |
| 193 | 217 | } |
| 194 | 218 | |
| 219 | + /** | |
| 220 | + * Add missing columns in an AcyMailing table | |
| 221 | + */ | |
| 195 | 222 | private function addMissingColumns(array $correctTableColumns, array $currentTableColumnNames, string $oneTableName): void |
| 196 | 223 | { |
| 197 | 224 | $idealColumnNames = array_keys($correctTableColumns); |
| 198 | 225 | $missingColumns = array_diff($idealColumnNames, $currentTableColumnNames); |
| @@ -214,9 +241,9 @@ | ||
| 214 | 241 | $isError = null; |
| 215 | 242 | } |
| 216 | 243 | |
| 217 | 244 | if ($isError === null) { |
| 218 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 245 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 219 | 246 | $this->messagesNoHtml[] = [ |
| 220 | 247 | 'error' => true, |
| 221 | 248 | 'color' => 'red', |
| 222 | 249 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_ADD_COLUMN_ERROR', $oneColumn, $oneTableName, $errorMessage), |
| @@ -253,9 +280,9 @@ | ||
| 253 | 280 | $isError = null; |
| 254 | 281 | } |
| 255 | 282 | |
| 256 | 283 | if ($isError === null) { |
| 257 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 284 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 258 | 285 | $this->messagesNoHtml[] = [ |
| 259 | 286 | 'error' => true, |
| 260 | 287 | 'color' => 'red', |
| 261 | 288 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_EXTRA_COLUMN_ERROR', $oneColumn, $oneTableName, $errorMessage), |
| @@ -303,8 +330,9 @@ | ||
| 303 | 330 | |
| 304 | 331 | if (strlen($defaultValue) === 0) { |
| 305 | 332 | continue; |
| 306 | 333 | } |
| 334 | + // Normalisation of current and expected default values | |
| 307 | 335 | $currentDefault = $currentTableColumns[$oneColumn]->COLUMN_DEFAULT; |
| 308 | 336 | if (!empty($currentDefault)) { |
| 309 | 337 | $currentDefault = trim($currentDefault, "'\""); |
| 310 | 338 | } |
| @@ -327,8 +355,9 @@ | ||
| 327 | 355 | if ($currentDefault === $expectedDefault) { |
| 328 | 356 | continue; |
| 329 | 357 | } |
| 330 | 358 | |
| 359 | + // if current value is surrounded by double quotes, replace them by quotes before comparing | |
| 331 | 360 | if (!empty($currentTableColumns[$oneColumn]->COLUMN_DEFAULT) && substr($currentTableColumns[$oneColumn]->COLUMN_DEFAULT, 0, 1) === '"') { |
| 332 | 361 | $currentTableColumns[$oneColumn]->COLUMN_DEFAULT = '\''.substr($currentTableColumns[$oneColumn]->COLUMN_DEFAULT, 1, -1).'\''; |
| 333 | 362 | } |
| 334 | 363 | |
| @@ -350,9 +379,9 @@ | ||
| 350 | 379 | $isError = null; |
| 351 | 380 | } |
| 352 | 381 | |
| 353 | 382 | if ($isError === null) { |
| 354 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 383 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 355 | 384 | $this->messagesNoHtml[] = [ |
| 356 | 385 | 'error' => true, |
| 357 | 386 | 'color' => 'red', |
| 358 | 387 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_WRONG_DEFAULT_VALUE_ERROR', $oneColumn, $oneTableName, $errorMessage), |
| @@ -366,10 +395,14 @@ | ||
| 366 | 395 | } |
| 367 | 396 | } |
| 368 | 397 | } |
| 369 | 398 | |
| 399 | + /** | |
| 400 | + * Adds the missing indexes / primary keys on an AcyMailing table | |
| 401 | + */ | |
| 370 | 402 | private function addMissingTableKeys(array $correctTableIndexes, string $oneTableName): void |
| 371 | 403 | { |
| 404 | + // Add missing index and primary keys | |
| 372 | 405 | $results = acym_loadObjectList('SHOW INDEX FROM '.$oneTableName, 'Key_name'); |
| 373 | 406 | if (empty($results)) { |
| 374 | 407 | $results = []; |
| 375 | 408 | } |
| @@ -379,8 +412,9 @@ | ||
| 379 | 412 | if (in_array($name, array_keys($results))) { |
| 380 | 413 | continue; |
| 381 | 414 | } |
| 382 | 415 | |
| 416 | + // The index / primary key is missing, add it | |
| 383 | 417 | |
| 384 | 418 | $keyName = $name === 'PRIMARY' ? 'primary key' : 'index '.$name; |
| 385 | 419 | |
| 386 | 420 | $this->messagesNoHtml[] = [ |
| @@ -395,9 +429,9 @@ | ||
| 395 | 429 | $isError = null; |
| 396 | 430 | } |
| 397 | 431 | |
| 398 | 432 | if ($isError === null) { |
| 399 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 433 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 400 | 434 | $this->messagesNoHtml[] = [ |
| 401 | 435 | 'error' => true, |
| 402 | 436 | 'color' => 'red', |
| 403 | 437 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_ADD_INDEX_ERROR', $keyName, $oneTableName, $errorMessage), |
| @@ -411,8 +445,11 @@ | ||
| 411 | 445 | } |
| 412 | 446 | } |
| 413 | 447 | } |
| 414 | 448 | |
| 449 | + /** | |
| 450 | + * Adds or fixes the table's foreign keys | |
| 451 | + */ | |
| 415 | 452 | private function addMissingTableConstraints(array $correctTableConstraints, string $oneTableName): void |
| 416 | 453 | { |
| 417 | 454 | if (empty($correctTableConstraints)) { |
| 418 | 455 | return; |
| @@ -443,8 +480,9 @@ | ||
| 443 | 480 | 'color' => 'blue', |
| 444 | 481 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_WRONG_FOREIGN_KEY', $constraintName, $oneTableName), |
| 445 | 482 | ]; |
| 446 | 483 | |
| 484 | + // The foreign key exists, but it is incorrect. We remove it then add the correct one | |
| 447 | 485 | if (!empty($foreignKeys[$constraintName])) { |
| 448 | 486 | try { |
| 449 | 487 | $isError = acym_query('ALTER TABLE `'.$oneTableName.'` DROP FOREIGN KEY `'.$constraintName.'`'); |
| 450 | 488 | } catch (\Exception $e) { |
| @@ -451,9 +489,9 @@ | ||
| 451 | 489 | $isError = null; |
| 452 | 490 | } |
| 453 | 491 | |
| 454 | 492 | if ($isError === null) { |
| 455 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 493 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 456 | 494 | $this->messagesNoHtml[] = [ |
| 457 | 495 | 'error' => true, |
| 458 | 496 | 'color' => 'red', |
| 459 | 497 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_ADD_FOREIGN_KEY_ERROR', $constraintName, $oneTableName, $errorMessage), |
| @@ -461,8 +499,9 @@ | ||
| 461 | 499 | continue; |
| 462 | 500 | } |
| 463 | 501 | } |
| 464 | 502 | |
| 503 | + // Add the missing foreign key | |
| 465 | 504 | try { |
| 466 | 505 | $isError = acym_query( |
| 467 | 506 | 'ALTER TABLE `'.$oneTableName.'` ADD CONSTRAINT `'.$constraintName.'` FOREIGN KEY (`'.$constraintInfo['column'].'`) REFERENCES `'.$constraintInfo['table'].'` (`'.$constraintInfo['table_column'].'`) ON DELETE NO ACTION ON UPDATE NO ACTION;' |
| 468 | 507 | ); |
| @@ -470,9 +509,9 @@ | ||
| 470 | 509 | $isError = null; |
| 471 | 510 | } |
| 472 | 511 | |
| 473 | 512 | if ($isError === null) { |
| 474 | - $errorMessage = (isset($e) ? $e->getMessage() : substr(strip_tags(acym_getDBError()), 0, 200)); | |
| 513 | + $errorMessage = (isset($e) ? $e->getMessage() : substr(acym_stripTags(acym_getDBError()), 0, 200)); | |
| 475 | 514 | $this->messagesNoHtml[] = [ |
| 476 | 515 | 'error' => true, |
| 477 | 516 | 'color' => 'red', |
| 478 | 517 | 'msg' => acym_translationSprintf('ACYM_CHECKDB_ADD_FOREIGN_KEY_ERROR', $constraintName, $oneTableName, $errorMessage), |
| @@ -488,8 +527,11 @@ | ||
| 488 | 527 | |
| 489 | 528 | acym_query('SET foreign_key_checks = 1'); |
| 490 | 529 | } |
| 491 | 530 | |
| 531 | + /** | |
| 532 | + * Clean the duplicates in the acym_url table, caused by a bug before the 12/04/19 | |
| 533 | + */ | |
| 492 | 534 | private function cleanDuplicatedUrls(bool $fromConfiguration): void |
| 493 | 535 | { |
| 494 | 536 | if (!$fromConfiguration) { |
| 495 | 537 | return; |
| @@ -510,8 +552,9 @@ | ||
| 510 | 552 | 'color' => 'blue', |
| 511 | 553 | 'msg' => acym_translation('ACYM_CHECKDB_DUPLICATED_URLS'), |
| 512 | 554 | ]; |
| 513 | 555 | |
| 556 | + // Make sure we don't reach the max execution time | |
| 514 | 557 | if (empty($maxExecutionTime) || $maxExecutionTime - 20 < 20) { |
| 515 | 558 | $maxExecutionTime = 20; |
| 516 | 559 | } else { |
| 517 | 560 | $maxExecutionTime -= 20; |
| @@ -543,8 +586,11 @@ | ||
| 543 | 586 | ]; |
| 544 | 587 | } |
| 545 | 588 | } |
| 546 | 589 | |
| 590 | + /** | |
| 591 | + * Fills the key column in the users table when missing | |
| 592 | + */ | |
| 547 | 593 | private function addMissingUserKeys(): void |
| 548 | 594 | { |
| 549 | 595 | $userClass = new UserClass(); |
| 550 | 596 | $nbAddedKeys = $userClass->addMissingKeys(); |
| @@ -559,8 +605,14 @@ | ||
| 559 | 605 | } |
| 560 | 606 | |
| 561 | 607 | public function redomigration(): void |
| 562 | 608 | { |
| 609 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 610 | + | |
| 611 | + if (!acym_isAllowed('configuration')) { | |
| 612 | + return; | |
| 613 | + } | |
| 614 | + | |
| 563 | 615 | $this->config->saveConfig(['migration' => 0]); |
| 564 | 616 | acym_redirect(acym_completeLink('dashboard', false, true)); |
| 565 | 617 | } |
| 566 | 618 | |
| @@ -565,8 +617,10 @@ | ||
| 565 | 617 | } |
| 566 | 618 | |
| 567 | 619 | public function scanSiteFiles(): void |
| 568 | 620 | { |
| 621 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 622 | + | |
| 569 | 623 | $maliciousFiles = []; |
| 570 | 624 | $siteFiles = acym_getFiles(ACYM_ROOT, '.', true, true); |
| 571 | 625 | foreach ($siteFiles as $oneFilePath) { |
| 572 | 626 | $lastSlashPos = strrpos($oneFilePath, '/'); |