PageRenderTime 67ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/classes/Tracking.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 1204 lines | 785 code | 134 blank | 285 comment | 93 complexity | 8196ebbd104a92d27248817b79f7b9af MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. /**
  3. * Functions used for database and table tracking
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin;
  7. use PhpMyAdmin\Html\Generator;
  8. use function __;
  9. use function array_key_exists;
  10. use function array_merge;
  11. use function array_multisort;
  12. use function count;
  13. use function date;
  14. use function htmlspecialchars;
  15. use function in_array;
  16. use function ini_set;
  17. use function intval;
  18. use function is_array;
  19. use function is_object;
  20. use function mb_strstr;
  21. use function preg_replace;
  22. use function rtrim;
  23. use function sprintf;
  24. use function strlen;
  25. use function strtotime;
  26. use const SORT_ASC;
  27. /**
  28. * PhpMyAdmin\Tracking class
  29. */
  30. class Tracking
  31. {
  32. /** @var SqlQueryForm */
  33. private $sqlQueryForm;
  34. /** @var Template */
  35. public $template;
  36. /** @var Relation */
  37. protected $relation;
  38. /** @var DatabaseInterface */
  39. private $dbi;
  40. public function __construct(
  41. SqlQueryForm $sqlQueryForm,
  42. Template $template,
  43. Relation $relation,
  44. DatabaseInterface $dbi
  45. ) {
  46. $this->sqlQueryForm = $sqlQueryForm;
  47. $this->template = $template;
  48. $this->relation = $relation;
  49. $this->dbi = $dbi;
  50. }
  51. /**
  52. * Filters tracking entries
  53. *
  54. * @param array $data the entries to filter
  55. * @param int $filter_ts_from "from" date
  56. * @param int $filter_ts_to "to" date
  57. * @param array $filter_users users
  58. *
  59. * @return array filtered entries
  60. */
  61. public function filter(
  62. array $data,
  63. $filter_ts_from,
  64. $filter_ts_to,
  65. array $filter_users
  66. ): array {
  67. $tmp_entries = [];
  68. $id = 0;
  69. foreach ($data as $entry) {
  70. $timestamp = strtotime($entry['date']);
  71. $filtered_user = in_array($entry['username'], $filter_users);
  72. if (
  73. $timestamp >= $filter_ts_from
  74. && $timestamp <= $filter_ts_to
  75. && (in_array('*', $filter_users) || $filtered_user)
  76. ) {
  77. $tmp_entries[] = [
  78. 'id' => $id,
  79. 'timestamp' => $timestamp,
  80. 'username' => $entry['username'],
  81. 'statement' => $entry['statement'],
  82. ];
  83. }
  84. $id++;
  85. }
  86. return $tmp_entries;
  87. }
  88. /**
  89. * Function to get the list versions of the table
  90. *
  91. * @return object|false
  92. */
  93. public function getListOfVersionsOfTable(string $db, string $table)
  94. {
  95. $cfgRelation = $this->relation->getRelationsParam();
  96. $query = sprintf(
  97. 'SELECT * FROM %s.%s WHERE db_name = \'%s\' AND table_name = \'%s\' ORDER BY version DESC',
  98. Util::backquote($cfgRelation['db']),
  99. Util::backquote($cfgRelation['tracking']),
  100. $this->dbi->escapeString($db),
  101. $this->dbi->escapeString($table)
  102. );
  103. return $this->dbi->query($query, DatabaseInterface::CONNECT_CONTROL, 0, false);
  104. }
  105. /**
  106. * Function to get html for main page parts that do not use $_REQUEST
  107. *
  108. * @param array $urlParams url parameters
  109. * @param string $textDir text direction
  110. * @param int $lastVersion last tracking version
  111. *
  112. * @return string
  113. */
  114. public function getHtmlForMainPage(
  115. string $db,
  116. string $table,
  117. $urlParams,
  118. $textDir,
  119. $lastVersion = null
  120. ) {
  121. global $cfg;
  122. $selectableTablesSqlResult = $this->getSqlResultForSelectableTables($db);
  123. $selectableTablesEntries = [];
  124. while ($entry = $this->dbi->fetchArray($selectableTablesSqlResult)) {
  125. $entry['is_tracked'] = Tracker::isTracked($entry['db_name'], $entry['table_name']);
  126. $selectableTablesEntries[] = $entry;
  127. }
  128. $selectableTablesNumRows = $this->dbi->numRows($selectableTablesSqlResult);
  129. $versionSqlResult = $this->getListOfVersionsOfTable($db, $table);
  130. if ($lastVersion === null && $versionSqlResult !== false) {
  131. $lastVersion = $this->getTableLastVersionNumber($versionSqlResult);
  132. }
  133. $versions = [];
  134. if ($versionSqlResult !== false) {
  135. $this->dbi->dataSeek($versionSqlResult, 0);
  136. while ($version = $this->dbi->fetchArray($versionSqlResult)) {
  137. $versions[] = $version;
  138. }
  139. }
  140. $type = $this->dbi->getTable($db, $table)->isView() ? 'view' : 'table';
  141. return $this->template->render('table/tracking/main', [
  142. 'url_params' => $urlParams,
  143. 'db' => $db,
  144. 'table' => $table,
  145. 'selectable_tables_num_rows' => $selectableTablesNumRows,
  146. 'selectable_tables_entries' => $selectableTablesEntries,
  147. 'selected_table' => $_POST['table'] ?? null,
  148. 'last_version' => $lastVersion,
  149. 'versions' => $versions,
  150. 'type' => $type,
  151. 'default_statements' => $cfg['Server']['tracking_default_statements'],
  152. 'text_dir' => $textDir,
  153. ]);
  154. }
  155. /**
  156. * Function to get the last version number of a table
  157. *
  158. * @param object $sql_result sql result
  159. *
  160. * @return int
  161. */
  162. public function getTableLastVersionNumber($sql_result)
  163. {
  164. $maxversion = $this->dbi->fetchArray($sql_result);
  165. return intval(is_array($maxversion) ? $maxversion['version'] : null);
  166. }
  167. /**
  168. * Function to get sql results for selectable tables
  169. *
  170. * @return mixed
  171. */
  172. public function getSqlResultForSelectableTables(string $db)
  173. {
  174. $relation = $this->relation;
  175. $cfgRelation = $relation->getRelationsParam();
  176. $sql_query = ' SELECT DISTINCT db_name, table_name FROM ' .
  177. Util::backquote($cfgRelation['db']) . '.' .
  178. Util::backquote($cfgRelation['tracking']) .
  179. " WHERE db_name = '" . $this->dbi->escapeString($db) .
  180. "' " .
  181. ' ORDER BY db_name, table_name';
  182. return $relation->queryAsControlUser($sql_query);
  183. }
  184. /**
  185. * Function to get html for tracking report and tracking report export
  186. *
  187. * @param array $data data
  188. * @param array $url_params url params
  189. * @param bool $selection_schema selection schema
  190. * @param bool $selection_data selection data
  191. * @param bool $selection_both selection both
  192. * @param int $filter_ts_to filter time stamp from
  193. * @param int $filter_ts_from filter time stamp tp
  194. * @param array $filter_users filter users
  195. *
  196. * @return string
  197. */
  198. public function getHtmlForTrackingReport(
  199. array $data,
  200. array $url_params,
  201. $selection_schema,
  202. $selection_data,
  203. $selection_both,
  204. $filter_ts_to,
  205. $filter_ts_from,
  206. array $filter_users
  207. ) {
  208. $html = '<h3>' . __('Tracking report')
  209. . ' [<a href="' . Url::getFromRoute('/table/tracking', $url_params) . '">' . __('Close')
  210. . '</a>]</h3>';
  211. $html .= '<small>' . __('Tracking statements') . ' '
  212. . htmlspecialchars($data['tracking']) . '</small><br>';
  213. $html .= '<br>';
  214. [$str1, $str2, $str3, $str4, $str5] = $this->getHtmlForElementsOfTrackingReport(
  215. $selection_schema,
  216. $selection_data,
  217. $selection_both
  218. );
  219. // Prepare delete link content here
  220. $drop_image_or_text = '';
  221. if (Util::showIcons('ActionLinksMode')) {
  222. $drop_image_or_text .= Generator::getImage(
  223. 'b_drop',
  224. __('Delete tracking data row from report')
  225. );
  226. }
  227. if (Util::showText('ActionLinksMode')) {
  228. $drop_image_or_text .= __('Delete');
  229. }
  230. /*
  231. * First, list tracked data definition statements
  232. */
  233. if (count($data['ddlog']) == 0 && count($data['dmlog']) === 0) {
  234. $msg = Message::notice(__('No data'));
  235. echo $msg->getDisplay();
  236. }
  237. $html .= $this->getHtmlForTrackingReportExportForm1(
  238. $data,
  239. $url_params,
  240. $selection_schema,
  241. $selection_data,
  242. $selection_both,
  243. $filter_ts_to,
  244. $filter_ts_from,
  245. $filter_users,
  246. $str1,
  247. $str2,
  248. $str3,
  249. $str4,
  250. $str5,
  251. $drop_image_or_text
  252. );
  253. $html .= $this->getHtmlForTrackingReportExportForm2($url_params, $str1, $str2, $str3, $str4, $str5);
  254. $html .= "<br><br><hr><br>\n";
  255. return $html;
  256. }
  257. /**
  258. * Generate HTML element for report form
  259. *
  260. * @param bool $selection_schema selection schema
  261. * @param bool $selection_data selection data
  262. * @param bool $selection_both selection both
  263. *
  264. * @return array
  265. */
  266. public function getHtmlForElementsOfTrackingReport(
  267. $selection_schema,
  268. $selection_data,
  269. $selection_both
  270. ) {
  271. $str1 = '<select name="logtype">'
  272. . '<option value="schema"'
  273. . ($selection_schema ? ' selected="selected"' : '') . '>'
  274. . __('Structure only') . '</option>'
  275. . '<option value="data"'
  276. . ($selection_data ? ' selected="selected"' : '') . '>'
  277. . __('Data only') . '</option>'
  278. . '<option value="schema_and_data"'
  279. . ($selection_both ? ' selected="selected"' : '') . '>'
  280. . __('Structure and data') . '</option>'
  281. . '</select>';
  282. $str2 = '<input type="text" name="date_from" value="'
  283. . htmlspecialchars($_POST['date_from']) . '" size="19">';
  284. $str3 = '<input type="text" name="date_to" value="'
  285. . htmlspecialchars($_POST['date_to']) . '" size="19">';
  286. $str4 = '<input type="text" name="users" value="'
  287. . htmlspecialchars($_POST['users']) . '">';
  288. $str5 = '<input type="hidden" name="list_report" value="1">'
  289. . '<input class="btn btn-primary" type="submit" value="' . __('Go') . '">';
  290. return [
  291. $str1,
  292. $str2,
  293. $str3,
  294. $str4,
  295. $str5,
  296. ];
  297. }
  298. /**
  299. * Generate HTML for export form
  300. *
  301. * @param array $data data
  302. * @param array $url_params url params
  303. * @param bool $selection_schema selection schema
  304. * @param bool $selection_data selection data
  305. * @param bool $selection_both selection both
  306. * @param int $filter_ts_to filter time stamp from
  307. * @param int $filter_ts_from filter time stamp tp
  308. * @param array $filter_users filter users
  309. * @param string $str1 HTML for logtype select
  310. * @param string $str2 HTML for "from date"
  311. * @param string $str3 HTML for "to date"
  312. * @param string $str4 HTML for user
  313. * @param string $str5 HTML for "list report"
  314. * @param string $drop_image_or_text HTML for image or text
  315. *
  316. * @return string HTML for form
  317. */
  318. public function getHtmlForTrackingReportExportForm1(
  319. array $data,
  320. array $url_params,
  321. $selection_schema,
  322. $selection_data,
  323. $selection_both,
  324. $filter_ts_to,
  325. $filter_ts_from,
  326. array $filter_users,
  327. $str1,
  328. $str2,
  329. $str3,
  330. $str4,
  331. $str5,
  332. $drop_image_or_text
  333. ) {
  334. $ddlog_count = 0;
  335. $html = '<form method="post" action="' . Url::getFromRoute('/table/tracking') . '">';
  336. $html .= Url::getHiddenInputs($url_params + [
  337. 'report' => 'true',
  338. 'version' => $_POST['version'],
  339. ]);
  340. $html .= sprintf(
  341. __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'),
  342. $str1,
  343. $str2,
  344. $str3,
  345. $str4,
  346. $str5
  347. );
  348. if ($selection_schema || $selection_both && count($data['ddlog']) > 0) {
  349. [$temp, $ddlog_count] = $this->getHtmlForDataDefinitionStatements(
  350. $data,
  351. $filter_users,
  352. $filter_ts_from,
  353. $filter_ts_to,
  354. $url_params,
  355. $drop_image_or_text
  356. );
  357. $html .= $temp;
  358. unset($temp);
  359. }
  360. /*
  361. * Secondly, list tracked data manipulation statements
  362. */
  363. if (($selection_data || $selection_both) && count($data['dmlog']) > 0) {
  364. $html .= $this->getHtmlForDataManipulationStatements(
  365. $data,
  366. $filter_users,
  367. $filter_ts_from,
  368. $filter_ts_to,
  369. $url_params,
  370. $ddlog_count,
  371. $drop_image_or_text
  372. );
  373. }
  374. $html .= '</form>';
  375. return $html;
  376. }
  377. /**
  378. * Generate HTML for export form
  379. *
  380. * @param array $url_params Parameters
  381. * @param string $str1 HTML for logtype select
  382. * @param string $str2 HTML for "from date"
  383. * @param string $str3 HTML for "to date"
  384. * @param string $str4 HTML for user
  385. * @param string $str5 HTML for "list report"
  386. *
  387. * @return string HTML for form
  388. */
  389. public function getHtmlForTrackingReportExportForm2(
  390. array $url_params,
  391. $str1,
  392. $str2,
  393. $str3,
  394. $str4,
  395. $str5
  396. ) {
  397. $html = '<form method="post" action="' . Url::getFromRoute('/table/tracking') . '">';
  398. $html .= Url::getHiddenInputs($url_params + [
  399. 'report' => 'true',
  400. 'version' => $_POST['version'],
  401. ]);
  402. $html .= sprintf(
  403. __('Show %1$s with dates from %2$s to %3$s by user %4$s %5$s'),
  404. $str1,
  405. $str2,
  406. $str3,
  407. $str4,
  408. $str5
  409. );
  410. $html .= '</form>';
  411. $html .= '<form class="disableAjax" method="post" action="' . Url::getFromRoute('/table/tracking') . '">';
  412. $html .= Url::getHiddenInputs($url_params + [
  413. 'report' => 'true',
  414. 'version' => $_POST['version'],
  415. 'logtype' => $_POST['logtype'],
  416. 'date_from' => $_POST['date_from'],
  417. 'date_to' => $_POST['date_to'],
  418. 'users' => $_POST['users'],
  419. 'report_export' => 'true',
  420. ]);
  421. $str_export1 = '<select name="export_type">'
  422. . '<option value="sqldumpfile">' . __('SQL dump (file download)')
  423. . '</option>'
  424. . '<option value="sqldump">' . __('SQL dump') . '</option>'
  425. . '<option value="execution" onclick="alert(\''
  426. . Sanitize::escapeJsString(
  427. __('This option will replace your table and contained data.')
  428. )
  429. . '\')">' . __('SQL execution') . '</option></select>';
  430. $str_export2 = '<input class="btn btn-primary" type="submit" value="' . __('Go') . '">';
  431. $html .= '<br>' . sprintf(__('Export as %s'), $str_export1)
  432. . $str_export2 . '<br>';
  433. $html .= '</form>';
  434. return $html;
  435. }
  436. /**
  437. * Function to get html for data manipulation statements
  438. *
  439. * @param array $data data
  440. * @param array $filter_users filter users
  441. * @param int $filter_ts_from filter time staml from
  442. * @param int $filter_ts_to filter time stamp to
  443. * @param array $url_params url parameters
  444. * @param int $ddlog_count data definition log count
  445. * @param string $drop_image_or_text drop image or text
  446. *
  447. * @return string
  448. */
  449. public function getHtmlForDataManipulationStatements(
  450. array $data,
  451. array $filter_users,
  452. $filter_ts_from,
  453. $filter_ts_to,
  454. array $url_params,
  455. $ddlog_count,
  456. $drop_image_or_text
  457. ) {
  458. // no need for the second returned parameter
  459. [$html] = $this->getHtmlForDataStatements(
  460. $data,
  461. $filter_users,
  462. $filter_ts_from,
  463. $filter_ts_to,
  464. $url_params,
  465. $drop_image_or_text,
  466. 'dmlog',
  467. __('Data manipulation statement'),
  468. $ddlog_count,
  469. 'dml_versions'
  470. );
  471. return $html;
  472. }
  473. /**
  474. * Function to get html for data definition statements in schema snapshot
  475. *
  476. * @param array $data data
  477. * @param array $filter_users filter users
  478. * @param int $filter_ts_from filter time stamp from
  479. * @param int $filter_ts_to filter time stamp to
  480. * @param array $url_params url parameters
  481. * @param string $drop_image_or_text drop image or text
  482. *
  483. * @return array
  484. */
  485. public function getHtmlForDataDefinitionStatements(
  486. array $data,
  487. array $filter_users,
  488. $filter_ts_from,
  489. $filter_ts_to,
  490. array $url_params,
  491. $drop_image_or_text
  492. ) {
  493. [$html, $line_number] = $this->getHtmlForDataStatements(
  494. $data,
  495. $filter_users,
  496. $filter_ts_from,
  497. $filter_ts_to,
  498. $url_params,
  499. $drop_image_or_text,
  500. 'ddlog',
  501. __('Data definition statement'),
  502. 1,
  503. 'ddl_versions'
  504. );
  505. return [
  506. $html,
  507. $line_number,
  508. ];
  509. }
  510. /**
  511. * Function to get html for data statements in schema snapshot
  512. *
  513. * @param array $data data
  514. * @param array $filterUsers filter users
  515. * @param int $filterTsFrom filter time stamp from
  516. * @param int $filterTsTo filter time stamp to
  517. * @param array $urlParams url parameters
  518. * @param string $dropImageOrText drop image or text
  519. * @param string $whichLog dmlog|ddlog
  520. * @param string $headerMessage message for this section
  521. * @param int $lineNumber line number
  522. * @param string $tableId id for the table element
  523. *
  524. * @return array [$html, $lineNumber]
  525. */
  526. private function getHtmlForDataStatements(
  527. array $data,
  528. array $filterUsers,
  529. $filterTsFrom,
  530. $filterTsTo,
  531. array $urlParams,
  532. $dropImageOrText,
  533. $whichLog,
  534. $headerMessage,
  535. $lineNumber,
  536. $tableId
  537. ) {
  538. $offset = $lineNumber;
  539. $entries = [];
  540. foreach ($data[$whichLog] as $entry) {
  541. $timestamp = strtotime($entry['date']);
  542. if (
  543. $timestamp >= $filterTsFrom
  544. && $timestamp <= $filterTsTo
  545. && (in_array('*', $filterUsers)
  546. || in_array($entry['username'], $filterUsers))
  547. ) {
  548. $entry['formated_statement'] = Generator::formatSql($entry['statement'], true);
  549. $deleteParam = 'delete_' . $whichLog;
  550. $entry['url_params'] = Url::getCommon($urlParams + [
  551. 'report' => 'true',
  552. 'version' => $_POST['version'],
  553. $deleteParam => $lineNumber - $offset,
  554. ], '');
  555. $entry['line_number'] = $lineNumber;
  556. $entries[] = $entry;
  557. }
  558. $lineNumber++;
  559. }
  560. $html = $this->template->render('table/tracking/report_table', [
  561. 'table_id' => $tableId,
  562. 'header_message' => $headerMessage,
  563. 'entries' => $entries,
  564. 'drop_image_or_text' => $dropImageOrText,
  565. ]);
  566. return [
  567. $html,
  568. $lineNumber,
  569. ];
  570. }
  571. /**
  572. * Function to get html for schema snapshot
  573. *
  574. * @param array $params url parameters
  575. */
  576. public function getHtmlForSchemaSnapshot(array $params): string
  577. {
  578. $html = '<h3>' . __('Structure snapshot')
  579. . ' [<a href="' . Url::getFromRoute('/table/tracking', $params) . '">' . __('Close')
  580. . '</a>]</h3>';
  581. $data = Tracker::getTrackedData($_POST['db'], $_POST['table'], $_POST['version']);
  582. // Get first DROP TABLE/VIEW and CREATE TABLE/VIEW statements
  583. $drop_create_statements = $data['ddlog'][0]['statement'];
  584. if (
  585. mb_strstr($data['ddlog'][0]['statement'], 'DROP TABLE')
  586. || mb_strstr($data['ddlog'][0]['statement'], 'DROP VIEW')
  587. ) {
  588. $drop_create_statements .= $data['ddlog'][1]['statement'];
  589. }
  590. // Print SQL code
  591. $html .= Generator::getMessage(
  592. sprintf(
  593. __('Version %s snapshot (SQL code)'),
  594. htmlspecialchars($_POST['version'])
  595. ),
  596. $drop_create_statements
  597. );
  598. // Unserialize snapshot
  599. $temp = Core::safeUnserialize($data['schema_snapshot']);
  600. if ($temp === null) {
  601. $temp = [
  602. 'COLUMNS' => [],
  603. 'INDEXES' => [],
  604. ];
  605. }
  606. $columns = $temp['COLUMNS'];
  607. $indexes = $temp['INDEXES'];
  608. $html .= $this->getHtmlForColumns($columns);
  609. if (count($indexes) > 0) {
  610. $html .= $this->getHtmlForIndexes($indexes);
  611. }
  612. $html .= '<br><hr><br>';
  613. return $html;
  614. }
  615. /**
  616. * Function to get html for displaying columns in the schema snapshot
  617. *
  618. * @param array $columns columns
  619. *
  620. * @return string
  621. */
  622. public function getHtmlForColumns(array $columns)
  623. {
  624. return $this->template->render('table/tracking/structure_snapshot_columns', ['columns' => $columns]);
  625. }
  626. /**
  627. * Function to get html for the indexes in schema snapshot
  628. *
  629. * @param array $indexes indexes
  630. *
  631. * @return string
  632. */
  633. public function getHtmlForIndexes(array $indexes)
  634. {
  635. return $this->template->render('table/tracking/structure_snapshot_indexes', ['indexes' => $indexes]);
  636. }
  637. /**
  638. * Function to handle the tracking report
  639. *
  640. * @param array $data tracked data
  641. *
  642. * @return string HTML for the message
  643. */
  644. public function deleteTrackingReportRows(string $db, string $table, array &$data)
  645. {
  646. $html = '';
  647. if (isset($_POST['delete_ddlog'])) {
  648. // Delete ddlog row data
  649. $html .= $this->deleteFromTrackingReportLog(
  650. $db,
  651. $table,
  652. $data,
  653. 'ddlog',
  654. 'DDL',
  655. __('Tracking data definition successfully deleted')
  656. );
  657. }
  658. if (isset($_POST['delete_dmlog'])) {
  659. // Delete dmlog row data
  660. $html .= $this->deleteFromTrackingReportLog(
  661. $db,
  662. $table,
  663. $data,
  664. 'dmlog',
  665. 'DML',
  666. __('Tracking data manipulation successfully deleted')
  667. );
  668. }
  669. return $html;
  670. }
  671. /**
  672. * Function to delete from a tracking report log
  673. *
  674. * @param array $data tracked data
  675. * @param string $which_log ddlog|dmlog
  676. * @param string $type DDL|DML
  677. * @param string $message success message
  678. *
  679. * @return string HTML for the message
  680. */
  681. public function deleteFromTrackingReportLog(string $db, string $table, array &$data, $which_log, $type, $message)
  682. {
  683. $html = '';
  684. $delete_id = $_POST['delete_' . $which_log];
  685. // Only in case of valid id
  686. if ($delete_id == (int) $delete_id) {
  687. unset($data[$which_log][$delete_id]);
  688. $successfullyDeleted = Tracker::changeTrackingData(
  689. $db,
  690. $table,
  691. $_POST['version'],
  692. $type,
  693. $data[$which_log]
  694. );
  695. if ($successfullyDeleted) {
  696. $msg = Message::success($message);
  697. } else {
  698. $msg = Message::rawError(__('Query error'));
  699. }
  700. $html .= $msg->getDisplay();
  701. }
  702. return $html;
  703. }
  704. /**
  705. * Function to export as sql dump
  706. *
  707. * @param array $entries entries
  708. *
  709. * @return string HTML SQL query form
  710. */
  711. public function exportAsSqlDump(string $db, string $table, array $entries)
  712. {
  713. $html = '';
  714. $new_query = '# '
  715. . __(
  716. 'You can execute the dump by creating and using a temporary database. '
  717. . 'Please ensure that you have the privileges to do so.'
  718. )
  719. . "\n"
  720. . '# ' . __('Comment out these two lines if you do not need them.') . "\n"
  721. . "\n"
  722. . "CREATE database IF NOT EXISTS pma_temp_db; \n"
  723. . "USE pma_temp_db; \n"
  724. . "\n";
  725. foreach ($entries as $entry) {
  726. $new_query .= $entry['statement'];
  727. }
  728. $msg = Message::success(
  729. __('SQL statements exported. Please copy the dump or execute it.')
  730. );
  731. $html .= $msg->getDisplay();
  732. $html .= $this->sqlQueryForm->getHtml('', '', $new_query, 'sql');
  733. return $html;
  734. }
  735. /**
  736. * Function to export as sql execution
  737. *
  738. * @param array $entries entries
  739. *
  740. * @return array
  741. */
  742. public function exportAsSqlExecution(array $entries)
  743. {
  744. $sql_result = [];
  745. foreach ($entries as $entry) {
  746. $sql_result = $this->dbi->query("/*NOTRACK*/\n" . $entry['statement']);
  747. }
  748. return $sql_result;
  749. }
  750. /**
  751. * Function to export as entries
  752. *
  753. * @param array $entries entries
  754. */
  755. public function exportAsFileDownload(array $entries): void
  756. {
  757. ini_set('url_rewriter.tags', '');
  758. // Replace all multiple whitespaces by a single space
  759. $table = htmlspecialchars(preg_replace('/\s+/', ' ', $_POST['table']));
  760. $dump = '# ' . sprintf(
  761. __('Tracking report for table `%s`'),
  762. $table
  763. )
  764. . "\n" . '# ' . date('Y-m-d H:i:s') . "\n";
  765. foreach ($entries as $entry) {
  766. $dump .= $entry['statement'];
  767. }
  768. $filename = 'log_' . $table . '.sql';
  769. ResponseRenderer::getInstance()->disable();
  770. Core::downloadHeader(
  771. $filename,
  772. 'text/x-sql',
  773. strlen($dump)
  774. );
  775. echo $dump;
  776. exit;
  777. }
  778. /**
  779. * Function to activate or deactivate tracking
  780. *
  781. * @param string $action activate|deactivate
  782. *
  783. * @return string HTML for the success message
  784. */
  785. public function changeTracking(string $db, string $table, $action)
  786. {
  787. $html = '';
  788. if ($action === 'activate') {
  789. $status = Tracker::activateTracking($db, $table, $_POST['version']);
  790. $message = __('Tracking for %1$s was activated at version %2$s.');
  791. } else {
  792. $status = Tracker::deactivateTracking($db, $table, $_POST['version']);
  793. $message = __('Tracking for %1$s was deactivated at version %2$s.');
  794. }
  795. if ($status) {
  796. $msg = Message::success(
  797. sprintf(
  798. $message,
  799. htmlspecialchars($db . '.' . $table),
  800. htmlspecialchars($_POST['version'])
  801. )
  802. );
  803. $html .= $msg->getDisplay();
  804. }
  805. return $html;
  806. }
  807. /**
  808. * Function to get tracking set
  809. *
  810. * @return string
  811. */
  812. public function getTrackingSet()
  813. {
  814. $tracking_set = '';
  815. // a key is absent from the request if it has been removed from
  816. // tracking_default_statements in the config
  817. if (isset($_POST['alter_table']) && $_POST['alter_table'] == true) {
  818. $tracking_set .= 'ALTER TABLE,';
  819. }
  820. if (isset($_POST['rename_table']) && $_POST['rename_table'] == true) {
  821. $tracking_set .= 'RENAME TABLE,';
  822. }
  823. if (isset($_POST['create_table']) && $_POST['create_table'] == true) {
  824. $tracking_set .= 'CREATE TABLE,';
  825. }
  826. if (isset($_POST['drop_table']) && $_POST['drop_table'] == true) {
  827. $tracking_set .= 'DROP TABLE,';
  828. }
  829. if (isset($_POST['alter_view']) && $_POST['alter_view'] == true) {
  830. $tracking_set .= 'ALTER VIEW,';
  831. }
  832. if (isset($_POST['create_view']) && $_POST['create_view'] == true) {
  833. $tracking_set .= 'CREATE VIEW,';
  834. }
  835. if (isset($_POST['drop_view']) && $_POST['drop_view'] == true) {
  836. $tracking_set .= 'DROP VIEW,';
  837. }
  838. if (isset($_POST['create_index']) && $_POST['create_index'] == true) {
  839. $tracking_set .= 'CREATE INDEX,';
  840. }
  841. if (isset($_POST['drop_index']) && $_POST['drop_index'] == true) {
  842. $tracking_set .= 'DROP INDEX,';
  843. }
  844. if (isset($_POST['insert']) && $_POST['insert'] == true) {
  845. $tracking_set .= 'INSERT,';
  846. }
  847. if (isset($_POST['update']) && $_POST['update'] == true) {
  848. $tracking_set .= 'UPDATE,';
  849. }
  850. if (isset($_POST['delete']) && $_POST['delete'] == true) {
  851. $tracking_set .= 'DELETE,';
  852. }
  853. if (isset($_POST['truncate']) && $_POST['truncate'] == true) {
  854. $tracking_set .= 'TRUNCATE,';
  855. }
  856. $tracking_set = rtrim($tracking_set, ',');
  857. return $tracking_set;
  858. }
  859. /**
  860. * Deletes a tracking version
  861. *
  862. * @param string $version tracking version
  863. *
  864. * @return string HTML of the success message
  865. */
  866. public function deleteTrackingVersion(string $db, string $table, $version)
  867. {
  868. $html = '';
  869. $versionDeleted = Tracker::deleteTracking($db, $table, $version);
  870. if ($versionDeleted) {
  871. $msg = Message::success(
  872. sprintf(
  873. __('Version %1$s of %2$s was deleted.'),
  874. htmlspecialchars($version),
  875. htmlspecialchars($db . '.' . $table)
  876. )
  877. );
  878. $html .= $msg->getDisplay();
  879. }
  880. return $html;
  881. }
  882. /**
  883. * Function to create the tracking version
  884. *
  885. * @return string HTML of the success message
  886. */
  887. public function createTrackingVersion(string $db, string $table)
  888. {
  889. $html = '';
  890. $tracking_set = $this->getTrackingSet();
  891. $versionCreated = Tracker::createVersion(
  892. $db,
  893. $table,
  894. $_POST['version'],
  895. $tracking_set,
  896. $this->dbi->getTable($db, $table)->isView()
  897. );
  898. if ($versionCreated) {
  899. $msg = Message::success(
  900. sprintf(
  901. __('Version %1$s was created, tracking for %2$s is active.'),
  902. htmlspecialchars($_POST['version']),
  903. htmlspecialchars($db . '.' . $table)
  904. )
  905. );
  906. $html .= $msg->getDisplay();
  907. }
  908. return $html;
  909. }
  910. /**
  911. * Create tracking version for multiple tables
  912. *
  913. * @param array $selected list of selected tables
  914. */
  915. public function createTrackingForMultipleTables(string $db, array $selected): void
  916. {
  917. $tracking_set = $this->getTrackingSet();
  918. foreach ($selected as $selected_table) {
  919. Tracker::createVersion(
  920. $db,
  921. $selected_table,
  922. $_POST['version'],
  923. $tracking_set,
  924. $this->dbi->getTable($db, $selected_table)->isView()
  925. );
  926. }
  927. }
  928. /**
  929. * Function to get the entries
  930. *
  931. * @param array $data data
  932. * @param int $filter_ts_from filter time stamp from
  933. * @param int $filter_ts_to filter time stamp to
  934. * @param array $filter_users filter users
  935. *
  936. * @return array
  937. */
  938. public function getEntries(array $data, $filter_ts_from, $filter_ts_to, array $filter_users)
  939. {
  940. $entries = [];
  941. // Filtering data definition statements
  942. if ($_POST['logtype'] === 'schema' || $_POST['logtype'] === 'schema_and_data') {
  943. $entries = array_merge(
  944. $entries,
  945. $this->filter(
  946. $data['ddlog'],
  947. $filter_ts_from,
  948. $filter_ts_to,
  949. $filter_users
  950. )
  951. );
  952. }
  953. // Filtering data manipulation statements
  954. if ($_POST['logtype'] === 'data' || $_POST['logtype'] === 'schema_and_data') {
  955. $entries = array_merge(
  956. $entries,
  957. $this->filter(
  958. $data['dmlog'],
  959. $filter_ts_from,
  960. $filter_ts_to,
  961. $filter_users
  962. )
  963. );
  964. }
  965. // Sort it
  966. $ids = $timestamps = $usernames = $statements = [];
  967. foreach ($entries as $key => $row) {
  968. $ids[$key] = $row['id'];
  969. $timestamps[$key] = $row['timestamp'];
  970. $usernames[$key] = $row['username'];
  971. $statements[$key] = $row['statement'];
  972. }
  973. array_multisort($timestamps, SORT_ASC, $ids, SORT_ASC, $usernames, SORT_ASC, $statements, SORT_ASC, $entries);
  974. return $entries;
  975. }
  976. /**
  977. * Get HTML for tracked and untracked tables
  978. *
  979. * @param string $db current database
  980. * @param array $urlParams url parameters
  981. * @param string $textDir text direction
  982. *
  983. * @return string HTML
  984. */
  985. public function getHtmlForDbTrackingTables(
  986. string $db,
  987. array $urlParams,
  988. string $textDir
  989. ) {
  990. $relation = $this->relation;
  991. $cfgRelation = $relation->getRelationsParam();
  992. // Prepare statement to get HEAD version
  993. $allTablesQuery = ' SELECT table_name, MAX(version) as version FROM ' .
  994. Util::backquote($cfgRelation['db']) . '.' .
  995. Util::backquote($cfgRelation['tracking']) .
  996. ' WHERE db_name = \'' . $this->dbi->escapeString($db) .
  997. '\' ' .
  998. ' GROUP BY table_name' .
  999. ' ORDER BY table_name ASC';
  1000. $allTablesResult = $relation->queryAsControlUser($allTablesQuery);
  1001. $untrackedTables = $this->getUntrackedTables($db);
  1002. // If a HEAD version exists
  1003. $versions = [];
  1004. $headVersionExists = is_object($allTablesResult)
  1005. && $this->dbi->numRows($allTablesResult) > 0;
  1006. if ($headVersionExists) {
  1007. while ($oneResult = $this->dbi->fetchArray($allTablesResult)) {
  1008. [$tableName, $versionNumber] = $oneResult;
  1009. $tableQuery = ' SELECT * FROM ' .
  1010. Util::backquote($cfgRelation['db']) . '.' .
  1011. Util::backquote($cfgRelation['tracking']) .
  1012. ' WHERE `db_name` = \''
  1013. . $this->dbi->escapeString($db)
  1014. . '\' AND `table_name` = \''
  1015. . $this->dbi->escapeString($tableName)
  1016. . '\' AND `version` = \'' . $versionNumber . '\'';
  1017. $tableResult = $relation->queryAsControlUser($tableQuery);
  1018. $versions[] = $this->dbi->fetchArray($tableResult);
  1019. }
  1020. }
  1021. return $this->template->render('database/tracking/tables', [
  1022. 'db' => $db,
  1023. 'head_version_exists' => $headVersionExists,
  1024. 'untracked_tables_exists' => count($untrackedTables) > 0,
  1025. 'versions' => $versions,
  1026. 'url_params' => $urlParams,
  1027. 'text_dir' => $textDir,
  1028. 'untracked_tables' => $untrackedTables,
  1029. ]);
  1030. }
  1031. /**
  1032. * Helper function: Recursive function for getting table names from $table_list
  1033. *
  1034. * @param array $table_list Table list
  1035. * @param string $db Current database
  1036. * @param bool $testing Testing
  1037. *
  1038. * @return array
  1039. */
  1040. public function extractTableNames(array $table_list, $db, $testing = false)
  1041. {
  1042. global $cfg;
  1043. $untracked_tables = [];
  1044. $sep = $cfg['NavigationTreeTableSeparator'];
  1045. foreach ($table_list as $key => $value) {
  1046. if (is_array($value) && array_key_exists('is' . $sep . 'group', $value) && $value['is' . $sep . 'group']) {
  1047. // Recursion step
  1048. $untracked_tables = array_merge($this->extractTableNames($value, $db, $testing), $untracked_tables);
  1049. } else {
  1050. if (is_array($value) && ($testing || Tracker::getVersion($db, $value['Name']) == -1)) {
  1051. $untracked_tables[] = $value['Name'];
  1052. }
  1053. }
  1054. }
  1055. return $untracked_tables;
  1056. }
  1057. /**
  1058. * Get untracked tables
  1059. *
  1060. * @param string $db current database
  1061. *
  1062. * @return array
  1063. */
  1064. public function getUntrackedTables($db)
  1065. {
  1066. $table_list = Util::getTableList($db);
  1067. //Use helper function to get table list recursively.
  1068. return $this->extractTableNames($table_list, $db);
  1069. }
  1070. }