Tracking.php 43 KB

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