diff --git a/libraries/classes/Controllers/Database/Operations/CollationController.php b/libraries/classes/Controllers/Database/Operations/CollationController.php new file mode 100644 index 000000000000..212e893324ec --- /dev/null +++ b/libraries/classes/Controllers/Database/Operations/CollationController.php @@ -0,0 +1,112 @@ +operations = $operations; + $this->dbi = $dbi; + } + + public function __invoke(): void + { + global $db, $cfg, $errorUrl; + + if (! $this->response->isAjax()) { + return; + } + + if (empty($_POST['db_collation'])) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', Message::error(__('No collation provided.'))); + + return; + } + + Util::checkParameters(['db']); + + $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database'); + $errorUrl .= Url::getCommon(['db' => $db], '&'); + + if (! $this->hasDatabase()) { + return; + } + + $sql_query = 'ALTER DATABASE ' . Util::backquote($db) + . ' DEFAULT' . Util::getCharsetQueryPart($_POST['db_collation'] ?? ''); + $this->dbi->query($sql_query); + $message = Message::success(); + + /** + * Changes tables charset if requested by the user + */ + if ( + isset($_POST['change_all_tables_collations']) && + $_POST['change_all_tables_collations'] === 'on' + ) { + [$tables] = Util::getDbInfo($db, null); + foreach ($tables as $tableName => $data) { + if ($this->dbi->getTable($db, $tableName)->isView()) { + // Skip views, we can not change the collation of a view. + // issue #15283 + continue; + } + + $sql_query = 'ALTER TABLE ' + . Util::backquote($db) + . '.' + . Util::backquote($tableName) + . ' DEFAULT ' + . Util::getCharsetQueryPart($_POST['db_collation'] ?? ''); + $this->dbi->query($sql_query); + + /** + * Changes columns charset if requested by the user + */ + if ( + ! isset($_POST['change_all_tables_columns_collations']) || + $_POST['change_all_tables_columns_collations'] !== 'on' + ) { + continue; + } + + $this->operations->changeAllColumnsCollation($db, $tableName, $_POST['db_collation']); + } + } + + $this->response->setRequestStatus($message->isSuccess()); + $this->response->addJSON('message', $message); + } +} diff --git a/libraries/classes/Controllers/Database/OperationsController.php b/libraries/classes/Controllers/Database/OperationsController.php index 3d9566a86095..17f148293cdc 100644 --- a/libraries/classes/Controllers/Database/OperationsController.php +++ b/libraries/classes/Controllers/Database/OperationsController.php @@ -68,7 +68,7 @@ public function __construct( $this->dbi = $dbi; } - public function index(): void + public function __invoke(): void { global $cfg, $db, $server, $sql_query, $move, $message, $tables_full, $errorUrl; global $export_sql_plugin, $views, $sqlConstratints, $local_query, $reload, $urlParams, $tables; @@ -354,74 +354,4 @@ public function index(): void 'collations' => $collations, ]); } - - public function collation(): void - { - global $db, $cfg, $errorUrl; - - if (! $this->response->isAjax()) { - return; - } - - if (empty($_POST['db_collation'])) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', Message::error(__('No collation provided.'))); - - return; - } - - Util::checkParameters(['db']); - - $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database'); - $errorUrl .= Url::getCommon(['db' => $db], '&'); - - if (! $this->hasDatabase()) { - return; - } - - $sql_query = 'ALTER DATABASE ' . Util::backquote($db) - . ' DEFAULT' . Util::getCharsetQueryPart($_POST['db_collation'] ?? ''); - $this->dbi->query($sql_query); - $message = Message::success(); - - /** - * Changes tables charset if requested by the user - */ - if ( - isset($_POST['change_all_tables_collations']) && - $_POST['change_all_tables_collations'] === 'on' - ) { - [$tables] = Util::getDbInfo($db, null); - foreach ($tables as $tableName => $data) { - if ($this->dbi->getTable($db, $tableName)->isView()) { - // Skip views, we can not change the collation of a view. - // issue #15283 - continue; - } - - $sql_query = 'ALTER TABLE ' - . Util::backquote($db) - . '.' - . Util::backquote($tableName) - . ' DEFAULT ' - . Util::getCharsetQueryPart($_POST['db_collation'] ?? ''); - $this->dbi->query($sql_query); - - /** - * Changes columns charset if requested by the user - */ - if ( - ! isset($_POST['change_all_tables_columns_collations']) || - $_POST['change_all_tables_columns_collations'] !== 'on' - ) { - continue; - } - - $this->operations->changeAllColumnsCollation($db, $tableName, $_POST['db_collation']); - } - } - - $this->response->setRequestStatus($message->isSuccess()); - $this->response->addJSON('message', $message); - } } diff --git a/libraries/classes/Controllers/Database/Structure/AddPrefixController.php b/libraries/classes/Controllers/Database/Structure/AddPrefixController.php new file mode 100644 index 000000000000..7a7cfadc2931 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/AddPrefixController.php @@ -0,0 +1,34 @@ +response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $params = ['db' => $db]; + foreach ($selected as $selectedValue) { + $params['selected'][] = $selectedValue; + } + + $this->response->disable(); + $this->render('database/structure/add_prefix', ['url_params' => $params]); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/AddPrefixTableController.php b/libraries/classes/Controllers/Database/Structure/AddPrefixTableController.php new file mode 100644 index 000000000000..711a562d6d98 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/AddPrefixTableController.php @@ -0,0 +1,64 @@ +dbi = $dbi; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $message, $sql_query; + + $selected = $_POST['selected'] ?? []; + + $sql_query = ''; + $selectedCount = count($selected); + + for ($i = 0; $i < $selectedCount; $i++) { + $newTableName = $_POST['add_prefix'] . $selected[$i]; + $aQuery = 'ALTER TABLE ' . Util::backquote($selected[$i]) + . ' RENAME ' . Util::backquote($newTableName); + + $sql_query .= $aQuery . ';' . "\n"; + $this->dbi->selectDb($db); + $this->dbi->query($aQuery); + } + + $message = Message::success(); + + if (empty($_POST['message'])) { + $_POST['message'] = $message; + } + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/CentralColumns/AddController.php b/libraries/classes/Controllers/Database/Structure/CentralColumns/AddController.php new file mode 100644 index 000000000000..cb10f368c681 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/CentralColumns/AddController.php @@ -0,0 +1,59 @@ +dbi = $dbi; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $message; + + $selected = $_POST['selected_tbl'] ?? []; + + if (empty($selected)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $centralColumns = new CentralColumns($this->dbi); + $error = $centralColumns->syncUniqueColumns($selected); + + $message = $error instanceof Message ? $error : Message::success(__('Success!')); + + unset($_POST['submit_mult']); + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/CentralColumns/MakeConsistentController.php b/libraries/classes/Controllers/Database/Structure/CentralColumns/MakeConsistentController.php new file mode 100644 index 000000000000..a0dfe49e701a --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/CentralColumns/MakeConsistentController.php @@ -0,0 +1,59 @@ +dbi = $dbi; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $message; + + $selected = $_POST['selected_tbl'] ?? []; + + if (empty($selected)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $centralColumns = new CentralColumns($this->dbi); + $error = $centralColumns->makeConsistentWithList($db, $selected); + + $message = $error instanceof Message ? $error : Message::success(__('Success!')); + + unset($_POST['submit_mult']); + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/CentralColumns/RemoveController.php b/libraries/classes/Controllers/Database/Structure/CentralColumns/RemoveController.php new file mode 100644 index 000000000000..c58785214cd1 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/CentralColumns/RemoveController.php @@ -0,0 +1,59 @@ +dbi = $dbi; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $message; + + $selected = $_POST['selected_tbl'] ?? []; + + if (empty($selected)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $centralColumns = new CentralColumns($this->dbi); + $error = $centralColumns->deleteColumnsFromList($_POST['db'], $selected); + + $message = $error instanceof Message ? $error : Message::success(__('Success!')); + + unset($_POST['submit_mult']); + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/ChangePrefixFormController.php b/libraries/classes/Controllers/Database/Structure/ChangePrefixFormController.php new file mode 100644 index 000000000000..6529585125fc --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/ChangePrefixFormController.php @@ -0,0 +1,43 @@ +response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $route = '/database/structure/replace-prefix'; + if ($submitMult === 'copy_tbl_change_prefix') { + $route = '/database/structure/copy-table-with-prefix'; + } + + $urlParams = ['db' => $db]; + foreach ($selected as $selectedValue) { + $urlParams['selected'][] = $selectedValue; + } + + $this->response->disable(); + $this->render('database/structure/change_prefix_form', [ + 'route' => $route, + 'url_params' => $urlParams, + ]); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/CopyFormController.php b/libraries/classes/Controllers/Database/Structure/CopyFormController.php new file mode 100644 index 000000000000..70ba83100959 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/CopyFormController.php @@ -0,0 +1,45 @@ +response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $urlParams = ['db' => $db]; + foreach ($selected as $selectedValue) { + $urlParams['selected'][] = $selectedValue; + } + + $databasesList = $dblist->databases; + foreach ($databasesList as $key => $databaseName) { + if ($databaseName == $db) { + $databasesList->offsetUnset($key); + break; + } + } + + $this->response->disable(); + $this->render('database/structure/copy_form', [ + 'url_params' => $urlParams, + 'options' => $databasesList->getList(), + ]); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/CopyTableController.php b/libraries/classes/Controllers/Database/Structure/CopyTableController.php new file mode 100644 index 000000000000..7af069693c3e --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/CopyTableController.php @@ -0,0 +1,81 @@ +operations = $operations; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $message; + + $selected = $_POST['selected'] ?? []; + $targetDb = $_POST['target_db'] ?? null; + $selectedCount = count($selected); + + for ($i = 0; $i < $selectedCount; $i++) { + Table::moveCopy( + $db, + $selected[$i], + $targetDb, + $selected[$i], + $_POST['what'], + false, + 'one_table', + isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true' + ); + + if (empty($_POST['adjust_privileges'])) { + continue; + } + + $this->operations->adjustPrivilegesCopyTable( + $db, + $selected[$i], + $targetDb, + $selected[$i] + ); + } + + $message = Message::success(); + + if (empty($_POST['message'])) { + $_POST['message'] = $message; + } + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/CopyTableWithPrefixController.php b/libraries/classes/Controllers/Database/Structure/CopyTableWithPrefixController.php new file mode 100644 index 000000000000..de07cf8f4b6e --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/CopyTableWithPrefixController.php @@ -0,0 +1,67 @@ +structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $message; + + $selected = $_POST['selected'] ?? []; + $fromPrefix = $_POST['from_prefix'] ?? null; + $toPrefix = $_POST['to_prefix'] ?? null; + + $selectedCount = count($selected); + + for ($i = 0; $i < $selectedCount; $i++) { + $current = $selected[$i]; + $newTableName = $toPrefix . mb_substr($current, mb_strlen((string) $fromPrefix)); + + Table::moveCopy( + $db, + $current, + $db, + $newTableName, + 'data', + false, + 'one_table', + isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true' + ); + } + + $message = Message::success(); + + if (empty($_POST['message'])) { + $_POST['message'] = $message; + } + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/DropFormController.php b/libraries/classes/Controllers/Database/Structure/DropFormController.php new file mode 100644 index 000000000000..91793c13736a --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/DropFormController.php @@ -0,0 +1,86 @@ +dbi = $dbi; + } + + public function __invoke(): void + { + global $db; + + $selected = $_POST['selected_tbl'] ?? []; + + if (empty($selected)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $views = $this->dbi->getVirtualTables($db); + + $fullQueryViews = ''; + $fullQuery = ''; + + foreach ($selected as $selectedValue) { + $current = $selectedValue; + if (! empty($views) && in_array($current, $views)) { + $fullQueryViews .= (empty($fullQueryViews) ? 'DROP VIEW ' : ', ') + . Util::backquote(htmlspecialchars($current)); + } else { + $fullQuery .= (empty($fullQuery) ? 'DROP TABLE ' : ', ') + . Util::backquote(htmlspecialchars($current)); + } + } + + if (! empty($fullQuery)) { + $fullQuery .= ';
' . "\n"; + } + + if (! empty($fullQueryViews)) { + $fullQuery .= $fullQueryViews . ';
' . "\n"; + } + + $urlParams = ['db' => $db]; + foreach ($selected as $selectedValue) { + $urlParams['selected'][] = $selectedValue; + } + + foreach ($views as $current) { + $urlParams['views'][] = $current; + } + + $this->render('database/structure/drop_form', [ + 'url_params' => $urlParams, + 'full_query' => $fullQuery, + 'is_foreign_key_check' => ForeignKey::isCheckEnabled(), + ]); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/DropTableController.php b/libraries/classes/Controllers/Database/Structure/DropTableController.php new file mode 100644 index 000000000000..113a918eed29 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/DropTableController.php @@ -0,0 +1,136 @@ +dbi = $dbi; + $this->relationCleanup = $relationCleanup; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $message, $reload, $sql_query; + + $reload = $_POST['reload'] ?? $reload ?? null; + $multBtn = $_POST['mult_btn'] ?? ''; + $selected = $_POST['selected'] ?? []; + + $views = $this->dbi->getVirtualTables($db); + + if ($multBtn !== __('Yes')) { + $message = Message::success(__('No change')); + + if (empty($_POST['message'])) { + $_POST['message'] = Message::success(); + } + + unset($_POST['mult_btn']); + + ($this->structureController)(); + + return; + } + + $defaultFkCheckValue = ForeignKey::handleDisableCheckInit(); + $sql_query = ''; + $sqlQueryViews = ''; + $selectedCount = count($selected); + + for ($i = 0; $i < $selectedCount; $i++) { + $this->relationCleanup->table($db, $selected[$i]); + $current = $selected[$i]; + + if (! empty($views) && in_array($current, $views)) { + $sqlQueryViews .= (empty($sqlQueryViews) ? 'DROP VIEW ' : ', ') . Util::backquote($current); + } else { + $sql_query .= (empty($sql_query) ? 'DROP TABLE ' : ', ') . Util::backquote($current); + } + + $reload = 1; + } + + if (! empty($sql_query)) { + $sql_query .= ';'; + } elseif (! empty($sqlQueryViews)) { + $sql_query = $sqlQueryViews . ';'; + unset($sqlQueryViews); + } + + // Unset cache values for tables count, issue #14205 + if (isset($_SESSION['tmpval'])) { + if (isset($_SESSION['tmpval']['table_limit_offset'])) { + unset($_SESSION['tmpval']['table_limit_offset']); + } + + if (isset($_SESSION['tmpval']['table_limit_offset_db'])) { + unset($_SESSION['tmpval']['table_limit_offset_db']); + } + } + + $this->dbi->selectDb($db); + $result = $this->dbi->tryQuery($sql_query); + + if ($result && ! empty($sqlQueryViews)) { + $sql_query .= ' ' . $sqlQueryViews . ';'; + $result = $this->dbi->tryQuery($sqlQueryViews); + unset($sqlQueryViews); + } + + if (! $result) { + $message = Message::error((string) $this->dbi->getError()); + } + + ForeignKey::handleDisableCheckCleanup($defaultFkCheckValue); + + $message = Message::success(); + + if (empty($_POST['message'])) { + $_POST['message'] = $message; + } + + unset($_POST['mult_btn']); + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/EmptyFormController.php b/libraries/classes/Controllers/Database/Structure/EmptyFormController.php new file mode 100644 index 000000000000..556d64493b10 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/EmptyFormController.php @@ -0,0 +1,44 @@ +response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $fullQuery = ''; + $urlParams = ['db' => $db]; + + foreach ($selected as $selectedValue) { + $fullQuery .= 'TRUNCATE '; + $fullQuery .= Util::backquote(htmlspecialchars($selectedValue)) . ';
'; + $urlParams['selected'][] = $selectedValue; + } + + $this->render('database/structure/empty_form', [ + 'url_params' => $urlParams, + 'full_query' => $fullQuery, + 'is_foreign_key_check' => ForeignKey::isCheckEnabled(), + ]); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/EmptyTableController.php b/libraries/classes/Controllers/Database/Structure/EmptyTableController.php new file mode 100644 index 000000000000..c543eb0090d0 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/EmptyTableController.php @@ -0,0 +1,123 @@ +dbi = $dbi; + $this->relation = $relation; + $this->relationCleanup = $relationCleanup; + $this->operations = $operations; + $this->flash = $flash; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $table, $message, $sql_query; + + $multBtn = $_POST['mult_btn'] ?? ''; + $selected = $_POST['selected'] ?? []; + + if ($multBtn !== __('Yes')) { + $this->flash->addMessage('success', __('No change')); + $this->redirect('/database/structure', ['db' => $db]); + + return; + } + + $defaultFkCheckValue = ForeignKey::handleDisableCheckInit(); + + $sql_query = ''; + $selectedCount = count($selected); + + for ($i = 0; $i < $selectedCount; $i++) { + $aQuery = 'TRUNCATE '; + $aQuery .= Util::backquote($selected[$i]); + + $sql_query .= $aQuery . ';' . "\n"; + $this->dbi->selectDb($db); + $this->dbi->query($aQuery); + } + + if (! empty($_REQUEST['pos'])) { + $sql = new Sql( + $this->dbi, + $this->relation, + $this->relationCleanup, + $this->operations, + new Transformations(), + $this->template + ); + + $_REQUEST['pos'] = $sql->calculatePosForLastPage($db, $table, $_REQUEST['pos']); + } + + ForeignKey::handleDisableCheckCleanup($defaultFkCheckValue); + + $message = Message::success(); + + if (empty($_POST['message'])) { + $_POST['message'] = $message; + } + + unset($_POST['mult_btn']); + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/FavoriteTableController.php b/libraries/classes/Controllers/Database/Structure/FavoriteTableController.php new file mode 100644 index 000000000000..596dc6de9d92 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/FavoriteTableController.php @@ -0,0 +1,193 @@ +relation = $relation; + } + + public function __invoke(): void + { + global $cfg, $db, $errorUrl; + + $parameters = [ + 'favorite_table' => $_REQUEST['favorite_table'] ?? null, + 'favoriteTables' => $_REQUEST['favoriteTables'] ?? null, + 'sync_favorite_tables' => $_REQUEST['sync_favorite_tables'] ?? null, + ]; + + Util::checkParameters(['db']); + + $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database'); + $errorUrl .= Url::getCommon(['db' => $db], '&'); + + if (! $this->hasDatabase() || ! $this->response->isAjax()) { + return; + } + + $favoriteInstance = RecentFavoriteTable::getInstance('favorite'); + if (isset($parameters['favoriteTables'])) { + $favoriteTables = json_decode($parameters['favoriteTables'], true); + } else { + $favoriteTables = []; + } + + // Required to keep each user's preferences separate. + $user = sha1($cfg['Server']['user']); + + // Request for Synchronization of favorite tables. + if (isset($parameters['sync_favorite_tables'])) { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['favoritework']) { + $this->response->addJSON($this->synchronizeFavoriteTables( + $favoriteInstance, + $user, + $favoriteTables + )); + } + + return; + } + + $changes = true; + $favoriteTable = $parameters['favorite_table'] ?? ''; + $alreadyFavorite = $this->checkFavoriteTable($favoriteTable); + + if (isset($_REQUEST['remove_favorite'])) { + if ($alreadyFavorite) { + // If already in favorite list, remove it. + $favoriteInstance->remove($this->db, $favoriteTable); + $alreadyFavorite = false; // for favorite_anchor template + } + } elseif (isset($_REQUEST['add_favorite'])) { + if (! $alreadyFavorite) { + $numTables = count($favoriteInstance->getTables()); + if ($numTables == $cfg['NumFavoriteTables']) { + $changes = false; + } else { + // Otherwise add to favorite list. + $favoriteInstance->add($this->db, $favoriteTable); + $alreadyFavorite = true; // for favorite_anchor template + } + } + } + + $favoriteTables[$user] = $favoriteInstance->getTables(); + + $json = []; + $json['changes'] = $changes; + if (! $changes) { + $json['message'] = $this->template->render('components/error_message', [ + 'msg' => __('Favorite List is full!'), + ]); + $this->response->addJSON($json); + + return; + } + + // Check if current table is already in favorite list. + $favoriteParams = [ + 'db' => $this->db, + 'ajax_request' => true, + 'favorite_table' => $favoriteTable, + ($alreadyFavorite ? 'remove' : 'add') . '_favorite' => true, + ]; + + $json['user'] = $user; + $json['favoriteTables'] = json_encode($favoriteTables); + $json['list'] = $favoriteInstance->getHtmlList(); + $json['anchor'] = $this->template->render('database/structure/favorite_anchor', [ + 'table_name_hash' => md5($favoriteTable), + 'db_table_name_hash' => md5($this->db . '.' . $favoriteTable), + 'fav_params' => $favoriteParams, + 'already_favorite' => $alreadyFavorite, + ]); + + $this->response->addJSON($json); + } + + /** + * Synchronize favorite tables + * + * @param RecentFavoriteTable $favoriteInstance Instance of this class + * @param string $user The user hash + * @param array $favoriteTables Existing favorites + * + * @return array + */ + private function synchronizeFavoriteTables( + RecentFavoriteTable $favoriteInstance, + string $user, + array $favoriteTables + ): array { + $favoriteInstanceTables = $favoriteInstance->getTables(); + + if ( + empty($favoriteInstanceTables) + && isset($favoriteTables[$user]) + ) { + foreach ($favoriteTables[$user] as $key => $value) { + $favoriteInstance->add($value['db'], $value['table']); + } + } + + $favoriteTables[$user] = $favoriteInstance->getTables(); + + $json = [ + 'favoriteTables' => json_encode($favoriteTables), + 'list' => $favoriteInstance->getHtmlList(), + ]; + $serverId = $GLOBALS['server']; + // Set flag when localStorage and pmadb(if present) are in sync. + $_SESSION['tmpval']['favorites_synced'][$serverId] = true; + + return $json; + } + + /** + * Function to check if a table is already in favorite list. + * + * @param string $currentTable current table + */ + private function checkFavoriteTable(string $currentTable): bool + { + // ensure $_SESSION['tmpval']['favoriteTables'] is initialized + RecentFavoriteTable::getInstance('favorite'); + $favoriteTables = $_SESSION['tmpval']['favoriteTables'][$GLOBALS['server']] ?? []; + foreach ($favoriteTables as $value) { + if ($value['db'] == $this->db && $value['table'] == $currentTable) { + return true; + } + } + + return false; + } +} diff --git a/libraries/classes/Controllers/Database/Structure/RealRowCountController.php b/libraries/classes/Controllers/Database/Structure/RealRowCountController.php new file mode 100644 index 000000000000..144614079323 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/RealRowCountController.php @@ -0,0 +1,84 @@ +dbi = $dbi; + } + + public function __invoke(): void + { + global $cfg, $db, $errorUrl; + + $parameters = [ + 'real_row_count_all' => $_REQUEST['real_row_count_all'] ?? null, + 'table' => $_REQUEST['table'] ?? null, + ]; + + Util::checkParameters(['db']); + + $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database'); + $errorUrl .= Url::getCommon(['db' => $db], '&'); + + if (! $this->hasDatabase() || ! $this->response->isAjax()) { + return; + } + + [$tables] = Util::getDbInfo($this->db, '_structure'); + + // If there is a request to update all table's row count. + if (! isset($parameters['real_row_count_all'])) { + // Get the real row count for the table. + $realRowCount = (int) $this->dbi + ->getTable($this->db, (string) $parameters['table']) + ->getRealRowCountTable(); + // Format the number. + $realRowCount = Util::formatNumber($realRowCount, 0); + + $this->response->addJSON(['real_row_count' => $realRowCount]); + + return; + } + + // Array to store the results. + $realRowCountAll = []; + // Iterate over each table and fetch real row count. + foreach ($tables as $table) { + $rowCount = $this->dbi + ->getTable($this->db, $table['TABLE_NAME']) + ->getRealRowCountTable(); + $realRowCountAll[] = [ + 'table' => $table['TABLE_NAME'], + 'row_count' => $rowCount, + ]; + } + + $this->response->addJSON(['real_row_count_all' => json_encode($realRowCountAll)]); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/ReplacePrefixController.php b/libraries/classes/Controllers/Database/Structure/ReplacePrefixController.php new file mode 100644 index 000000000000..2ac587aaa9c9 --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/ReplacePrefixController.php @@ -0,0 +1,76 @@ +dbi = $dbi; + $this->structureController = $structureController; + } + + public function __invoke(): void + { + global $db, $message, $sql_query; + + $selected = $_POST['selected'] ?? []; + $fromPrefix = $_POST['from_prefix'] ?? ''; + $toPrefix = $_POST['to_prefix'] ?? ''; + + $sql_query = ''; + $selectedCount = count($selected); + + for ($i = 0; $i < $selectedCount; $i++) { + $current = $selected[$i]; + $subFromPrefix = mb_substr($current, 0, mb_strlen((string) $fromPrefix)); + + if ($subFromPrefix === $fromPrefix) { + $newTableName = $toPrefix . mb_substr($current, mb_strlen((string) $fromPrefix)); + } else { + $newTableName = $current; + } + + $aQuery = 'ALTER TABLE ' . Util::backquote($selected[$i]) + . ' RENAME ' . Util::backquote($newTableName); + + $sql_query .= $aQuery . ';' . "\n"; + $this->dbi->selectDb($db); + $this->dbi->query($aQuery); + } + + $message = Message::success(); + + if (empty($_POST['message'])) { + $_POST['message'] = $message; + } + + ($this->structureController)(); + } +} diff --git a/libraries/classes/Controllers/Database/Structure/ShowCreateController.php b/libraries/classes/Controllers/Database/Structure/ShowCreateController.php new file mode 100644 index 000000000000..f148316e947f --- /dev/null +++ b/libraries/classes/Controllers/Database/Structure/ShowCreateController.php @@ -0,0 +1,69 @@ +dbi = $dbi; + } + + public function __invoke(): void + { + $selected = $_POST['selected_tbl'] ?? []; + + if (empty($selected)) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', __('No table selected.')); + + return; + } + + $tables = $this->getShowCreateTables($selected); + + $showCreate = $this->template->render('database/structure/show_create', ['tables' => $tables]); + + $this->response->addJSON('message', $showCreate); + } + + /** + * @param string[] $selected Selected tables. + * + * @return array>> + */ + private function getShowCreateTables(array $selected): array + { + $tables = ['tables' => [], 'views' => []]; + + foreach ($selected as $table) { + $object = $this->dbi->getTable($this->db, $table); + + $tables[$object->isView() ? 'views' : 'tables'][] = [ + 'name' => Core::mimeDefaultFunction($table), + 'show_create' => Core::mimeDefaultFunction($object->showCreate()), + ]; + } + + return $tables; + } +} diff --git a/libraries/classes/Controllers/Database/StructureController.php b/libraries/classes/Controllers/Database/StructureController.php index 00b65caeaf3d..3bbd1e5b70c2 100644 --- a/libraries/classes/Controllers/Database/StructureController.php +++ b/libraries/classes/Controllers/Database/StructureController.php @@ -7,12 +7,9 @@ use PhpMyAdmin\Charsets; use PhpMyAdmin\CheckUserPrivileges; use PhpMyAdmin\Config\PageSettings; -use PhpMyAdmin\Core; -use PhpMyAdmin\Database\CentralColumns; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\FlashMessages; use PhpMyAdmin\Html\Generator; -use PhpMyAdmin\Message; use PhpMyAdmin\Operations; use PhpMyAdmin\RecentFavoriteTable; use PhpMyAdmin\Relation; @@ -21,15 +18,11 @@ use PhpMyAdmin\ReplicationInfo; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Sanitize; -use PhpMyAdmin\Sql; use PhpMyAdmin\StorageEngine; -use PhpMyAdmin\Table; use PhpMyAdmin\Template; use PhpMyAdmin\Tracker; -use PhpMyAdmin\Transformations; use PhpMyAdmin\Url; use PhpMyAdmin\Util; -use PhpMyAdmin\Utils\ForeignKey; use function __; use function array_search; @@ -39,15 +32,11 @@ use function implode; use function in_array; use function is_string; -use function json_decode; -use function json_encode; use function max; -use function mb_strlen; use function mb_substr; use function md5; use function preg_match; use function preg_quote; -use function sha1; use function sprintf; use function str_replace; use function strlen; @@ -151,7 +140,7 @@ private function getDatabaseInfo(string $subPart): void $this->isShowStats = $isShowStats; } - public function index(): void + public function __invoke(): void { global $cfg, $db, $errorUrl; @@ -235,198 +224,6 @@ public function index(): void ]); } - public function addRemoveFavoriteTablesAction(): void - { - global $cfg, $db, $errorUrl; - - $parameters = [ - 'favorite_table' => $_REQUEST['favorite_table'] ?? null, - 'favoriteTables' => $_REQUEST['favoriteTables'] ?? null, - 'sync_favorite_tables' => $_REQUEST['sync_favorite_tables'] ?? null, - ]; - - Util::checkParameters(['db']); - - $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database'); - $errorUrl .= Url::getCommon(['db' => $db], '&'); - - if (! $this->hasDatabase() || ! $this->response->isAjax()) { - return; - } - - $favoriteInstance = RecentFavoriteTable::getInstance('favorite'); - if (isset($parameters['favoriteTables'])) { - $favoriteTables = json_decode($parameters['favoriteTables'], true); - } else { - $favoriteTables = []; - } - - // Required to keep each user's preferences separate. - $user = sha1($cfg['Server']['user']); - - // Request for Synchronization of favorite tables. - if (isset($parameters['sync_favorite_tables'])) { - $cfgRelation = $this->relation->getRelationsParam(); - if ($cfgRelation['favoritework']) { - $this->response->addJSON($this->synchronizeFavoriteTables( - $favoriteInstance, - $user, - $favoriteTables - )); - } - - return; - } - - $changes = true; - $favoriteTable = $parameters['favorite_table'] ?? ''; - $alreadyFavorite = $this->checkFavoriteTable($favoriteTable); - - if (isset($_REQUEST['remove_favorite'])) { - if ($alreadyFavorite) { - // If already in favorite list, remove it. - $favoriteInstance->remove($this->db, $favoriteTable); - $alreadyFavorite = false; // for favorite_anchor template - } - } elseif (isset($_REQUEST['add_favorite'])) { - if (! $alreadyFavorite) { - $numTables = count($favoriteInstance->getTables()); - if ($numTables == $cfg['NumFavoriteTables']) { - $changes = false; - } else { - // Otherwise add to favorite list. - $favoriteInstance->add($this->db, $favoriteTable); - $alreadyFavorite = true; // for favorite_anchor template - } - } - } - - $favoriteTables[$user] = $favoriteInstance->getTables(); - - $json = []; - $json['changes'] = $changes; - if (! $changes) { - $json['message'] = $this->template->render('components/error_message', [ - 'msg' => __('Favorite List is full!'), - ]); - $this->response->addJSON($json); - - return; - } - - // Check if current table is already in favorite list. - $favoriteParams = [ - 'db' => $this->db, - 'ajax_request' => true, - 'favorite_table' => $favoriteTable, - ($alreadyFavorite ? 'remove' : 'add') . '_favorite' => true, - ]; - - $json['user'] = $user; - $json['favoriteTables'] = json_encode($favoriteTables); - $json['list'] = $favoriteInstance->getHtmlList(); - $json['anchor'] = $this->template->render('database/structure/favorite_anchor', [ - 'table_name_hash' => md5($favoriteTable), - 'db_table_name_hash' => md5($this->db . '.' . $favoriteTable), - 'fav_params' => $favoriteParams, - 'already_favorite' => $alreadyFavorite, - ]); - - $this->response->addJSON($json); - } - - /** - * Handles request for real row count on database level view page. - */ - public function handleRealRowCountRequestAction(): void - { - global $cfg, $db, $errorUrl; - - $parameters = [ - 'real_row_count_all' => $_REQUEST['real_row_count_all'] ?? null, - 'table' => $_REQUEST['table'] ?? null, - ]; - - Util::checkParameters(['db']); - - $errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database'); - $errorUrl .= Url::getCommon(['db' => $db], '&'); - - if (! $this->hasDatabase() || ! $this->response->isAjax()) { - return; - } - - // If there is a request to update all table's row count. - if (! isset($parameters['real_row_count_all'])) { - // Get the real row count for the table. - $realRowCount = (int) $this->dbi - ->getTable($this->db, (string) $parameters['table']) - ->getRealRowCountTable(); - // Format the number. - $realRowCount = Util::formatNumber($realRowCount, 0); - - $this->response->addJSON(['real_row_count' => $realRowCount]); - - return; - } - - // Array to store the results. - $realRowCountAll = []; - // Iterate over each table and fetch real row count. - foreach ($this->tables as $table) { - $rowCount = $this->dbi - ->getTable($this->db, $table['TABLE_NAME']) - ->getRealRowCountTable(); - $realRowCountAll[] = [ - 'table' => $table['TABLE_NAME'], - 'row_count' => $rowCount, - ]; - } - - $this->response->addJSON(['real_row_count_all' => json_encode($realRowCountAll)]); - } - - public function copyTable(): void - { - global $db, $message; - - $selected = $_POST['selected'] ?? []; - $targetDb = $_POST['target_db'] ?? null; - $selectedCount = count($selected); - - for ($i = 0; $i < $selectedCount; $i++) { - Table::moveCopy( - $db, - $selected[$i], - $targetDb, - $selected[$i], - $_POST['what'], - false, - 'one_table', - isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true' - ); - - if (empty($_POST['adjust_privileges'])) { - continue; - } - - $this->operations->adjustPrivilegesCopyTable( - $db, - $selected[$i], - $targetDb, - $selected[$i] - ); - } - - $message = Message::success(); - - if (empty($_POST['message'])) { - $_POST['message'] = $message; - } - - $this->index(); - } - /** * @param array $replicaInfo */ @@ -892,44 +689,6 @@ protected function getReplicationStatus($replicaInfo, string $table): array ]; } - /** - * Synchronize favorite tables - * - * @param RecentFavoriteTable $favoriteInstance Instance of this class - * @param string $user The user hash - * @param array $favoriteTables Existing favorites - * - * @return array - */ - protected function synchronizeFavoriteTables( - RecentFavoriteTable $favoriteInstance, - string $user, - array $favoriteTables - ): array { - $favoriteInstanceTables = $favoriteInstance->getTables(); - - if ( - empty($favoriteInstanceTables) - && isset($favoriteTables[$user]) - ) { - foreach ($favoriteTables[$user] as $key => $value) { - $favoriteInstance->add($value['db'], $value['table']); - } - } - - $favoriteTables[$user] = $favoriteInstance->getTables(); - - $json = [ - 'favoriteTables' => json_encode($favoriteTables), - 'list' => $favoriteInstance->getHtmlList(), - ]; - $serverId = $GLOBALS['server']; - // Set flag when localStorage and pmadb(if present) are in sync. - $_SESSION['tmpval']['favorites_synced'][$serverId] = true; - - return $json; - } - /** * Function to check if a table is already in favorite list. * @@ -1240,524 +999,4 @@ protected function getValuesForMroongaTable( $sumSize, ]; } - - public function showCreate(): void - { - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $tables = $this->getShowCreateTables($selected); - - $showCreate = $this->template->render('database/structure/show_create', ['tables' => $tables]); - - $this->response->addJSON('message', $showCreate); - } - - /** - * @param string[] $selected Selected tables. - * - * @return array>> - */ - private function getShowCreateTables(array $selected): array - { - $tables = ['tables' => [], 'views' => []]; - - foreach ($selected as $table) { - $object = $this->dbi->getTable($this->db, $table); - - $tables[$object->isView() ? 'views' : 'tables'][] = [ - 'name' => Core::mimeDefaultFunction($table), - 'show_create' => Core::mimeDefaultFunction($object->showCreate()), - ]; - } - - return $tables; - } - - public function copyForm(): void - { - global $db, $dblist; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $urlParams = ['db' => $db]; - foreach ($selected as $selectedValue) { - $urlParams['selected'][] = $selectedValue; - } - - $databasesList = $dblist->databases; - foreach ($databasesList as $key => $databaseName) { - if ($databaseName == $db) { - $databasesList->offsetUnset($key); - break; - } - } - - $this->response->disable(); - $this->render('database/structure/copy_form', [ - 'url_params' => $urlParams, - 'options' => $databasesList->getList(), - ]); - } - - public function centralColumnsAdd(): void - { - global $message; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $centralColumns = new CentralColumns($this->dbi); - $error = $centralColumns->syncUniqueColumns($selected); - - $message = $error instanceof Message ? $error : Message::success(__('Success!')); - - unset($_POST['submit_mult']); - - $this->index(); - } - - public function centralColumnsMakeConsistent(): void - { - global $db, $message; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $centralColumns = new CentralColumns($this->dbi); - $error = $centralColumns->makeConsistentWithList($db, $selected); - - $message = $error instanceof Message ? $error : Message::success(__('Success!')); - - unset($_POST['submit_mult']); - - $this->index(); - } - - public function centralColumnsRemove(): void - { - global $message; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $centralColumns = new CentralColumns($this->dbi); - $error = $centralColumns->deleteColumnsFromList($_POST['db'], $selected); - - $message = $error instanceof Message ? $error : Message::success(__('Success!')); - - unset($_POST['submit_mult']); - - $this->index(); - } - - public function addPrefix(): void - { - global $db; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $params = ['db' => $db]; - foreach ($selected as $selectedValue) { - $params['selected'][] = $selectedValue; - } - - $this->response->disable(); - $this->render('database/structure/add_prefix', ['url_params' => $params]); - } - - public function changePrefixForm(): void - { - global $db; - - $selected = $_POST['selected_tbl'] ?? []; - $submitMult = $_POST['submit_mult'] ?? ''; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $route = '/database/structure/replace-prefix'; - if ($submitMult === 'copy_tbl_change_prefix') { - $route = '/database/structure/copy-table-with-prefix'; - } - - $urlParams = ['db' => $db]; - foreach ($selected as $selectedValue) { - $urlParams['selected'][] = $selectedValue; - } - - $this->response->disable(); - $this->render('database/structure/change_prefix_form', [ - 'route' => $route, - 'url_params' => $urlParams, - ]); - } - - public function dropForm(): void - { - global $db; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $views = $this->dbi->getVirtualTables($db); - - $fullQueryViews = ''; - $fullQuery = ''; - - foreach ($selected as $selectedValue) { - $current = $selectedValue; - if (! empty($views) && in_array($current, $views)) { - $fullQueryViews .= (empty($fullQueryViews) ? 'DROP VIEW ' : ', ') - . Util::backquote(htmlspecialchars($current)); - } else { - $fullQuery .= (empty($fullQuery) ? 'DROP TABLE ' : ', ') - . Util::backquote(htmlspecialchars($current)); - } - } - - if (! empty($fullQuery)) { - $fullQuery .= ';
' . "\n"; - } - - if (! empty($fullQueryViews)) { - $fullQuery .= $fullQueryViews . ';
' . "\n"; - } - - $urlParams = ['db' => $db]; - foreach ($selected as $selectedValue) { - $urlParams['selected'][] = $selectedValue; - } - - foreach ($views as $current) { - $urlParams['views'][] = $current; - } - - $this->render('database/structure/drop_form', [ - 'url_params' => $urlParams, - 'full_query' => $fullQuery, - 'is_foreign_key_check' => ForeignKey::isCheckEnabled(), - ]); - } - - public function emptyForm(): void - { - global $db; - - $selected = $_POST['selected_tbl'] ?? []; - - if (empty($selected)) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', __('No table selected.')); - - return; - } - - $fullQuery = ''; - $urlParams = ['db' => $db]; - - foreach ($selected as $selectedValue) { - $fullQuery .= 'TRUNCATE '; - $fullQuery .= Util::backquote(htmlspecialchars($selectedValue)) . ';
'; - $urlParams['selected'][] = $selectedValue; - } - - $this->render('database/structure/empty_form', [ - 'url_params' => $urlParams, - 'full_query' => $fullQuery, - 'is_foreign_key_check' => ForeignKey::isCheckEnabled(), - ]); - } - - public function dropTable(): void - { - global $db, $message, $reload, $sql_query; - - $reload = $_POST['reload'] ?? $reload ?? null; - $multBtn = $_POST['mult_btn'] ?? ''; - $selected = $_POST['selected'] ?? []; - - $views = $this->dbi->getVirtualTables($db); - - if ($multBtn !== __('Yes')) { - $message = Message::success(__('No change')); - - if (empty($_POST['message'])) { - $_POST['message'] = Message::success(); - } - - unset($_POST['mult_btn']); - - $this->index(); - - return; - } - - $defaultFkCheckValue = ForeignKey::handleDisableCheckInit(); - $sql_query = ''; - $sqlQueryViews = ''; - $selectedCount = count($selected); - - for ($i = 0; $i < $selectedCount; $i++) { - $this->relationCleanup->table($db, $selected[$i]); - $current = $selected[$i]; - - if (! empty($views) && in_array($current, $views)) { - $sqlQueryViews .= (empty($sqlQueryViews) ? 'DROP VIEW ' : ', ') . Util::backquote($current); - } else { - $sql_query .= (empty($sql_query) ? 'DROP TABLE ' : ', ') . Util::backquote($current); - } - - $reload = 1; - } - - if (! empty($sql_query)) { - $sql_query .= ';'; - } elseif (! empty($sqlQueryViews)) { - $sql_query = $sqlQueryViews . ';'; - unset($sqlQueryViews); - } - - // Unset cache values for tables count, issue #14205 - if (isset($_SESSION['tmpval'])) { - if (isset($_SESSION['tmpval']['table_limit_offset'])) { - unset($_SESSION['tmpval']['table_limit_offset']); - } - - if (isset($_SESSION['tmpval']['table_limit_offset_db'])) { - unset($_SESSION['tmpval']['table_limit_offset_db']); - } - } - - $this->dbi->selectDb($db); - $result = $this->dbi->tryQuery($sql_query); - - if ($result && ! empty($sqlQueryViews)) { - $sql_query .= ' ' . $sqlQueryViews . ';'; - $result = $this->dbi->tryQuery($sqlQueryViews); - unset($sqlQueryViews); - } - - if (! $result) { - $message = Message::error((string) $this->dbi->getError()); - } - - ForeignKey::handleDisableCheckCleanup($defaultFkCheckValue); - - $message = Message::success(); - - if (empty($_POST['message'])) { - $_POST['message'] = $message; - } - - unset($_POST['mult_btn']); - - $this->index(); - } - - public function emptyTable(): void - { - global $db, $table, $message, $sql_query; - - $multBtn = $_POST['mult_btn'] ?? ''; - $selected = $_POST['selected'] ?? []; - - if ($multBtn !== __('Yes')) { - $this->flash->addMessage('success', __('No change')); - $this->redirect('/database/structure', ['db' => $db]); - - return; - } - - $defaultFkCheckValue = ForeignKey::handleDisableCheckInit(); - - $sql_query = ''; - $selectedCount = count($selected); - - for ($i = 0; $i < $selectedCount; $i++) { - $aQuery = 'TRUNCATE '; - $aQuery .= Util::backquote($selected[$i]); - - $sql_query .= $aQuery . ';' . "\n"; - $this->dbi->selectDb($db); - $this->dbi->query($aQuery); - } - - if (! empty($_REQUEST['pos'])) { - $sql = new Sql( - $this->dbi, - $this->relation, - $this->relationCleanup, - $this->operations, - new Transformations(), - $this->template - ); - - $_REQUEST['pos'] = $sql->calculatePosForLastPage($db, $table, $_REQUEST['pos']); - } - - ForeignKey::handleDisableCheckCleanup($defaultFkCheckValue); - - $message = Message::success(); - - if (empty($_POST['message'])) { - $_POST['message'] = $message; - } - - unset($_POST['mult_btn']); - - $this->index(); - } - - public function addPrefixTable(): void - { - global $db, $message, $sql_query; - - $selected = $_POST['selected'] ?? []; - - $sql_query = ''; - $selectedCount = count($selected); - - for ($i = 0; $i < $selectedCount; $i++) { - $newTableName = $_POST['add_prefix'] . $selected[$i]; - $aQuery = 'ALTER TABLE ' . Util::backquote($selected[$i]) - . ' RENAME ' . Util::backquote($newTableName); - - $sql_query .= $aQuery . ';' . "\n"; - $this->dbi->selectDb($db); - $this->dbi->query($aQuery); - } - - $message = Message::success(); - - if (empty($_POST['message'])) { - $_POST['message'] = $message; - } - - $this->index(); - } - - public function replacePrefix(): void - { - global $db, $message, $sql_query; - - $selected = $_POST['selected'] ?? []; - $fromPrefix = $_POST['from_prefix'] ?? ''; - $toPrefix = $_POST['to_prefix'] ?? ''; - - $sql_query = ''; - $selectedCount = count($selected); - - for ($i = 0; $i < $selectedCount; $i++) { - $current = $selected[$i]; - $subFromPrefix = mb_substr($current, 0, mb_strlen((string) $fromPrefix)); - - if ($subFromPrefix === $fromPrefix) { - $newTableName = $toPrefix . mb_substr( - $current, - mb_strlen((string) $fromPrefix) - ); - } else { - $newTableName = $current; - } - - $aQuery = 'ALTER TABLE ' . Util::backquote($selected[$i]) - . ' RENAME ' . Util::backquote($newTableName); - - $sql_query .= $aQuery . ';' . "\n"; - $this->dbi->selectDb($db); - $this->dbi->query($aQuery); - } - - $message = Message::success(); - - if (empty($_POST['message'])) { - $_POST['message'] = $message; - } - - $this->index(); - } - - public function copyTableWithPrefix(): void - { - global $db, $message; - - $selected = $_POST['selected'] ?? []; - $fromPrefix = $_POST['from_prefix'] ?? null; - $toPrefix = $_POST['to_prefix'] ?? null; - - $selectedCount = count($selected); - - for ($i = 0; $i < $selectedCount; $i++) { - $current = $selected[$i]; - $newTableName = $toPrefix . mb_substr($current, mb_strlen((string) $fromPrefix)); - - Table::moveCopy( - $db, - $current, - $db, - $newTableName, - 'data', - false, - 'one_table', - isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true' - ); - } - - $message = Message::success(); - - if (empty($_POST['message'])) { - $_POST['message'] = $message; - } - - $this->index(); - } } diff --git a/libraries/classes/Controllers/Export/CheckTimeOutController.php b/libraries/classes/Controllers/Export/CheckTimeOutController.php new file mode 100644 index 000000000000..10da31508313 --- /dev/null +++ b/libraries/classes/Controllers/Export/CheckTimeOutController.php @@ -0,0 +1,24 @@ +response->setAjax(true); + + if (isset($_SESSION['pma_export_error'])) { + unset($_SESSION['pma_export_error']); + $this->response->addJSON('message', 'timeout'); + + return; + } + + $this->response->addJSON('message', 'success'); + } +} diff --git a/libraries/classes/Controllers/Export/ExportController.php b/libraries/classes/Controllers/Export/ExportController.php index 2c514890e84b..78f28a751417 100644 --- a/libraries/classes/Controllers/Export/ExportController.php +++ b/libraries/classes/Controllers/Export/ExportController.php @@ -718,18 +718,4 @@ public function __invoke(ServerRequest $request): void echo $this->export->dumpBuffer; } - - public function checkTimeOut(): void - { - $this->response->setAjax(true); - - if (isset($_SESSION['pma_export_error'])) { - unset($_SESSION['pma_export_error']); - $this->response->addJSON('message', 'timeout'); - - return; - } - - $this->response->addJSON('message', 'success'); - } } diff --git a/libraries/classes/Controllers/Server/Databases/CreateController.php b/libraries/classes/Controllers/Server/Databases/CreateController.php new file mode 100644 index 000000000000..0c6b1442b0af --- /dev/null +++ b/libraries/classes/Controllers/Server/Databases/CreateController.php @@ -0,0 +1,119 @@ +dbi = $dbi; + } + + public function __invoke(): void + { + global $cfg, $db; + + $params = [ + 'new_db' => $_POST['new_db'] ?? null, + 'db_collation' => $_POST['db_collation'] ?? null, + ]; + + if (! isset($params['new_db']) || mb_strlen($params['new_db']) === 0 || ! $this->response->isAjax()) { + $this->response->addJSON(['message' => Message::error()]); + + return; + } + + // lower_case_table_names=1 `DB` becomes `db` + if ($this->dbi->getLowerCaseNames() === '1') { + $params['new_db'] = mb_strtolower( + $params['new_db'] + ); + } + + /** + * Builds and executes the db creation sql query + */ + $sqlQuery = 'CREATE DATABASE ' . Util::backquote($params['new_db']); + if (! empty($params['db_collation'])) { + [$databaseCharset] = explode('_', $params['db_collation']); + $charsets = Charsets::getCharsets( + $this->dbi, + $cfg['Server']['DisableIS'] + ); + $collations = Charsets::getCollations( + $this->dbi, + $cfg['Server']['DisableIS'] + ); + if ( + array_key_exists($databaseCharset, $charsets) + && array_key_exists($params['db_collation'], $collations[$databaseCharset]) + ) { + $sqlQuery .= ' DEFAULT' + . Util::getCharsetQueryPart($params['db_collation']); + } + } + + $sqlQuery .= ';'; + + $result = $this->dbi->tryQuery($sqlQuery); + + if (! $result) { + // avoid displaying the not-created db name in header or navi panel + $db = ''; + + $message = Message::rawError((string) $this->dbi->getError()); + $json = ['message' => $message]; + + $this->response->setRequestStatus(false); + } else { + $db = $params['new_db']; + + $message = Message::success(__('Database %1$s has been created.')); + $message->addParam($params['new_db']); + + $scriptName = Util::getScriptNameForOption( + $cfg['DefaultTabDatabase'], + 'database' + ); + + $json = [ + 'message' => $message, + 'sql_query' => Generator::getMessage('', $sqlQuery, 'success'), + 'url' => $scriptName . Url::getCommon( + ['db' => $params['new_db']], + ! str_contains($scriptName, '?') ? '?' : '&' + ), + ]; + } + + $this->response->addJSON($json); + } +} diff --git a/libraries/classes/Controllers/Server/Databases/DestroyController.php b/libraries/classes/Controllers/Server/Databases/DestroyController.php new file mode 100644 index 000000000000..4f7d50cf0f1b --- /dev/null +++ b/libraries/classes/Controllers/Server/Databases/DestroyController.php @@ -0,0 +1,122 @@ +dbi = $dbi; + $this->transformations = $transformations; + $this->relationCleanup = $relationCleanup; + } + + public function __invoke(): void + { + global $selected, $errorUrl, $cfg, $dblist, $reload; + + $params = [ + 'drop_selected_dbs' => $_POST['drop_selected_dbs'] ?? null, + 'selected_dbs' => $_POST['selected_dbs'] ?? null, + ]; + /** @var Message|int $message */ + $message = -1; + + if ( + ! isset($params['drop_selected_dbs']) + || ! $this->response->isAjax() + || (! $this->dbi->isSuperUser() && ! $cfg['AllowUserDropDatabase']) + ) { + $message = Message::error(); + $json = ['message' => $message]; + $this->response->setRequestStatus($message->isSuccess()); + $this->response->addJSON($json); + + return; + } + + if (! isset($params['selected_dbs'])) { + $message = Message::error(__('No databases selected.')); + $json = ['message' => $message]; + $this->response->setRequestStatus($message->isSuccess()); + $this->response->addJSON($json); + + return; + } + + $errorUrl = Url::getFromRoute('/server/databases'); + $selected = $_POST['selected_dbs']; + $rebuildDatabaseList = false; + $sqlQuery = ''; + $numberOfDatabases = count($selected); + + for ($i = 0; $i < $numberOfDatabases; $i++) { + $this->relationCleanup->database($selected[$i]); + $aQuery = 'DROP DATABASE ' . Util::backquote($selected[$i]); + $reload = true; + $rebuildDatabaseList = true; + + $sqlQuery .= $aQuery . ';' . "\n"; + $this->dbi->query($aQuery); + $this->transformations->clear($selected[$i]); + } + + if ($rebuildDatabaseList) { + $dblist->databases->build(); + } + + if ($message === -1) { // no error message + $message = Message::success( + _ngettext( + '%1$d database has been dropped successfully.', + '%1$d databases have been dropped successfully.', + $numberOfDatabases + ) + ); + $message->addParam($numberOfDatabases); + } + + $json = []; + if ($message instanceof Message) { + $json = ['message' => $message]; + $this->response->setRequestStatus($message->isSuccess()); + } + + $this->response->addJSON($json); + } +} diff --git a/libraries/classes/Controllers/Server/DatabasesController.php b/libraries/classes/Controllers/Server/DatabasesController.php index 90691103732c..ad5d353971d5 100644 --- a/libraries/classes/Controllers/Server/DatabasesController.php +++ b/libraries/classes/Controllers/Server/DatabasesController.php @@ -10,8 +10,6 @@ use PhpMyAdmin\CheckUserPrivileges; use PhpMyAdmin\Controllers\AbstractController; use PhpMyAdmin\DatabaseInterface; -use PhpMyAdmin\Html\Generator; -use PhpMyAdmin\Message; use PhpMyAdmin\Query\Utilities; use PhpMyAdmin\RelationCleanup; use PhpMyAdmin\ReplicationInfo; @@ -22,14 +20,10 @@ use PhpMyAdmin\Util; use function __; -use function _ngettext; -use function array_key_exists; use function array_keys; use function array_search; use function count; -use function explode; use function in_array; -use function mb_strlen; use function mb_strtolower; use function str_contains; use function strlen; @@ -86,7 +80,7 @@ public function __construct( $checkUserPrivileges->getPrivileges(); } - public function index(): void + public function __invoke(): void { global $cfg, $server, $dblist, $is_create_db_priv; global $db_to_create, $text_dir, $errorUrl; @@ -187,164 +181,6 @@ public function index(): void ]); } - public function create(): void - { - global $cfg, $db; - - $params = [ - 'new_db' => $_POST['new_db'] ?? null, - 'db_collation' => $_POST['db_collation'] ?? null, - ]; - - if (! isset($params['new_db']) || mb_strlen($params['new_db']) === 0 || ! $this->response->isAjax()) { - $this->response->addJSON(['message' => Message::error()]); - - return; - } - - // lower_case_table_names=1 `DB` becomes `db` - if ($this->dbi->getLowerCaseNames() === '1') { - $params['new_db'] = mb_strtolower( - $params['new_db'] - ); - } - - /** - * Builds and executes the db creation sql query - */ - $sqlQuery = 'CREATE DATABASE ' . Util::backquote($params['new_db']); - if (! empty($params['db_collation'])) { - [$databaseCharset] = explode('_', $params['db_collation']); - $charsets = Charsets::getCharsets( - $this->dbi, - $cfg['Server']['DisableIS'] - ); - $collations = Charsets::getCollations( - $this->dbi, - $cfg['Server']['DisableIS'] - ); - if ( - array_key_exists($databaseCharset, $charsets) - && array_key_exists($params['db_collation'], $collations[$databaseCharset]) - ) { - $sqlQuery .= ' DEFAULT' - . Util::getCharsetQueryPart($params['db_collation']); - } - } - - $sqlQuery .= ';'; - - $result = $this->dbi->tryQuery($sqlQuery); - - if (! $result) { - // avoid displaying the not-created db name in header or navi panel - $db = ''; - - $message = Message::rawError((string) $this->dbi->getError()); - $json = ['message' => $message]; - - $this->response->setRequestStatus(false); - } else { - $db = $params['new_db']; - - $message = Message::success(__('Database %1$s has been created.')); - $message->addParam($params['new_db']); - - $scriptName = Util::getScriptNameForOption( - $cfg['DefaultTabDatabase'], - 'database' - ); - - $json = [ - 'message' => $message, - 'sql_query' => Generator::getMessage('', $sqlQuery, 'success'), - 'url' => $scriptName . Url::getCommon( - ['db' => $params['new_db']], - ! str_contains($scriptName, '?') ? '?' : '&' - ), - ]; - } - - $this->response->addJSON($json); - } - - /** - * Handles dropping multiple databases - */ - public function destroy(): void - { - global $selected, $errorUrl, $cfg, $dblist, $reload; - - $params = [ - 'drop_selected_dbs' => $_POST['drop_selected_dbs'] ?? null, - 'selected_dbs' => $_POST['selected_dbs'] ?? null, - ]; - /** @var Message|int $message */ - $message = -1; - - if ( - ! isset($params['drop_selected_dbs']) - || ! $this->response->isAjax() - || (! $this->dbi->isSuperUser() && ! $cfg['AllowUserDropDatabase']) - ) { - $message = Message::error(); - $json = ['message' => $message]; - $this->response->setRequestStatus($message->isSuccess()); - $this->response->addJSON($json); - - return; - } - - if (! isset($params['selected_dbs'])) { - $message = Message::error(__('No databases selected.')); - $json = ['message' => $message]; - $this->response->setRequestStatus($message->isSuccess()); - $this->response->addJSON($json); - - return; - } - - $errorUrl = Url::getFromRoute('/server/databases'); - $selected = $_POST['selected_dbs']; - $rebuildDatabaseList = false; - $sqlQuery = ''; - $numberOfDatabases = count($selected); - - for ($i = 0; $i < $numberOfDatabases; $i++) { - $this->relationCleanup->database($selected[$i]); - $aQuery = 'DROP DATABASE ' . Util::backquote($selected[$i]); - $reload = true; - $rebuildDatabaseList = true; - - $sqlQuery .= $aQuery . ';' . "\n"; - $this->dbi->query($aQuery); - $this->transformations->clear($selected[$i]); - } - - if ($rebuildDatabaseList) { - $dblist->databases->build(); - } - - if ($message === -1) { // no error message - $message = Message::success( - _ngettext( - '%1$d database has been dropped successfully.', - '%1$d databases have been dropped successfully.', - $numberOfDatabases - ) - ); - $message->addParam($numberOfDatabases); - } - - $json = []; - if ($message instanceof Message) { - $json = ['message' => $message]; - $this->response->setRequestStatus($message->isSuccess()); - } - - $this->response->addJSON($json); - } - /** * Extracts parameters sort order and sort by * diff --git a/libraries/routes.php b/libraries/routes.php index 3acb0f863386..6a3f993d0da1 100644 --- a/libraries/routes.php +++ b/libraries/routes.php @@ -20,6 +20,7 @@ use PhpMyAdmin\Controllers\Database\MultiTableQuery\QueryController; use PhpMyAdmin\Controllers\Database\MultiTableQuery\TablesController as MultiTableQueryTablesController; use PhpMyAdmin\Controllers\Database\MultiTableQueryController; +use PhpMyAdmin\Controllers\Database\Operations\CollationController; use PhpMyAdmin\Controllers\Database\OperationsController; use PhpMyAdmin\Controllers\Database\QueryByExampleController; use PhpMyAdmin\Controllers\Database\RoutinesController; @@ -27,11 +28,13 @@ use PhpMyAdmin\Controllers\Database\SqlAutoCompleteController; use PhpMyAdmin\Controllers\Database\SqlController as DatabaseSqlController; use PhpMyAdmin\Controllers\Database\SqlFormatController; +use PhpMyAdmin\Controllers\Database\Structure; use PhpMyAdmin\Controllers\Database\StructureController; use PhpMyAdmin\Controllers\Database\TrackingController; use PhpMyAdmin\Controllers\Database\TriggersController; use PhpMyAdmin\Controllers\DatabaseController; use PhpMyAdmin\Controllers\ErrorReportController; +use PhpMyAdmin\Controllers\Export\CheckTimeOutController; use PhpMyAdmin\Controllers\Export\ExportController; use PhpMyAdmin\Controllers\Export\TablesController; use PhpMyAdmin\Controllers\Export\Template\CreateController as TemplateCreateController; @@ -61,6 +64,8 @@ use PhpMyAdmin\Controllers\SchemaExportController; use PhpMyAdmin\Controllers\Server\BinlogController; use PhpMyAdmin\Controllers\Server\CollationsController; +use PhpMyAdmin\Controllers\Server\Databases\CreateController as DatabasesCreateController; +use PhpMyAdmin\Controllers\Server\Databases\DestroyController; use PhpMyAdmin\Controllers\Server\DatabasesController; use PhpMyAdmin\Controllers\Server\EnginesController; use PhpMyAdmin\Controllers\Server\ExportController as ServerExportController; @@ -145,8 +150,8 @@ $routes->post('/query', QueryController::class); }); $routes->addGroup('/operations', static function (RouteCollector $routes): void { - $routes->addRoute(['GET', 'POST'], '', [OperationsController::class, 'index']); - $routes->post('/collation', [OperationsController::class, 'collation']); + $routes->addRoute(['GET', 'POST'], '', OperationsController::class); + $routes->post('/collation', CollationController::class); }); $routes->addRoute(['GET', 'POST'], '/qbe', QueryByExampleController::class); $routes->addRoute(['GET', 'POST'], '/routines', RoutinesController::class); @@ -157,33 +162,24 @@ $routes->post('/format', SqlFormatController::class); }); $routes->addGroup('/structure', static function (RouteCollector $routes): void { - $routes->addRoute(['GET', 'POST'], '', [StructureController::class, 'index']); - $routes->post('/add-prefix', [StructureController::class, 'addPrefix']); - $routes->post('/add-prefix-table', [StructureController::class, 'addPrefixTable']); - $routes->post('/central-columns-add', [StructureController::class, 'centralColumnsAdd']); - $routes->post('/central-columns-make-consistent', [ - StructureController::class, - 'centralColumnsMakeConsistent', - ]); - $routes->post('/central-columns-remove', [StructureController::class, 'centralColumnsRemove']); - $routes->post('/change-prefix-form', [StructureController::class, 'changePrefixForm']); - $routes->post('/copy-form', [StructureController::class, 'copyForm']); - $routes->post('/copy-table', [StructureController::class, 'copyTable']); - $routes->post('/copy-table-with-prefix', [StructureController::class, 'copyTableWithPrefix']); - $routes->post('/drop-form', [StructureController::class, 'dropForm']); - $routes->post('/drop-table', [StructureController::class, 'dropTable']); - $routes->post('/empty-form', [StructureController::class, 'emptyForm']); - $routes->post('/empty-table', [StructureController::class, 'emptyTable']); - $routes->addRoute(['GET', 'POST'], '/favorite-table', [ - StructureController::class, - 'addRemoveFavoriteTablesAction', - ]); - $routes->addRoute(['GET', 'POST'], '/real-row-count', [ - StructureController::class, - 'handleRealRowCountRequestAction', - ]); - $routes->post('/replace-prefix', [StructureController::class, 'replacePrefix']); - $routes->post('/show-create', [StructureController::class, 'showCreate']); + $routes->addRoute(['GET', 'POST'], '', StructureController::class); + $routes->post('/add-prefix', Structure\AddPrefixController::class); + $routes->post('/add-prefix-table', Structure\AddPrefixTableController::class); + $routes->post('/central-columns-add', Structure\CentralColumns\AddController::class); + $routes->post('/central-columns-make-consistent', Structure\CentralColumns\MakeConsistentController::class); + $routes->post('/central-columns-remove', Structure\CentralColumns\RemoveController::class); + $routes->post('/change-prefix-form', Structure\ChangePrefixFormController::class); + $routes->post('/copy-form', Structure\CopyFormController::class); + $routes->post('/copy-table', Structure\CopyTableController::class); + $routes->post('/copy-table-with-prefix', Structure\CopyTableWithPrefixController::class); + $routes->post('/drop-form', Structure\DropFormController::class); + $routes->post('/drop-table', Structure\DropTableController::class); + $routes->post('/empty-form', Structure\EmptyFormController::class); + $routes->post('/empty-table', Structure\EmptyTableController::class); + $routes->addRoute(['GET', 'POST'], '/favorite-table', Structure\FavoriteTableController::class); + $routes->addRoute(['GET', 'POST'], '/real-row-count', Structure\RealRowCountController::class); + $routes->post('/replace-prefix', Structure\ReplacePrefixController::class); + $routes->post('/show-create', Structure\ShowCreateController::class); }); $routes->addRoute(['GET', 'POST'], '/tracking', TrackingController::class); $routes->addRoute(['GET', 'POST'], '/triggers', TriggersController::class); @@ -192,7 +188,7 @@ $routes->addRoute(['GET', 'POST'], '/error-report', ErrorReportController::class); $routes->addGroup('/export', static function (RouteCollector $routes): void { $routes->addRoute(['GET', 'POST'], '', ExportController::class); - $routes->get('/check-time-out', [ExportController::class, 'checkTimeOut']); + $routes->get('/check-time-out', CheckTimeOutController::class); $routes->post('/tables', TablesController::class); $routes->addGroup('/template', static function (RouteCollector $routes): void { $routes->post('/create', TemplateCreateController::class); @@ -227,9 +223,9 @@ $routes->addRoute(['GET', 'POST'], '/binlog', BinlogController::class); $routes->get('/collations', CollationsController::class); $routes->addGroup('/databases', static function (RouteCollector $routes): void { - $routes->addRoute(['GET', 'POST'], '', [DatabasesController::class, 'index']); - $routes->post('/create', [DatabasesController::class, 'create']); - $routes->post('/destroy', [DatabasesController::class, 'destroy']); + $routes->addRoute(['GET', 'POST'], '', DatabasesController::class); + $routes->post('/create', DatabasesCreateController::class); + $routes->post('/destroy', DestroyController::class); }); $routes->addGroup('/engines', static function (RouteCollector $routes): void { $routes->get('', [EnginesController::class, 'index']); diff --git a/libraries/services_controllers.php b/libraries/services_controllers.php index b6ae3c6ead85..1d23024e1ed1 100644 --- a/libraries/services_controllers.php +++ b/libraries/services_controllers.php @@ -152,6 +152,16 @@ '$dbi' => '@dbi', ], ], + PhpMyAdmin\Controllers\Database\Operations\CollationController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Operations\CollationController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$operations' => '@operations', + '$dbi' => '@dbi', + ], + ], PhpMyAdmin\Controllers\Database\OperationsController::class => [ 'class' => PhpMyAdmin\Controllers\Database\OperationsController::class, 'arguments' => [ @@ -230,6 +240,168 @@ '$db' => '%db%', ], ], + PhpMyAdmin\Controllers\Database\Structure\AddPrefixController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\AddPrefixController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\AddPrefixTableController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\AddPrefixTableController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\CentralColumns\AddController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\CentralColumns\AddController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\CentralColumns\MakeConsistentController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\CentralColumns\MakeConsistentController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\CentralColumns\RemoveController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\CentralColumns\RemoveController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\ChangePrefixFormController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\ChangePrefixFormController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\CopyFormController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\CopyFormController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\CopyTableController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\CopyTableController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$operations' => '@operations', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\CopyTableWithPrefixController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\CopyTableWithPrefixController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\DropFormController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\DropFormController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\DropTableController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\DropTableController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$relationCleanup' => '@relation_cleanup', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\EmptyFormController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\EmptyFormController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\EmptyTableController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\EmptyTableController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$relation' => '@relation', + '$relationCleanup' => '@relation_cleanup', + '$operations' => '@operations', + '$flash' => '@flash', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\FavoriteTableController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\FavoriteTableController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$relation' => '@relation', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\RealRowCountController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\RealRowCountController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + ], + ], + PhpMyAdmin\Controllers\Database\Structure\ReplacePrefixController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\ReplacePrefixController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + '$structureController' => '@' . PhpMyAdmin\Controllers\Database\StructureController::class, + ], + ], + PhpMyAdmin\Controllers\Database\Structure\ShowCreateController::class => [ + 'class' => PhpMyAdmin\Controllers\Database\Structure\ShowCreateController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$db' => '%db%', + '$dbi' => '@dbi', + ], + ], PhpMyAdmin\Controllers\Database\StructureController::class => [ 'class' => PhpMyAdmin\Controllers\Database\StructureController::class, 'arguments' => [ @@ -279,6 +451,13 @@ '$errorHandler' => '@error_handler', ], ], + PhpMyAdmin\Controllers\Export\CheckTimeOutController::class => [ + 'class' => PhpMyAdmin\Controllers\Export\CheckTimeOutController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + ], + ], PhpMyAdmin\Controllers\Export\ExportController::class => [ 'class' => PhpMyAdmin\Controllers\Export\ExportController::class, 'arguments' => [ @@ -523,6 +702,24 @@ '$dbi' => '@dbi', ], ], + PhpMyAdmin\Controllers\Server\Databases\CreateController::class => [ + 'class' => PhpMyAdmin\Controllers\Server\Databases\CreateController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$dbi' => '@dbi', + ], + ], + PhpMyAdmin\Controllers\Server\Databases\DestroyController::class => [ + 'class' => PhpMyAdmin\Controllers\Server\Databases\DestroyController::class, + 'arguments' => [ + '$response' => '@response', + '$template' => '@template', + '$dbi' => '@dbi', + '$transformations' => '@transformations', + '$relationCleanup' => '@relation_cleanup', + ], + ], PhpMyAdmin\Controllers\Server\DatabasesController::class => [ 'class' => PhpMyAdmin\Controllers\Server\DatabasesController::class, 'arguments' => [ diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 16b4ff82ae73..63360a3ad191 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1352,12 +1352,38 @@ $table - - - $_POST['comment'] + + $_POST['db_collation'] $_POST['db_collation'] ?? '' $_POST['db_collation'] ?? '' + $db + $db + $db + $tableName + + + ['db' => $db] + + + $data + $tableName + + + Util::backquote($db) + Util::backquote($db) + Util::backquote($tableName) + + + $_POST['db_collation'] + + + $data + + + + + $_POST['comment'] $_POST['newname'] $_POST['newname'] $_POST['newname'] @@ -1366,14 +1392,9 @@ $db $db $db - $db - $db - $db $message - $tableName - - ['db' => $db] + ['db' => $db] @@ -1382,21 +1403,13 @@ $cfg['Servers'][$server] - - $data + $db $db - $tableName - - Util::backquote($db) - Util::backquote($db) + Util::backquote($db) - Util::backquote($tableName) - - $_POST['db_collation'] - ! $_error ! $_error @@ -1407,9 +1420,6 @@ '' - - $data - @@ -1505,28 +1515,295 @@ $query + + + $params['selected'][] + $selected + $selectedValue + + + + + $db + $selected + $selected[$i] + + + $selected[$i] + + + $selected + + + $_POST['add_prefix'] + Util::backquote($newTableName) + Util::backquote($selected[$i]) + + + + + $selected + + + $selected + + + + + $db + $selected + + + $selected + + + + + $_POST['db'] + $selected + + + $selected + + + + + $selected + $selectedValue + $submitMult + $urlParams['selected'][] + + + + + $databaseName + $databasesList + $key + $selected + $selectedValue + $urlParams['selected'][] + + + getList + offsetUnset + + + $dblist->databases + + + + + $_POST['what'] + $db + $db + $selected + $selected[$i] + $selected[$i] + $selected[$i] + $selected[$i] + $targetDb + + + $selected[$i] + + + $selected + $targetDb + + + + + $current + $current + $db + $db + $selected + + + $selected[$i] + + + $current + $fromPrefix + $selected + $toPrefix + + + $toPrefix + + + + + $current + $current + $db + + + $current + $current + $selected + $selectedValue + $selectedValue + $urlParams['selected'][] + $urlParams['views'][] + + + Util::backquote(htmlspecialchars($current)) + Util::backquote(htmlspecialchars($current)) + + + + + $current + $current + $db + $selected + $selected[$i] + + + $_SESSION['tmpval']['table_limit_offset'] + $_SESSION['tmpval']['table_limit_offset_db'] + $selected[$i] + + + $current + $multBtn + $reload + $result + $result + $selected + + + Util::backquote($current) + Util::backquote($current) + + + + + $selectedValue + + + $selected + $selectedValue + + + Util::backquote(htmlspecialchars($selectedValue)) + + + + + $_REQUEST['pos'] + $db + $db + $selected + $selected[$i] + $table + + + $selected[$i] + + + $multBtn + $selected + + + Util::backquote($selected[$i]) + + + + + $favoriteTable + $favoriteTables + $parameters['favoriteTables'] + $value['db'] + $value['table'] + + + ['db' => $db] + + + $_SESSION['tmpval']['favoriteTables'][$GLOBALS['server']] + $value['db'] + $value['db'] + $value['table'] + $value['table'] + + + $_SESSION['tmpval']['favorites_synced'] + $favoriteTables[$user] + + + $favoriteTable + $favoriteTables + $favoriteTables + $key + $value + $value + + + $key + + + + + $table['TABLE_NAME'] + + + ['db' => $db] + + + $table['TABLE_NAME'] + $table['TABLE_NAME'] + + + $table + + + + + $current + $current + $db + $newTableName + $selected + $selected[$i] + + + $selected[$i] + + + $current + $fromPrefix + $newTableName + $selected + $toPrefix + + + $toPrefix + Util::backquote($newTableName) + Util::backquote($selected[$i]) + + + + + $object->showCreate() + $selected + + + $selected + + $formattedOverhead $formattedSize - - $_POST['db'] + $_POST['master_connection'] ?? null - $_POST['what'] - $_REQUEST['pos'] $checkTime $checkTimeAll $createTime $createTimeAll - $current - $current - $current - $current - $current - $current - $current - $current $currentTable $currentTable $currentTable['Collation'] @@ -1543,25 +1820,9 @@ $currentTable['TABLE_NAME'] $currentTable['TABLE_NAME'] $currentTable['TABLE_NAME'] - $db - $db - $db - $db - $db - $db - $db - $db - $db - $db - $db $dbTable $dbTable - $favoriteTable - $favoriteTables - $newTableName - $object->showCreate() $overheadSize - $parameters['favoriteTables'] $replicaInfo['Do_DB'] $replicaInfo['Do_DB'] $replicaInfo['Do_DB'] @@ -1570,45 +1831,17 @@ $replicaInfo['Ignore_Table'] $replicaInfo['Wild_Do_Table'] $replicaInfo['Wild_Ignore_Table'] - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selectedValue - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] $sumSize - $table $tableIsView - $table['TABLE_NAME'] - $targetDb $truename $updateTime $updateTimeAll - $value['db'] - $value['table'] - - ['db' => $db] - ['db' => $db] + ['db' => $db] - + $_SESSION['tmpval']['favoriteTables'][$GLOBALS['server']] - $_SESSION['tmpval']['table_limit_offset'] - $_SESSION['tmpval']['table_limit_offset_db'] $currentTable['Check_time'] $currentTable['Collation'] $currentTable['Create_time'] @@ -1625,135 +1858,54 @@ $currentTable['TABLE_ROWS'] $currentTable['TABLE_TYPE'] $currentTable['Update_time'] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $selected[$i] - $table['TABLE_NAME'] - $table['TABLE_NAME'] $value['db'] - $value['db'] - $value['table'] $value['table'] - - $_SESSION['tmpval']['favorites_synced'] + $currentTable['TABLE_ROWS'] - $favoriteTables[$user] - + $charset $checkTime $checkTimeAll $createTime $createTimeAll - $current - $current - $current - $current - $current $currentTable $currentTable['Rows'] $currentTable['TABLE_ROWS'] $currentTable['TABLE_ROWS'] - $databaseName - $databasesList $dbTable - $favoriteTable - $favoriteTables $favoriteTables - $fromPrefix - $fromPrefix - $key - $key - $multBtn - $multBtn - $newTableName $overheadSize - $params['selected'][] - $reload - $result - $result $searchDb $searchDoDBInDB $searchDoDBInTruename $searchTable - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selected - $selectedValue - $selectedValue - $selectedValue - $selectedValue - $selectedValue - $selectedValue - $submitMult $sumEntries - $table - $targetDb $this->dbIsSystemSchema $this->isShowStats $this->numTables $this->position $this->tables $this->totalNumTables - $toPrefix - $toPrefix $truename $updateTime $updateTimeAll - $urlParams['selected'][] - $urlParams['selected'][] - $urlParams['selected'][] $urlParams['sort'] $urlParams['sort_order'] - $urlParams['views'][] - $value $value - + getCharset - getList - offsetUnset - - $_POST['add_prefix'] + $currentTable['Data_free'] $currentTable['Data_length'] $currentTable['Data_length'] $currentTable['Data_length'] $currentTable['TABLE_NAME'] $currentTable['TABLE_ROWS'] - $toPrefix - $toPrefix - Util::backquote($current) - Util::backquote($current) - Util::backquote($newTableName) - Util::backquote($newTableName) - Util::backquote($selected[$i]) - Util::backquote($selected[$i]) - Util::backquote($selected[$i]) - Util::backquote(htmlspecialchars($current)) - Util::backquote(htmlspecialchars($current)) - Util::backquote(htmlspecialchars($selectedValue)) - - $dblist->databases - $formattedOverhead $formattedSize @@ -1772,8 +1924,7 @@ $tables $totalNumTables - - $key + $keyname @@ -2369,19 +2520,62 @@ $this->collations[$charset->getName()] - - - $_POST['master_connection'] ?? null + + $collations[$databaseCharset] - $database['DEFAULT_COLLATION_NAME'] - $database['SCHEMA_NAME'] - $dblist->databases $params['db_collation'] $params['db_collation'] $params['db_collation'] $params['new_db'] $params['new_db'] $params['new_db'] + + + ['db' => $params['new_db']] + + + $db + $result + + + Util::backquote($params['new_db']) + + + + + $selected + $selected[$i] + $selected[$i] + $selected[$i] + + + $selected[$i] + $selected[$i] + $selected[$i] + + + $selected + + + build + + + Util::backquote($selected[$i]) + + + $dblist->databases + + + $sqlQuery + $sqlQuery + + + + + $_POST['master_connection'] ?? null + $database['DEFAULT_COLLATION_NAME'] + $database['SCHEMA_NAME'] + $dblist->databases $params['sort_by'] $params['sort_order'] $primaryInfo['Do_DB'] @@ -2390,16 +2584,11 @@ $replicaInfo['Do_DB'] $replicaInfo['Do_DB'] $replicaInfo['Ignore_DB'] - $selected - $selected[$i] - $selected[$i] - $selected[$i] - + ['db' => $database['SCHEMA_NAME']] - ['db' => $params['new_db']] - + $database[$key] $database['DEFAULT_COLLATION_NAME'] $database['SCHEMA_NAME'] @@ -2412,9 +2601,6 @@ $database['SCHEMA_NAME'] $database['SCHEMA_NAME'] $database['SCHEMA_NAME'] - $selected[$i] - $selected[$i] - $selected[$i] $totalStatistics[$key]['raw'] @@ -2425,28 +2611,19 @@ $databases[$database['SCHEMA_NAME']] $databases[$database['SCHEMA_NAME']] - + $database - $db $key $key $key $key - $result - $selected $statistics[$key]['raw'] $totalStatistics[$key]['raw'] - - build - - + $totalStatistics[$key]['raw'] - Util::backquote($params['new_db']) - Util::backquote($selected[$i]) - - $dblist->databases + $dblist->databases @@ -2461,10 +2638,6 @@ 0 - - $sqlQuery - $sqlQuery - @@ -17206,14 +17379,24 @@ include $this->object->defaultSource + + + $json + + + $json['favoriteTables'] + + + $json + + - + $currentTable - $json $result $result - + $currentTable $currentTable $currentTable @@ -17229,7 +17412,6 @@ $currentTable['Data_free'] $currentTable['Rows'] $currentTable['TABLE_ROWS'] - $json['favoriteTables'] $overheadSize $overheadSize $sumSize @@ -17245,8 +17427,7 @@ $currentTable['ENGINE'] $currentTable['ENGINE'] - - $json + $result $result [$currentTable, , , $sumSize] @@ -17279,11 +17460,15 @@ $responseMessage - - + + $actual['message'] $actual['message'] $actual['message'] + + + + $actual['message'] $actual['message'] diff --git a/test/classes/Controllers/Database/Structure/FavoriteTableControllerTest.php b/test/classes/Controllers/Database/Structure/FavoriteTableControllerTest.php new file mode 100644 index 000000000000..31eb2229f422 --- /dev/null +++ b/test/classes/Controllers/Database/Structure/FavoriteTableControllerTest.php @@ -0,0 +1,63 @@ +getMockBuilder(RecentFavoriteTable::class) + ->disableOriginalConstructor() + ->getMock(); + $favoriteInstance->expects($this->exactly(2)) + ->method('getTables') + ->will($this->onConsecutiveCalls([[]], [['db' => 'db', 'table' => 'table']])); + + $class = new ReflectionClass(FavoriteTableController::class); + $method = $class->getMethod('synchronizeFavoriteTables'); + $method->setAccessible(true); + $template = new Template(); + + $controller = new FavoriteTableController( + new ResponseStub(), + $template, + 'db', + new Relation($this->dbi, $template) + ); + + // The user hash for test + $user = 'abcdefg'; + $favoriteTable = [ + $user => [ + [ + 'db' => 'db', + 'table' => 'table', + ], + ], + ]; + + $json = $method->invokeArgs($controller, [$favoriteInstance, $user, $favoriteTable]); + + $this->assertEquals(json_encode($favoriteTable), $json['favoriteTables'] ?? ''); + $this->assertArrayHasKey('list', $json); + } +} diff --git a/test/classes/Controllers/Database/Structure/RealRowCountControllerTest.php b/test/classes/Controllers/Database/Structure/RealRowCountControllerTest.php new file mode 100644 index 000000000000..07ddd9ad8dee --- /dev/null +++ b/test/classes/Controllers/Database/Structure/RealRowCountControllerTest.php @@ -0,0 +1,50 @@ +setAjax(true); + + $_REQUEST['table'] = 'City'; + + (new RealRowCountController($response, new Template(), 'world', $this->dbi))(); + + $json = $response->getJSONResult(); + $this->assertEquals('4,079', $json['real_row_count']); + + $_REQUEST['real_row_count_all'] = 'on'; + + (new RealRowCountController($response, new Template(), 'world', $this->dbi))(); + + $json = $response->getJSONResult(); + $expected = [ + ['table' => 'City', 'row_count' => 4079], + ['table' => 'Country', 'row_count' => 239], + ['table' => 'CountryLanguage', 'row_count' => 984], + ]; + $this->assertEquals(json_encode($expected), $json['real_row_count_all']); + } +} diff --git a/test/classes/Controllers/Database/StructureControllerTest.php b/test/classes/Controllers/Database/StructureControllerTest.php index 576a1e7804b4..5aa1a450a53c 100644 --- a/test/classes/Controllers/Database/StructureControllerTest.php +++ b/test/classes/Controllers/Database/StructureControllerTest.php @@ -8,7 +8,6 @@ use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\FlashMessages; use PhpMyAdmin\Operations; -use PhpMyAdmin\RecentFavoriteTable; use PhpMyAdmin\Relation; use PhpMyAdmin\RelationCleanup; use PhpMyAdmin\Replication; @@ -16,12 +15,9 @@ use PhpMyAdmin\Template; use PhpMyAdmin\Tests\AbstractTestCase; use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub; -use PHPUnit\Framework\MockObject\MockObject; use ReflectionClass; use ReflectionException; -use function json_encode; - /** * @covers \PhpMyAdmin\Controllers\Database\StructureController */ @@ -392,113 +388,6 @@ public function testCheckFavoriteTable(): void ); } - /** - * Tests for synchronizeFavoriteTables() - */ - public function testSynchronizeFavoriteTables(): void - { - $favoriteInstance = $this->getFavoriteTablesMock(); - - $class = new ReflectionClass(StructureController::class); - $method = $class->getMethod('synchronizeFavoriteTables'); - $method->setAccessible(true); - - $controller = new StructureController( - $this->response, - $this->template, - $GLOBALS['db'], - $this->relation, - $this->replication, - $this->relationCleanup, - $this->operations, - $GLOBALS['dbi'], - $this->flash - ); - - // The user hash for test - $user = 'abcdefg'; - $favoriteTable = [ - $user => [ - [ - 'db' => 'db', - 'table' => 'table', - ], - ], - ]; - - $json = $method->invokeArgs($controller, [$favoriteInstance, $user, $favoriteTable]); - - $this->assertEquals(json_encode($favoriteTable), $json['favoriteTables'] ?? ''); - $this->assertArrayHasKey('list', $json); - } - - /** - * @return MockObject|RecentFavoriteTable - */ - private function getFavoriteTablesMock() - { - $favoriteInstance = $this->getMockBuilder(RecentFavoriteTable::class) - ->disableOriginalConstructor() - ->getMock(); - $favoriteInstance->expects($this->exactly(2)) - ->method('getTables') - ->will($this->onConsecutiveCalls([[]], [['db' => 'db', 'table' => 'table']])); - - return $favoriteInstance; - } - - /** - * Tests for handleRealRowCountRequestAction() - */ - public function testHandleRealRowCountRequestAction(): void - { - global $is_db; - - $is_db = true; - - $this->response->setAjax(true); - $controller = new StructureController( - $this->response, - $this->template, - $GLOBALS['db'], - $this->relation, - $this->replication, - $this->relationCleanup, - $this->operations, - $GLOBALS['dbi'], - $this->flash - ); - // Showing statistics - $class = new ReflectionClass(StructureController::class); - $property = $class->getProperty('tables'); - $property->setAccessible(true); - - $_REQUEST['table'] = 'table'; - $controller->handleRealRowCountRequestAction(); - $json = $this->response->getJSONResult(); - $this->assertEquals( - 6, - $json['real_row_count'] - ); - - // Fall into another branch - $property->setValue($controller, [['TABLE_NAME' => 'table']]); - $_REQUEST['real_row_count_all'] = 'abc'; - $controller->handleRealRowCountRequestAction(); - $json = $this->response->getJSONResult(); - - $expectedResult = [ - [ - 'table' => 'table', - 'row_count' => 6, - ], - ]; - $this->assertEquals( - json_encode($expectedResult), - $json['real_row_count_all'] - ); - } - /** * @throws ReflectionException */ diff --git a/test/classes/Controllers/Server/Databases/CreateControllerTest.php b/test/classes/Controllers/Server/Databases/CreateControllerTest.php new file mode 100644 index 000000000000..53116cfa6aae --- /dev/null +++ b/test/classes/Controllers/Server/Databases/CreateControllerTest.php @@ -0,0 +1,61 @@ +setAjax(true); + + $template = new Template(); + $controller = new CreateController($response, $template, $this->dbi); + + $_POST['new_db'] = 'test_db_error'; + + $controller(); + $actual = $response->getJSONResult(); + + $this->assertArrayHasKey('message', $actual); + $this->assertStringContainsString('