ajax.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. "use strict";
  2. /* global ErrorReport */
  3. // js/error_report.js
  4. /* global MicroHistory */
  5. // js/microhistory.js
  6. /**
  7. * This object handles ajax requests for pages. It also
  8. * handles the reloading of the main menu and scripts.
  9. *
  10. * @test-module AJAX
  11. */
  12. var AJAX = {
  13. /**
  14. * @var bool active Whether we are busy
  15. */
  16. active: false,
  17. /**
  18. * @var object source The object whose event initialized the request
  19. */
  20. source: null,
  21. /**
  22. * @var object xhr A reference to the ajax request that is currently running
  23. */
  24. xhr: null,
  25. /**
  26. * @var object lockedTargets, list of locked targets
  27. */
  28. lockedTargets: {},
  29. /**
  30. * @var function Callback to execute after a successful request
  31. * Used by PMA_commonFunctions from common.js
  32. */
  33. callback: function callback() {},
  34. /**
  35. * @var bool debug Makes noise in your Firebug console
  36. */
  37. debug: false,
  38. /**
  39. * @var object $msgbox A reference to a jQuery object that links to a message
  40. * box that is generated by Functions.ajaxShowMessage()
  41. */
  42. $msgbox: null,
  43. /**
  44. * Given the filename of a script, returns a hash to be
  45. * used to refer to all the events registered for the file
  46. *
  47. * @param key string key The filename for which to get the event name
  48. *
  49. * @return int
  50. */
  51. hash: function hash(key) {
  52. var newKey = key;
  53. /* https://burtleburtle.net/bob/hash/doobs.html#one */
  54. newKey += '';
  55. var len = newKey.length;
  56. var hash = 0;
  57. var i = 0;
  58. for (; i < len; ++i) {
  59. hash += newKey.charCodeAt(i);
  60. hash += hash << 10;
  61. hash ^= hash >> 6;
  62. }
  63. hash += hash << 3;
  64. hash ^= hash >> 11;
  65. hash += hash << 15;
  66. return Math.abs(hash);
  67. },
  68. /**
  69. * Registers an onload event for a file
  70. *
  71. * @param file string file The filename for which to register the event
  72. * @param func function func The function to execute when the page is ready
  73. *
  74. * @return self For chaining
  75. */
  76. registerOnload: function registerOnload(file, func) {
  77. var eventName = 'onload_' + AJAX.hash(file);
  78. $(document).on(eventName, func);
  79. if (this.debug) {
  80. // eslint-disable-next-line no-console
  81. console.log( // no need to translate
  82. 'Registered event ' + eventName + ' for file ' + file);
  83. }
  84. return this;
  85. },
  86. /**
  87. * Registers a teardown event for a file. This is useful to execute functions
  88. * that unbind events for page elements that are about to be removed.
  89. *
  90. * @param string file The filename for which to register the event
  91. * @param function func The function to execute when
  92. * the page is about to be torn down
  93. *
  94. * @return self For chaining
  95. */
  96. registerTeardown: function registerTeardown(file, func) {
  97. var eventName = 'teardown_' + AJAX.hash(file);
  98. $(document).on(eventName, func);
  99. if (this.debug) {
  100. // eslint-disable-next-line no-console
  101. console.log( // no need to translate
  102. 'Registered event ' + eventName + ' for file ' + file);
  103. }
  104. return this;
  105. },
  106. /**
  107. * Called when a page has finished loading, once for every
  108. * file that registered to the onload event of that file.
  109. *
  110. * @param string file The filename for which to fire the event
  111. *
  112. * @return void
  113. */
  114. fireOnload: function fireOnload(file) {
  115. var eventName = 'onload_' + AJAX.hash(file);
  116. $(document).trigger(eventName);
  117. if (this.debug) {
  118. // eslint-disable-next-line no-console
  119. console.log( // no need to translate
  120. 'Fired event ' + eventName + ' for file ' + file);
  121. }
  122. },
  123. /**
  124. * Called just before a page is torn down, once for every
  125. * file that registered to the teardown event of that file.
  126. *
  127. * @param string file The filename for which to fire the event
  128. *
  129. * @return void
  130. */
  131. fireTeardown: function fireTeardown(file) {
  132. var eventName = 'teardown_' + AJAX.hash(file);
  133. $(document).triggerHandler(eventName);
  134. if (this.debug) {
  135. // eslint-disable-next-line no-console
  136. console.log( // no need to translate
  137. 'Fired event ' + eventName + ' for file ' + file);
  138. }
  139. },
  140. /**
  141. * function to handle lock page mechanism
  142. *
  143. * @param event the event object
  144. *
  145. * @return void
  146. */
  147. lockPageHandler: function lockPageHandler(event) {
  148. // don't consider checkbox event
  149. if (typeof event.target !== 'undefined') {
  150. if (event.target.type === 'checkbox') {
  151. return;
  152. }
  153. }
  154. var newHash = null;
  155. var oldHash = null;
  156. var lockId; // CodeMirror lock
  157. if (event.data.value === 3) {
  158. newHash = event.data.content;
  159. oldHash = true;
  160. lockId = 'cm';
  161. } else {
  162. // Don't lock on enter.
  163. if (0 === event.charCode) {
  164. return;
  165. }
  166. lockId = $(this).data('lock-id');
  167. if (typeof lockId === 'undefined') {
  168. return;
  169. }
  170. /*
  171. * @todo Fix Code mirror does not give correct full value (query)
  172. * in textarea, it returns only the change in content.
  173. */
  174. if (event.data.value === 1) {
  175. newHash = AJAX.hash($(this).val());
  176. } else {
  177. newHash = AJAX.hash($(this).is(':checked'));
  178. }
  179. oldHash = $(this).data('val-hash');
  180. } // Set lock if old value !== new value
  181. // otherwise release lock
  182. if (oldHash !== newHash) {
  183. AJAX.lockedTargets[lockId] = true;
  184. } else {
  185. delete AJAX.lockedTargets[lockId];
  186. } // Show lock icon if locked targets is not empty.
  187. // otherwise remove lock icon
  188. if (!jQuery.isEmptyObject(AJAX.lockedTargets)) {
  189. $('#lock_page_icon').html(Functions.getImage('s_lock', Messages.strLockToolTip).toString());
  190. } else {
  191. $('#lock_page_icon').html('');
  192. }
  193. },
  194. /**
  195. * resets the lock
  196. *
  197. * @return void
  198. */
  199. resetLock: function resetLock() {
  200. AJAX.lockedTargets = {};
  201. $('#lock_page_icon').html('');
  202. },
  203. handleMenu: {
  204. replace: function replace(content) {
  205. $('#floating_menubar').html(content) // Remove duplicate wrapper
  206. // TODO: don't send it in the response
  207. .children().first().remove();
  208. $('#topmenu').menuResizer(Functions.mainMenuResizerCallback);
  209. }
  210. },
  211. /**
  212. * Event handler for clicks on links and form submissions
  213. *
  214. * @param object e Event data
  215. *
  216. * @return void
  217. */
  218. requestHandler: function requestHandler(event) {
  219. // In some cases we don't want to handle the request here and either
  220. // leave the browser deal with it natively (e.g: file download)
  221. // or leave an existing ajax event handler present elsewhere deal with it
  222. var href = $(this).attr('href');
  223. if (typeof event !== 'undefined' && (event.shiftKey || event.ctrlKey)) {
  224. return true;
  225. } else if ($(this).attr('target')) {
  226. return true;
  227. } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
  228. // reset the lockedTargets object, as specified AJAX operation has finished
  229. AJAX.resetLock();
  230. return true;
  231. } else if (href && href.match(/^#/)) {
  232. return true;
  233. } else if (href && href.match(/^mailto/)) {
  234. return true;
  235. } else if ($(this).hasClass('ui-datepicker-next') || $(this).hasClass('ui-datepicker-prev')) {
  236. return true;
  237. }
  238. if (typeof event !== 'undefined') {
  239. event.preventDefault();
  240. event.stopImmediatePropagation();
  241. } // triggers a confirm dialog if:
  242. // the user has performed some operations on loaded page
  243. // the user clicks on some link, (won't trigger for buttons)
  244. // the click event is not triggered by script
  245. if (typeof event !== 'undefined' && event.type === 'click' && event.isTrigger !== true && !jQuery.isEmptyObject(AJAX.lockedTargets) && confirm(Messages.strConfirmNavigation) === false) {
  246. return false;
  247. }
  248. AJAX.resetLock();
  249. var isLink = !!href || false;
  250. var previousLinkAborted = false;
  251. if (AJAX.active === true) {
  252. // Cancel the old request if abortable, when the user requests
  253. // something else. Otherwise silently bail out, as there is already
  254. // a request well in progress.
  255. if (AJAX.xhr) {
  256. // In case of a link request, attempt aborting
  257. AJAX.xhr.abort();
  258. if (AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') {
  259. // If aborted
  260. AJAX.$msgbox = Functions.ajaxShowMessage(Messages.strAbortedRequest);
  261. AJAX.active = false;
  262. AJAX.xhr = null;
  263. previousLinkAborted = true;
  264. } else {
  265. // If can't abort
  266. return false;
  267. }
  268. } else {
  269. // In case submitting a form, don't attempt aborting
  270. return false;
  271. }
  272. }
  273. AJAX.source = $(this);
  274. $('html, body').animate({
  275. scrollTop: 0
  276. }, 'fast');
  277. var url = isLink ? href : $(this).attr('action');
  278. var argsep = CommonParams.get('arg_separator');
  279. var params = 'ajax_request=true' + argsep + 'ajax_page_request=true';
  280. var dataPost = AJAX.source.getPostData();
  281. if (!isLink) {
  282. params += argsep + $(this).serialize();
  283. } else if (dataPost) {
  284. params += argsep + dataPost;
  285. isLink = false;
  286. }
  287. if (!(history && history.pushState)) {
  288. // Add a list of menu hashes that we have in the cache to the request
  289. params += MicroHistory.menus.getRequestParam();
  290. }
  291. if (AJAX.debug) {
  292. // eslint-disable-next-line no-console
  293. console.log('Loading: ' + url); // no need to translate
  294. }
  295. if (isLink) {
  296. AJAX.active = true;
  297. AJAX.$msgbox = Functions.ajaxShowMessage(); // Save reference for the new link request
  298. AJAX.xhr = $.get(url, params, AJAX.responseHandler);
  299. if (history && history.pushState) {
  300. var state = {
  301. url: href
  302. };
  303. if (previousLinkAborted) {
  304. // hack: there is already an aborted entry on stack
  305. // so just modify the aborted one
  306. history.replaceState(state, null, href);
  307. } else {
  308. history.pushState(state, null, href);
  309. }
  310. }
  311. } else {
  312. /**
  313. * Manually fire the onsubmit event for the form, if any.
  314. * The event was saved in the jQuery data object by an onload
  315. * handler defined below. Workaround for bug #3583316
  316. */
  317. var onsubmit = $(this).data('onsubmit'); // Submit the request if there is no onsubmit handler
  318. // or if it returns a value that evaluates to true
  319. if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) {
  320. AJAX.active = true;
  321. AJAX.$msgbox = Functions.ajaxShowMessage();
  322. if ($(this).attr('id') === 'login_form') {
  323. $.post(url, params, AJAX.loginResponseHandler);
  324. } else {
  325. $.post(url, params, AJAX.responseHandler);
  326. }
  327. }
  328. }
  329. },
  330. /**
  331. * Response handler to handle login request from login modal after session expiration
  332. *
  333. * To refer to self use 'AJAX', instead of 'this' as this function
  334. * is called in the jQuery context.
  335. *
  336. * @param object data Event data
  337. *
  338. * @return void
  339. */
  340. loginResponseHandler: function loginResponseHandler(data) {
  341. if (typeof data === 'undefined' || data === null) {
  342. return;
  343. }
  344. Functions.ajaxRemoveMessage(AJAX.$msgbox);
  345. CommonParams.set('token', data.new_token);
  346. AJAX.scriptHandler.load([]);
  347. if (data.displayMessage) {
  348. $('#page_content').prepend(data.displayMessage);
  349. Functions.highlightSql($('#page_content'));
  350. }
  351. $('#pma_errors').remove();
  352. var msg = '';
  353. if (data.errSubmitMsg) {
  354. msg = data.errSubmitMsg;
  355. }
  356. if (data.errors) {
  357. $('<div></div>', {
  358. id: 'pma_errors',
  359. class: 'clearfloat'
  360. }).insertAfter('#selflink').append(data.errors); // bind for php error reporting forms (bottom)
  361. $('#pma_ignore_errors_bottom').on('click', function (e) {
  362. e.preventDefault();
  363. Functions.ignorePhpErrors();
  364. });
  365. $('#pma_ignore_all_errors_bottom').on('click', function (e) {
  366. e.preventDefault();
  367. Functions.ignorePhpErrors(false);
  368. }); // In case of 'sendErrorReport'='always'
  369. // submit the hidden error reporting form.
  370. if (data.sendErrorAlways === '1' && data.stopErrorReportLoop !== '1') {
  371. $('#pma_report_errors_form').trigger('submit');
  372. Functions.ajaxShowMessage(Messages.phpErrorsBeingSubmitted, false);
  373. $('html, body').animate({
  374. scrollTop: $(document).height()
  375. }, 'slow');
  376. } else if (data.promptPhpErrors) {
  377. // otherwise just prompt user if it is set so.
  378. msg = msg + Messages.phpErrorsFound; // scroll to bottom where all the errors are displayed.
  379. $('html, body').animate({
  380. scrollTop: $(document).height()
  381. }, 'slow');
  382. }
  383. }
  384. Functions.ajaxShowMessage(msg, false); // bind for php error reporting forms (popup)
  385. $('#pma_ignore_errors_popup').on('click', function () {
  386. Functions.ignorePhpErrors();
  387. });
  388. $('#pma_ignore_all_errors_popup').on('click', function () {
  389. Functions.ignorePhpErrors(false);
  390. });
  391. if (typeof data.success !== 'undefined' && data.success) {
  392. // reload page if user trying to login has changed
  393. if (CommonParams.get('user') !== data.params.user) {
  394. window.location = 'index.php';
  395. Functions.ajaxShowMessage(Messages.strLoading, false);
  396. AJAX.active = false;
  397. AJAX.xhr = null;
  398. return;
  399. } // remove the login modal if the login is successful otherwise show error.
  400. if (typeof data.logged_in !== 'undefined' && data.logged_in === 1) {
  401. if ($('#modalOverlay').length) {
  402. $('#modalOverlay').remove();
  403. }
  404. $('fieldset.disabled_for_expiration').removeAttr('disabled').removeClass('disabled_for_expiration');
  405. AJAX.fireTeardown('functions.js');
  406. AJAX.fireOnload('functions.js');
  407. }
  408. if (typeof data.new_token !== 'undefined') {
  409. $('input[name=token]').val(data.new_token);
  410. }
  411. } else if (typeof data.logged_in !== 'undefined' && data.logged_in === 0) {
  412. $('#modalOverlay').replaceWith(data.error);
  413. } else {
  414. Functions.ajaxShowMessage(data.error, false);
  415. AJAX.active = false;
  416. AJAX.xhr = null;
  417. Functions.handleRedirectAndReload(data);
  418. if (data.fieldWithError) {
  419. $(':input.error').removeClass('error');
  420. $('#' + data.fieldWithError).addClass('error');
  421. }
  422. }
  423. },
  424. /**
  425. * Called after the request that was initiated by this.requestHandler()
  426. * has completed successfully or with a caught error. For completely
  427. * failed requests or requests with uncaught errors, see the .ajaxError
  428. * handler at the bottom of this file.
  429. *
  430. * To refer to self use 'AJAX', instead of 'this' as this function
  431. * is called in the jQuery context.
  432. *
  433. * @param object e Event data
  434. *
  435. * @return void
  436. */
  437. responseHandler: function responseHandler(data) {
  438. if (typeof data === 'undefined' || data === null) {
  439. return;
  440. }
  441. if (typeof data.success !== 'undefined' && data.success) {
  442. $('html, body').animate({
  443. scrollTop: 0
  444. }, 'fast');
  445. Functions.ajaxRemoveMessage(AJAX.$msgbox);
  446. if (data.redirect) {
  447. Functions.ajaxShowMessage(data.redirect, false);
  448. AJAX.active = false;
  449. AJAX.xhr = null;
  450. return;
  451. }
  452. AJAX.scriptHandler.reset(function () {
  453. if (data.reloadNavigation) {
  454. Navigation.reload();
  455. }
  456. if (data.title) {
  457. $('title').replaceWith(data.title);
  458. }
  459. if (data.menu) {
  460. if (history && history.pushState) {
  461. var state = {
  462. url: data.selflink,
  463. menu: data.menu
  464. };
  465. history.replaceState(state, null);
  466. AJAX.handleMenu.replace(data.menu);
  467. } else {
  468. MicroHistory.menus.replace(data.menu);
  469. MicroHistory.menus.add(data.menuHash, data.menu);
  470. }
  471. } else if (data.menuHash) {
  472. if (!(history && history.pushState)) {
  473. MicroHistory.menus.replace(MicroHistory.menus.get(data.menuHash));
  474. }
  475. }
  476. if (data.disableNaviSettings) {
  477. Navigation.disableSettings();
  478. } else {
  479. Navigation.ensureSettings(data.selflink);
  480. } // Remove all containers that may have
  481. // been added outside of #page_content
  482. $('body').children().not('#pma_navigation').not('#floating_menubar').not('#page_nav_icons').not('#page_content').not('#selflink').not('#pma_header').not('#pma_footer').not('#pma_demo').not('#pma_console_container').not('#prefs_autoload').remove(); // Replace #page_content with new content
  483. if (data.message && data.message.length > 0) {
  484. $('#page_content').replaceWith('<div id=\'page_content\'>' + data.message + '</div>');
  485. Functions.highlightSql($('#page_content'));
  486. Functions.checkNumberOfFields();
  487. }
  488. if (data.selflink) {
  489. var source = data.selflink.split('?')[0]; // Check for faulty links
  490. var $selflinkReplace = {
  491. 'index.php?route=/import': 'index.php?route=/table/sql',
  492. 'index.php?route=/table/chart': 'index.php?route=/sql',
  493. 'index.php?route=/table/gis-visualization': 'index.php?route=/sql'
  494. };
  495. if ($selflinkReplace[source]) {
  496. var replacement = $selflinkReplace[source];
  497. data.selflink = data.selflink.replace(source, replacement);
  498. }
  499. $('#selflink').find('> a').attr('href', data.selflink);
  500. }
  501. if (data.params) {
  502. CommonParams.setAll(data.params);
  503. }
  504. if (data.scripts) {
  505. AJAX.scriptHandler.load(data.scripts);
  506. }
  507. if (data.selflink && data.scripts && data.menuHash && data.params) {
  508. if (!(history && history.pushState)) {
  509. MicroHistory.add(data.selflink, data.scripts, data.menuHash, data.params, AJAX.source.attr('rel'));
  510. }
  511. }
  512. if (data.displayMessage) {
  513. $('#page_content').prepend(data.displayMessage);
  514. Functions.highlightSql($('#page_content'));
  515. }
  516. $('#pma_errors').remove();
  517. var msg = '';
  518. if (data.errSubmitMsg) {
  519. msg = data.errSubmitMsg;
  520. }
  521. if (data.errors) {
  522. $('<div></div>', {
  523. id: 'pma_errors',
  524. class: 'clearfloat'
  525. }).insertAfter('#selflink').append(data.errors); // bind for php error reporting forms (bottom)
  526. $('#pma_ignore_errors_bottom').on('click', function (e) {
  527. e.preventDefault();
  528. Functions.ignorePhpErrors();
  529. });
  530. $('#pma_ignore_all_errors_bottom').on('click', function (e) {
  531. e.preventDefault();
  532. Functions.ignorePhpErrors(false);
  533. }); // In case of 'sendErrorReport'='always'
  534. // submit the hidden error reporting form.
  535. if (data.sendErrorAlways === '1' && data.stopErrorReportLoop !== '1') {
  536. $('#pma_report_errors_form').trigger('submit');
  537. Functions.ajaxShowMessage(Messages.phpErrorsBeingSubmitted, false);
  538. $('html, body').animate({
  539. scrollTop: $(document).height()
  540. }, 'slow');
  541. } else if (data.promptPhpErrors) {
  542. // otherwise just prompt user if it is set so.
  543. msg = msg + Messages.phpErrorsFound; // scroll to bottom where all the errors are displayed.
  544. $('html, body').animate({
  545. scrollTop: $(document).height()
  546. }, 'slow');
  547. }
  548. }
  549. Functions.ajaxShowMessage(msg, false); // bind for php error reporting forms (popup)
  550. $('#pma_ignore_errors_popup').on('click', function () {
  551. Functions.ignorePhpErrors();
  552. });
  553. $('#pma_ignore_all_errors_popup').on('click', function () {
  554. Functions.ignorePhpErrors(false);
  555. });
  556. if (typeof AJAX.callback === 'function') {
  557. AJAX.callback.call();
  558. }
  559. AJAX.callback = function () {};
  560. });
  561. } else {
  562. Functions.ajaxShowMessage(data.error, false);
  563. Functions.ajaxRemoveMessage(AJAX.$msgbox);
  564. var $ajaxError = $('<div></div>');
  565. $ajaxError.attr({
  566. 'id': 'ajaxError'
  567. });
  568. $('#page_content').append($ajaxError);
  569. $ajaxError.html(data.error);
  570. $('html, body').animate({
  571. scrollTop: $(document).height()
  572. }, 200);
  573. AJAX.active = false;
  574. AJAX.xhr = null;
  575. Functions.handleRedirectAndReload(data);
  576. if (data.fieldWithError) {
  577. $(':input.error').removeClass('error');
  578. $('#' + data.fieldWithError).addClass('error');
  579. }
  580. }
  581. },
  582. /**
  583. * This object is in charge of downloading scripts,
  584. * keeping track of what's downloaded and firing
  585. * the onload event for them when the page is ready.
  586. */
  587. scriptHandler: {
  588. /**
  589. * @var array scripts The list of files already downloaded
  590. */
  591. scripts: [],
  592. /**
  593. * @var string scriptsVersion version of phpMyAdmin from which the
  594. * scripts have been loaded
  595. */
  596. scriptsVersion: null,
  597. /**
  598. * @var array scriptsToBeLoaded The list of files that
  599. * need to be downloaded
  600. */
  601. scriptsToBeLoaded: [],
  602. /**
  603. * @var array scriptsToBeFired The list of files for which
  604. * to fire the onload and unload events
  605. */
  606. scriptsToBeFired: [],
  607. scriptsCompleted: false,
  608. /**
  609. * Records that a file has been downloaded
  610. *
  611. * @param string file The filename
  612. * @param string fire Whether this file will be registering
  613. * onload/teardown events
  614. *
  615. * @return self For chaining
  616. */
  617. add: function add(file, fire) {
  618. this.scripts.push(file);
  619. if (fire) {
  620. // Record whether to fire any events for the file
  621. // This is necessary to correctly tear down the initial page
  622. this.scriptsToBeFired.push(file);
  623. }
  624. return this;
  625. },
  626. /**
  627. * Download a list of js files in one request
  628. *
  629. * @param array files An array of filenames and flags
  630. *
  631. * @return void
  632. */
  633. load: function load(files, callback) {
  634. var self = this;
  635. var i; // Clear loaded scripts if they are from another version of phpMyAdmin.
  636. // Depends on common params being set before loading scripts in responseHandler
  637. if (self.scriptsVersion === null) {
  638. self.scriptsVersion = CommonParams.get('PMA_VERSION');
  639. } else if (self.scriptsVersion !== CommonParams.get('PMA_VERSION')) {
  640. self.scripts = [];
  641. self.scriptsVersion = CommonParams.get('PMA_VERSION');
  642. }
  643. self.scriptsCompleted = false;
  644. self.scriptsToBeFired = []; // We need to first complete list of files to load
  645. // as next loop will directly fire requests to load them
  646. // and that triggers removal of them from
  647. // self.scriptsToBeLoaded
  648. for (i in files) {
  649. self.scriptsToBeLoaded.push(files[i].name);
  650. if (files[i].fire) {
  651. self.scriptsToBeFired.push(files[i].name);
  652. }
  653. }
  654. for (i in files) {
  655. var script = files[i].name; // Only for scripts that we don't already have
  656. if ($.inArray(script, self.scripts) === -1) {
  657. this.add(script);
  658. this.appendScript(script, callback);
  659. } else {
  660. self.done(script, callback);
  661. }
  662. } // Trigger callback if there is nothing else to load
  663. self.done(null, callback);
  664. },
  665. /**
  666. * Called whenever all files are loaded
  667. *
  668. * @return void
  669. */
  670. done: function done(script, callback) {
  671. if (typeof ErrorReport !== 'undefined') {
  672. ErrorReport.wrapGlobalFunctions();
  673. }
  674. if ($.inArray(script, this.scriptsToBeFired)) {
  675. AJAX.fireOnload(script);
  676. }
  677. if ($.inArray(script, this.scriptsToBeLoaded)) {
  678. this.scriptsToBeLoaded.splice($.inArray(script, this.scriptsToBeLoaded), 1);
  679. }
  680. if (script === null) {
  681. this.scriptsCompleted = true;
  682. }
  683. /* We need to wait for last signal (with null) or last script load */
  684. AJAX.active = this.scriptsToBeLoaded.length > 0 || !this.scriptsCompleted;
  685. /* Run callback on last script */
  686. if (!AJAX.active && typeof callback === 'function') {
  687. callback();
  688. }
  689. },
  690. /**
  691. * Appends a script element to the head to load the scripts
  692. *
  693. * @return void
  694. */
  695. appendScript: function appendScript(name, callback) {
  696. var head = document.head || document.getElementsByTagName('head')[0];
  697. var script = document.createElement('script');
  698. var self = this;
  699. script.type = 'text/javascript';
  700. var file = name.indexOf('vendor/') !== -1 ? name : 'dist/' + name;
  701. script.src = 'js/' + file + '?' + 'v=' + encodeURIComponent(CommonParams.get('PMA_VERSION'));
  702. script.async = false;
  703. script.onload = function () {
  704. self.done(name, callback);
  705. };
  706. head.appendChild(script);
  707. },
  708. /**
  709. * Fires all the teardown event handlers for the current page
  710. * and rebinds all forms and links to the request handler
  711. *
  712. * @param function callback The callback to call after resetting
  713. *
  714. * @return void
  715. */
  716. reset: function reset(callback) {
  717. for (var i in this.scriptsToBeFired) {
  718. AJAX.fireTeardown(this.scriptsToBeFired[i]);
  719. }
  720. this.scriptsToBeFired = [];
  721. /**
  722. * Re-attach a generic event handler to clicks
  723. * on pages and submissions of forms
  724. */
  725. $(document).off('click', 'a').on('click', 'a', AJAX.requestHandler);
  726. $(document).off('submit', 'form').on('submit', 'form', AJAX.requestHandler);
  727. if (!(history && history.pushState)) {
  728. MicroHistory.update();
  729. }
  730. callback();
  731. }
  732. }
  733. };
  734. /**
  735. * Here we register a function that will remove the onsubmit event from all
  736. * forms that will be handled by the generic page loader. We then save this
  737. * event handler in the "jQuery data", so that we can fire it up later in
  738. * AJAX.requestHandler().
  739. *
  740. * See bug #3583316
  741. */
  742. AJAX.registerOnload('functions.js', function () {
  743. // Registering the onload event for functions.js
  744. // ensures that it will be fired for all pages
  745. $('form').not('.ajax').not('.disableAjax').each(function () {
  746. if ($(this).attr('onsubmit')) {
  747. $(this).data('onsubmit', this.onsubmit).attr('onsubmit', '');
  748. }
  749. });
  750. var $pageContent = $('#page_content');
  751. /**
  752. * Workaround for passing submit button name,value on ajax form submit
  753. * by appending hidden element with submit button name and value.
  754. */
  755. $pageContent.on('click', 'form input[type=submit]', function () {
  756. var buttonName = $(this).attr('name');
  757. if (typeof buttonName === 'undefined') {
  758. return;
  759. }
  760. $(this).closest('form').append($('<input>', {
  761. 'type': 'hidden',
  762. 'name': buttonName,
  763. 'value': $(this).val()
  764. }));
  765. });
  766. /**
  767. * Attach event listener to events when user modify visible
  768. * Input,Textarea and select fields to make changes in forms
  769. */
  770. $pageContent.on('keyup change', 'form.lock-page textarea, ' + 'form.lock-page input[type="text"], ' + 'form.lock-page input[type="number"], ' + 'form.lock-page select', {
  771. value: 1
  772. }, AJAX.lockPageHandler);
  773. $pageContent.on('change', 'form.lock-page input[type="checkbox"], ' + 'form.lock-page input[type="radio"]', {
  774. value: 2
  775. }, AJAX.lockPageHandler);
  776. /**
  777. * Reset lock when lock-page form reset event is fired
  778. * Note: reset does not bubble in all browser so attach to
  779. * form directly.
  780. */
  781. $('form.lock-page').on('reset', function () {
  782. AJAX.resetLock();
  783. });
  784. });
  785. /**
  786. * Page load event handler
  787. */
  788. $(function () {
  789. var menuContent = $('<div></div>').append($('#server-breadcrumb').clone()).append($('#topmenucontainer').clone()).html();
  790. if (history && history.pushState) {
  791. // set initial state reload
  792. var initState = 'state' in window.history && window.history.state !== null;
  793. var initURL = $('#selflink').find('> a').attr('href') || location.href;
  794. var state = {
  795. url: initURL,
  796. menu: menuContent
  797. };
  798. history.replaceState(state, null);
  799. $(window).on('popstate', function (event) {
  800. var initPop = !initState && location.href === initURL;
  801. initState = true; // check if popstate fired on first page itself
  802. if (initPop) {
  803. return;
  804. }
  805. var state = event.originalEvent.state;
  806. if (state && state.menu) {
  807. AJAX.$msgbox = Functions.ajaxShowMessage();
  808. var params = 'ajax_request=true' + CommonParams.get('arg_separator') + 'ajax_page_request=true';
  809. var url = state.url || location.href;
  810. $.get(url, params, AJAX.responseHandler); // TODO: Check if sometimes menu is not retrieved from server,
  811. // Not sure but it seems menu was missing only for printview which
  812. // been removed lately, so if it's right some dead menu checks/fallbacks
  813. // may need to be removed from this file and Header.php
  814. // AJAX.handleMenu.replace(event.originalEvent.state.menu);
  815. }
  816. });
  817. } else {
  818. // Fallback to microhistory mechanism
  819. AJAX.scriptHandler.load([{
  820. 'name': 'microhistory.js',
  821. 'fire': 1
  822. }], function () {
  823. // The cache primer is set by the footer class
  824. if (MicroHistory.primer.url) {
  825. MicroHistory.menus.add(MicroHistory.primer.menuHash, menuContent);
  826. }
  827. $(function () {
  828. // Queue up this event twice to make sure that we get a copy
  829. // of the page after all other onload events have been fired
  830. if (MicroHistory.primer.url) {
  831. MicroHistory.add(MicroHistory.primer.url, MicroHistory.primer.scripts, MicroHistory.primer.menuHash);
  832. }
  833. });
  834. });
  835. }
  836. });
  837. /**
  838. * Attach a generic event handler to clicks
  839. * on pages and submissions of forms
  840. */
  841. $(document).on('click', 'a', AJAX.requestHandler);
  842. $(document).on('submit', 'form', AJAX.requestHandler);
  843. /**
  844. * Gracefully handle fatal server errors
  845. * (e.g: 500 - Internal server error)
  846. */
  847. $(document).on('ajaxError', function (event, request) {
  848. if (AJAX.debug) {
  849. // eslint-disable-next-line no-console
  850. console.log('AJAX error: status=' + request.status + ', text=' + request.statusText);
  851. } // Don't handle aborted requests
  852. if (request.status !== 0 || request.statusText !== 'abort') {
  853. var details = '';
  854. var state = request.state();
  855. if (request.status !== 0) {
  856. details += '<div>' + Functions.escapeHtml(Functions.sprintf(Messages.strErrorCode, request.status)) + '</div>';
  857. }
  858. details += '<div>' + Functions.escapeHtml(Functions.sprintf(Messages.strErrorText, request.statusText + ' (' + state + ')')) + '</div>';
  859. if (state === 'rejected' || state === 'timeout') {
  860. details += '<div>' + Functions.escapeHtml(Messages.strErrorConnection) + '</div>';
  861. }
  862. Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert">' + Messages.strErrorProcessingRequest + details + '</div>', false);
  863. AJAX.active = false;
  864. AJAX.xhr = null;
  865. }
  866. });