Tracking.php 40 KB

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