DatabasesController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PhpMyAdmin\Controllers\Server\DatabasesController
  5. *
  6. * @package PhpMyAdmin\Controllers
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin\Controllers\Server;
  10. use PhpMyAdmin\Charsets;
  11. use PhpMyAdmin\Charsets\Charset;
  12. use PhpMyAdmin\Charsets\Collation;
  13. use PhpMyAdmin\Controllers\AbstractController;
  14. use PhpMyAdmin\DatabaseInterface;
  15. use PhpMyAdmin\Message;
  16. use PhpMyAdmin\Url;
  17. use PhpMyAdmin\Util;
  18. /**
  19. * Handles viewing and creating and deleting databases
  20. *
  21. * @package PhpMyAdmin\Controllers
  22. */
  23. class DatabasesController extends AbstractController
  24. {
  25. /**
  26. * @var array array of database details
  27. */
  28. private $databases = [];
  29. /**
  30. * @var int number of databases
  31. */
  32. private $databaseCount = 0;
  33. /**
  34. * @var string sort by column
  35. */
  36. private $sortBy;
  37. /**
  38. * @var string sort order of databases
  39. */
  40. private $sortOrder;
  41. /**
  42. * @var boolean whether to show database statistics
  43. */
  44. private $hasStatistics;
  45. /**
  46. * @var int position in list navigation
  47. */
  48. private $position;
  49. /**
  50. * Index action
  51. *
  52. * @param array $params Request parameters
  53. *
  54. * @return string HTML
  55. */
  56. public function indexAction(array $params): string
  57. {
  58. global $cfg, $server, $dblist, $is_create_db_priv;
  59. global $replication_info, $db_to_create, $pmaThemeImage, $text_dir;
  60. include_once ROOT_PATH . 'libraries/replication.inc.php';
  61. include_once ROOT_PATH . 'libraries/server_common.inc.php';
  62. $this->setSortDetails($params['sort_by'], $params['sort_order']);
  63. $this->hasStatistics = ! empty($params['statistics']);
  64. $this->position = ! empty($params['pos']) ? (int) $params['pos'] : 0;
  65. /**
  66. * Gets the databases list
  67. */
  68. if ($server > 0) {
  69. $this->databases = $this->dbi->getDatabasesFull(
  70. null,
  71. $this->hasStatistics,
  72. DatabaseInterface::CONNECT_USER,
  73. $this->sortBy,
  74. $this->sortOrder,
  75. $this->position,
  76. true
  77. );
  78. $this->databaseCount = count($dblist->databases);
  79. }
  80. $urlParams = [
  81. 'statistics' => $this->hasStatistics,
  82. 'pos' => $this->position,
  83. 'sort_by' => $this->sortBy,
  84. 'sort_order' => $this->sortOrder,
  85. ];
  86. $databases = $this->getDatabases($replication_types ?? []);
  87. $charsetsList = [];
  88. if ($cfg['ShowCreateDb'] && $is_create_db_priv) {
  89. $charsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
  90. $collations = Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
  91. $serverCollation = $this->dbi->getServerCollation();
  92. /** @var Charset $charset */
  93. foreach ($charsets as $charset) {
  94. $collationsList = [];
  95. /** @var Collation $collation */
  96. foreach ($collations[$charset->getName()] as $collation) {
  97. $collationsList[] = [
  98. 'name' => $collation->getName(),
  99. 'description' => $collation->getDescription(),
  100. 'is_selected' => $serverCollation === $collation->getName(),
  101. ];
  102. }
  103. $charsetsList[] = [
  104. 'name' => $charset->getName(),
  105. 'description' => $charset->getDescription(),
  106. 'collations' => $collationsList,
  107. ];
  108. }
  109. }
  110. $headerStatistics = $this->getStatisticsColumns();
  111. return $this->template->render('server/databases/index', [
  112. 'is_create_database_shown' => $cfg['ShowCreateDb'],
  113. 'has_create_database_privileges' => $is_create_db_priv,
  114. 'has_statistics' => $this->hasStatistics,
  115. 'database_to_create' => $db_to_create,
  116. 'databases' => $databases['databases'],
  117. 'total_statistics' => $databases['total_statistics'],
  118. 'header_statistics' => $headerStatistics,
  119. 'charsets' => $charsetsList,
  120. 'database_count' => $this->databaseCount,
  121. 'pos' => $this->position,
  122. 'url_params' => $urlParams,
  123. 'max_db_list' => $cfg['MaxDbList'],
  124. 'has_master_replication' => $replication_info['master']['status'],
  125. 'has_slave_replication' => $replication_info['slave']['status'],
  126. 'is_drop_allowed' => $this->dbi->isSuperuser() || $cfg['AllowUserDropDatabase'],
  127. 'default_tab_database' => $cfg['DefaultTabDatabase'],
  128. 'pma_theme_image' => $pmaThemeImage,
  129. 'text_dir' => $text_dir,
  130. ]);
  131. }
  132. /**
  133. * Handles creating a new database
  134. *
  135. * @param array $params Request parameters
  136. *
  137. * @return array JSON
  138. */
  139. public function createDatabaseAction(array $params): array
  140. {
  141. global $cfg, $db;
  142. // lower_case_table_names=1 `DB` becomes `db`
  143. if ($this->dbi->getLowerCaseNames() === '1') {
  144. $params['new_db'] = mb_strtolower(
  145. $params['new_db']
  146. );
  147. }
  148. /**
  149. * Builds and executes the db creation sql query
  150. */
  151. $sqlQuery = 'CREATE DATABASE ' . Util::backquote($params['new_db']);
  152. if (! empty($params['db_collation'])) {
  153. list($databaseCharset) = explode('_', $params['db_collation']);
  154. $charsets = Charsets::getCharsets(
  155. $this->dbi,
  156. $cfg['Server']['DisableIS']
  157. );
  158. $collations = Charsets::getCollations(
  159. $this->dbi,
  160. $cfg['Server']['DisableIS']
  161. );
  162. if (in_array($databaseCharset, array_keys($charsets))
  163. && in_array($params['db_collation'], array_keys($collations[$databaseCharset]))
  164. ) {
  165. $sqlQuery .= ' DEFAULT'
  166. . Util::getCharsetQueryPart($params['db_collation']);
  167. }
  168. }
  169. $sqlQuery .= ';';
  170. $result = $this->dbi->tryQuery($sqlQuery);
  171. if (! $result) {
  172. // avoid displaying the not-created db name in header or navi panel
  173. $db = '';
  174. $message = Message::rawError($this->dbi->getError());
  175. $json = ['message' => $message];
  176. $this->response->setRequestStatus(false);
  177. } else {
  178. $db = $params['new_db'];
  179. $message = Message::success(__('Database %1$s has been created.'));
  180. $message->addParam($params['new_db']);
  181. $json = [
  182. 'message' => $message,
  183. 'sql_query' => Util::getMessage(null, $sqlQuery, 'success'),
  184. 'url_query' => Util::getScriptNameForOption(
  185. $cfg['DefaultTabDatabase'],
  186. 'database'
  187. ) . Url::getCommon(['db' => $params['new_db']]),
  188. ];
  189. }
  190. return $json;
  191. }
  192. /**
  193. * Handles dropping multiple databases
  194. *
  195. * @param array $params Request parameters
  196. *
  197. * @return array JSON
  198. */
  199. public function dropDatabasesAction(array $params): array
  200. {
  201. global $submit_mult, $mult_btn, $selected;
  202. if (! isset($params['selected_dbs'])) {
  203. $message = Message::error(__('No databases selected.'));
  204. } else {
  205. $action = 'server_databases.php';
  206. $err_url = $action . Url::getCommon();
  207. $submit_mult = 'drop_db';
  208. $mult_btn = __('Yes');
  209. include ROOT_PATH . 'libraries/mult_submits.inc.php';
  210. if (empty($message)) { // no error message
  211. $numberOfDatabases = count($selected);
  212. $message = Message::success(
  213. _ngettext(
  214. '%1$d database has been dropped successfully.',
  215. '%1$d databases have been dropped successfully.',
  216. $numberOfDatabases
  217. )
  218. );
  219. $message->addParam($numberOfDatabases);
  220. }
  221. }
  222. $json = [];
  223. if ($message instanceof Message) {
  224. $json = ['message' => $message];
  225. $this->response->setRequestStatus($message->isSuccess());
  226. }
  227. return $json;
  228. }
  229. /**
  230. * Extracts parameters sort order and sort by
  231. *
  232. * @param string|null $sortBy sort by
  233. * @param string|null $sortOrder sort order
  234. *
  235. * @return void
  236. */
  237. private function setSortDetails(?string $sortBy, ?string $sortOrder): void
  238. {
  239. if (empty($sortBy)) {
  240. $this->sortBy = 'SCHEMA_NAME';
  241. } else {
  242. $sortByWhitelist = [
  243. 'SCHEMA_NAME',
  244. 'DEFAULT_COLLATION_NAME',
  245. 'SCHEMA_TABLES',
  246. 'SCHEMA_TABLE_ROWS',
  247. 'SCHEMA_DATA_LENGTH',
  248. 'SCHEMA_INDEX_LENGTH',
  249. 'SCHEMA_LENGTH',
  250. 'SCHEMA_DATA_FREE',
  251. ];
  252. $this->sortBy = 'SCHEMA_NAME';
  253. if (in_array($sortBy, $sortByWhitelist)) {
  254. $this->sortBy = $sortBy;
  255. }
  256. }
  257. $this->sortOrder = 'asc';
  258. if (isset($sortOrder)
  259. && mb_strtolower($sortOrder) === 'desc'
  260. ) {
  261. $this->sortOrder = 'desc';
  262. }
  263. }
  264. /**
  265. * Returns database list
  266. *
  267. * @param array $replicationTypes replication types
  268. *
  269. * @return array
  270. */
  271. private function getDatabases(array $replicationTypes): array
  272. {
  273. global $cfg, $replication_info;
  274. $databases = [];
  275. $totalStatistics = $this->getStatisticsColumns();
  276. foreach ($this->databases as $database) {
  277. $replication = [
  278. 'master' => [
  279. 'status' => $replication_info['master']['status'],
  280. ],
  281. 'slave' => [
  282. 'status' => $replication_info['slave']['status'],
  283. ],
  284. ];
  285. foreach ($replicationTypes as $type) {
  286. if ($replication_info[$type]['status']) {
  287. $key = array_search(
  288. $database["SCHEMA_NAME"],
  289. $replication_info[$type]['Ignore_DB']
  290. );
  291. if (strlen((string) $key) > 0) {
  292. $replication[$type]['is_replicated'] = false;
  293. } else {
  294. $key = array_search(
  295. $database["SCHEMA_NAME"],
  296. $replication_info[$type]['Do_DB']
  297. );
  298. if (strlen((string) $key) > 0
  299. || count($replication_info[$type]['Do_DB']) === 0
  300. ) {
  301. // if ($key != null) did not work for index "0"
  302. $replication[$type]['is_replicated'] = true;
  303. }
  304. }
  305. }
  306. }
  307. $statistics = $this->getStatisticsColumns();
  308. if ($this->hasStatistics) {
  309. foreach (array_keys($statistics) as $key) {
  310. $statistics[$key]['raw'] = $database[$key] ?? null;
  311. $totalStatistics[$key]['raw'] += (int) $database[$key] ?? 0;
  312. }
  313. }
  314. $databases[$database['SCHEMA_NAME']] = [
  315. 'name' => $database['SCHEMA_NAME'],
  316. 'collation' => [],
  317. 'statistics' => $statistics,
  318. 'replication' => $replication,
  319. 'is_system_schema' => $this->dbi->isSystemSchema(
  320. $database['SCHEMA_NAME'],
  321. true
  322. ),
  323. ];
  324. $collation = Charsets::findCollationByName(
  325. $this->dbi,
  326. $cfg['Server']['DisableIS'],
  327. $database['DEFAULT_COLLATION_NAME']
  328. );
  329. if ($collation !== null) {
  330. $databases[$database['SCHEMA_NAME']]['collation'] = [
  331. 'name' => $collation->getName(),
  332. 'description' => $collation->getDescription(),
  333. ];
  334. }
  335. }
  336. return [
  337. 'databases' => $databases,
  338. 'total_statistics' => $totalStatistics,
  339. ];
  340. }
  341. /**
  342. * Prepares the statistics columns
  343. *
  344. * @return array
  345. */
  346. private function getStatisticsColumns(): array
  347. {
  348. return [
  349. 'SCHEMA_TABLES' => [
  350. 'title' => __('Tables'),
  351. 'format' => 'number',
  352. 'raw' => 0,
  353. ],
  354. 'SCHEMA_TABLE_ROWS' => [
  355. 'title' => __('Rows'),
  356. 'format' => 'number',
  357. 'raw' => 0,
  358. ],
  359. 'SCHEMA_DATA_LENGTH' => [
  360. 'title' => __('Data'),
  361. 'format' => 'byte',
  362. 'raw' => 0,
  363. ],
  364. 'SCHEMA_INDEX_LENGTH' => [
  365. 'title' => __('Indexes'),
  366. 'format' => 'byte',
  367. 'raw' => 0,
  368. ],
  369. 'SCHEMA_LENGTH' => [
  370. 'title' => __('Total'),
  371. 'format' => 'byte',
  372. 'raw' => 0,
  373. ],
  374. 'SCHEMA_DATA_FREE' => [
  375. 'title' => __('Overhead'),
  376. 'format' => 'byte',
  377. 'raw' => 0,
  378. ],
  379. ];
  380. }
  381. }