sql.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  1. /**
  2. * @fileoverview functions used wherever an sql query form is used
  3. *
  4. * @requires jQuery
  5. * @requires js/functions.js
  6. *
  7. * @test-module Sql
  8. */
  9. /* global isStorageSupported */ // js/config.js
  10. /* global codeMirrorEditor */ // js/functions.js
  11. /* global makeGrid */ // js/makegrid.js
  12. /* global sqlBoxLocked */ // js/functions.js
  13. var Sql = {};
  14. /**
  15. * decode a string URL_encoded
  16. *
  17. * @param {string} str
  18. * @return {string} the URL-decoded string
  19. */
  20. Sql.urlDecode = function (str) {
  21. if (typeof str !== 'undefined') {
  22. return decodeURIComponent(str.replace(/\+/g, '%20'));
  23. }
  24. };
  25. /**
  26. * encode a string URL_decoded
  27. *
  28. * @param {string} str
  29. * @return {string} the URL-encoded string
  30. */
  31. Sql.urlEncode = function (str) {
  32. if (typeof str !== 'undefined') {
  33. return encodeURIComponent(str).replace(/%20/g, '+');
  34. }
  35. };
  36. /**
  37. * Saves SQL query in local storage or cookie
  38. *
  39. * @param {string} query SQL query
  40. * @return {void}
  41. */
  42. Sql.autoSave = function (query) {
  43. if (query) {
  44. var key = Sql.getAutoSavedKey();
  45. if (isStorageSupported('localStorage')) {
  46. window.localStorage.setItem(key, query);
  47. } else {
  48. Cookies.set(key, query);
  49. }
  50. }
  51. };
  52. /**
  53. * Saves SQL query in local storage or cookie
  54. *
  55. * @param {string} db database name
  56. * @param {string} table table name
  57. * @param {string} query SQL query
  58. * @return {void}
  59. */
  60. Sql.showThisQuery = function (db, table, query) {
  61. var showThisQueryObject = {
  62. 'db': db,
  63. 'table': table,
  64. 'query': query
  65. };
  66. if (isStorageSupported('localStorage')) {
  67. window.localStorage.showThisQuery = 1;
  68. window.localStorage.showThisQueryObject = JSON.stringify(showThisQueryObject);
  69. } else {
  70. Cookies.set('showThisQuery', 1);
  71. Cookies.set('showThisQueryObject', JSON.stringify(showThisQueryObject));
  72. }
  73. };
  74. /**
  75. * Set query to codemirror if show this query is
  76. * checked and query for the db and table pair exists
  77. */
  78. Sql.setShowThisQuery = function () {
  79. var db = $('input[name="db"]').val();
  80. var table = $('input[name="table"]').val();
  81. if (isStorageSupported('localStorage')) {
  82. if (window.localStorage.showThisQueryObject !== undefined) {
  83. var storedDb = JSON.parse(window.localStorage.showThisQueryObject).db;
  84. var storedTable = JSON.parse(window.localStorage.showThisQueryObject).table;
  85. var storedQuery = JSON.parse(window.localStorage.showThisQueryObject).query;
  86. }
  87. if (window.localStorage.showThisQuery !== undefined && window.localStorage.showThisQuery === '1') {
  88. $('input[name="show_query"]').prop('checked', true);
  89. if (db === storedDb && table === storedTable) {
  90. if (codeMirrorEditor) {
  91. codeMirrorEditor.setValue(storedQuery);
  92. } else if (document.sqlform) {
  93. document.sqlform.sql_query.value = storedQuery;
  94. }
  95. }
  96. } else {
  97. $('input[name="show_query"]').prop('checked', false);
  98. }
  99. }
  100. };
  101. /**
  102. * Saves SQL query with sort in local storage or cookie
  103. *
  104. * @param {string} query SQL query
  105. * @return {void}
  106. */
  107. Sql.autoSaveWithSort = function (query) {
  108. if (query) {
  109. if (isStorageSupported('localStorage')) {
  110. window.localStorage.setItem('autoSavedSqlSort', query);
  111. } else {
  112. Cookies.set('autoSavedSqlSort', query);
  113. }
  114. }
  115. };
  116. /**
  117. * Clear saved SQL query with sort in local storage or cookie
  118. *
  119. * @return {void}
  120. */
  121. Sql.clearAutoSavedSort = function () {
  122. if (isStorageSupported('localStorage')) {
  123. window.localStorage.removeItem('autoSavedSqlSort');
  124. } else {
  125. Cookies.set('autoSavedSqlSort', '');
  126. }
  127. };
  128. /**
  129. * Get the field name for the current field. Required to construct the query
  130. * for grid editing
  131. *
  132. * @param $tableResults enclosing results table
  133. * @param $thisField jQuery object that points to the current field's tr
  134. *
  135. * @return {string}
  136. */
  137. Sql.getFieldName = function ($tableResults, $thisField) {
  138. var thisFieldIndex = $thisField.index();
  139. // ltr or rtl direction does not impact how the DOM was generated
  140. // check if the action column in the left exist
  141. var leftActionExist = !$tableResults.find('th').first().hasClass('draggable');
  142. // number of column span for checkbox and Actions
  143. var leftActionSkip = leftActionExist ? $tableResults.find('th').first().attr('colspan') - 1 : 0;
  144. // If this column was sorted, the text of the a element contains something
  145. // like <small>1</small> that is useful to indicate the order in case
  146. // of a sort on multiple columns; however, we dont want this as part
  147. // of the column name so we strip it ( .clone() to .end() )
  148. var fieldName = $tableResults.find('thead').find('th').eq(thisFieldIndex - leftActionSkip).find('a').clone() // clone the element
  149. .children() // select all the children
  150. .remove() // remove all of them
  151. .end() // go back to the selected element
  152. .text(); // grab the text
  153. // happens when just one row (headings contain no a)
  154. if (fieldName === '') {
  155. var $heading = $tableResults.find('thead').find('th').eq(thisFieldIndex - leftActionSkip).children('span');
  156. // may contain column comment enclosed in a span - detach it temporarily to read the column name
  157. var $tempColComment = $heading.children().detach();
  158. fieldName = $heading.text();
  159. // re-attach the column comment
  160. $heading.append($tempColComment);
  161. }
  162. fieldName = fieldName.trim();
  163. return fieldName;
  164. };
  165. /**
  166. * Unbind all event handlers before tearing down a page
  167. */
  168. AJAX.registerTeardown('sql.js', function () {
  169. $(document).off('click', 'a.delete_row.ajax');
  170. $(document).off('submit', '.bookmarkQueryForm');
  171. $('input#bkm_label').off('input');
  172. $(document).off('makeGrid', '.sqlqueryresults');
  173. $('#togglequerybox').off('click');
  174. $(document).off('click', '#button_submit_query');
  175. $(document).off('change', '#id_bookmark');
  176. $('input[name=\'bookmark_variable\']').off('keypress');
  177. $(document).off('submit', '#sqlqueryform.ajax');
  178. $(document).off('click', 'input[name=navig].ajax');
  179. $(document).off('submit', 'form[name=\'displayOptionsForm\'].ajax');
  180. $(document).off('mouseenter', 'th.column_heading.pointer');
  181. $(document).off('mouseleave', 'th.column_heading.pointer');
  182. $(document).off('click', 'th.column_heading.marker');
  183. $(document).off('scroll', window);
  184. $(document).off('keyup', '.filter_rows');
  185. if (codeMirrorEditor) {
  186. codeMirrorEditor.off('change');
  187. } else {
  188. $('#sqlquery').off('input propertychange');
  189. }
  190. $('body').off('click', '.navigation .showAllRows');
  191. $('body').off('click', 'a.browse_foreign');
  192. $('body').off('click', '#simulate_dml');
  193. $('body').off('keyup', '#sqlqueryform');
  194. $('body').off('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]');
  195. $(document).off('submit', '.maxRowsForm');
  196. $(document).off('click', '#view_as');
  197. $(document).off('click', '#sqlquery');
  198. });
  199. /**
  200. * @description <p>Ajax scripts for sql and browse pages</p>
  201. *
  202. * Actions ajaxified here:
  203. * <ul>
  204. * <li>Retrieve results of an SQL query</li>
  205. * <li>Paginate the results table</li>
  206. * <li>Sort the results table</li>
  207. * <li>Change table according to display options</li>
  208. * <li>Grid editing of data</li>
  209. * <li>Saving a bookmark</li>
  210. * </ul>
  211. *
  212. * @name document.ready
  213. * @memberOf jQuery
  214. */
  215. AJAX.registerOnload('sql.js', function () {
  216. if (codeMirrorEditor || document.sqlform) {
  217. Sql.setShowThisQuery();
  218. }
  219. $(function () {
  220. if (codeMirrorEditor) {
  221. codeMirrorEditor.on('change', function () {
  222. Sql.autoSave(codeMirrorEditor.getValue());
  223. });
  224. } else {
  225. $('#sqlquery').on('input propertychange', function () {
  226. Sql.autoSave($('#sqlquery').val());
  227. });
  228. var useLocalStorageValue = isStorageSupported('localStorage') && typeof window.localStorage.autoSavedSqlSort !== 'undefined';
  229. // Save sql query with sort
  230. if ($('#RememberSorting') !== undefined && $('#RememberSorting').is(':checked')) {
  231. $('select[name="sql_query"]').on('change', function () {
  232. Sql.autoSaveWithSort($(this).val());
  233. });
  234. $('.sortlink').on('click', function () {
  235. Sql.clearAutoSavedSort();
  236. });
  237. } else {
  238. Sql.clearAutoSavedSort();
  239. }
  240. // If sql query with sort for current table is stored, change sort by key select value
  241. var sortStoredQuery = useLocalStorageValue ? window.localStorage.autoSavedSqlSort : Cookies.get('autoSavedSqlSort');
  242. if (typeof sortStoredQuery !== 'undefined' && sortStoredQuery !== $('select[name="sql_query"]').val() && $('select[name="sql_query"] option[value="' + sortStoredQuery + '"]').length !== 0) {
  243. $('select[name="sql_query"]').val(sortStoredQuery).trigger('change');
  244. }
  245. }
  246. });
  247. // Delete row from SQL results
  248. $(document).on('click', 'a.delete_row.ajax', function (e) {
  249. e.preventDefault();
  250. var question = Functions.sprintf(Messages.strDoYouReally, Functions.escapeHtml($(this).closest('td').find('div').text()));
  251. var $link = $(this);
  252. $link.confirm(question, $link.attr('href'), function (url) {
  253. Functions.ajaxShowMessage();
  254. var argsep = CommonParams.get('arg_separator');
  255. var params = 'ajax_request=1' + argsep + 'is_js_confirmed=1';
  256. var postData = $link.getPostData();
  257. if (postData) {
  258. params += argsep + postData;
  259. }
  260. $.post(url, params, function (data) {
  261. if (data.success) {
  262. Functions.ajaxShowMessage(data.message);
  263. $link.closest('tr').remove();
  264. } else {
  265. Functions.ajaxShowMessage(data.error, false);
  266. }
  267. });
  268. });
  269. });
  270. // Ajaxification for 'Bookmark this SQL query'
  271. $(document).on('submit', '.bookmarkQueryForm', function (e) {
  272. e.preventDefault();
  273. Functions.ajaxShowMessage();
  274. var argsep = CommonParams.get('arg_separator');
  275. $.post($(this).attr('action'), 'ajax_request=1' + argsep + $(this).serialize(), function (data) {
  276. if (data.success) {
  277. Functions.ajaxShowMessage(data.message);
  278. } else {
  279. Functions.ajaxShowMessage(data.error, false);
  280. }
  281. });
  282. });
  283. /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */
  284. $('input#bkm_label').on('input', function () {
  285. $('input#id_bkm_all_users, input#id_bkm_replace').parent().toggle($(this).val().length > 0);
  286. }).trigger('input');
  287. /**
  288. * Attach Event Handler for 'Copy to clipboard'
  289. */
  290. $(document).on('click', '#copyToClipBoard', function (event) {
  291. event.preventDefault();
  292. var textArea = document.createElement('textarea');
  293. //
  294. // *** This styling is an extra step which is likely not required. ***
  295. //
  296. // Why is it here? To ensure:
  297. // 1. the element is able to have focus and selection.
  298. // 2. if element was to flash render it has minimal visual impact.
  299. // 3. less flakyness with selection and copying which **might** occur if
  300. // the textarea element is not visible.
  301. //
  302. // The likelihood is the element won't even render, not even a flash,
  303. // so some of these are just precautions. However in IE the element
  304. // is visible whilst the popup box asking the user for permission for
  305. // the web page to copy to the clipboard.
  306. //
  307. // Place in top-left corner of screen regardless of scroll position.
  308. textArea.style.position = 'fixed';
  309. textArea.style.top = 0;
  310. textArea.style.left = 0;
  311. // Ensure it has a small width and height. Setting to 1px / 1em
  312. // doesn't work as this gives a negative w/h on some browsers.
  313. textArea.style.width = '2em';
  314. textArea.style.height = '2em';
  315. // We don't need padding, reducing the size if it does flash render.
  316. textArea.style.padding = 0;
  317. // Clean up any borders.
  318. textArea.style.border = 'none';
  319. textArea.style.outline = 'none';
  320. textArea.style.boxShadow = 'none';
  321. // Avoid flash of white box if rendered for any reason.
  322. textArea.style.background = 'transparent';
  323. textArea.value = '';
  324. $('#server-breadcrumb a').each(function () {
  325. textArea.value += $(this).data('raw-text') + '/';
  326. });
  327. textArea.value += '\t\t' + window.location.href;
  328. textArea.value += '\n';
  329. $('.alert-success').each(function () {
  330. textArea.value += $(this).text() + '\n\n';
  331. });
  332. $('.sql pre').each(function () {
  333. textArea.value += $(this).text() + '\n\n';
  334. });
  335. $('.table_results .column_heading a').each(function () {
  336. // Don't copy ordering number text within <small> tag
  337. textArea.value += $(this).clone().find('small').remove().end().text() + '\t';
  338. });
  339. textArea.value += '\n';
  340. $('.table_results tbody tr').each(function () {
  341. if ($(this).hasClass('repeating_header_row')) {
  342. return;
  343. }
  344. $(this).find('.data span').each(function () {
  345. // Extract <em> tag for NULL values before converting to string to not mess up formatting
  346. var data = $(this).find('em').length !== 0 ? $(this).find('em')[0] : this;
  347. textArea.value += $(data).text() + '\t';
  348. });
  349. textArea.value += '\n';
  350. });
  351. // eslint-disable-next-line compat/compat
  352. document.body.appendChild(textArea);
  353. textArea.select();
  354. try {
  355. document.execCommand('copy');
  356. } catch (err) {
  357. alert('Sorry! Unable to copy');
  358. }
  359. // eslint-disable-next-line compat/compat
  360. document.body.removeChild(textArea);
  361. }); // end of Copy to Clipboard action
  362. /**
  363. * Attach the {@link makeGrid} function to a custom event, which will be
  364. * triggered manually everytime the table of results is reloaded
  365. * @memberOf jQuery
  366. */
  367. $(document).on('makeGrid', '.sqlqueryresults', function () {
  368. $('.table_results').each(function () {
  369. makeGrid(this);
  370. });
  371. });
  372. /**
  373. * Append the "Show/Hide query box" message to the query input form
  374. *
  375. * @memberOf jQuery
  376. * @name appendToggleSpan
  377. */
  378. // do not add this link more than once
  379. if (!$('#sqlqueryform').find('button').is('#togglequerybox')) {
  380. $('<button class="btn btn-secondary" id="togglequerybox"></button>').html(Messages.strHideQueryBox).appendTo('#sqlqueryform')
  381. // initially hidden because at this point, nothing else
  382. // appears under the link
  383. .hide();
  384. // Attach the toggling of the query box visibility to a click
  385. $('#togglequerybox').on('click', function () {
  386. var $link = $(this);
  387. $link.siblings().slideToggle('fast');
  388. if ($link.text() === Messages.strHideQueryBox) {
  389. $link.text(Messages.strShowQueryBox);
  390. // cheap trick to add a spacer between the menu tabs
  391. // and "Show query box"; feel free to improve!
  392. $('#togglequerybox_spacer').remove();
  393. $link.before('<br id="togglequerybox_spacer">');
  394. } else {
  395. $link.text(Messages.strHideQueryBox);
  396. }
  397. // avoid default click action
  398. return false;
  399. });
  400. }
  401. /**
  402. * Event handler for sqlqueryform.ajax button_submit_query
  403. *
  404. * @memberOf jQuery
  405. */
  406. $(document).on('click', '#button_submit_query', function () {
  407. $('.alert-success,.alert-danger').hide();
  408. // hide already existing error or success message
  409. var $form = $(this).closest('form');
  410. // the Go button related to query submission was clicked,
  411. // instead of the one related to Bookmarks, so empty the
  412. // id_bookmark selector to avoid misinterpretation in
  413. // /import about what needs to be done
  414. $form.find('select[name=id_bookmark]').val('');
  415. var isShowQuery = $('input[name="show_query"]').is(':checked');
  416. if (isShowQuery) {
  417. window.localStorage.showThisQuery = '1';
  418. var db = $('input[name="db"]').val();
  419. var table = $('input[name="table"]').val();
  420. var query;
  421. if (codeMirrorEditor) {
  422. query = codeMirrorEditor.getValue();
  423. } else {
  424. query = $('#sqlquery').val();
  425. }
  426. Sql.showThisQuery(db, table, query);
  427. } else {
  428. window.localStorage.showThisQuery = '0';
  429. }
  430. });
  431. /**
  432. * Event handler to show appropriate number of variable boxes
  433. * based on the bookmarked query
  434. */
  435. $(document).on('change', '#id_bookmark', function () {
  436. var varCount = $(this).find('option:selected').data('varcount');
  437. if (typeof varCount === 'undefined') {
  438. varCount = 0;
  439. }
  440. var $varDiv = $('#bookmarkVariables');
  441. $varDiv.empty();
  442. for (var i = 1; i <= varCount; i++) {
  443. $varDiv.append($('<div class="mb-3">'));
  444. $varDiv.append($('<label for="bookmarkVariable' + i + '">' + Functions.sprintf(Messages.strBookmarkVariable, i) + '</label>'));
  445. $varDiv.append($('<input class="form-control" type="text" size="10" name="bookmark_variable[' + i + ']" id="bookmarkVariable' + i + '">'));
  446. $varDiv.append($('</div>'));
  447. }
  448. if (varCount === 0) {
  449. $varDiv.parent().hide();
  450. } else {
  451. $varDiv.parent().show();
  452. }
  453. });
  454. /**
  455. * Event handler for hitting enter on sqlqueryform bookmark_variable
  456. * (the Variable textfield in Bookmarked SQL query section)
  457. *
  458. * @memberOf jQuery
  459. */
  460. $('input[name=bookmark_variable]').on('keypress', function (event) {
  461. // force the 'Enter Key' to implicitly click the #button_submit_bookmark
  462. var keycode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
  463. if (keycode === 13) {
  464. // keycode for enter key
  465. // When you press enter in the sqlqueryform, which
  466. // has 2 submit buttons, the default is to run the
  467. // #button_submit_query, because of the tabindex
  468. // attribute.
  469. // This submits #button_submit_bookmark instead,
  470. // because when you are in the Bookmarked SQL query
  471. // section and hit enter, you expect it to do the
  472. // same action as the Go button in that section.
  473. $('#button_submit_bookmark').trigger('click');
  474. return false;
  475. } else {
  476. return true;
  477. }
  478. });
  479. /**
  480. * Ajax Event handler for 'SQL Query Submit'
  481. *
  482. * @see Functions.ajaxShowMessage()
  483. * @memberOf jQuery
  484. * @name sqlqueryform_submit
  485. */
  486. $(document).on('submit', '#sqlqueryform.ajax', function (event) {
  487. event.preventDefault();
  488. var $form = $(this);
  489. if (codeMirrorEditor) {
  490. $form[0].elements.sql_query.value = codeMirrorEditor.getValue();
  491. }
  492. if (!Functions.checkSqlQuery($form[0])) {
  493. return false;
  494. }
  495. // remove any div containing a previous error message
  496. $('.alert-danger').remove();
  497. var $msgbox = Functions.ajaxShowMessage();
  498. var $sqlqueryresultsouter = $('#sqlqueryresultsouter');
  499. Functions.prepareForAjaxRequest($form);
  500. var argsep = CommonParams.get('arg_separator');
  501. $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_page_request=true', function (data) {
  502. if (typeof data !== 'undefined' && data.success === true) {
  503. // success happens if the query returns rows or not
  504. // show a message that stays on screen
  505. if (typeof data.action_bookmark !== 'undefined') {
  506. // view only
  507. if ('1' === data.action_bookmark) {
  508. $('#sqlquery').text(data.sql_query);
  509. // send to codemirror if possible
  510. Functions.setQuery(data.sql_query);
  511. }
  512. // delete
  513. if ('2' === data.action_bookmark) {
  514. $('#id_bookmark option[value=\'' + data.id_bookmark + '\']').remove();
  515. // if there are no bookmarked queries now (only the empty option),
  516. // remove the bookmark section
  517. if ($('#id_bookmark option').length === 1) {
  518. $('#fieldsetBookmarkOptions').hide();
  519. $('#fieldsetBookmarkOptionsFooter').hide();
  520. }
  521. }
  522. }
  523. $sqlqueryresultsouter.show().html(data.message);
  524. Functions.highlightSql($sqlqueryresultsouter);
  525. if (data.menu) {
  526. history.replaceState({
  527. menu: data.menu
  528. }, null);
  529. AJAX.handleMenu.replace(data.menu);
  530. }
  531. if (data.params) {
  532. CommonParams.setAll(data.params);
  533. }
  534. if (typeof data.ajax_reload !== 'undefined') {
  535. if (data.ajax_reload.reload) {
  536. if (data.ajax_reload.table_name) {
  537. CommonParams.set('table', data.ajax_reload.table_name);
  538. CommonActions.refreshMain();
  539. } else {
  540. Navigation.reload();
  541. }
  542. }
  543. } else if (typeof data.reload !== 'undefined') {
  544. // this happens if a USE or DROP command was typed
  545. CommonActions.setDb(data.db);
  546. var url;
  547. if (data.db) {
  548. if (data.table) {
  549. url = 'index.php?route=/table/sql';
  550. } else {
  551. url = 'index.php?route=/database/sql';
  552. }
  553. } else {
  554. url = 'index.php?route=/server/sql';
  555. }
  556. CommonActions.refreshMain(url, function () {
  557. $('#sqlqueryresultsouter').show().html(data.message);
  558. Functions.highlightSql($('#sqlqueryresultsouter'));
  559. });
  560. }
  561. $('.sqlqueryresults').trigger('makeGrid');
  562. $('#togglequerybox').show();
  563. if (typeof data.action_bookmark === 'undefined') {
  564. if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) {
  565. if ($('#togglequerybox').siblings(':visible').length > 0) {
  566. $('#togglequerybox').trigger('click');
  567. }
  568. }
  569. }
  570. } else if (typeof data !== 'undefined' && data.success === false) {
  571. // show an error message that stays on screen
  572. $sqlqueryresultsouter.show().html(data.error);
  573. $('html, body').animate({
  574. scrollTop: $(document).height()
  575. }, 200);
  576. }
  577. Functions.ajaxRemoveMessage($msgbox);
  578. }); // end $.post()
  579. }); // end SQL Query submit
  580. /**
  581. * Ajax Event handler for the display options
  582. * @memberOf jQuery
  583. * @name displayOptionsForm_submit
  584. */
  585. $(document).on('submit', 'form[name=\'displayOptionsForm\'].ajax', function (event) {
  586. event.preventDefault();
  587. var $form = $(this);
  588. var $msgbox = Functions.ajaxShowMessage();
  589. var argsep = CommonParams.get('arg_separator');
  590. $.post($form.attr('action'), $form.serialize() + argsep + 'ajax_request=true', function (data) {
  591. Functions.ajaxRemoveMessage($msgbox);
  592. var $sqlqueryresults = $form.parents('.sqlqueryresults');
  593. $sqlqueryresults.html(data.message).trigger('makeGrid');
  594. Functions.highlightSql($sqlqueryresults);
  595. }); // end $.post()
  596. }); // end displayOptionsForm handler
  597. // Filter row handling. --STARTS--
  598. $(document).on('keyup', '.filter_rows', function () {
  599. var uniqueId = $(this).data('for');
  600. var $targetTable = $('.table_results[data-uniqueId=\'' + uniqueId + '\']');
  601. var $headerCells = $targetTable.find('th[data-column]');
  602. var targetColumns = [];
  603. // To handle colspan=4, in case of edit, copy, etc options (Table row links). Add 3 dummy <TH> elements - only when the Table row links are NOT on the "Right"
  604. var rowLinksLocation = $targetTable.find('thead > tr > th').first();
  605. var dummyTh = rowLinksLocation[0].getAttribute('colspan') !== null ? '<th class="hide dummy_th"></th><th class="hide dummy_th"></th><th class="hide dummy_th"></th>' : ''; // Selecting columns that will be considered for filtering and searching.
  606. // Selecting columns that will be considered for filtering and searching.
  607. $headerCells.each(function () {
  608. targetColumns.push($(this).text().trim());
  609. });
  610. var phrase = $(this).val();
  611. // Set same value to both Filter rows fields.
  612. $('.filter_rows[data-for=\'' + uniqueId + '\']').not(this).val(phrase);
  613. // Handle colspan.
  614. $targetTable.find('thead > tr').prepend(dummyTh);
  615. $.uiTableFilter($targetTable, phrase, targetColumns);
  616. $targetTable.find('th.dummy_th').remove();
  617. });
  618. // Filter row handling. --ENDS--
  619. // Prompt to confirm on Show All
  620. $('body').on('click', '.navigation .showAllRows', function (e) {
  621. e.preventDefault();
  622. var $form = $(this).parents('form');
  623. Sql.submitShowAllForm = function () {
  624. var argsep = CommonParams.get('arg_separator');
  625. var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true';
  626. Functions.ajaxShowMessage();
  627. AJAX.source = $form;
  628. $.post($form.attr('action'), submitData, AJAX.responseHandler);
  629. };
  630. if (!$(this).is(':checked')) {
  631. // already showing all rows
  632. Sql.submitShowAllForm();
  633. } else {
  634. $form.confirm(Messages.strShowAllRowsWarning, $form.attr('action'), function () {
  635. Sql.submitShowAllForm();
  636. });
  637. }
  638. });
  639. $('body').on('keyup', '#sqlqueryform', function () {
  640. Functions.handleSimulateQueryButton();
  641. });
  642. /**
  643. * Ajax event handler for 'Simulate DML'.
  644. */
  645. $('body').on('click', '#simulate_dml', function () {
  646. var $form = $('#sqlqueryform');
  647. var query = '';
  648. var delimiter = $('#id_sql_delimiter').val();
  649. var dbName = $form.find('input[name="db"]').val();
  650. if (codeMirrorEditor) {
  651. query = codeMirrorEditor.getValue();
  652. } else {
  653. query = $('#sqlquery').val();
  654. }
  655. if (query.length === 0) {
  656. alert(Messages.strFormEmpty);
  657. $('#sqlquery').trigger('focus');
  658. return false;
  659. }
  660. var $msgbox = Functions.ajaxShowMessage();
  661. $.ajax({
  662. type: 'POST',
  663. url: 'index.php?route=/import/simulate-dml',
  664. data: {
  665. 'server': CommonParams.get('server'),
  666. 'db': dbName,
  667. 'ajax_request': '1',
  668. 'sql_query': query,
  669. 'sql_delimiter': delimiter
  670. },
  671. success: function (response) {
  672. Functions.ajaxRemoveMessage($msgbox);
  673. if (response.success) {
  674. var dialogContent = '<div class="preview_sql">';
  675. if (response.sql_data) {
  676. var len = response.sql_data.length;
  677. for (var i = 0; i < len; i++) {
  678. dialogContent += '<strong>' + Messages.strSQLQuery + '</strong>' + response.sql_data[i].sql_query + Messages.strAffectedRows + ' <a href="' + response.sql_data[i].matched_rows_url + '">' + response.sql_data[i].matched_rows + '</a><br>';
  679. if (i < len - 1) {
  680. dialogContent += '<hr>';
  681. }
  682. }
  683. } else {
  684. dialogContent += response.message;
  685. }
  686. dialogContent += '</div>';
  687. var $dialogContent = $(dialogContent);
  688. var modal = $('#simulateDmlModal');
  689. modal.modal('show');
  690. modal.find('.modal-body').first().html($dialogContent);
  691. modal.on('shown.bs.modal', function () {
  692. Functions.highlightSql(modal);
  693. });
  694. } else {
  695. Functions.ajaxShowMessage(response.error);
  696. }
  697. },
  698. error: function () {
  699. Functions.ajaxShowMessage(Messages.strErrorProcessingRequest);
  700. }
  701. });
  702. });
  703. /**
  704. * Handles multi submits of results browsing page such as edit, delete and export
  705. */
  706. $('body').on('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]', function (e) {
  707. e.preventDefault();
  708. var $button = $(this);
  709. var action = $button.val();
  710. var $form = $button.closest('form');
  711. var argsep = CommonParams.get('arg_separator');
  712. var submitData = $form.serialize() + argsep + 'ajax_request=true' + argsep + 'ajax_page_request=true' + argsep;
  713. Functions.ajaxShowMessage();
  714. AJAX.source = $form;
  715. var url;
  716. if (action === 'edit') {
  717. submitData = submitData + argsep + 'default_action=update';
  718. url = 'index.php?route=/table/change/rows';
  719. } else if (action === 'copy') {
  720. submitData = submitData + argsep + 'default_action=insert';
  721. url = 'index.php?route=/table/change/rows';
  722. } else if (action === 'export') {
  723. url = 'index.php?route=/table/export/rows';
  724. } else if (action === 'delete') {
  725. url = 'index.php?route=/table/delete/confirm';
  726. } else {
  727. return;
  728. }
  729. $.post(url, submitData, AJAX.responseHandler);
  730. });
  731. $(document).on('submit', '.maxRowsForm', function () {
  732. var unlimNumRows = $(this).find('input[name="unlim_num_rows"]').val();
  733. var maxRowsCheck = Functions.checkFormElementInRange(this, 'session_max_rows', Messages.strNotValidRowNumber, 1);
  734. var posCheck = Functions.checkFormElementInRange(this, 'pos', Messages.strNotValidRowNumber, 0, unlimNumRows > 0 ? unlimNumRows - 1 : null);
  735. return maxRowsCheck && posCheck;
  736. });
  737. $('#insertBtn').on('click', function () {
  738. Functions.insertValueQuery();
  739. });
  740. $('#view_as').on('click', function () {
  741. Functions.selectContent(this, sqlBoxLocked, true);
  742. });
  743. $('#sqlquery').on('click', function () {
  744. if ($(this).data('textarea-auto-select') === true) {
  745. Functions.selectContent(this, sqlBoxLocked, true);
  746. }
  747. });
  748. }); // end $()
  749. /**
  750. * Starting from some th, change the class of all td under it.
  751. * If isAddClass is specified, it will be used to determine whether to add or remove the class.
  752. *
  753. * @param $thisTh
  754. * @param {string} newClass
  755. * @param isAddClass
  756. */
  757. Sql.changeClassForColumn = function ($thisTh, newClass, isAddClass) {
  758. // index 0 is the th containing the big T
  759. var thIndex = $thisTh.index();
  760. var hasBigT = $thisTh.closest('tr').children().first().hasClass('column_action');
  761. // .eq() is zero-based
  762. if (hasBigT) {
  763. thIndex--;
  764. }
  765. var $table = $thisTh.parents('.table_results');
  766. if (!$table.length) {
  767. $table = $thisTh.parents('table').siblings('.table_results');
  768. }
  769. var $tds = $table.find('tbody tr').find('td.data').eq(thIndex);
  770. if (isAddClass === undefined) {
  771. $tds.toggleClass(newClass);
  772. } else {
  773. $tds.toggleClass(newClass, isAddClass);
  774. }
  775. };
  776. /**
  777. * Handles browse foreign values modal dialog
  778. *
  779. * @param {object} $thisA reference to the browse foreign value link
  780. */
  781. Sql.browseForeignDialog = function ($thisA) {
  782. var formId = '#browse_foreign_form';
  783. var showAllId = '#foreign_showAll';
  784. var tableId = '#browse_foreign_table';
  785. var filterId = '#input_foreign_filter';
  786. var $dialog = null;
  787. var argSep = CommonParams.get('arg_separator');
  788. var params = $thisA.getPostData();
  789. params += argSep + 'ajax_request=true';
  790. $.post($thisA.attr('href'), params, function (data) {
  791. // Creates browse foreign value dialog
  792. $dialog = $('<div>').append(data.message).dialog({
  793. classes: {
  794. 'ui-dialog-titlebar-close': 'btn-close'
  795. },
  796. title: Messages.strBrowseForeignValues,
  797. width: Math.min($(window).width() - 100, 700),
  798. maxHeight: $(window).height() - 100,
  799. dialogClass: 'browse_foreign_modal',
  800. close: function () {
  801. // remove event handlers attached to elements related to dialog
  802. $(tableId).off('click', 'td a.foreign_value');
  803. $(formId).off('click', showAllId);
  804. $(formId).off('submit');
  805. // remove dialog itself
  806. $(this).remove();
  807. },
  808. modal: true
  809. });
  810. }).done(function () {
  811. var showAll = false;
  812. $(tableId).on('click', 'td a.foreign_value', function (e) {
  813. e.preventDefault();
  814. var $input = $thisA.prev('input[type=text]');
  815. // Check if input exists or get CEdit edit_box
  816. if ($input.length === 0) {
  817. $input = $thisA.closest('.edit_area').prev('.edit_box');
  818. }
  819. // Set selected value as input value
  820. $input.val($(this).data('key'));
  821. // Unchecks the Ignore checkbox for the current row
  822. $input.trigger('change');
  823. $dialog.dialog('close');
  824. });
  825. $(formId).on('click', showAllId, function () {
  826. showAll = true;
  827. });
  828. $(formId).on('submit', function (e) {
  829. e.preventDefault();
  830. // if filter value is not equal to old value
  831. // then reset page number to 1
  832. if ($(filterId).val() !== $(filterId).data('old')) {
  833. $(formId).find('select[name=pos]').val('0');
  834. }
  835. var postParams = $(this).serializeArray();
  836. // if showAll button was clicked to submit form then
  837. // add showAll button parameter to form
  838. if (showAll) {
  839. postParams.push({
  840. name: $(showAllId).attr('name'),
  841. value: $(showAllId).val()
  842. });
  843. }
  844. // updates values in dialog
  845. $.post($(this).attr('action') + '&ajax_request=1', postParams, function (data) {
  846. var $obj = $('<div>').html(data.message);
  847. $(formId).html($obj.find(formId).html());
  848. $(tableId).html($obj.find(tableId).html());
  849. });
  850. showAll = false;
  851. });
  852. });
  853. };
  854. /**
  855. * Get the auto saved query key
  856. * @return {String}
  857. */
  858. Sql.getAutoSavedKey = function () {
  859. var db = $('input[name="db"]').val();
  860. var table = $('input[name="table"]').val();
  861. var key = db;
  862. if (table !== undefined) {
  863. key += '.' + table;
  864. }
  865. return 'autoSavedSql_' + key;
  866. };
  867. Sql.checkSavedQuery = function () {
  868. var key = Sql.getAutoSavedKey();
  869. if (isStorageSupported('localStorage') && typeof window.localStorage.getItem(key) === 'string') {
  870. Functions.ajaxShowMessage(Messages.strPreviousSaveQuery);
  871. } else if (Cookies.get(key)) {
  872. Functions.ajaxShowMessage(Messages.strPreviousSaveQuery);
  873. }
  874. };
  875. AJAX.registerOnload('sql.js', function () {
  876. $('body').on('click', 'a.browse_foreign', function (e) {
  877. e.preventDefault();
  878. Sql.browseForeignDialog($(this));
  879. });
  880. /**
  881. * vertical column highlighting in horizontal mode when hovering over the column header
  882. */
  883. $(document).on('mouseenter', 'th.column_heading.pointer', function () {
  884. Sql.changeClassForColumn($(this), 'hover', true);
  885. });
  886. $(document).on('mouseleave', 'th.column_heading.pointer', function () {
  887. Sql.changeClassForColumn($(this), 'hover', false);
  888. });
  889. /**
  890. * vertical column marking in horizontal mode when clicking the column header
  891. */
  892. $(document).on('click', 'th.column_heading.marker', function () {
  893. Sql.changeClassForColumn($(this), 'marked');
  894. });
  895. /**
  896. * create resizable table
  897. */
  898. $('.sqlqueryresults').trigger('makeGrid');
  899. /**
  900. * Check if there is any saved query
  901. */
  902. if (codeMirrorEditor || document.sqlform) {
  903. Sql.checkSavedQuery();
  904. }
  905. });
  906. /**
  907. * Profiling Chart
  908. */
  909. Sql.makeProfilingChart = function () {
  910. if ($('#profilingchart').length === 0 || $('#profilingchart').html().length !== 0 || !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer) {
  911. return;
  912. }
  913. var data = [];
  914. $.each(JSON.parse($('#profilingChartData').html()), function (key, value) {
  915. data.push([key, parseFloat(value)]);
  916. });
  917. // Remove chart and data divs contents
  918. $('#profilingchart').html('').show();
  919. $('#profilingChartData').html('');
  920. Functions.createProfilingChart('profilingchart', data);
  921. };
  922. /**
  923. * initialize profiling data tables
  924. */
  925. Sql.initProfilingTables = function () {
  926. if (!$.tablesorter) {
  927. return;
  928. }
  929. // Added to allow two direction sorting
  930. $('#profiletable').find('thead th').off('click mousedown');
  931. $('#profiletable').tablesorter({
  932. widgets: ['zebra'],
  933. sortList: [[0, 0]],
  934. textExtraction: function (node) {
  935. if (node.children.length > 0) {
  936. return node.children[0].innerHTML;
  937. } else {
  938. return node.innerHTML;
  939. }
  940. }
  941. });
  942. // Added to allow two direction sorting
  943. $('#profilesummarytable').find('thead th').off('click mousedown');
  944. $('#profilesummarytable').tablesorter({
  945. widgets: ['zebra'],
  946. sortList: [[1, 1]],
  947. textExtraction: function (node) {
  948. if (node.children.length > 0) {
  949. return node.children[0].innerHTML;
  950. } else {
  951. return node.innerHTML;
  952. }
  953. }
  954. });
  955. };
  956. AJAX.registerOnload('sql.js', function () {
  957. Sql.makeProfilingChart();
  958. Sql.initProfilingTables();
  959. });