diff --git a/.babelrc b/.babelrc
index 0339d5d0ee..4cfddf102a 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,4 @@
{
- "presets": ["env"]
+ "presets": ["env"],
+ "plugins": ["syntax-dynamic-import"]
}
diff --git a/.eslintrc.json b/.eslintrc.json
index 384c0813a0..e570104acf 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -5,9 +5,12 @@
"node": true,
"es6": true
},
+ "parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
+ "allowImportExportEverywhere": true,
+ "codeFrame": false,
"ecmaFeatures": {
"jsx": false
}
diff --git a/.gitignore b/.gitignore
index 0c05e127de..865ba315de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,3 +55,4 @@ composer.lock
/vendor/
# NPM
/node_modules/
+yarn.lock
diff --git a/js/common_params.php b/js/common_params.php
new file mode 100644
index 0000000000..d7a5cf7c39
--- /dev/null
+++ b/js/common_params.php
@@ -0,0 +1,40 @@
+getJsParams() as $name => $value) {
+ Sanitize::printJsValue("common_params['" . $name . "']", $value);
+}
+
+?>
diff --git a/js/functions.js b/js/functions.js
index f3a66b5a13..23003167c0 100644
--- a/js/functions.js
+++ b/js/functions.js
@@ -5105,3 +5105,63 @@ jQuery.fn.getPostData = function () {
}
return dataPost;
};
+
+/**
+ * @todo REMOVE THESE FUNCTIONS AFTER COMPLETE CODE BEING MODULAR
+ *
+ * Copy of functions copied from different files to make them globally
+ * available so that build does not break during modularisation
+ */
+/**
+ * server_privilages.js
+ */
+/**
+ * Validates the "add a user" form
+ *
+ * @return boolean whether the form is validated or not
+ */
+function checkAddUser (the_form) {
+ if (the_form.elements.pred_hostname.value === 'userdefined' && the_form.elements.hostname.value === '') {
+ alert(PMA_messages.strHostEmpty);
+ the_form.elements.hostname.focus();
+ return false;
+ }
+
+ if (the_form.elements.pred_username.value === 'userdefined' && the_form.elements.username.value === '') {
+ alert(PMA_messages.strUserEmpty);
+ the_form.elements.username.focus();
+ return false;
+ }
+
+ return PMA_checkPassword($(the_form));
+} // end of the 'checkAddUser()' function
+
+function checkPasswordStrength (value, meter_obj, meter_object_label, username) {
+ // List of words we don't want to appear in the password
+ var customDict = [
+ 'phpmyadmin',
+ 'mariadb',
+ 'mysql',
+ 'php',
+ 'my',
+ 'admin',
+ ];
+ if (username !== null) {
+ customDict.push(username);
+ }
+ var zxcvbn_obj = zxcvbn(value, customDict);
+ var strength = zxcvbn_obj.score;
+ strength = parseInt(strength);
+ meter_obj.val(strength);
+ switch (strength) {
+ case 0: meter_object_label.html(PMA_messages.strExtrWeak);
+ break;
+ case 1: meter_object_label.html(PMA_messages.strVeryWeak);
+ break;
+ case 2: meter_object_label.html(PMA_messages.strWeak);
+ break;
+ case 3: meter_object_label.html(PMA_messages.strGood);
+ break;
+ case 4: meter_object_label.html(PMA_messages.strStrong);
+ }
+}
diff --git a/js/messages.php b/js/messages.php
index 2e91f0d1bb..3a82821f93 100644
--- a/js/messages.php
+++ b/js/messages.php
@@ -755,6 +755,26 @@ foreach ($js_messages as $name => $js_message) {
Sanitize::printJsValue("PMA_messages['" . $name . "']", $js_message);
}
+/* for new js code */
+/* Calendar */
+echo "var globalVars = new Array();\n";
+echo "globalVars['themeCalendarImage'] = '" , $GLOBALS['pmaThemeImage']
+ , 'b_calendar.png' , "';\n";
+
+/* Image path */
+echo "globalVars['pmaThemeImage'] = '" , $GLOBALS['pmaThemeImage'] , "';\n";
+
+echo "globalVars['mysql_doc_template'] = '" , PhpMyAdmin\Util::getMySQLDocuURL('%s')
+ , "';\n";
+
+//Max input vars allowed by PHP.
+$maxInputVars = ini_get('max_input_vars');
+echo "globalVars['maxInputVars'] = '"
+ , (false === $maxInputVars || '' == $maxInputVars ? 'false' : (int)$maxInputVars)
+ , ';' . "'\n";
+/* for new js code */
+
+/* for old js code */
/* Calendar */
echo "var themeCalendarImage = '" , $GLOBALS['pmaThemeImage']
, 'b_calendar.png' , "';\n";
@@ -770,7 +790,144 @@ $maxInputVars = ini_get('max_input_vars');
echo 'var maxInputVars = '
, (false === $maxInputVars || '' == $maxInputVars ? 'false' : (int)$maxInputVars)
, ';' . "\n";
+/* for old js code */
+/* for new js code */
+echo "datePicker = new Array();\n";
+echo "timePicker = new Array();\n";
+/* l10n: Display text for calendar close link */
+Sanitize::printJsValue("datePicker['closeText']", __('Done'));
+/* l10n: Display text for previous month link in calendar */
+Sanitize::printJsValue(
+ "datePicker['prevText']",
+ _pgettext('Previous month', 'Prev')
+);
+/* l10n: Display text for next month link in calendar */
+Sanitize::printJsValue(
+ "datePicker['nextText']",
+ _pgettext('Next month', 'Next')
+);
+/* l10n: Display text for current month link in calendar */
+Sanitize::printJsValue("datePicker['currentText']", __('Today'));
+Sanitize::printJsValue(
+ "datePicker['monthNames']",
+ array(
+ __('January'),
+ __('February'),
+ __('March'),
+ __('April'),
+ __('May'),
+ __('June'),
+ __('July'),
+ __('August'),
+ __('September'),
+ __('October'),
+ __('November'),
+ __('December')
+ )
+);
+Sanitize::printJsValue(
+ "datePicker['monthNamesShort']",
+ array(
+ /* l10n: Short month name */
+ __('Jan'),
+ /* l10n: Short month name */
+ __('Feb'),
+ /* l10n: Short month name */
+ __('Mar'),
+ /* l10n: Short month name */
+ __('Apr'),
+ /* l10n: Short month name */
+ _pgettext('Short month name', 'May'),
+ /* l10n: Short month name */
+ __('Jun'),
+ /* l10n: Short month name */
+ __('Jul'),
+ /* l10n: Short month name */
+ __('Aug'),
+ /* l10n: Short month name */
+ __('Sep'),
+ /* l10n: Short month name */
+ __('Oct'),
+ /* l10n: Short month name */
+ __('Nov'),
+ /* l10n: Short month name */
+ __('Dec')
+ )
+);
+Sanitize::printJsValue(
+ "datePicker['dayNames']",
+ array(
+ __('Sunday'),
+ __('Monday'),
+ __('Tuesday'),
+ __('Wednesday'),
+ __('Thursday'),
+ __('Friday'),
+ __('Saturday')
+ )
+);
+Sanitize::printJsValue(
+ "datePicker['dayNamesShort']",
+ array(
+ /* l10n: Short week day name */
+ __('Sun'),
+ /* l10n: Short week day name */
+ __('Mon'),
+ /* l10n: Short week day name */
+ __('Tue'),
+ /* l10n: Short week day name */
+ __('Wed'),
+ /* l10n: Short week day name */
+ __('Thu'),
+ /* l10n: Short week day name */
+ __('Fri'),
+ /* l10n: Short week day name */
+ __('Sat')
+ )
+);
+Sanitize::printJsValue(
+ "datePicker['dayNamesMin']",
+ array(
+ /* l10n: Minimal week day name */
+ __('Su'),
+ /* l10n: Minimal week day name */
+ __('Mo'),
+ /* l10n: Minimal week day name */
+ __('Tu'),
+ /* l10n: Minimal week day name */
+ __('We'),
+ /* l10n: Minimal week day name */
+ __('Th'),
+ /* l10n: Minimal week day name */
+ __('Fr'),
+ /* l10n: Minimal week day name */
+ __('Sa')
+ )
+);
+/* l10n: Column header for week of the year in calendar */
+Sanitize::printJsValue("datePicker['weekHeader']", __('Wk'));
+
+Sanitize::printJsValue(
+ "datePicker['showMonthAfterYear']",
+ /* l10n: Month-year order for calendar, use either "calendar-month-year"
+ * or "calendar-year-month".
+ */
+ (__('calendar-month-year') == 'calendar-year-month')
+);
+/* l10n: Year suffix for calendar, "none" is empty. */
+$year_suffix = _pgettext('Year suffix', 'none');
+Sanitize::printJsValue(
+ "datePicker['yearSuffix']",
+ ($year_suffix == 'none' ? '' : $year_suffix)
+);
+Sanitize::printJsValue("timePicker['timeText']", __('Time'));
+Sanitize::printJsValue("timePicker['hourText']", __('Hour'));
+Sanitize::printJsValue("timePicker['minuteText']", __('Minute'));
+Sanitize::printJsValue("timePicker['secondText']", __('Second'));
+/* for new js code */
+
+/* for old js code */
echo "if ($.datepicker) {\n";
/* l10n: Display text for calendar close link */
Sanitize::printJsValue("$.datepicker.regional['']['closeText']", __('Done'));
@@ -908,11 +1065,78 @@ Sanitize::printJsValue("$.timepicker.regional['']['timeText']", __('Time'));
Sanitize::printJsValue("$.timepicker.regional['']['hourText']", __('Hour'));
Sanitize::printJsValue("$.timepicker.regional['']['minuteText']", __('Minute'));
Sanitize::printJsValue("$.timepicker.regional['']['secondText']", __('Second'));
+/* for old js code */
?>
$.extend($.timepicker._defaults, $.timepicker.regional['']);
} /* if ($.timepicker) */
diff --git a/js/server_privileges.js b/js/server_privileges.js
index eef6f26405..d03d68f3df 100644
--- a/js/server_privileges.js
+++ b/js/server_privileges.js
@@ -9,57 +9,6 @@
*
*/
-/**
- * Validates the "add a user" form
- *
- * @return boolean whether the form is validated or not
- */
-function checkAddUser (the_form) {
- if (the_form.elements.pred_hostname.value === 'userdefined' && the_form.elements.hostname.value === '') {
- alert(PMA_messages.strHostEmpty);
- the_form.elements.hostname.focus();
- return false;
- }
-
- if (the_form.elements.pred_username.value === 'userdefined' && the_form.elements.username.value === '') {
- alert(PMA_messages.strUserEmpty);
- the_form.elements.username.focus();
- return false;
- }
-
- return PMA_checkPassword($(the_form));
-} // end of the 'checkAddUser()' function
-
-function checkPasswordStrength (value, meter_obj, meter_object_label, username) {
- // List of words we don't want to appear in the password
- customDict = [
- 'phpmyadmin',
- 'mariadb',
- 'mysql',
- 'php',
- 'my',
- 'admin',
- ];
- if (username !== null) {
- customDict.push(username);
- }
- var zxcvbn_obj = zxcvbn(value, customDict);
- var strength = zxcvbn_obj.score;
- strength = parseInt(strength);
- meter_obj.val(strength);
- switch (strength) {
- case 0: meter_obj_label.html(PMA_messages.strExtrWeak);
- break;
- case 1: meter_obj_label.html(PMA_messages.strVeryWeak);
- break;
- case 2: meter_obj_label.html(PMA_messages.strWeak);
- break;
- case 3: meter_obj_label.html(PMA_messages.strGood);
- break;
- case 4: meter_obj_label.html(PMA_messages.strStrong);
- }
-}
-
/**
* AJAX scripts for server_privileges page.
*
@@ -75,7 +24,6 @@ function checkPasswordStrength (value, meter_obj, meter_object_label, username)
* @name document.ready
*/
-
/**
* Unbind all event handlers before tearing down a page
*/
diff --git a/js/src/ajax.js b/js/src/ajax.js
new file mode 100644
index 0000000000..0897884595
--- /dev/null
+++ b/js/src/ajax.js
@@ -0,0 +1,757 @@
+import { PMA_ajaxShowMessage } from './utils/show_ajax_messages';
+import { PMA_Messages as PMA_messages } from './variables/export_variables';
+import { PMA_commonParams } from './variables/common_params';
+import { jQuery as $ } from './utils/JqueryExtended';
+import { PMA_getImage } from './functions/get_image';
+
+/**
+ * This object handles ajax requests for pages. It also
+ * handles the reloading of the main menu and scripts.
+ */
+
+/**
+ * @todo this function is to be removed when complete code is modularised
+ */
+function checkNewCode (script) {
+ var check = script.split('.js');
+ // console.log(check);
+ if (check.length === 2) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+export let AJAX = {
+ /**
+ * @var bool test variable for checking this object
+ */
+ test: false,
+ /**
+ * @var bool active Whether we are busy
+ */
+ active: false,
+ /**
+ * @var object source The object whose event initialized the request
+ */
+ source: null,
+ /**
+ * @var object xhr A reference to the ajax request that is currently running
+ */
+ xhr: null,
+ /**
+ * @var object lockedTargets, list of locked targets
+ */
+ lockedTargets: {},
+ /**
+ * @var function Callback to execute after a successful request
+ * Used by PMA_commonFunctions from common.js
+ */
+ _callback: function () {},
+ /**
+ * @var bool _debug Makes noise in your Firebug console
+ */
+ _debug: false,
+ /**
+ * @var object $msgbox A reference to a jQuery object that links to a message
+ * box that is generated by PMA_ajaxShowMessage()
+ */
+ $msgbox: null,
+ /**
+ * Given the filename of a script, returns a hash to be
+ * used to refer to all the events registered for the file
+ *
+ * @param key string key The filename for which to get the event name
+ *
+ * @return int
+ */
+
+ hash: function (key) {
+ /* http://burtleburtle.net/bob/hash/doobs.html#one */
+ key += '';
+ var len = key.length;
+ var hash = 0;
+ var i = 0;
+ for (; i < len; ++i) {
+ hash += key.charCodeAt(i);
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return Math.abs(hash);
+ },
+ /**
+ * Registers an onload event for a file
+ *
+ * @param file string file The filename for which to register the event
+ * @param func function func The function to execute when the page is ready
+ *
+ * @return self For chaining
+ */
+ registerOnload: function (file, func) {
+ var eventName = 'onload_' + this.hash(file);
+ $(document).on(eventName, func);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ 'Registered event ' + eventName + ' for file ' + file
+ );
+ }
+ return this;
+ },
+ /**
+ * Registers a teardown event for a file. This is useful to execute functions
+ * that unbind events for page elements that are about to be removed.
+ *
+ * @param string file The filename for which to register the event
+ * @param function func The function to execute when
+ * the page is about to be torn down
+ *
+ * @return self For chaining
+ */
+ registerTeardown: function (file, func) {
+ var eventName = 'teardown_' + this.hash(file);
+ $(document).on(eventName, func);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ 'Registered event ' + eventName + ' for file ' + file
+ );
+ }
+ return this;
+ },
+ /**
+ * Called when a page has finished loading, once for every
+ * file that registered to the onload event of that file.
+ *
+ * @param string file The filename for which to fire the event
+ *
+ * @return void
+ */
+ fireOnload: function (file) {
+ var eventName = 'onload_' + this.hash(file);
+ $(document).trigger(eventName);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ 'Fired event ' + eventName + ' for file ' + file
+ );
+ }
+ },
+ /**
+ * Called just before a page is torn down, once for every
+ * file that registered to the teardown event of that file.
+ *
+ * @param string file The filename for which to fire the event
+ *
+ * @return void
+ */
+ fireTeardown: function (file) {
+ var eventName = 'teardown_' + this.hash(file);
+ $(document).triggerHandler(eventName);
+ if (this._debug) {
+ console.log(
+ // no need to translate
+ 'Fired event ' + eventName + ' for file ' + file
+ );
+ }
+ },
+ /**
+ * function to handle lock page mechanism
+ *
+ * @param event the event object
+ *
+ * @return void
+ */
+ lockPageHandler: function (event) {
+ var newHash = null;
+ var oldHash = null;
+ var lockId;
+ // CodeMirror lock
+ if (event.data.value === 3) {
+ newHash = event.data.content;
+ oldHash = true;
+ lockId = 'cm';
+ } else {
+ // Don't lock on enter.
+ if (0 === event.charCode) {
+ return;
+ }
+
+ lockId = $(this).data('lock-id');
+ if (typeof lockId === 'undefined') {
+ return;
+ }
+ /*
+ * @todo Fix Code mirror does not give correct full value (query)
+ * in textarea, it returns only the change in content.
+ */
+ if (event.data.value === 1) {
+ newHash = AJAX.hash($(this).val());
+ } else {
+ newHash = AJAX.hash($(this).is(':checked'));
+ }
+ oldHash = $(this).data('val-hash');
+ }
+ // Set lock if old value !== new value
+ // otherwise release lock
+ if (oldHash !== newHash) {
+ AJAX.lockedTargets[lockId] = true;
+ } else {
+ delete AJAX.lockedTargets[lockId];
+ }
+ // Show lock icon if locked targets is not empty.
+ // otherwise remove lock icon
+ if (!$.isEmptyObject(AJAX.lockedTargets)) {
+ $('#lock_page_icon').html(PMA_getImage('s_lock', PMA_messages.strLockToolTip).toString());
+ } else {
+ $('#lock_page_icon').html('');
+ }
+ },
+ /**
+ * resets the lock
+ *
+ * @return void
+ */
+ resetLock: function () {
+ AJAX.lockedTargets = {};
+ $('#lock_page_icon').html('');
+ },
+ handleMenu: {
+ replace: function (content) {
+ $('#floating_menubar').html(content)
+ // Remove duplicate wrapper
+ // TODO: don't send it in the response
+ .children().first().remove();
+ $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
+ }
+ },
+ /**
+ * Event handler for clicks on links and form submissions
+ *
+ * @param object e Event data
+ *
+ * @return void
+ */
+ requestHandler: function (event) {
+ // In some cases we don't want to handle the request here and either
+ // leave the browser deal with it natively (e.g: file download)
+ // or leave an existing ajax event handler present elsewhere deal with it
+ var href = $(this).attr('href');
+ if (typeof event !== 'undefined' && (event.shiftKey || event.ctrlKey)) {
+ return true;
+ } else if ($(this).attr('target')) {
+ return true;
+ } else if ($(this).hasClass('ajax') || $(this).hasClass('disableAjax')) {
+ // reset the lockedTargets object, as specified AJAX operation has finished
+ AJAX.resetLock();
+ return true;
+ } else if (href && href.match(/^#/)) {
+ return true;
+ } else if (href && href.match(/^mailto/)) {
+ return true;
+ } else if ($(this).hasClass('ui-datepicker-next') ||
+ $(this).hasClass('ui-datepicker-prev')
+ ) {
+ return true;
+ }
+ if (typeof event !== 'undefined') {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+
+ // triggers a confirm dialog if:
+ // the user has performed some operations on loaded page
+ // the user clicks on some link, (won't trigger for buttons)
+ // the click event is not triggered by script
+
+ if (typeof event !== 'undefined' && event.type === 'click' &&
+ event.isTrigger !== true &&
+ !$.isEmptyObject(AJAX.lockedTargets)
+ ) {
+ if (confirm(PMA_messages.strConfirmNavigation) === false) {
+ return false;
+ } else {
+ if (isStorageSupported('localStorage')) {
+ window.localStorage.removeItem('auto_saved_sql');
+ } else {
+ Cookies.set('auto_saved_sql', '');
+ }
+ }
+ }
+ AJAX.resetLock();
+ var isLink = !! href || false;
+ var previousLinkAborted = false;
+ if (AJAX.active === true) {
+ // Cancel the old request if abortable, when the user requests
+ // something else. Otherwise silently bail out, as there is already
+ // a request well in progress.
+ if (AJAX.xhr) {
+ // In case of a link request, attempt aborting
+ AJAX.xhr.abort();
+ if (AJAX.xhr.status === 0 && AJAX.xhr.statusText === 'abort') {
+ // If aborted
+ AJAX.$msgbox = PMA_ajaxShowMessage(PMA_messages.strAbortedRequest);
+ AJAX.active = false;
+ AJAX.xhr = null;
+ previousLinkAborted = true;
+ } else {
+ // If can't abort
+ return false;
+ }
+ } else {
+ // In case submitting a form, don't attempt aborting
+ return false;
+ }
+ }
+ AJAX.source = $(this);
+
+ $('html, body').animate({ scrollTop: 0 }, 'fast');
+
+ var url = isLink ? href : $(this).attr('action');
+ var argsep = PMA_commonParams.get('arg_separator');
+ var params = 'ajax_request=true' + argsep + 'ajax_page_request=true';
+ var dataPost = AJAX.source.getPostData();
+ if (! isLink) {
+ params += argsep + $(this).serialize();
+ } else if (dataPost) {
+ params += argsep + dataPost;
+ isLink = false;
+ }
+ if (! (history && history.pushState)) {
+ // Add a list of menu hashes that we have in the cache to the request
+ params += PMA_MicroHistory.menus.getRequestParam();
+ }
+
+ if (AJAX._debug) {
+ console.log('Loading: ' + url); // no need to translate
+ }
+
+ if (isLink) {
+ AJAX.active = true;
+ AJAX.$msgbox = PMA_ajaxShowMessage();
+ // Save reference for the new link request
+ AJAX.xhr = $.get(url, params, AJAX.responseHandler);
+ if (history && history.pushState) {
+ var state = {
+ url : href
+ };
+ if (previousLinkAborted) {
+ // hack: there is already an aborted entry on stack
+ // so just modify the aborted one
+ history.replaceState(state, null, href);
+ } else {
+ history.pushState(state, null, href);
+ }
+ }
+ } else {
+ /**
+ * Manually fire the onsubmit event for the form, if any.
+ * The event was saved in the jQuery data object by an onload
+ * handler defined below. Workaround for bug #3583316
+ */
+ var onsubmit = $(this).data('onsubmit');
+ // Submit the request if there is no onsubmit handler
+ // or if it returns a value that evaluates to true
+ if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) {
+ AJAX.active = true;
+ AJAX.$msgbox = PMA_ajaxShowMessage();
+ $.post(url, params, AJAX.responseHandler);
+ }
+ }
+ },
+ /**
+ * Called after the request that was initiated by this.requestHandler()
+ * has completed successfully or with a caught error. For completely
+ * failed requests or requests with uncaught errors, see the .ajaxError
+ * handler at the bottom of this file.
+ *
+ * To refer to self use 'AJAX', instead of 'this' as this function
+ * is called in the jQuery context.
+ *
+ * @param object e Event data
+ *
+ * @return void
+ */
+ responseHandler: function (data) {
+ if (typeof data === 'undefined' || data === null) {
+ return;
+ }
+ if (typeof data.success !== 'undefined' && data.success) {
+ $('html, body').animate({ scrollTop: 0 }, 'fast');
+ PMA_ajaxRemoveMessage(AJAX.$msgbox);
+
+ if (data._redirect) {
+ PMA_ajaxShowMessage(data._redirect, false);
+ AJAX.active = false;
+ AJAX.xhr = null;
+ return;
+ }
+
+ AJAX.scriptHandler.reset(function () {
+ if (data._reloadNavigation) {
+ PMA_reloadNavigation();
+ }
+
+ if (data._title) {
+ $('title').replaceWith(data._title);
+ }
+ if (data._menu) {
+ if (history && history.pushState) {
+ var state = {
+ url : data._selflink,
+ menu : data._menu
+ };
+ history.replaceState(state, null);
+ AJAX.handleMenu.replace(data._menu);
+ } else {
+ PMA_MicroHistory.menus.replace(data._menu);
+ PMA_MicroHistory.menus.add(data._menuHash, data._menu);
+ }
+ } else if (data._menuHash) {
+ if (! (history && history.pushState)) {
+ PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash));
+ }
+ }
+ if (data._disableNaviSettings) {
+ PMA_disableNaviSettings();
+ } else {
+ PMA_ensureNaviSettings(data._selflink);
+ }
+
+ // Remove all containers that may have
+ // been added outside of #page_content
+ $('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
+ if (data.message && data.message.length > 0) {
+ $('#page_content').replaceWith(
+ '
' + data.message + '
'
+ );
+ PMA_highlightSQL($('#page_content'));
+ checkNumberOfFields();
+ }
+
+ if (data._selflink) {
+ var source = data._selflink.split('?')[0];
+ // Check for faulty links
+ var $selflink_replace = {
+ 'import.php': 'tbl_sql.php',
+ 'tbl_chart.php': 'sql.php',
+ 'tbl_gis_visualization.php': 'sql.php'
+ };
+ if ($selflink_replace[source]) {
+ var replacement = $selflink_replace[source];
+ data._selflink = data._selflink.replace(source, replacement);
+ }
+ $('#selflink').find('> a').attr('href', data._selflink);
+ }
+ if (data._params) {
+ PMA_commonParams.setAll(data._params);
+ }
+ if (data._scripts) {
+ AJAX.scriptHandler.load(data._scripts);
+ }
+ if (data._selflink && data._scripts && data._menuHash && data._params) {
+ if (! (history && history.pushState)) {
+ PMA_MicroHistory.add(
+ data._selflink,
+ data._scripts,
+ data._menuHash,
+ data._params,
+ AJAX.source.attr('rel')
+ );
+ }
+ }
+ if (data._displayMessage) {
+ $('#page_content').prepend(data._displayMessage);
+ PMA_highlightSQL($('#page_content'));
+ }
+
+ $('#pma_errors').remove();
+
+ var msg = '';
+ if (data._errSubmitMsg) {
+ msg = data._errSubmitMsg;
+ }
+ if (data._errors) {
+ $('', { id : 'pma_errors', class : 'clearfloat' })
+ .insertAfter('#selflink')
+ .append(data._errors);
+ // bind for php error reporting forms (bottom)
+ $('#pma_ignore_errors_bottom').on('click', function (e) {
+ e.preventDefault();
+ PMA_ignorePhpErrors();
+ });
+ $('#pma_ignore_all_errors_bottom').on('click', function (e) {
+ e.preventDefault();
+ PMA_ignorePhpErrors(false);
+ });
+ // In case of 'sendErrorReport'='always'
+ // submit the hidden error reporting form.
+ if (data._sendErrorAlways === '1' &&
+ data._stopErrorReportLoop !== '1'
+ ) {
+ $('#pma_report_errors_form').submit();
+ PMA_ajaxShowMessage(PMA_messages.phpErrorsBeingSubmitted, false);
+ $('html, body').animate({ scrollTop:$(document).height() }, 'slow');
+ } else if (data._promptPhpErrors) {
+ // otherwise just prompt user if it is set so.
+ msg = msg + PMA_messages.phpErrorsFound;
+ // scroll to bottom where all the errors are displayed.
+ $('html, body').animate({ scrollTop:$(document).height() }, 'slow');
+ }
+ }
+ PMA_ajaxShowMessage(msg, false);
+ // bind for php error reporting forms (popup)
+ $('#pma_ignore_errors_popup').on('click', function () {
+ PMA_ignorePhpErrors();
+ });
+ $('#pma_ignore_all_errors_popup').on('click', function () {
+ PMA_ignorePhpErrors(false);
+ });
+
+ if (typeof AJAX._callback === 'function') {
+ AJAX._callback.call();
+ }
+ AJAX._callback = function () {};
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ AJAX.active = false;
+ AJAX.xhr = null;
+ PMA_handleRedirectAndReload(data);
+ if (data.fieldWithError) {
+ $(':input.error').removeClass('error');
+ $('#' + data.fieldWithError).addClass('error');
+ }
+ }
+ },
+ /**
+ * This object is in charge of downloading scripts,
+ * keeping track of what's downloaded and firing
+ * the onload event for them when the page is ready.
+ */
+ scriptHandler: {
+ /**
+ * @var array _scripts The list of files already downloaded
+ */
+ _scripts: [],
+ /**
+ * @var string _scriptsVersion version of phpMyAdmin from which the
+ * scripts have been loaded
+ */
+ _scriptsVersion: null,
+ /**
+ * @var array _scriptsToBeLoaded The list of files that
+ * need to be downloaded
+ */
+ _scriptsToBeLoaded: [],
+ /**
+ * @var array _scriptsToBeFired The list of files for which
+ * to fire the onload and unload events
+ */
+ _scriptsToBeFired: [],
+ _scriptsCompleted: false,
+ /**
+ * Records that a file has been downloaded
+ *
+ * @param string file The filename
+ * @param string fire Whether this file will be registering
+ * onload/teardown events
+ *
+ * @return self For chaining
+ */
+ add: function (file, fire, callback) {
+ var self = this;
+ this._scripts.push(file);
+ if (fire) {
+ // Record whether to fire any events for the file
+ // This is necessary to correctly tear down the initial page
+ this._scriptsToBeFired.push(file);
+ }
+ /**
+ * @todo This condition is to be removed once all the files are modularised
+ */
+ if (checkNewCode(file)) {
+ var fileImports = ['server_privileges', 'server_databases'];
+ if ($.inArray(file, fileImports) !== -1) {
+ // Dynamic import to load the files dynamically
+ // This is used for the purpose of code splitting
+ import(`./${file}`)
+ .then((module) => {
+ /**
+ * setTimeout is used so that scripts run only when content is
+ * available.
+ */
+ setTimeout(function () {
+ /**
+ * @var i The name of the module exported in corresponding file
+ */
+ for (var i in module) {
+ // If the export has onload in its name, register onload for that export
+ if (i.indexOf('onload') !== -1) {
+ AJAX.registerOnload(`${file}`, module[i]);
+ } else if (i.indexOf('teardown') !== -1) {
+ // If the export has teardown in its name, register teardown for that export
+ AJAX.registerTeardown(file, module[i]);
+ }
+ }
+ // Firinf onload for the files being dynamically imported.
+ AJAX.fireOnload(file);
+ }, 250);
+ // AJAX.fireTeardown('server_databases_new.js');
+ })
+ // Error of Dynamica Imoprts need to be handled with Tracekit gracefully
+ .catch(e => console.log(e));
+ }
+ }
+ return this;
+ },
+ /**
+ * Download a list of js files in one request
+ *
+ * @param array files An array of filenames and flags
+ *
+ * @return void
+ */
+ load: function (files, callback) {
+ var self = this;
+ var i;
+ // Clear loaded scripts if they are from another version of phpMyAdmin.
+ // Depends on common params being set before loading scripts in responseHandler
+ if (self._scriptsVersion === null) {
+ self._scriptsVersion = PMA_commonParams.get('PMA_VERSION');
+ } else if (self._scriptsVersion !== PMA_commonParams.get('PMA_VERSION')) {
+ self._scripts = [];
+ self._scriptsVersion = PMA_commonParams.get('PMA_VERSION');
+ }
+ self._scriptsCompleted = false;
+ self._scriptsToBeFired = [];
+ // We need to first complete list of files to load
+ // as next loop will directly fire requests to load them
+ // and that triggers removal of them from
+ // self._scriptsToBeLoaded
+ for (i in files) {
+ self._scriptsToBeLoaded.push(files[i].name);
+ if (files[i].fire) {
+ self._scriptsToBeFired.push(files[i].name);
+ }
+ }
+ for (i in files) {
+ var script = files[i].name;
+ // Only for scripts that we don't already have
+ if ($.inArray(script, self._scripts) === -1) {
+ this.add(script, 0, callback);
+ /**
+ * @todo To be removed once complete code is modularised
+ * Only for appending js files in the header if not already present
+ */
+ if (!checkNewCode(script)) {
+ this.appendScript(script, callback);
+ }
+ }
+ self.done(script, callback);
+ }
+ // Trigger callback if there is nothing else to load
+ self.done(null, callback);
+ },
+ /**
+ * Called whenever all files are loaded
+ *
+ * @return void
+ */
+ done: function (script, callback) {
+ if (typeof ErrorReport !== 'undefined') {
+ ErrorReport.wrap_global_functions();
+ }
+ if ($.inArray(script, this._scriptsToBeFired)) {
+ AJAX.fireOnload(script);
+ }
+ if ($.inArray(script, this._scriptsToBeLoaded)) {
+ this._scriptsToBeLoaded.splice($.inArray(script, this._scriptsToBeLoaded), 1);
+ }
+ if (script === null) {
+ this._scriptsCompleted = true;
+ }
+ /* We need to wait for last signal (with null) or last script load */
+ AJAX.active = (this._scriptsToBeLoaded.length > 0) || ! this._scriptsCompleted;
+ /* Run callback on last script */
+ if (! AJAX.active && $.isFunction(callback)) {
+ callback();
+ }
+ },
+ /**
+ * Appends a script element to the head to load the scripts
+ *
+ * @return void
+ * this is to be removed in modularised code
+ * no appending of scripts is needed
+ *
+ * @todo This method will be of no use in modular code
+ */
+ appendScript: function (name, callback) {
+ var head = document.head || document.getElementsByTagName('head')[0];
+ var script = document.createElement('script');
+ var self = this;
+
+ script.type = 'text/javascript';
+ script.src = 'js/' + name + '?' + 'v=' + encodeURIComponent(PMA_commonParams.get('PMA_VERSION'));
+ script.async = false;
+
+ script.onload = function () {
+ self.done(name, callback);
+ };
+ head.appendChild(script);
+ },
+ /**
+ * Fires all the teardown event handlers for the current page
+ * and rebinds all forms and links to the request handler
+ *
+ * @param function callback The callback to call after resetting
+ *
+ * @return void
+ */
+ reset: function (callback) {
+ for (var i in this._scriptsToBeFired) {
+ AJAX.fireTeardown(this._scriptsToBeFired[i]);
+ }
+ this._scriptsToBeFired = [];
+ /**
+ * Re-attach a generic event handler to clicks
+ * on pages and submissions of forms
+ */
+ $(document).off('click', 'a').on('click', 'a', AJAX.requestHandler);
+ $(document).off('submit', 'form').on('submit', 'form', AJAX.requestHandler);
+ if (! (history && history.pushState)) {
+ PMA_MicroHistory.update();
+ }
+ callback();
+ }
+ }
+};
+
+/**
+ * Attach a generic event handler to clicks
+ * on pages and submissions of forms
+ */
+$(document).on('click', 'a', AJAX.requestHandler);
+$(document).on('submit', 'form', AJAX.requestHandler);
+
+/**
+ * @todo this is to be removed when complete code is modularised
+ * Exporsing module to window for use with non modular code
+ */
+window.AJAX = AJAX;
diff --git a/js/src/consts/doclinks.js b/js/src/consts/doclinks.js
new file mode 100644
index 0000000000..a07acce03a
--- /dev/null
+++ b/js/src/consts/doclinks.js
@@ -0,0 +1,365 @@
+/**
+ * Definition of links to MySQL documentation.
+ */
+
+export const mysql_doc_keyword = {
+ /* Multi word */
+ 'CHARACTER SET': Array('charset'),
+ 'SHOW AUTHORS': Array('show-authors'),
+ 'SHOW BINARY LOGS': Array('show-binary-logs'),
+ 'SHOW BINLOG EVENTS': Array('show-binlog-events'),
+ 'SHOW CHARACTER SET': Array('show-character-set'),
+ 'SHOW COLLATION': Array('show-collation'),
+ 'SHOW COLUMNS': Array('show-columns'),
+ 'SHOW CONTRIBUTORS': Array('show-contributors'),
+ 'SHOW CREATE DATABASE': Array('show-create-database'),
+ 'SHOW CREATE EVENT': Array('show-create-event'),
+ 'SHOW CREATE FUNCTION': Array('show-create-function'),
+ 'SHOW CREATE PROCEDURE': Array('show-create-procedure'),
+ 'SHOW CREATE TABLE': Array('show-create-table'),
+ 'SHOW CREATE TRIGGER': Array('show-create-trigger'),
+ 'SHOW CREATE VIEW': Array('show-create-view'),
+ 'SHOW DATABASES': Array('show-databases'),
+ 'SHOW ENGINE': Array('show-engine'),
+ 'SHOW ENGINES': Array('show-engines'),
+ 'SHOW ERRORS': Array('show-errors'),
+ 'SHOW EVENTS': Array('show-events'),
+ 'SHOW FUNCTION CODE': Array('show-function-code'),
+ 'SHOW FUNCTION STATUS': Array('show-function-status'),
+ 'SHOW GRANTS': Array('show-grants'),
+ 'SHOW INDEX': Array('show-index'),
+ 'SHOW MASTER STATUS': Array('show-master-status'),
+ 'SHOW OPEN TABLES': Array('show-open-tables'),
+ 'SHOW PLUGINS': Array('show-plugins'),
+ 'SHOW PRIVILEGES': Array('show-privileges'),
+ 'SHOW PROCEDURE CODE': Array('show-procedure-code'),
+ 'SHOW PROCEDURE STATUS': Array('show-procedure-status'),
+ 'SHOW PROCESSLIST': Array('show-processlist'),
+ 'SHOW PROFILE': Array('show-profile'),
+ 'SHOW PROFILES': Array('show-profiles'),
+ 'SHOW RELAYLOG EVENTS': Array('show-relaylog-events'),
+ 'SHOW SLAVE HOSTS': Array('show-slave-hosts'),
+ 'SHOW SLAVE STATUS': Array('show-slave-status'),
+ 'SHOW STATUS': Array('show-status'),
+ 'SHOW TABLE STATUS': Array('show-table-status'),
+ 'SHOW TABLES': Array('show-tables'),
+ 'SHOW TRIGGERS': Array('show-triggers'),
+ 'SHOW VARIABLES': Array('show-variables'),
+ 'SHOW WARNINGS': Array('show-warnings'),
+ 'LOAD DATA INFILE': Array('load-data'),
+ 'LOAD XML': Array('load-xml'),
+ 'LOCK TABLES': Array('lock-tables'),
+ 'UNLOCK TABLES': Array('lock-tables'),
+ 'ALTER DATABASE': Array('alter-database'),
+ 'ALTER EVENT': Array('alter-event'),
+ 'ALTER LOGFILE GROUP': Array('alter-logfile-group'),
+ 'ALTER FUNCTION': Array('alter-function'),
+ 'ALTER PROCEDURE': Array('alter-procedure'),
+ 'ALTER SERVER': Array('alter-server'),
+ 'ALTER TABLE': Array('alter-table'),
+ 'ALTER TABLESPACE': Array('alter-tablespace'),
+ 'ALTER VIEW': Array('alter-view'),
+ 'CREATE DATABASE': Array('create-database'),
+ 'CREATE EVENT': Array('create-event'),
+ 'CREATE FUNCTION': Array('create-function'),
+ 'CREATE INDEX': Array('create-index'),
+ 'CREATE LOGFILE GROUP': Array('create-logfile-group'),
+ 'CREATE PROCEDURE': Array('create-procedure'),
+ 'CREATE SERVER': Array('create-server'),
+ 'CREATE TABLE': Array('create-table'),
+ 'CREATE TABLESPACE': Array('create-tablespace'),
+ 'CREATE TRIGGER': Array('create-trigger'),
+ 'CREATE VIEW': Array('create-view'),
+ 'DROP DATABASE': Array('drop-database'),
+ 'DROP EVENT': Array('drop-event'),
+ 'DROP FUNCTION': Array('drop-function'),
+ 'DROP INDEX': Array('drop-index'),
+ 'DROP LOGFILE GROUP': Array('drop-logfile-group'),
+ 'DROP PROCEDURE': Array('drop-procedure'),
+ 'DROP SERVER': Array('drop-server'),
+ 'DROP TABLE': Array('drop-table'),
+ 'DROP TABLESPACE': Array('drop-tablespace'),
+ 'DROP TRIGGER': Array('drop-trigger'),
+ 'DROP VIEW': Array('drop-view'),
+ 'RENAME TABLE': Array('rename-table'),
+ 'TRUNCATE TABLE': Array('truncate-table'),
+
+ /* Statements */
+ 'SELECT': Array('select'),
+ 'SET': Array('set'),
+ 'EXPLAIN': Array('explain'),
+ 'DESCRIBE': Array('describe'),
+ 'DELETE': Array('delete'),
+ 'SHOW': Array('show'),
+ 'UPDATE': Array('update'),
+ 'INSERT': Array('insert'),
+ 'REPLACE': Array('replace'),
+ 'CALL': Array('call'),
+ 'DO': Array('do'),
+ 'HANDLER': Array('handler'),
+ 'COLLATE': Array('charset-collations'),
+
+ /* Functions */
+ 'ABS': Array('mathematical-functions', 'function_abs'),
+ 'ACOS': Array('mathematical-functions', 'function_acos'),
+ 'ADDDATE': Array('date-and-time-functions', 'function_adddate'),
+ 'ADDTIME': Array('date-and-time-functions', 'function_addtime'),
+ 'AES_DECRYPT': Array('encryption-functions', 'function_aes_decrypt'),
+ 'AES_ENCRYPT': Array('encryption-functions', 'function_aes_encrypt'),
+ 'AND': Array('logical-operators', 'operator_and'),
+ 'ASCII': Array('string-functions', 'function_ascii'),
+ 'ASIN': Array('mathematical-functions', 'function_asin'),
+ 'ATAN2': Array('mathematical-functions', 'function_atan2'),
+ 'ATAN': Array('mathematical-functions', 'function_atan'),
+ 'AVG': Array('group-by-functions', 'function_avg'),
+ 'BENCHMARK': Array('information-functions', 'function_benchmark'),
+ 'BIN': Array('string-functions', 'function_bin'),
+ 'BINARY': Array('cast-functions', 'operator_binary'),
+ 'BIT_AND': Array('group-by-functions', 'function_bit_and'),
+ 'BIT_COUNT': Array('bit-functions', 'function_bit_count'),
+ 'BIT_LENGTH': Array('string-functions', 'function_bit_length'),
+ 'BIT_OR': Array('group-by-functions', 'function_bit_or'),
+ 'BIT_XOR': Array('group-by-functions', 'function_bit_xor'),
+ 'CASE': Array('control-flow-functions', 'operator_case'),
+ 'CAST': Array('cast-functions', 'function_cast'),
+ 'CEIL': Array('mathematical-functions', 'function_ceil'),
+ 'CEILING': Array('mathematical-functions', 'function_ceiling'),
+ 'CHAR_LENGTH': Array('string-functions', 'function_char_length'),
+ 'CHAR': Array('string-functions', 'function_char'),
+ 'CHARACTER_LENGTH': Array('string-functions', 'function_character_length'),
+ 'CHARSET': Array('information-functions', 'function_charset'),
+ 'COALESCE': Array('comparison-operators', 'function_coalesce'),
+ 'COERCIBILITY': Array('information-functions', 'function_coercibility'),
+ 'COLLATION': Array('information-functions', 'function_collation'),
+ 'COMPRESS': Array('encryption-functions', 'function_compress'),
+ 'CONCAT_WS': Array('string-functions', 'function_concat_ws'),
+ 'CONCAT': Array('string-functions', 'function_concat'),
+ 'CONNECTION_ID': Array('information-functions', 'function_connection_id'),
+ 'CONV': Array('mathematical-functions', 'function_conv'),
+ 'CONVERT_TZ': Array('date-and-time-functions', 'function_convert_tz'),
+ 'Convert': Array('cast-functions', 'function_convert'),
+ 'COS': Array('mathematical-functions', 'function_cos'),
+ 'COT': Array('mathematical-functions', 'function_cot'),
+ 'COUNT': Array('group-by-functions', 'function_count'),
+ 'CRC32': Array('mathematical-functions', 'function_crc32'),
+ 'CURDATE': Array('date-and-time-functions', 'function_curdate'),
+ 'CURRENT_DATE': Array('date-and-time-functions', 'function_current_date'),
+ 'CURRENT_TIME': Array('date-and-time-functions', 'function_current_time'),
+ 'CURRENT_TIMESTAMP': Array('date-and-time-functions', 'function_current_timestamp'),
+ 'CURRENT_USER': Array('information-functions', 'function_current_user'),
+ 'CURTIME': Array('date-and-time-functions', 'function_curtime'),
+ 'DATABASE': Array('information-functions', 'function_database'),
+ 'DATE_ADD': Array('date-and-time-functions', 'function_date_add'),
+ 'DATE_FORMAT': Array('date-and-time-functions', 'function_date_format'),
+ 'DATE_SUB': Array('date-and-time-functions', 'function_date_sub'),
+ 'DATE': Array('date-and-time-functions', 'function_date'),
+ 'DATEDIFF': Array('date-and-time-functions', 'function_datediff'),
+ 'DAY': Array('date-and-time-functions', 'function_day'),
+ 'DAYNAME': Array('date-and-time-functions', 'function_dayname'),
+ 'DAYOFMONTH': Array('date-and-time-functions', 'function_dayofmonth'),
+ 'DAYOFWEEK': Array('date-and-time-functions', 'function_dayofweek'),
+ 'DAYOFYEAR': Array('date-and-time-functions', 'function_dayofyear'),
+ 'DECLARE': Array('declare', 'declare'),
+ 'DECODE': Array('encryption-functions', 'function_decode'),
+ 'DEFAULT': Array('miscellaneous-functions', 'function_default'),
+ 'DEGREES': Array('mathematical-functions', 'function_degrees'),
+ 'DES_DECRYPT': Array('encryption-functions', 'function_des_decrypt'),
+ 'DES_ENCRYPT': Array('encryption-functions', 'function_des_encrypt'),
+ 'DIV': Array('arithmetic-functions', 'operator_div'),
+ 'ELT': Array('string-functions', 'function_elt'),
+ 'ENCODE': Array('encryption-functions', 'function_encode'),
+ 'ENCRYPT': Array('encryption-functions', 'function_encrypt'),
+ 'EXP': Array('mathematical-functions', 'function_exp'),
+ 'EXPORT_SET': Array('string-functions', 'function_export_set'),
+ 'EXTRACT': Array('date-and-time-functions', 'function_extract'),
+ 'ExtractValue': Array('xml-functions', 'function_extractvalue'),
+ 'FIELD': Array('string-functions', 'function_field'),
+ 'FIND_IN_SET': Array('string-functions', 'function_find_in_set'),
+ 'FLOOR': Array('mathematical-functions', 'function_floor'),
+ 'FORMAT': Array('string-functions', 'function_format'),
+ 'FOUND_ROWS': Array('information-functions', 'function_found_rows'),
+ 'FROM_DAYS': Array('date-and-time-functions', 'function_from_days'),
+ 'FROM_UNIXTIME': Array('date-and-time-functions', 'function_from_unixtime'),
+ 'GET_FORMAT': Array('date-and-time-functions', 'function_get_format'),
+ 'GET_LOCK': Array('miscellaneous-functions', 'function_get_lock'),
+ 'GREATEST': Array('comparison-operators', 'function_greatest'),
+ 'GROUP_CONCAT': Array('group-by-functions', 'function_group_concat'),
+ 'HEX': Array('string-functions', 'function_hex'),
+ 'HOUR': Array('date-and-time-functions', 'function_hour'),
+ 'IF': Array('control-flow-functions', 'function_if'),
+ 'IFNULL': Array('control-flow-functions', 'function_ifnull'),
+ 'IN': Array('comparison-operators', 'function_in'),
+ 'INET_ATON': Array('miscellaneous-functions', 'function_inet_aton'),
+ 'INET_NTOA': Array('miscellaneous-functions', 'function_inet_ntoa'),
+ 'INSTR': Array('string-functions', 'function_instr'),
+ 'INTERVAL': Array('comparison-operators', 'function_interval'),
+ 'IS_FREE_LOCK': Array('miscellaneous-functions', 'function_is_free_lock'),
+ 'IS_USED_LOCK': Array('miscellaneous-functions', 'function_is_used_lock'),
+ 'IS': Array('comparison-operators', 'operator_is'),
+ 'ISNULL': Array('comparison-operators', 'function_isnull'),
+ 'LAST_DAY': Array('date-and-time-functions', 'function_last_day'),
+ 'LAST_INSERT_ID': Array('information-functions', 'function_last_insert_id'),
+ 'LCASE': Array('string-functions', 'function_lcase'),
+ 'LEAST': Array('comparison-operators', 'function_least'),
+ 'LEFT': Array('string-functions', 'function_left'),
+ 'LENGTH': Array('string-functions', 'function_length'),
+ 'LIKE': Array('string-comparison-functions', 'operator_like'),
+ 'LN': Array('mathematical-functions', 'function_ln'),
+ 'LOAD_FILE': Array('string-functions', 'function_load_file'),
+ 'LOCALTIME': Array('date-and-time-functions', 'function_localtime'),
+ 'LOCALTIMESTAMP': Array('date-and-time-functions', 'function_localtimestamp'),
+ 'LOCATE': Array('string-functions', 'function_locate'),
+ 'LOG10': Array('mathematical-functions', 'function_log10'),
+ 'LOG2': Array('mathematical-functions', 'function_log2'),
+ 'LOG': Array('mathematical-functions', 'function_log'),
+ 'LOWER': Array('string-functions', 'function_lower'),
+ 'LPAD': Array('string-functions', 'function_lpad'),
+ 'LTRIM': Array('string-functions', 'function_ltrim'),
+ 'MAKE_SET': Array('string-functions', 'function_make_set'),
+ 'MAKEDATE': Array('date-and-time-functions', 'function_makedate'),
+ 'MAKETIME': Array('date-and-time-functions', 'function_maketime'),
+ 'MASTER_POS_WAIT': Array('miscellaneous-functions', 'function_master_pos_wait'),
+ 'MATCH': Array('fulltext-search', 'function_match'),
+ 'MAX': Array('group-by-functions', 'function_max'),
+ 'MD5': Array('encryption-functions', 'function_md5'),
+ 'MICROSECOND': Array('date-and-time-functions', 'function_microsecond'),
+ 'MID': Array('string-functions', 'function_mid'),
+ 'MIN': Array('group-by-functions', 'function_min'),
+ 'MINUTE': Array('date-and-time-functions', 'function_minute'),
+ 'MOD': Array('mathematical-functions', 'function_mod'),
+ 'MONTH': Array('date-and-time-functions', 'function_month'),
+ 'MONTHNAME': Array('date-and-time-functions', 'function_monthname'),
+ 'NAME_CONST': Array('miscellaneous-functions', 'function_name_const'),
+ 'NOT': Array('logical-operators', 'operator_not'),
+ 'NOW': Array('date-and-time-functions', 'function_now'),
+ 'NULLIF': Array('control-flow-functions', 'function_nullif'),
+ 'OCT': Array('mathematical-functions', 'function_oct'),
+ 'OCTET_LENGTH': Array('string-functions', 'function_octet_length'),
+ 'OLD_PASSWORD': Array('encryption-functions', 'function_old_password'),
+ 'OR': Array('logical-operators', 'operator_or'),
+ 'ORD': Array('string-functions', 'function_ord'),
+ 'PASSWORD': Array('encryption-functions', 'function_password'),
+ 'PERIOD_ADD': Array('date-and-time-functions', 'function_period_add'),
+ 'PERIOD_DIFF': Array('date-and-time-functions', 'function_period_diff'),
+ 'PI': Array('mathematical-functions', 'function_pi'),
+ 'POSITION': Array('string-functions', 'function_position'),
+ 'POW': Array('mathematical-functions', 'function_pow'),
+ 'POWER': Array('mathematical-functions', 'function_power'),
+ 'QUARTER': Array('date-and-time-functions', 'function_quarter'),
+ 'QUOTE': Array('string-functions', 'function_quote'),
+ 'RADIANS': Array('mathematical-functions', 'function_radians'),
+ 'RAND': Array('mathematical-functions', 'function_rand'),
+ 'REGEXP': Array('regexp', 'operator_regexp'),
+ 'RELEASE_LOCK': Array('miscellaneous-functions', 'function_release_lock'),
+ 'REPEAT': Array('string-functions', 'function_repeat'),
+ 'REVERSE': Array('string-functions', 'function_reverse'),
+ 'RIGHT': Array('string-functions', 'function_right'),
+ 'RLIKE': Array('regexp', 'operator_rlike'),
+ 'ROUND': Array('mathematical-functions', 'function_round'),
+ 'ROW_COUNT': Array('information-functions', 'function_row_count'),
+ 'RPAD': Array('string-functions', 'function_rpad'),
+ 'RTRIM': Array('string-functions', 'function_rtrim'),
+ 'SCHEMA': Array('information-functions', 'function_schema'),
+ 'SEC_TO_TIME': Array('date-and-time-functions', 'function_sec_to_time'),
+ 'SECOND': Array('date-and-time-functions', 'function_second'),
+ 'SESSION_USER': Array('information-functions', 'function_session_user'),
+ 'SHA': Array('encryption-functions', 'function_sha1'),
+ 'SHA1': Array('encryption-functions', 'function_sha1'),
+ 'SIGN': Array('mathematical-functions', 'function_sign'),
+ 'SIN': Array('mathematical-functions', 'function_sin'),
+ 'SLEEP': Array('miscellaneous-functions', 'function_sleep'),
+ 'SOUNDEX': Array('string-functions', 'function_soundex'),
+ 'SPACE': Array('string-functions', 'function_space'),
+ 'SQRT': Array('mathematical-functions', 'function_sqrt'),
+ 'STD': Array('group-by-functions', 'function_std'),
+ 'STDDEV_POP': Array('group-by-functions', 'function_stddev_pop'),
+ 'STDDEV_SAMP': Array('group-by-functions', 'function_stddev_samp'),
+ 'STDDEV': Array('group-by-functions', 'function_stddev'),
+ 'STR_TO_DATE': Array('date-and-time-functions', 'function_str_to_date'),
+ 'STRCMP': Array('string-comparison-functions', 'function_strcmp'),
+ 'SUBDATE': Array('date-and-time-functions', 'function_subdate'),
+ 'SUBSTR': Array('string-functions', 'function_substr'),
+ 'SUBSTRING_INDEX': Array('string-functions', 'function_substring_index'),
+ 'SUBSTRING': Array('string-functions', 'function_substring'),
+ 'SUBTIME': Array('date-and-time-functions', 'function_subtime'),
+ 'SUM': Array('group-by-functions', 'function_sum'),
+ 'SYSDATE': Array('date-and-time-functions', 'function_sysdate'),
+ 'SYSTEM_USER': Array('information-functions', 'function_system_user'),
+ 'TAN': Array('mathematical-functions', 'function_tan'),
+ 'TIME_FORMAT': Array('date-and-time-functions', 'function_time_format'),
+ 'TIME_TO_SEC': Array('date-and-time-functions', 'function_time_to_sec'),
+ 'TIME': Array('date-and-time-functions', 'function_time'),
+ 'TIMEDIFF': Array('date-and-time-functions', 'function_timediff'),
+ 'TIMESTAMP': Array('date-and-time-functions', 'function_timestamp'),
+ 'TIMESTAMPADD': Array('date-and-time-functions', 'function_timestampadd'),
+ 'TIMESTAMPDIFF': Array('date-and-time-functions', 'function_timestampdiff'),
+ 'TO_DAYS': Array('date-and-time-functions', 'function_to_days'),
+ 'TRIM': Array('string-functions', 'function_trim'),
+ 'TRUNCATE': Array('mathematical-functions', 'function_truncate'),
+ 'UCASE': Array('string-functions', 'function_ucase'),
+ 'UNCOMPRESS': Array('encryption-functions', 'function_uncompress'),
+ 'UNCOMPRESSED_LENGTH': Array('encryption-functions', 'function_uncompressed_length'),
+ 'UNHEX': Array('string-functions', 'function_unhex'),
+ 'UNIX_TIMESTAMP': Array('date-and-time-functions', 'function_unix_timestamp'),
+ 'UpdateXML': Array('xml-functions', 'function_updatexml'),
+ 'UPPER': Array('string-functions', 'function_upper'),
+ 'USER': Array('information-functions', 'function_user'),
+ 'UTC_DATE': Array('date-and-time-functions', 'function_utc_date'),
+ 'UTC_TIME': Array('date-and-time-functions', 'function_utc_time'),
+ 'UTC_TIMESTAMP': Array('date-and-time-functions', 'function_utc_timestamp'),
+ 'UUID_SHORT': Array('miscellaneous-functions', 'function_uuid_short'),
+ 'UUID': Array('miscellaneous-functions', 'function_uuid'),
+ 'VALUES': Array('miscellaneous-functions', 'function_values'),
+ 'VAR_POP': Array('group-by-functions', 'function_var_pop'),
+ 'VAR_SAMP': Array('group-by-functions', 'function_var_samp'),
+ 'VARIANCE': Array('group-by-functions', 'function_variance'),
+ 'VERSION': Array('information-functions', 'function_version'),
+ 'WEEK': Array('date-and-time-functions', 'function_week'),
+ 'WEEKDAY': Array('date-and-time-functions', 'function_weekday'),
+ 'WEEKOFYEAR': Array('date-and-time-functions', 'function_weekofyear'),
+ 'XOR': Array('logical-operators', 'operator_xor'),
+ 'YEAR': Array('date-and-time-functions', 'function_year'),
+ 'YEARWEEK': Array('date-and-time-functions', 'function_yearweek'),
+ 'SOUNDS_LIKE': Array('string-functions', 'operator_sounds-like'),
+ 'IS_NOT_NULL': Array('comparison-operators', 'operator_is-not-null'),
+ 'IS_NOT': Array('comparison-operators', 'operator_is-not'),
+ 'IS_NULL': Array('comparison-operators', 'operator_is-null'),
+ 'NOT_LIKE': Array('string-comparison-functions', 'operator_not-like'),
+ 'NOT_REGEXP': Array('regexp', 'operator_not-regexp'),
+ 'COUNT_DISTINCT': Array('group-by-functions', 'function_count-distinct'),
+ 'NOT_IN': Array('comparison-operators', 'function_not-in')
+};
+
+export const mysql_doc_builtin = {
+ 'TINYINT': Array('numeric-types'),
+ 'SMALLINT': Array('numeric-types'),
+ 'MEDIUMINT': Array('numeric-types'),
+ 'INT': Array('numeric-types'),
+ 'BIGINT': Array('numeric-types'),
+ 'DECIMAL': Array('numeric-types'),
+ 'FLOAT': Array('numeric-types'),
+ 'DOUBLE': Array('numeric-types'),
+ 'REAL': Array('numeric-types'),
+ 'BIT': Array('numeric-types'),
+ 'BOOLEAN': Array('numeric-types'),
+ 'SERIAL': Array('numeric-types'),
+ 'DATE': Array('date-and-time-types'),
+ 'DATETIME': Array('date-and-time-types'),
+ 'TIMESTAMP': Array('date-and-time-types'),
+ 'TIME': Array('date-and-time-types'),
+ 'YEAR': Array('date-and-time-types'),
+ 'CHAR': Array('string-types'),
+ 'VARCHAR': Array('string-types'),
+ 'TINYTEXT': Array('string-types'),
+ 'TEXT': Array('string-types'),
+ 'MEDIUMTEXT': Array('string-types'),
+ 'LONGTEXT': Array('string-types'),
+ 'BINARY': Array('string-types'),
+ 'VARBINARY': Array('string-types'),
+ 'TINYBLOB': Array('string-types'),
+ 'MEDIUMBLOB': Array('string-types'),
+ 'BLOB': Array('string-types'),
+ 'LONGBLOB': Array('string-types'),
+ 'ENUM': Array('string-types'),
+ 'SET': Array('string-types')
+};
diff --git a/js/src/consts/files.js b/js/src/consts/files.js
new file mode 100644
index 0000000000..930c3b3735
--- /dev/null
+++ b/js/src/consts/files.js
@@ -0,0 +1,14 @@
+/**
+ * List of JS files need to be loaded at the time of initial page load.
+ * Eg. For server_privileges.php, server_privileges.js is required
+ */
+
+/**
+ * @type {Object} files
+ */
+const files = {
+ server_privileges: ['server_privileges'],
+ server_databases: ['server_databases']
+};
+
+export default files;
diff --git a/js/src/functions/get_image.js b/js/src/functions/get_image.js
new file mode 100644
index 0000000000..167197e84a
--- /dev/null
+++ b/js/src/functions/get_image.js
@@ -0,0 +1,78 @@
+import { escapeHtml } from '../utils/Sanitise';
+/**
+ * Returns an HTML IMG tag for a particular image from a theme,
+ * which may be an actual file or an icon from a sprite
+ *
+ * @param string image The name of the file to get
+ * @param string alternate Used to set 'alt' and 'title' attributes of the image
+ * @param object attributes An associative array of other attributes
+ *
+ * @return Object The requested image, this object has two methods:
+ * .toString() - Returns the IMG tag for the requested image
+ * .attr(name) - Returns a particular attribute of the IMG
+ * tag given it's name
+ * .attr(name, value) - Sets a particular attribute of the IMG
+ * tag to the given value
+ */
+export function PMA_getImage (image, alternate, attributes) {
+ // custom image object, it will eventually be returned by this functions
+ var retval = {
+ data: {
+ // this is private
+ alt: '',
+ title: '',
+ src: 'themes/dot.gif',
+ },
+ attr: function (name, value) {
+ if (value === undefined) {
+ if (this.data[name] === undefined) {
+ return '';
+ } else {
+ return this.data[name];
+ }
+ } else {
+ this.data[name] = value;
+ }
+ },
+ toString: function () {
+ var retval = '<' + 'img';
+ for (var i in this.data) {
+ retval += ' ' + i + '="' + this.data[i] + '"';
+ }
+ retval += ' /' + '>';
+ return retval;
+ }
+ };
+ // initialise missing parameters
+ if (attributes === undefined) {
+ attributes = {};
+ }
+ if (alternate === undefined) {
+ alternate = '';
+ }
+ // set alt
+ if (attributes.alt !== undefined) {
+ retval.attr('alt', escapeHtml(attributes.alt));
+ } else {
+ retval.attr('alt', escapeHtml(alternate));
+ }
+ // set title
+ if (attributes.title !== undefined) {
+ retval.attr('title', escapeHtml(attributes.title));
+ } else {
+ retval.attr('title', escapeHtml(alternate));
+ }
+ // set css classes
+ retval.attr('class', 'icon ic_' + image);
+ // set all other attrubutes
+ for (var i in attributes) {
+ if (i === 'src') {
+ // do not allow to override the 'src' attribute
+ continue;
+ }
+
+ retval.attr(i, attributes[i]);
+ }
+
+ return retval;
+}
diff --git a/js/src/functions/navigation.js b/js/src/functions/navigation.js
new file mode 100644
index 0000000000..d8e8a4d6b9
--- /dev/null
+++ b/js/src/functions/navigation.js
@@ -0,0 +1,159 @@
+/**
+ * Expand the navigation and highlight the current database or table/view
+ *
+ * @returns void
+ */
+import { PMA_commonParams } from '../variables/common_params';
+export function PMA_showCurrentNavigation () {
+ var db = PMA_commonParams.get('db');
+ var table = PMA_commonParams.get('table');
+
+ $('#pma_navigation_tree').find('li.selected').removeClass('selected');
+ if (db) {
+ var $dbItem = findLoadedItem($('#pma_navigation_tree').find('> div'), db, 'database', !table);
+ if ($('#navi_db_select').length && $('option:selected', $('#navi_db_select')).length) {
+ if (!PMA_selectCurrentDb()) {
+ return;
+ }
+ // If loaded database in navigation is not same as current one
+ if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text() !== $('#navi_db_select').val()) {
+ loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
+ handleTableOrDb(table, $('#pma_navigation_tree_content'));
+ var $children = $('#pma_navigation_tree_content').children('div.list_container');
+ $children.promise().done(navTreeStateUpdate);
+ });
+ } else {
+ handleTableOrDb(table, $('#pma_navigation_tree_content'));
+ }
+ } else if ($dbItem) {
+ var $expander = $dbItem.children('div:first').children('a.expander');
+ // if not loaded or loaded but collapsed
+ if (!$expander.hasClass('loaded') || $expander.find('img').is('.ic_b_plus')) {
+ expandTreeNode($expander, function () {
+ handleTableOrDb(table, $dbItem);
+ });
+ } else {
+ handleTableOrDb(table, $dbItem);
+ }
+ }
+ } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
+ $('#navi_db_select').val('').hide().trigger('change');
+ }
+ PMA_showFullName($('#pma_navigation_tree'));
+
+ function handleTableOrDb (table, $dbItem) {
+ if (table) {
+ loadAndHighlightTableOrView($dbItem, table);
+ } else {
+ var $container = $dbItem.children('div.list_container');
+ var $tableContainer = $container.children('ul').children('li.tableContainer');
+ if ($tableContainer.length > 0) {
+ var $expander = $tableContainer.children('div:first').children('a.expander');
+ $tableContainer.addClass('selected');
+ expandTreeNode($expander, function () {
+ scrollToView($dbItem, true);
+ });
+ } else {
+ scrollToView($dbItem, true);
+ }
+ }
+ }
+
+ function findLoadedItem ($container, name, clazz, doSelect) {
+ var ret = false;
+ $container.children('ul').children('li').each(function () {
+ var $li = $(this);
+ // this is a navigation group, recurse
+ if ($li.is('.navGroup')) {
+ var $container = $li.children('div.list_container');
+ var $childRet = findLoadedItem($container, name, clazz, doSelect);
+ if ($childRet) {
+ ret = $childRet;
+ return false;
+ }
+ } else {
+ // this is a real navigation item
+ // name and class matches
+ if ((clazz && $li.is('.' + clazz) || !clazz) && $li.children('a').text() === name) {
+ if (doSelect) {
+ $li.addClass('selected');
+ }
+ // taverse up and expand and parent navigation groups
+ $li.parents('.navGroup').each(function () {
+ var $cont = $(this).children('div.list_container');
+ if (!$cont.is(':visible')) {
+ $(this).children('div:first').children('a.expander').click();
+ }
+ });
+ ret = $li;
+ return false;
+ }
+ }
+ });
+ return ret;
+ }
+
+ function loadAndHighlightTableOrView ($dbItem, itemName) {
+ var $container = $dbItem.children('div.list_container');
+ var $expander;
+ var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
+ // If item already there in some container
+ if ($whichItem) {
+ // get the relevant container while may also be a subcontainer
+ var $relatedContainer = $whichItem.closest('li.subContainer').length ? $whichItem.closest('li.subContainer') : $dbItem;
+ $whichItem = findLoadedItem($relatedContainer.children('div.list_container'), itemName, null, true);
+ // Show directly
+ showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
+ // else if item not there, try loading once
+ } else {
+ var $sub_containers = $dbItem.find('.subContainer');
+ // If there are subContainers i.e. tableContainer or viewContainer
+ if ($sub_containers.length > 0) {
+ var $containers = [];
+ $sub_containers.each(function (index) {
+ $containers[index] = $(this);
+ $expander = $containers[index].children('div:first').children('a.expander');
+ if (!$expander.hasClass('loaded')) {
+ loadAndShowTableOrView($expander, $containers[index], itemName);
+ }
+ });
+ // else if no subContainers
+ } else {
+ $expander = $dbItem.children('div:first').children('a.expander');
+ if (!$expander.hasClass('loaded')) {
+ loadAndShowTableOrView($expander, $dbItem, itemName);
+ }
+ }
+ }
+ }
+
+ function loadAndShowTableOrView ($expander, $relatedContainer, itemName) {
+ loadChildNodes(true, $expander, function (data) {
+ var $whichItem = findLoadedItem($relatedContainer.children('div.list_container'), itemName, null, true);
+ if ($whichItem) {
+ showTableOrView($whichItem, $expander);
+ }
+ });
+ }
+
+ function showTableOrView ($whichItem, $expander) {
+ expandTreeNode($expander, function (data) {
+ if ($whichItem) {
+ scrollToView($whichItem, false);
+ }
+ });
+ }
+
+ function isItemInContainer ($container, name, clazz) {
+ var $whichItem = null;
+ $items = $container.find(clazz);
+ var found = false;
+ $items.each(function () {
+ if ($(this).children('a').text() === name) {
+ $whichItem = $(this);
+ return false;
+ }
+ });
+ return $whichItem;
+ }
+}
diff --git a/js/src/index.js b/js/src/index.js
new file mode 100644
index 0000000000..452a988f19
--- /dev/null
+++ b/js/src/index.js
@@ -0,0 +1,86 @@
+import { AJAX } from './ajax';
+import './variables/import_variables';
+import { jQuery as $ } from './utils/JqueryExtended';
+import files from './consts/files';
+
+/**
+ * Page load event handler
+ */
+$(function () {
+ var menuContent = $('')
+ .append($('#serverinfo').clone())
+ .append($('#topmenucontainer').clone())
+ .html();
+ if (history && history.pushState) {
+ // set initial state reload
+ var initState = ('state' in window.history && window.history.state !== null);
+ var initURL = $('#selflink').find('> a').attr('href') || location.href;
+ var state = {
+ url : initURL,
+ menu : menuContent
+ };
+ history.replaceState(state, null);
+
+ $(window).on('popstate', function (event) {
+ var initPop = (! initState && location.href === initURL);
+ initState = true;
+ // check if popstate fired on first page itself
+ if (initPop) {
+ return;
+ }
+ var state = event.originalEvent.state;
+ if (state && state.menu) {
+ AJAX.$msgbox = PMA_ajaxShowMessage();
+ var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true';
+ var url = state.url || location.href;
+ $.get(url, params, AJAX.responseHandler);
+ // TODO: Check if sometimes menu is not retrieved from server,
+ // Not sure but it seems menu was missing only for printview which
+ // been removed lately, so if it's right some dead menu checks/fallbacks
+ // may need to be removed from this file and Header.php
+ // AJAX.handleMenu.replace(event.originalEvent.state.menu);
+ }
+ });
+ } else {
+ // Fallback to microhistory mechanism
+ AJAX.scriptHandler
+ .load([{ 'name' : 'microhistory.js', 'fire' : 1 }], function () {
+ // The cache primer is set by the footer class
+ if (PMA_MicroHistory.primer.url) {
+ PMA_MicroHistory.menus.add(
+ PMA_MicroHistory.primer.menuHash,
+ menuContent
+ );
+ }
+ $(function () {
+ // Queue up this event twice to make sure that we get a copy
+ // of the page after all other onload events have been fired
+ if (PMA_MicroHistory.primer.url) {
+ PMA_MicroHistory.add(
+ PMA_MicroHistory.primer.url,
+ PMA_MicroHistory.primer.scripts,
+ PMA_MicroHistory.primer.menuHash
+ );
+ }
+ });
+ });
+ }
+});
+
+/**
+ * This block of code is for importing javascript files needed
+ * for the first time loading of the page.
+ */
+let firstPage = window.location.pathname.replace('/', '').replace('.php', '');
+let indexStart = window.location.search.indexOf('target') + 7;
+let indexEnd = window.location.search.indexOf('.php');
+let indexPage = window.location.search.slice(indexStart, indexEnd);
+if (typeof files[firstPage] !== 'undefined' && firstPage.toLocaleLowerCase() !== 'index') {
+ for (let i in files[firstPage]) {
+ AJAX.scriptHandler.add(files[firstPage][i], 1);
+ }
+} else if (typeof files[indexPage] !== 'undefined' && firstPage.toLocaleLowerCase() === 'index') {
+ for (let i in files[indexPage]) {
+ AJAX.scriptHandler.add(files[indexPage][i], 1);
+ }
+}
diff --git a/js/src/server_databases.js b/js/src/server_databases.js
new file mode 100644
index 0000000000..af5c2c350f
--- /dev/null
+++ b/js/src/server_databases.js
@@ -0,0 +1,151 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used on the server databases list page
+ * @name Server Databases
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @required js/functions.js
+ */
+import { PMA_sprintf } from './utils/sprintf';
+import './variables/import_variables';
+import { PMA_ajaxShowMessage } from './utils/show_ajax_messages';
+import { escapeHtml } from './utils/Sanitise';
+import { PMA_Messages as PMA_messages } from './variables/export_variables';
+import { jQuery as $ } from './utils/JqueryExtended';
+import { AJAX } from './ajax';
+import { PMA_commonParams } from './variables/common_params';
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+export function teardown1 () {
+ $(document).off('submit', '#dbStatsForm');
+ $(document).off('submit', '#create_database_form.ajax');
+}
+
+export function onload1 () {
+ /**
+ * Attach Event Handler for 'Drop Databases'
+ */
+ $(document).on('submit', '#dbStatsForm', function (event) {
+ // debugger;
+ event.preventDefault();
+
+ var $form = $(this);
+
+ /**
+ * @var selected_dbs Array containing the names of the checked databases
+ */
+ var selected_dbs = [];
+ // loop over all checked checkboxes, except the .checkall_box checkbox
+ $form.find('input:checkbox:checked:not(.checkall_box)').each(function () {
+ $(this).closest('tr').addClass('removeMe');
+ selected_dbs[selected_dbs.length] = 'DROP DATABASE `' + escapeHtml($(this).val()) + '`;';
+ });
+ if (! selected_dbs.length) {
+ PMA_ajaxShowMessage(
+ $('').text(
+ PMA_messages.strNoDatabasesSelected
+ ),
+ 2000
+ );
+ return;
+ }
+ /**
+ * @var question String containing the question to be asked for confirmation
+ */
+ var question = PMA_messages.strDropDatabaseStrongWarning + ' ' +
+ PMA_sprintf(PMA_messages.strDoYouReally, selected_dbs.join(' '));
+
+ var argsep = PMA_commonParams.get('arg_separator');
+ $(this).PMA_confirm(
+ question,
+ $form.prop('action') + '?' + $(this).serialize() +
+ argsep + 'drop_selected_dbs=1' + argsep + 'is_js_confirmed=1' + argsep + 'ajax_request=true',
+ function (url) {
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest, false);
+
+ var params = getJSConfirmCommonParam(this);
+
+ $.post(url, params, function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+
+ var $rowsToRemove = $form.find('tr.removeMe');
+ var $databasesCount = $('#filter-rows-count');
+ var newCount = parseInt($databasesCount.text(), 10) - $rowsToRemove.length;
+ $databasesCount.text(newCount);
+
+ $rowsToRemove.remove();
+ $form.find('tbody').PMA_sort_table('.name');
+ if ($form.find('tbody').find('tr').length === 0) {
+ // user just dropped the last db on this page
+ PMA_commonActions.refreshMain();
+ }
+ PMA_reloadNavigation();
+ } else {
+ $form.find('tr.removeMe').removeClass('removeMe');
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ }
+ ); // end $.PMA_confirm()
+ }); // end of Drop Database action
+
+ /**
+ * Attach Ajax event handlers for 'Create Database'.
+ */
+ $(document).on('submit', '#create_database_form.ajax', function (event) {
+ event.preventDefault();
+
+ var $form = $(this);
+
+ // TODO Remove this section when all browsers support HTML5 "required" property
+ var newDbNameInput = $form.find('input[name=new_db]');
+ if (newDbNameInput.val() === '') {
+ newDbNameInput.focus();
+ alert(PMA_messages.strFormEmpty);
+ return;
+ }
+ // end remove
+
+ PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ PMA_prepareForAjaxRequest($form);
+
+ $.post($form.attr('action'), $form.serialize(), function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+
+ var $databases_count_object = $('#filter-rows-count');
+ var databases_count = parseInt($databases_count_object.text(), 10) + 1;
+ $databases_count_object.text(databases_count);
+ PMA_reloadNavigation();
+
+ // make ajax request to load db structure page - taken from ajax.js
+ var dbStruct_url = data.url_query;
+ dbStruct_url = dbStruct_url.replace(/amp;/ig, '');
+ var params = 'ajax_request=true' + PMA_commonParams.get('arg_separator') + 'ajax_page_request=true';
+ if (! (history && history.pushState)) {
+ params += PMA_MicroHistory.menus.getRequestParam();
+ }
+ $.get(dbStruct_url, params, AJAX.responseHandler);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ }); // end $(document).on()
+
+ /* Don't show filter if number of databases are very few */
+ var databasesCount = $('#filter-rows-count').html();
+ if (databasesCount <= 10) {
+ $('#tableFilter').hide();
+ }
+
+ var tableRows = $('.server_databases');
+ $.each(tableRows, function (index, item) {
+ $(this).click(function () {
+ PMA_commonActions.setDb($(this).attr('data'));
+ });
+ });
+}
diff --git a/js/src/server_privileges.js b/js/src/server_privileges.js
new file mode 100644
index 0000000000..8bbc69c386
--- /dev/null
+++ b/js/src/server_privileges.js
@@ -0,0 +1,436 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used in server privilege pages
+ * @name Server Privileges
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @requires js/functions.js
+ *
+ */
+import { PMA_sprintf } from './utils/sprintf';
+import { checkPasswordStrength, displayPasswordGenerateButton } from './utils/password';
+// import { AJAX } from './ajax';
+import { PMA_Messages as PMA_messages } from './variables/export_variables';
+import { PMA_ajaxShowMessage, PMA_ajaxRemoveMessage } from './utils/show_ajax_messages';
+import { PMA_commonParams } from './variables/common_params';
+import { jQuery as $ } from './utils/JqueryExtended';
+
+/**
+ * AJAX scripts for server_privileges page.
+ *
+ * Actions ajaxified here:
+ * Add user
+ * Revoke a user
+ * Edit privileges
+ * Export privileges
+ * Paginate table of users
+ * Flush privileges
+ *
+ * @memberOf jQuery
+ * @name document.ready
+ */
+
+/**
+ * Unbind all event handlers before tearing down a page
+ */
+export function teardown1 () {
+ $('#fieldset_add_user_login').off('change', 'input[name=\'username\']');
+ $(document).off('click', '#fieldset_delete_user_footer #buttonGo.ajax');
+ $(document).off('click', 'a.edit_user_group_anchor.ajax');
+ $(document).off('click', 'button.mult_submit[value=export]');
+ $(document).off('click', 'a.export_user_anchor.ajax');
+ $(document).off('click', '#initials_table a.ajax');
+ $('#checkbox_drop_users_db').off('click');
+ $(document).off('click', '.checkall_box');
+ $(document).off('change', '#checkbox_SSL_priv');
+ $(document).off('change', 'input[name="ssl_type"]');
+ $(document).off('change', '#select_authentication_plugin');
+}
+
+export function onload1 () {
+ /**
+ * Display a warning if there is already a user by the name entered as the username.
+ */
+ $('#fieldset_add_user_login').on('change', 'input[name=\'username\']', function () {
+ var username = $(this).val();
+ var $warning = $('#user_exists_warning');
+ if ($('#select_pred_username').val() === 'userdefined' && username !== '') {
+ var href = $('form[name=\'usersForm\']').attr('action');
+ var params = {
+ 'ajax_request' : true,
+ 'server' : PMA_commonParams.get('server'),
+ 'validate_username' : true,
+ 'username' : username
+ };
+ $.get(href, params, function (data) {
+ if (data.user_exists) {
+ $warning.show();
+ } else {
+ $warning.hide();
+ }
+ });
+ } else {
+ $warning.hide();
+ }
+ });
+
+ /**
+ * Indicating password strength
+ */
+ var meter_obj;
+ var meter_obj_label;
+ var username;
+ $(document).on('keyup', '#text_pma_pw', function () {
+ meter_obj = $('#password_strength_meter');
+ meter_obj_label = $('#password_strength');
+ username = $('input[name="username"]');
+ username = username.val();
+ checkPasswordStrength($(this).val(), meter_obj, meter_obj_label, username);
+ });
+
+ $(document).on('keyup', '#text_pma_change_pw', function () {
+ meter_obj = $('#change_password_strength_meter');
+ meter_obj_label = $('#change_password_strength');
+ checkPasswordStrength($(this).val(), meter_obj, meter_obj_label, PMA_commonParams.get('user'));
+ });
+
+ /**
+ * Display a notice if sha256_password is selected
+ */
+ $(document).on('change', '#select_authentication_plugin', function () {
+ var selected_plugin = $(this).val();
+ if (selected_plugin === 'sha256_password') {
+ $('#ssl_reqd_warning').show();
+ } else {
+ $('#ssl_reqd_warning').hide();
+ }
+ });
+
+ /**
+ * AJAX handler for 'Revoke User'
+ *
+ * @see PMA_ajaxShowMessage()
+ * @memberOf jQuery
+ * @name revoke_user_click
+ */
+ $(document).on('click', '#fieldset_delete_user_footer #buttonGo.ajax', function (event) {
+ event.preventDefault();
+
+ var $thisButton = $(this);
+ var $form = $('#usersForm');
+
+ $thisButton.PMA_confirm(PMA_messages.strDropUserWarning, $form.attr('action'), function (url) {
+ var $drop_users_db_checkbox = $('#checkbox_drop_users_db');
+ if ($drop_users_db_checkbox.is(':checked')) {
+ var is_confirmed = confirm(PMA_messages.strDropDatabaseStrongWarning + '\n' + PMA_sprintf(PMA_messages.strDoYouReally, 'DROP DATABASE'));
+ if (! is_confirmed) {
+ // Uncheck the drop users database checkbox
+ $drop_users_db_checkbox.prop('checked', false);
+ }
+ }
+
+ PMA_ajaxShowMessage(PMA_messages.strRemovingSelectedUsers);
+
+ var argsep = PMA_commonParams.get('arg_separator');
+ $.post(url, $form.serialize() + argsep + 'delete=' + $thisButton.val() + argsep + 'ajax_request=true', function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ PMA_ajaxShowMessage(data.message);
+ // Refresh navigation, if we droppped some databases with the name
+ // that is the same as the username of the deleted user
+ if ($('#checkbox_drop_users_db:checked').length) {
+ PMA_reloadNavigation();
+ }
+ // Remove the revoked user from the users list
+ $form.find('input:checkbox:checked').parents('tr').slideUp('medium', function () {
+ var this_user_initial = $(this).find('input:checkbox').val().charAt(0).toUpperCase();
+ $(this).remove();
+
+ // If this is the last user with this_user_initial, remove the link from #initials_table
+ if ($('#tableuserrights').find('input:checkbox[value^="' + this_user_initial + '"], input:checkbox[value^="' + this_user_initial.toLowerCase() + '"]').length === 0) {
+ $('#initials_table').find('td > a:contains(' + this_user_initial + ')').parent('td').html(this_user_initial);
+ }
+
+ // Re-check the classes of each row
+ $form
+ .find('tbody').find('tr:odd')
+ .removeClass('even').addClass('odd')
+ .end()
+ .find('tr:even')
+ .removeClass('odd').addClass('even');
+
+ // update the checkall checkbox
+ $(checkboxes_sel).trigger('change');
+ });
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.post()
+ });
+ }); // end Revoke User
+
+ $(document).on('click', 'a.edit_user_group_anchor.ajax', function (event) {
+ event.preventDefault();
+ $(this).parents('tr').addClass('current_row');
+ var $msg = PMA_ajaxShowMessage();
+ $.get(
+ $(this).attr('href'),
+ {
+ 'ajax_request': true,
+ 'edit_user_group_dialog': true
+ },
+ function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ PMA_ajaxRemoveMessage($msg);
+ var buttonOptions = {};
+ buttonOptions[PMA_messages.strGo] = function () {
+ var usrGroup = $('#changeUserGroupDialog')
+ .find('select[name="userGroup"]')
+ .val();
+ var $message = PMA_ajaxShowMessage();
+ var argsep = PMA_commonParams.get('arg_separator');
+ $.post(
+ 'server_privileges.php',
+ $('#changeUserGroupDialog').find('form').serialize() + argsep + 'ajax_request=1',
+ function (data) {
+ PMA_ajaxRemoveMessage($message);
+ if (typeof data !== 'undefined' && data.success === true) {
+ $('#usersForm')
+ .find('.current_row')
+ .removeClass('current_row')
+ .find('.usrGroup')
+ .text(usrGroup);
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ $('#usersForm')
+ .find('.current_row')
+ .removeClass('current_row');
+ }
+ }
+ );
+ $(this).dialog('close');
+ };
+ buttonOptions[PMA_messages.strClose] = function () {
+ $(this).dialog('close');
+ };
+ var $dialog = $('')
+ .attr('id', 'changeUserGroupDialog')
+ .append(data.message)
+ .dialog({
+ width: 500,
+ minWidth: 300,
+ modal: true,
+ buttons: buttonOptions,
+ title: $('legend', $(data.message)).text(),
+ close: function () {
+ $(this).remove();
+ }
+ });
+ $dialog.find('legend').remove();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ $('#usersForm')
+ .find('.current_row')
+ .removeClass('current_row');
+ }
+ }
+ );
+ });
+
+ /**
+ * AJAX handler for 'Export Privileges'
+ *
+ * @see PMA_ajaxShowMessage()
+ * @memberOf jQuery
+ * @name export_user_click
+ */
+ $(document).on('click', 'button.mult_submit[value=export]', function (event) {
+ event.preventDefault();
+ // can't export if no users checked
+ if ($(this.form).find('input:checked').length === 0) {
+ PMA_ajaxShowMessage(PMA_messages.strNoAccountSelected, 2000, 'success');
+ return;
+ }
+ var $msgbox = PMA_ajaxShowMessage();
+ var button_options = {};
+ button_options[PMA_messages.strClose] = function () {
+ $(this).dialog('close');
+ };
+ var argsep = PMA_commonParams.get('arg_separator');
+ $.post(
+ $(this.form).prop('action'),
+ $(this.form).serialize() + argsep + 'submit_mult=export' + argsep + 'ajax_request=true',
+ function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ var $ajaxDialog = $('')
+ .append(data.message)
+ .dialog({
+ title: data.title,
+ width: 500,
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ PMA_ajaxRemoveMessage($msgbox);
+ // Attach syntax highlighted editor to export dialog
+ PMA_getSQLEditor($ajaxDialog.find('textarea'));
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }
+ ); // end $.post
+ });
+ // if exporting non-ajax, highlight anyways
+ PMA_getSQLEditor($('textarea.export'));
+
+ $(document).on('click', 'a.export_user_anchor.ajax', function (event) {
+ event.preventDefault();
+ var $msgbox = PMA_ajaxShowMessage();
+ /**
+ * @var button_options Object containing options for jQueryUI dialog buttons
+ */
+ var button_options = {};
+ button_options[PMA_messages.strClose] = function () {
+ $(this).dialog('close');
+ };
+ $.get($(this).attr('href'), { 'ajax_request': true }, function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ var $ajaxDialog = $('')
+ .append(data.message)
+ .dialog({
+ title: data.title,
+ width: 500,
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ }
+ });
+ PMA_ajaxRemoveMessage($msgbox);
+ // Attach syntax highlighted editor to export dialog
+ PMA_getSQLEditor($ajaxDialog.find('textarea'));
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get
+ }); // end export privileges
+
+ /**
+ * AJAX handler to Paginate the Users Table
+ *
+ * @see PMA_ajaxShowMessage()
+ * @name paginate_users_table_click
+ * @memberOf jQuery
+ */
+ $(document).on('click', '#initials_table a.ajax', function (event) {
+ event.preventDefault();
+ var $msgbox = PMA_ajaxShowMessage();
+ $.get($(this).attr('href'), { 'ajax_request' : true }, function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ PMA_ajaxRemoveMessage($msgbox);
+ // This form is not on screen when first entering Privileges
+ // if there are more than 50 users
+ $('div.notice').remove();
+ $('#usersForm').hide('medium').remove();
+ $('#fieldset_add_user').hide('medium').remove();
+ $('#initials_table')
+ .prop('id', 'initials_table_old')
+ .after(data.message).show('medium')
+ .siblings('h2').not(':first').remove();
+ // prevent double initials table
+ $('#initials_table_old').remove();
+ } else {
+ PMA_ajaxShowMessage(data.error, false);
+ }
+ }); // end $.get
+ }); // end of the paginate users table
+
+ $(document).on('change', 'input[name="ssl_type"]', function (e) {
+ var $div = $('#specified_div');
+ if ($('#ssl_type_SPECIFIED').is(':checked')) {
+ $div.find('input').prop('disabled', false);
+ } else {
+ $div.find('input').prop('disabled', true);
+ }
+ });
+
+ $(document).on('change', '#checkbox_SSL_priv', function (e) {
+ var $div = $('#require_ssl_div');
+ if ($(this).is(':checked')) {
+ $div.find('input').prop('disabled', false);
+ $('#ssl_type_SPECIFIED').trigger('change');
+ } else {
+ $div.find('input').prop('disabled', true);
+ }
+ });
+
+ $('#checkbox_SSL_priv').trigger('change');
+
+ /*
+ * Create submenu for simpler interface
+ */
+ var addOrUpdateSubmenu = function () {
+ var $topmenu2 = $('#topmenu2');
+ var $edit_user_dialog = $('#edit_user_dialog');
+ var submenu_label;
+ var submenu_link;
+ var link_number;
+
+ // if submenu exists yet, remove it first
+ if ($topmenu2.length > 0) {
+ $topmenu2.remove();
+ }
+
+ // construct a submenu from the existing fieldsets
+ $topmenu2 = $('
').prop('id', 'topmenu2');
+
+ $('#edit_user_dialog .submenu-item').each(function () {
+ submenu_label = $(this).find('legend[data-submenu-label]').data('submenu-label');
+
+ submenu_link = $('')
+ .prop('href', '#')
+ .html(submenu_label);
+
+ $('')
+ .append(submenu_link)
+ .appendTo($topmenu2);
+ });
+
+ // click handlers for submenu
+ $topmenu2.find('a').click(function (e) {
+ e.preventDefault();
+ // if already active, ignore click
+ if ($(this).hasClass('tabactive')) {
+ return;
+ }
+ $topmenu2.find('a').removeClass('tabactive');
+ $(this).addClass('tabactive');
+
+ // which section to show now?
+ link_number = $topmenu2.find('a').index($(this));
+ // hide all sections but the one to show
+ $('#edit_user_dialog .submenu-item').hide().eq(link_number).show();
+ });
+
+ // make first menu item active
+ // TODO: support URL hash history
+ $topmenu2.find('> :first-child a').addClass('tabactive');
+ $edit_user_dialog.prepend($topmenu2);
+
+ // hide all sections but the first
+ $('#edit_user_dialog .submenu-item').hide().eq(0).show();
+
+ // scroll to the top
+ $('html, body').animate({ scrollTop: 0 }, 'fast');
+ };
+
+ $('input.autofocus').focus();
+ $(checkboxes_sel).trigger('change');
+ displayPasswordGenerateButton();
+ if ($('#edit_user_dialog').length > 0) {
+ addOrUpdateSubmenu();
+ }
+
+ var windowwidth = $(window).width();
+ $('.jsresponsive').css('max-width', (windowwidth - 35) + 'px');
+}
diff --git a/js/src/utils/JqueryExtended.js b/js/src/utils/JqueryExtended.js
new file mode 100644
index 0000000000..2e4ccfd477
--- /dev/null
+++ b/js/src/utils/JqueryExtended.js
@@ -0,0 +1,253 @@
+import $ from 'jquery';
+import 'jquery-migrate';
+import 'jquery-ui-bundle';
+import 'jquery-ui-timepicker-addon';
+import 'jquery-mousewheel';
+import 'jquery.event.drag';
+import 'jquery-validation';
+import { methods } from './menu_resizer';
+// TODO: To use this import for replacing variables used in this file for
+// extending various strings for localization.
+// import { GlobalVariables, timePicker, validations } from '../variables/export_variables';
+import { PMA_Messages as PMA_messages } from '../variables/export_variables';
+
+/**
+ * Make sure that ajax requests will not be cached
+ * by appending a random variable to their parameters
+ */
+$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
+ var nocache = new Date().getTime() + '' + Math.floor(Math.random() * 1000000);
+ if (typeof options.data === 'string') {
+ options.data += '&_nocache=' + nocache + '&token=' + encodeURIComponent(PMA_commonParams.get('token'));
+ } else if (typeof options.data === 'object') {
+ options.data = $.extend(originalOptions.data, { '_nocache' : nocache, 'token': PMA_commonParams.get('token') });
+ }
+});
+
+/**
+ * Comes from menu_resizer.js
+ */
+$.fn.menuResizer = function (method) {
+ if (methods[method]) {
+ return methods[method].call(this);
+ } else if (typeof method === 'function') {
+ return methods.init.apply(this, [method]);
+ } else {
+ $.error('Method ' + method + ' does not exist on jQuery.menuResizer');
+ }
+};
+
+/**
+ * comes from makegrid.js
+ */
+$.fn.noSelect = function (p) { // no select plugin by Paulo P.Marinas
+ var prevent = (p === null) ? true : p;
+ var is_msie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv\:11\./);
+ var is_firefox = navigator.userAgent.indexOf('Firefox') > -1;
+ var is_safari = navigator.userAgent.indexOf('Safari') > -1;
+ var is_opera = navigator.userAgent.indexOf('Presto') > -1;
+ if (prevent) {
+ return this.each(function () {
+ if (is_msie || is_safari) {
+ $(this).on('selectstart', false);
+ } else if (is_firefox) {
+ $(this).css('MozUserSelect', 'none');
+ $('body').trigger('focus');
+ } else if (is_opera) {
+ $(this).on('mousedown', false);
+ } else {
+ $(this).attr('unselectable', 'on');
+ }
+ });
+ } else {
+ return this.each(function () {
+ if (is_msie || is_safari) {
+ $(this).off('selectstart');
+ } else if (is_firefox) {
+ $(this).css('MozUserSelect', 'inherit');
+ } else if (is_opera) {
+ $(this).off('mousedown');
+ } else {
+ $(this).removeAttr('unselectable');
+ }
+ });
+ }
+};
+
+/**
+ * comes from functions.js
+ */
+/**
+ * jQuery plugin to correctly filter input fields by value, needed
+ * because some nasty values may break selector syntax
+ */
+$.fn.filterByValue = function (value) {
+ return this.filter(function () {
+ return $(this).val() === value;
+ });
+};
+
+/**
+ * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
+ * return a jQuery object yet and hence cannot be chained
+ *
+ * @param string question
+ * @param string url URL to be passed to the callbackFn to make
+ * an Ajax call to
+ * @param function callbackFn callback to execute after user clicks on OK
+ * @param function openCallback optional callback to run when dialog is shown
+ */
+
+$.fn.PMA_confirm = function (question, url, callbackFn, openCallback) {
+ var confirmState = PMA_commonParams.get('confirm');
+ if (! confirmState) {
+ // user does not want to confirm
+ if ($.isFunction(callbackFn)) {
+ callbackFn.call(this, url);
+ return true;
+ }
+ }
+ if (PMA_messages.strDoYouReally === '') {
+ return true;
+ }
+
+ /**
+ * @var button_options Object that stores the options passed to jQueryUI
+ * dialog
+ */
+ var button_options = [
+ {
+ text: PMA_messages.strOK,
+ 'class': 'submitOK',
+ click: function () {
+ $(this).dialog('close');
+ if ($.isFunction(callbackFn)) {
+ callbackFn.call(this, url);
+ }
+ }
+ },
+ {
+ text: PMA_messages.strCancel,
+ 'class': 'submitCancel',
+ click: function () {
+ $(this).dialog('close');
+ }
+ }
+ ];
+
+ $('', { 'id': 'confirm_dialog', 'title': PMA_messages.strConfirm })
+ .prepend(question)
+ .dialog({
+ buttons: button_options,
+ close: function () {
+ $(this).remove();
+ },
+ open: openCallback,
+ modal: true
+ });
+};
+
+/**
+ * jQuery function to sort a table's body after a new row has been appended to it.
+ *
+ * @param string text_selector string to select the sortKey's text
+ *
+ * @return jQuery Object for chaining purposes
+ */
+$.fn.PMA_sort_table = function (text_selector) {
+ return this.each(function () {
+ /**
+ * @var table_body Object referring to the table's element
+ */
+ var table_body = $(this);
+ /**
+ * @var rows Object referring to the collection of rows in {@link table_body}
+ */
+ var rows = $(this).find('tr').get();
+
+ // get the text of the field that we will sort by
+ $.each(rows, function (index, row) {
+ row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase());
+ });
+
+ // get the sorted order
+ rows.sort(function (a, b) {
+ if (a.sortKey < b.sortKey) {
+ return -1;
+ }
+ if (a.sortKey > b.sortKey) {
+ return 1;
+ }
+ return 0;
+ });
+
+ // pull out each row from the table and then append it according to it's order
+ $.each(rows, function (index, row) {
+ $(table_body).append(row);
+ row.sortKey = null;
+ });
+ });
+};
+
+/**
+ * Return POST data as stored by Util::linkOrButton
+ */
+$.fn.getPostData = function () {
+ var dataPost = this.attr('data-post');
+ // Strip possible leading ?
+ if (dataPost !== undefined && dataPost.substring(0,1) === '?') {
+ dataPost = dataPost.substr(1);
+ }
+ return dataPost;
+};
+
+/**
+ * Replacing default datepicker strings for localization
+ */
+if ($.datepicker) {
+ // Creating copy of datepicker strings object
+ var datePicker = Object.assign(window.datePicker);
+ // Deleting datepicker variable from window as it is of no use now
+ delete window.datePicker;
+
+ for (let key in datePicker) {
+ $.datepicker.regional[''][key] = datePicker[key];
+ }
+}
+
+/**
+ * Replacing default timepicker strings for localozation
+ */
+if ($.timePicker) {
+ // Creating copy of timepicker strings object
+ var timePicker = Object.assign(window.timePicker);
+ // Deleting timepicker variable from window as it is of no use now
+ delete window.timePicker;
+
+ for (let key in timePicker) {
+ $.timepicker.regional[''][key] = timePicker[key];
+ }
+}
+
+export function extendingValidatorMessages () {
+ // Creating copy of validationMessage strings object
+ var validateMessage = Object.assign(window.validationMessage);
+ // Deleting validationMessage variable from window as it is of no use now
+ delete window.validationMessage;
+ // Replacing default validation messages forr localization
+ $.extend($.validator.messages, validateMessage);
+
+ // Creating copy of validationFormat strings object
+ var validateFormat = Object.assign(window.validationFormat);
+ // Deleting validationFormat variable from window as it is of no use now
+ delete window.validationFormat;
+ for (let i in validateFormat) {
+ validateFormat[i] = $.validator.format(validateFormat[i]);
+ }
+ // Replacing default validation messages forr localization
+ $.extend($.validator.messages, validateFormat);
+}
+
+window.jQ = $;
+
+export const jQuery = $;
diff --git a/js/src/utils/Sanitise.js b/js/src/utils/Sanitise.js
new file mode 100644
index 0000000000..ac7a8cb62f
--- /dev/null
+++ b/js/src/utils/Sanitise.js
@@ -0,0 +1,35 @@
+/**
+ * HTML escaping
+ */
+
+export function escapeHtml (unsafe) {
+ if (typeof(unsafe) !== 'undefined') {
+ return unsafe
+ .toString()
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ } else {
+ return false;
+ }
+}
+
+export function escapeJsString (unsafe) {
+ if (typeof(unsafe) !== 'undefined') {
+ return unsafe
+ .toString()
+ .replace('\x00', '')
+ .replace('\\', '\\\\')
+ .replace('\'', '\\\'')
+ .replace(''', '\\\'')
+ .replace('"', '\"')
+ .replace('"', '\"')
+ .replace('\n', '\n')
+ .replace('\r', '\r')
+ .replace(/<\/script/gi, '\' + \'script');
+ } else {
+ return false;
+ }
+}
diff --git a/js/src/utils/menu_resizer.js b/js/src/utils/menu_resizer.js
new file mode 100644
index 0000000000..d09a4fc5ea
--- /dev/null
+++ b/js/src/utils/menu_resizer.js
@@ -0,0 +1,209 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Handles the resizing of a menu according to the available screen width
+ *
+ * Uses themes/original/css/resizable-menu.css.php
+ *
+ * To initialise:
+ * $('#myMenu').menuResizer(function () {
+ * // This function will be called to find out how much
+ * // available horizontal space there is for the menu
+ * return $('body').width() - 5; // Some extra margin for good measure
+ * });
+ *
+ * To trigger a resize operation:
+ * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize()
+ *
+ * To restore the menu to a state like before it was initialized:
+ * $('#myMenu').menuResizer('destroy');
+ *
+ * @package PhpMyAdmin
+ */
+function MenuResizer ($container, widthCalculator) {
+ var self = this;
+ self.$container = $container;
+ self.widthCalculator = widthCalculator;
+ var windowWidth = $(window).width();
+
+ if (windowWidth < 768) {
+ $('#pma_navigation_resizer').css({ 'width': '0px' });
+ }
+ // Sets the image for the left and right scroll indicator
+ $('.scrollindicator--left').html($(PMA_getImage('b_left').toString()));
+ $('.scrollindicator--right').html($(PMA_getImage('b_right').toString()));
+
+ // Set the width of the navigation bar without scroll indicator
+ $('.navigationbar').css({ 'width': widthCalculator.call($container) - 60 });
+
+ // Scroll the navigation bar on click
+ $('.scrollindicator--right').on('click', function () {
+ $('.navigationbar').scrollLeft($('.navigationbar').scrollLeft() + 70);
+ });
+ $('.scrollindicator--left').on('click', function () {
+ $('.navigationbar').scrollLeft($('.navigationbar').scrollLeft() - 70);
+ });
+
+ // create submenu container
+ var link = $('', { href: '#', 'class': 'tab nowrap' })
+ .text(PMA_messages.strMore)
+ .on('click', false); // same as event.preventDefault()
+ var img = $container.find('li img');
+ if (img.length) {
+ $(PMA_getImage('b_more').toString()).prependTo(link);
+ }
+ var $submenu = $('', { 'class': 'submenu' })
+ .append(link)
+ .append($('
'))
+ .on('mouseenter', function () {
+ if ($(this).find('ul .tabactive').length === 0) {
+ $(this)
+ .addClass('submenuhover')
+ .find('> a')
+ .addClass('tabactive');
+ }
+ })
+ .on('mouseleave', function () {
+ if ($(this).find('ul .tabactive').length === 0) {
+ $(this)
+ .removeClass('submenuhover')
+ .find('> a')
+ .removeClass('tabactive');
+ }
+ });
+ $container.children('.clearfloat').remove();
+ $container.append($submenu).append('');
+ setTimeout(function () {
+ self.resize();
+ }, 4);
+}
+MenuResizer.prototype.resize = function () {
+ var wmax = this.widthCalculator.call(this.$container);
+ var windowWidth = $(window).width();
+ var $submenu = this.$container.find('.submenu:last');
+ var submenu_w = $submenu.outerWidth(true);
+ var $submenu_ul = $submenu.find('ul');
+ var $li = this.$container.find('> li');
+ var $li2 = $submenu_ul.find('li');
+ var more_shown = $li2.length > 0;
+ // Calculate the total width used by all the shown tabs
+ var total_len = more_shown ? submenu_w : 0;
+ var l = $li.length - 1;
+ var i;
+ for (i = 0; i < l; i++) {
+ total_len += $($li[i]).outerWidth(true);
+ }
+
+ var hasVScroll = document.body.scrollHeight > document.body.clientHeight;
+ if (hasVScroll) {
+ windowWidth += 15;
+ }
+ var navigationwidth = wmax;
+ if (windowWidth < 768) {
+ wmax = 2000;
+ }
+
+ // Now hide menu elements that don't fit into the menubar
+ var hidden = false; // Whether we have hidden any tabs
+ while (total_len >= wmax && --l >= 0) { // Process the tabs backwards
+ hidden = true;
+ var el = $($li[l]);
+ var el_width = el.outerWidth(true);
+ el.data('width', el_width);
+ if (! more_shown) {
+ total_len -= el_width;
+ el.prependTo($submenu_ul);
+ total_len += submenu_w;
+ more_shown = true;
+ } else {
+ total_len -= el_width;
+ el.prependTo($submenu_ul);
+ }
+ }
+ // If we didn't hide any tabs, then there might be some space to show some
+ if (! hidden) {
+ // Show menu elements that do fit into the menubar
+ for (i = 0, l = $li2.length; i < l; i++) {
+ total_len += $($li2[i]).data('width');
+ // item fits or (it is the last item
+ // and it would fit if More got removed)
+ if (total_len < wmax ||
+ (i === $li2.length - 1 && total_len - submenu_w < wmax)
+ ) {
+ $($li2[i]).insertBefore($submenu);
+ } else {
+ break;
+ }
+ }
+ }
+ // Show/hide the "More" tab as needed
+ if (windowWidth < 768) {
+ $('.navigationbar').css({ 'width': windowWidth - 80 - $('#pma_navigation').width() });
+ $submenu.removeClass('shown');
+ $('.navigationbar').css({ 'overflow': 'hidden' });
+ } else {
+ $('.navigationbar').css({ 'width': 'auto' });
+ $('.navigationbar').css({ 'overflow': 'visible' });
+ if ($submenu_ul.find('li').length > 0) {
+ $submenu.addClass('shown');
+ } else {
+ $submenu.removeClass('shown');
+ }
+ }
+ if (this.$container.find('> li').length === 1) {
+ // If there is only the "More" tab left, then we need
+ // to align the submenu to the left edge of the tab
+ $submenu_ul.removeClass().addClass('only');
+ } else {
+ // Otherwise we align the submenu to the right edge of the tab
+ $submenu_ul.removeClass().addClass('notonly');
+ }
+ if ($submenu.find('.tabactive').length) {
+ $submenu
+ .addClass('active')
+ .find('> a')
+ .removeClass('tab')
+ .addClass('tabactive');
+ } else {
+ $submenu
+ .removeClass('active')
+ .find('> a')
+ .addClass('tab')
+ .removeClass('tabactive');
+ }
+};
+MenuResizer.prototype.destroy = function () {
+ var $submenu = this.$container.find('li.submenu').removeData();
+ $submenu.find('li').appendTo(this.$container);
+ $submenu.remove();
+};
+
+/** Public API */
+export var methods = {
+ init: function (widthCalculator) {
+ return this.each(function () {
+ var $this = $(this);
+ if (! $this.data('menuResizer')) {
+ $this.data(
+ 'menuResizer',
+ new MenuResizer($this, widthCalculator)
+ );
+ }
+ });
+ },
+ resize: function () {
+ return this.each(function () {
+ var self = $(this).data('menuResizer');
+ if (self) {
+ self.resize();
+ }
+ });
+ },
+ destroy: function () {
+ return this.each(function () {
+ var self = $(this).data('menuResizer');
+ if (self) {
+ self.destroy();
+ }
+ });
+ }
+};
diff --git a/js/src/utils/password.js b/js/src/utils/password.js
new file mode 100644
index 0000000000..99293230af
--- /dev/null
+++ b/js/src/utils/password.js
@@ -0,0 +1,179 @@
+import { PMA_Messages as PMA_messages } from '../variables/export_variables';
+import zxcvbn from 'zxcvbn';
+
+/**
+ * Generate a new password and copy it to the password input areas
+ *
+ * @param passwd_form object the form that holds the password fields
+ *
+ * @return boolean always true
+ */
+export function suggestPassword (passwd_form) {
+ // restrict the password to just letters and numbers to avoid problems:
+ // "editors and viewers regard the password as multiple words and
+ // things like double click no longer work"
+ var pwchars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ';
+ var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :)
+ var passwd = passwd_form.generated_pw;
+ var randomWords = new Int32Array(passwordlength);
+
+ passwd.value = '';
+
+ // First we're going to try to use a built-in CSPRNG
+ if (window.crypto && window.crypto.getRandomValues) {
+ window.crypto.getRandomValues(randomWords);
+ } else if (window.msCrypto && window.msCrypto.getRandomValues) {
+ // Because of course IE calls it msCrypto instead of being standard
+ window.msCrypto.getRandomValues(randomWords);
+ } else {
+ // Fallback to Math.random
+ for (let i = 0; i < passwordlength; i++) {
+ randomWords[i] = Math.floor(Math.random() * pwchars.length);
+ }
+ }
+
+ for (let i = 0; i < passwordlength; i++) {
+ passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
+ }
+
+ var $jquery_passwd_form = $(passwd_form);
+
+ passwd_form.elements.pma_pw.value = passwd.value;
+ passwd_form.elements.pma_pw2.value = passwd.value;
+ var meter_obj = $jquery_passwd_form.find('meter[name="pw_meter"]').first();
+ var meter_obj_label = $jquery_passwd_form.find('span[name="pw_strength"]').first();
+ checkPasswordStrength(passwd.value, meter_obj, meter_obj_label);
+ return true;
+}
+
+/**
+ * for PhpMyAdmin\Display\ChangePassword
+ * libraries/user_password.php
+ *
+ */
+export function displayPasswordGenerateButton () {
+ var generatePwdRow = $('
').addClass('vmiddle');
+ $('
').html(PMA_messages.strGeneratePassword).appendTo(generatePwdRow);
+ var pwdCell = $('
').appendTo(generatePwdRow);
+ var pwdButton = $('')
+ .attr({ type: 'button', id: 'button_generate_password', value: PMA_messages.strGenerate })
+ .addClass('button')
+ .on('click', function () {
+ suggestPassword(this.form);
+ });
+ var pwdTextbox = $('')
+ .attr({ type: 'text', name: 'generated_pw', id: 'generated_pw' });
+ pwdCell.append(pwdButton).append(pwdTextbox);
+
+ $('#tr_element_before_generate_password').parent().append(generatePwdRow);
+
+ var generatePwdDiv = $('').addClass('item');
+ $('').attr({ for: 'button_generate_password' })
+ .html(PMA_messages.strGeneratePassword + ':')
+ .appendTo(generatePwdDiv);
+ var optionsSpan = $('').addClass('options')
+ .appendTo(generatePwdDiv);
+ pwdButton.clone(true).appendTo(optionsSpan);
+ pwdTextbox.clone(true).appendTo(generatePwdDiv);
+ $('#div_element_before_generate_password').parent().append(generatePwdDiv);
+}
+
+/**
+ * Validates the "add a user" form
+ *
+ * @return {boolean} whether the form is validated or not
+ */
+export function checkAddUser (the_form) {
+ if (the_form.elements.pred_hostname.value === 'userdefined' && the_form.elements.hostname.value === '') {
+ alert(PMA_messages.strHostEmpty);
+ the_form.elements.hostname.focus();
+ return false;
+ }
+
+ if (the_form.elements.pred_username.value === 'userdefined' && the_form.elements.username.value === '') {
+ alert(PMA_messages.strUserEmpty);
+ the_form.elements.username.focus();
+ return false;
+ }
+
+ return PMA_checkPassword($(the_form));
+} // end of the 'checkAddUser()' function
+
+/**
+ * Function to check the password strength
+ *
+ * @param {string} value Passworrd string
+ * @param {object} meter_obj jQuery object to show strength in meter
+ * @param {object} meter_object_label jQuery object to show text of password strnegth
+ * @param {string} username Username string
+ *
+ * @returns {void}
+ */
+export function checkPasswordStrength (value, meter_obj, meter_object_label, username) {
+ // List of words we don't want to appear in the password
+ var customDict = [
+ 'phpmyadmin',
+ 'mariadb',
+ 'mysql',
+ 'php',
+ 'my',
+ 'admin',
+ ];
+ if (username !== null) {
+ customDict.push(username);
+ }
+ var zxcvbn_obj = zxcvbn(value, customDict);
+ var strength = zxcvbn_obj.score;
+ strength = parseInt(strength);
+ meter_obj.val(strength);
+ switch (strength) {
+ case 0: meter_object_label.html(PMA_messages.strExtrWeak);
+ break;
+ case 1: meter_object_label.html(PMA_messages.strVeryWeak);
+ break;
+ case 2: meter_object_label.html(PMA_messages.strWeak);
+ break;
+ case 3: meter_object_label.html(PMA_messages.strGood);
+ break;
+ case 4: meter_object_label.html(PMA_messages.strStrong);
+ }
+}
+
+/**
+ * Validates the password field in a form
+ *
+ * @see PMA_messages.strPasswordEmpty
+ * @see PMA_messages.strPasswordNotSame
+ * @param object $the_form The form to be validated
+ * @return bool
+ */
+function PMA_checkPassword ($the_form) {
+ // Did the user select 'no password'?
+ if ($the_form.find('#nopass_1').is(':checked')) {
+ return true;
+ } else {
+ var $pred = $the_form.find('#select_pred_password');
+ if ($pred.length && ($pred.val() === 'none' || $pred.val() === 'keep')) {
+ return true;
+ }
+ }
+
+ var $password = $the_form.find('input[name=pma_pw]');
+ var $password_repeat = $the_form.find('input[name=pma_pw2]');
+ var alert_msg = false;
+
+ if ($password.val() === '') {
+ alert_msg = PMA_messages.strPasswordEmpty;
+ } else if ($password.val() !== $password_repeat.val()) {
+ alert_msg = PMA_messages.strPasswordNotSame;
+ }
+
+ if (alert_msg) {
+ alert(alert_msg);
+ $password.val('');
+ $password_repeat.val('');
+ $password.focus();
+ return false;
+ }
+ return true;
+}
diff --git a/js/src/utils/show_ajax_messages.js b/js/src/utils/show_ajax_messages.js
new file mode 100644
index 0000000000..6aeebf8dfd
--- /dev/null
+++ b/js/src/utils/show_ajax_messages.js
@@ -0,0 +1,190 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/**
+ * Functions for adding and removing ajax messages
+ */
+
+/**
+ * Module imports
+ */
+import { PMA_Messages as PMA_messages } from '../variables/export_variables';
+
+/**
+ * @var int ajax_message_count Number of AJAX messages shown since page load
+ */
+let ajax_message_count = 0;
+
+/**
+ * Create a jQuery UI tooltip
+ *
+ * @param $elements jQuery object representing the elements
+ * @param item the item
+ * (see https://api.jqueryui.com/tooltip/#option-items)
+ * @param myContent content of the tooltip
+ * @param additionalOptions to override the default options
+ *
+ */
+export function PMA_tooltip ($elements, item, myContent, additionalOptions) {
+ if ($('#no_hint').length > 0) {
+ return;
+ }
+
+ var defaultOptions = {
+ content: myContent,
+ items: item,
+ tooltipClass: 'tooltip',
+ track: true,
+ show: false,
+ hide: false
+ };
+
+ $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
+}
+
+/**
+ * Show a message on the top of the page for an Ajax request
+ *
+ * Sample usage:
+ *
+ * 1) var $msg = PMA_ajaxShowMessage();
+ * This will show a message that reads "Loading...". Such a message will not
+ * disappear automatically and cannot be dismissed by the user. To remove this
+ * message either the PMA_ajaxRemoveMessage($msg) function must be called or
+ * another message must be show with PMA_ajaxShowMessage() function.
+ *
+ * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
+ * This is a special case. The behaviour is same as above,
+ * just with a different message
+ *
+ * 3) var $msg = PMA_ajaxShowMessage('The operation was successful');
+ * This will show a message that will disappear automatically and it can also
+ * be dismissed by the user.
+ *
+ * 4) var $msg = PMA_ajaxShowMessage('Some error', false);
+ * This will show a message that will not disappear automatically, but it
+ * can be dismissed by the user after he has finished reading it.
+ *
+ * @param string message string containing the message to be shown.
+ * optional, defaults to 'Loading...'
+ * @param mixed timeout number of milliseconds for the message to be visible
+ * optional, defaults to 5000. If set to 'false', the
+ * notification will never disappear
+ * @param string type string to dictate the type of message shown.
+ * optional, defaults to normal notification.
+ * If set to 'error', the notification will show message
+ * with red background.
+ * If set to 'success', the notification will show with
+ * a green background.
+ * @return jQuery object jQuery Element that holds the message div
+ * this object can be passed to PMA_ajaxRemoveMessage()
+ * to remove the notification
+ */
+
+export const PMA_ajaxShowMessage = (message, timeout, type) => {
+ /**
+ * @var self_closing Whether the notification will automatically disappear
+ */
+ var self_closing = true;
+ /**
+ * @var dismissable Whether the user will be able to remove
+ * the notification by clicking on it
+ */
+ var dismissable = true;
+ // Handle the case when a empty data.message is passed.
+ // We don't want the empty message
+ if (message === '') {
+ return true;
+ } else if (! message) {
+ // If the message is undefined, show the default
+ message = PMA_messages.strLoading;
+ dismissable = false;
+ self_closing = false;
+ } else if (message === PMA_messages.strProcessingRequest) {
+ // This is another case where the message should not disappear
+ dismissable = false;
+ self_closing = false;
+ }
+ // Figure out whether (or after how long) to remove the notification
+ if (timeout === undefined) {
+ timeout = 5000;
+ } else if (timeout === false) {
+ self_closing = false;
+ }
+ // Determine type of message, add styling as required
+ if (type === 'error') {
+ message = '