aboutsummaryrefslogtreecommitdiff
path: root/srcs/phpmyadmin/js/functions.js
diff options
context:
space:
mode:
Diffstat (limited to 'srcs/phpmyadmin/js/functions.js')
-rw-r--r--srcs/phpmyadmin/js/functions.js5148
1 files changed, 5148 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/js/functions.js b/srcs/phpmyadmin/js/functions.js
new file mode 100644
index 0000000..cf973b5
--- /dev/null
+++ b/srcs/phpmyadmin/js/functions.js
@@ -0,0 +1,5148 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+
+/* global isStorageSupported */ // js/config.js
+/* global ChartType, ColumnType, DataTable, JQPlotChartFactory */ // js/chart.js
+/* global DatabaseStructure */ // js/database/structure.js
+/* global mysqlDocBuiltin, mysqlDocKeyword */ // js/doclinks.js
+/* global Indexes */ // js/indexes.js
+/* global maxInputVars, mysqlDocTemplate, pmaThemeImage */ // js/messages.php
+/* global MicroHistory */ // js/microhistory.js
+/* global checkPasswordStrength */ // js/server/privileges.js
+/* global sprintf */ // js/vendor/sprintf.js
+/* global Int32Array */ // ES6
+
+/**
+ * general function, usually for data manipulation pages
+ *
+ */
+var Functions = {};
+
+/**
+ * @var sqlBoxLocked lock for the sqlbox textarea in the querybox
+ */
+// eslint-disable-next-line no-unused-vars
+var sqlBoxLocked = false;
+
+/**
+ * @var {array} holds elements which content should only selected once
+ */
+var onlyOnceElements = [];
+
+/**
+ * @var {int} ajaxMessageCount Number of AJAX messages shown since page load
+ */
+var ajaxMessageCount = 0;
+
+/**
+ * @var codeMirrorEditor object containing CodeMirror editor of the query editor in SQL tab
+ */
+var codeMirrorEditor = false;
+
+/**
+ * @var codeMirrorInlineEditor object containing CodeMirror editor of the inline query editor
+ */
+var codeMirrorInlineEditor = false;
+
+/**
+ * @var {boolean} sqlAutoCompleteInProgress shows if Table/Column name autocomplete AJAX is in progress
+ */
+var sqlAutoCompleteInProgress = false;
+
+/**
+ * @var sqlAutoComplete object containing list of columns in each table
+ */
+var sqlAutoComplete = false;
+
+/**
+ * @var {string} sqlAutoCompleteDefaultTable string containing default table to autocomplete columns
+ */
+var sqlAutoCompleteDefaultTable = '';
+
+/**
+ * @var {array} centralColumnList array to hold the columns in central list per db.
+ */
+var centralColumnList = [];
+
+/**
+ * @var {array} primaryIndexes array to hold 'Primary' index columns.
+ */
+// eslint-disable-next-line no-unused-vars
+var primaryIndexes = [];
+
+/**
+ * @var {array} uniqueIndexes array to hold 'Unique' index columns.
+ */
+// eslint-disable-next-line no-unused-vars
+var uniqueIndexes = [];
+
+/**
+ * @var {array} indexes array to hold 'Index' columns.
+ */
+// eslint-disable-next-line no-unused-vars
+var indexes = [];
+
+/**
+ * @var {array} fulltextIndexes array to hold 'Fulltext' columns.
+ */
+// eslint-disable-next-line no-unused-vars
+var fulltextIndexes = [];
+
+/**
+ * @var {array} spatialIndexes array to hold 'Spatial' columns.
+ */
+// eslint-disable-next-line no-unused-vars
+var spatialIndexes = [];
+
+/**
+ * Make sure that ajax requests will not be cached
+ * by appending a random variable to their parameters
+ */
+$.ajaxPrefilter(function (options, originalOptions) {
+ var nocache = new Date().getTime() + '' + Math.floor(Math.random() * 1000000);
+ if (typeof options.data === 'string') {
+ options.data += '&_nocache=' + nocache + '&token=' + encodeURIComponent(CommonParams.get('token'));
+ } else if (typeof options.data === 'object') {
+ options.data = $.extend(originalOptions.data, { '_nocache' : nocache, 'token': CommonParams.get('token') });
+ }
+});
+
+/**
+ * Adds a date/time picker to an element
+ *
+ * @param {object} $thisElement a jQuery object pointing to the element
+ */
+Functions.addDatepicker = function ($thisElement, type, options) {
+ if (type !== 'date' && type !== 'time' && type !== 'datetime' && type !== 'timestamp') {
+ return;
+ }
+
+ var showTimepicker = true;
+ if (type === 'date') {
+ showTimepicker = false;
+ }
+
+ // Getting the current Date and time
+ var currentDateTime = new Date();
+
+ var defaultOptions = {
+ timeInput : true,
+ hour: currentDateTime.getHours(),
+ minute: currentDateTime.getMinutes(),
+ second: currentDateTime.getSeconds(),
+ showOn: 'button',
+ buttonImage: pmaThemeImage + 'b_calendar.png',
+ buttonImageOnly: true,
+ stepMinutes: 1,
+ stepHours: 1,
+ showSecond: true,
+ showMillisec: true,
+ showMicrosec: true,
+ showTimepicker: showTimepicker,
+ showButtonPanel: false,
+ dateFormat: 'yy-mm-dd', // yy means year with four digits
+ timeFormat: 'HH:mm:ss.lc',
+ constrainInput: false,
+ altFieldTimeOnly: false,
+ showAnim: '',
+ beforeShow: function (input, inst) {
+ // Remember that we came from the datepicker; this is used
+ // in table/change.js by verificationsAfterFieldChange()
+ $thisElement.data('comes_from', 'datepicker');
+ if ($(input).closest('.cEdit').length > 0) {
+ setTimeout(function () {
+ inst.dpDiv.css({
+ top: 0,
+ left: 0,
+ position: 'relative'
+ });
+ }, 0);
+ }
+ setTimeout(function () {
+ // Fix wrong timepicker z-index, doesn't work without timeout
+ $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
+ // Integrate tooltip text into dialog
+ var tooltip = $thisElement.tooltip('instance');
+ if (typeof tooltip !== 'undefined') {
+ tooltip.disable();
+ var $note = $('<p class="note"></div>');
+ $note.text(tooltip.option('content'));
+ $('div.ui-datepicker').append($note);
+ }
+ }, 0);
+ },
+ onSelect: function () {
+ $thisElement.data('datepicker').inline = true;
+ },
+ onClose: function () {
+ // The value is no more from the date picker
+ $thisElement.data('comes_from', '');
+ if (typeof $thisElement.data('datepicker') !== 'undefined') {
+ $thisElement.data('datepicker').inline = false;
+ }
+ var tooltip = $thisElement.tooltip('instance');
+ if (typeof tooltip !== 'undefined') {
+ tooltip.enable();
+ }
+ }
+ };
+ if (type === 'time') {
+ $thisElement.timepicker($.extend(defaultOptions, options));
+ // Add a tip regarding entering MySQL allowed-values for TIME data-type
+ Functions.tooltip($thisElement, 'input', Messages.strMysqlAllowedValuesTipTime);
+ } else {
+ $thisElement.datetimepicker($.extend(defaultOptions, options));
+ }
+};
+
+/**
+ * Add a date/time picker to each element that needs it
+ * (only when jquery-ui-timepicker-addon.js is loaded)
+ */
+Functions.addDateTimePicker = function () {
+ if ($.timepicker !== undefined) {
+ $('input.timefield, input.datefield, input.datetimefield').each(function () {
+ var decimals = $(this).parent().attr('data-decimals');
+ var type = $(this).parent().attr('data-type');
+
+ var showMillisec = false;
+ var showMicrosec = false;
+ var timeFormat = 'HH:mm:ss';
+ var hourMax = 23;
+ // check for decimal places of seconds
+ if (decimals > 0 && type.indexOf('time') !== -1) {
+ if (decimals > 3) {
+ showMillisec = true;
+ showMicrosec = true;
+ timeFormat = 'HH:mm:ss.lc';
+ } else {
+ showMillisec = true;
+ timeFormat = 'HH:mm:ss.l';
+ }
+ }
+ if (type === 'time') {
+ hourMax = 99;
+ }
+ Functions.addDatepicker($(this), type, {
+ showMillisec: showMillisec,
+ showMicrosec: showMicrosec,
+ timeFormat: timeFormat,
+ hourMax: hourMax
+ });
+ // Add a tip regarding entering MySQL allowed-values
+ // for TIME and DATE data-type
+ if ($(this).hasClass('timefield')) {
+ Functions.tooltip($(this), 'input', Messages.strMysqlAllowedValuesTipTime);
+ } else if ($(this).hasClass('datefield')) {
+ Functions.tooltip($(this), 'input', Messages.strMysqlAllowedValuesTipDate);
+ }
+ });
+ }
+};
+
+/**
+ * Handle redirect and reload flags sent as part of AJAX requests
+ *
+ * @param data ajax response data
+ */
+Functions.handleRedirectAndReload = function (data) {
+ if (parseInt(data.redirect_flag) === 1) {
+ // add one more GET param to display session expiry msg
+ if (window.location.href.indexOf('?') === -1) {
+ window.location.href += '?session_expired=1';
+ } else {
+ window.location.href += CommonParams.get('arg_separator') + 'session_expired=1';
+ }
+ window.location.reload();
+ } else if (parseInt(data.reload_flag) === 1) {
+ window.location.reload();
+ }
+};
+
+/**
+ * Creates an SQL editor which supports auto completing etc.
+ *
+ * @param $textarea jQuery object wrapping the textarea to be made the editor
+ * @param options optional options for CodeMirror
+ * @param resize optional resizing ('vertical', 'horizontal', 'both')
+ * @param lintOptions additional options for lint
+ */
+Functions.getSqlEditor = function ($textarea, options, resize, lintOptions) {
+ var resizeType = resize;
+ if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') {
+ // merge options for CodeMirror
+ var defaults = {
+ lineNumbers: true,
+ matchBrackets: true,
+ extraKeys: { 'Ctrl-Space': 'autocomplete' },
+ hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
+ indentUnit: 4,
+ mode: 'text/x-mysql',
+ lineWrapping: true
+ };
+
+ if (CodeMirror.sqlLint) {
+ $.extend(defaults, {
+ gutters: ['CodeMirror-lint-markers'],
+ lint: {
+ 'getAnnotations': CodeMirror.sqlLint,
+ 'async': true,
+ 'lintOptions': lintOptions
+ }
+ });
+ }
+
+ $.extend(true, defaults, options);
+
+ // create CodeMirror editor
+ var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults);
+ // allow resizing
+ if (! resizeType) {
+ resizeType = 'vertical';
+ }
+ var handles = '';
+ if (resizeType === 'vertical') {
+ handles = 's';
+ }
+ if (resizeType === 'both') {
+ handles = 'all';
+ }
+ if (resizeType === 'horizontal') {
+ handles = 'e, w';
+ }
+ $(codemirrorEditor.getWrapperElement())
+ .css('resize', resizeType)
+ .resizable({
+ handles: handles,
+ resize: function () {
+ codemirrorEditor.setSize($(this).width(), $(this).height());
+ }
+ });
+ // enable autocomplete
+ codemirrorEditor.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
+
+ // page locking
+ codemirrorEditor.on('change', function (e) {
+ e.data = {
+ value: 3,
+ content: codemirrorEditor.isClean(),
+ };
+ AJAX.lockPageHandler(e);
+ });
+
+ return codemirrorEditor;
+ }
+ return null;
+};
+
+/**
+ * Clear text selection
+ */
+Functions.clearSelection = function () {
+ if (document.selection && document.selection.empty) {
+ document.selection.empty();
+ } else if (window.getSelection) {
+ var sel = window.getSelection();
+ if (sel.empty) {
+ sel.empty();
+ }
+ if (sel.removeAllRanges) {
+ sel.removeAllRanges();
+ }
+ }
+};
+
+/**
+ * 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
+ *
+ */
+Functions.tooltip = function ($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));
+};
+
+/**
+ * HTML escaping
+ */
+Functions.escapeHtml = function (unsafe) {
+ if (typeof(unsafe) !== 'undefined') {
+ return unsafe
+ .toString()
+ .replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#039;');
+ } else {
+ return false;
+ }
+};
+
+Functions.escapeJsString = function (unsafe) {
+ if (typeof(unsafe) !== 'undefined') {
+ return unsafe
+ .toString()
+ .replace('\x00', '')
+ .replace('\\', '\\\\')
+ .replace('\'', '\\\'')
+ .replace('&#039;', '\\&#039;')
+ .replace('"', '\\"')
+ .replace('&quot;', '\\&quot;')
+ .replace('\n', '\n')
+ .replace('\r', '\r')
+ .replace(/<\/script/gi, '</\' + \'script');
+ } else {
+ return false;
+ }
+};
+
+Functions.escapeBacktick = function (s) {
+ return s.replace('`', '``');
+};
+
+Functions.escapeSingleQuote = function (s) {
+ return s.replace('\\', '\\\\').replace('\'', '\\\'');
+};
+
+Functions.sprintf = function () {
+ return sprintf.apply(this, arguments);
+};
+
+/**
+ * Hides/shows the default value input field, depending on the default type
+ * Ticks the NULL checkbox if NULL is chosen as default value.
+ */
+Functions.hideShowDefaultValue = function ($defaultType) {
+ if ($defaultType.val() === 'USER_DEFINED') {
+ $defaultType.siblings('.default_value').show().focus();
+ } else {
+ $defaultType.siblings('.default_value').hide();
+ if ($defaultType.val() === 'NULL') {
+ var $nullCheckbox = $defaultType.closest('tr').find('.allow_null');
+ $nullCheckbox.prop('checked', true);
+ }
+ }
+};
+
+/**
+ * Hides/shows the input field for column expression based on whether
+ * VIRTUAL/PERSISTENT is selected
+ *
+ * @param $virtuality virtuality dropdown
+ */
+Functions.hideShowExpression = function ($virtuality) {
+ if ($virtuality.val() === '') {
+ $virtuality.siblings('.expression').hide();
+ } else {
+ $virtuality.siblings('.expression').show();
+ }
+};
+
+/**
+ * Show notices for ENUM columns; add/hide the default value
+ *
+ */
+Functions.verifyColumnsProperties = function () {
+ $('select.column_type').each(function () {
+ Functions.showNoticeForEnum($(this));
+ });
+ $('select.default_type').each(function () {
+ Functions.hideShowDefaultValue($(this));
+ });
+ $('select.virtuality').each(function () {
+ Functions.hideShowExpression($(this));
+ });
+};
+
+/**
+ * Add a hidden field to the form to indicate that this will be an
+ * Ajax request (only if this hidden field does not exist)
+ *
+ * @param $form object the form
+ */
+Functions.prepareForAjaxRequest = function ($form) {
+ if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
+ $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true">');
+ }
+};
+
+/**
+ * Generate a new password and copy it to the password input areas
+ *
+ * @param {object} passwordForm the form that holds the password fields
+ *
+ * @return {boolean} always true
+ */
+Functions.suggestPassword = function (passwordForm) {
+ // 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 = passwordForm.generated_pw;
+ var randomWords = new Int32Array(passwordlength);
+
+ passwd.value = '';
+
+ var i;
+
+ // 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 (i = 0; i < passwordlength; i++) {
+ randomWords[i] = Math.floor(Math.random() * pwchars.length);
+ }
+ }
+
+ for (i = 0; i < passwordlength; i++) {
+ passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
+ }
+
+ var $jQueryPasswordForm = $(passwordForm);
+
+ passwordForm.elements.pma_pw.value = passwd.value;
+ passwordForm.elements.pma_pw2.value = passwd.value;
+ var meterObj = $jQueryPasswordForm.find('meter[name="pw_meter"]').first();
+ var meterObjLabel = $jQueryPasswordForm.find('span[name="pw_strength"]').first();
+ checkPasswordStrength(passwd.value, meterObj, meterObjLabel);
+ return true;
+};
+
+/**
+ * Version string to integer conversion.
+ */
+Functions.parseVersionString = function (str) {
+ if (typeof(str) !== 'string') {
+ return false;
+ }
+ var add = 0;
+ // Parse possible alpha/beta/rc/
+ var state = str.split('-');
+ if (state.length >= 2) {
+ if (state[1].substr(0, 2) === 'rc') {
+ add = - 20 - parseInt(state[1].substr(2), 10);
+ } else if (state[1].substr(0, 4) === 'beta') {
+ add = - 40 - parseInt(state[1].substr(4), 10);
+ } else if (state[1].substr(0, 5) === 'alpha') {
+ add = - 60 - parseInt(state[1].substr(5), 10);
+ } else if (state[1].substr(0, 3) === 'dev') {
+ /* We don't handle dev, it's git snapshot */
+ add = 0;
+ }
+ }
+ // Parse version
+ var x = str.split('.');
+ // Use 0 for non existing parts
+ var maj = parseInt(x[0], 10) || 0;
+ var min = parseInt(x[1], 10) || 0;
+ var pat = parseInt(x[2], 10) || 0;
+ var hotfix = parseInt(x[3], 10) || 0;
+ return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
+};
+
+/**
+ * Indicates current available version on main page.
+ */
+Functions.currentVersion = function (data) {
+ if (data && data.version && data.date) {
+ var current = Functions.parseVersionString($('span.version').text());
+ var latest = Functions.parseVersionString(data.version);
+ var url = 'https://www.phpmyadmin.net/files/' + Functions.escapeHtml(encodeURIComponent(data.version)) + '/';
+ var versionInformationMessage = document.createElement('span');
+ versionInformationMessage.className = 'latest';
+ var versionInformationMessageLink = document.createElement('a');
+ versionInformationMessageLink.href = url;
+ versionInformationMessageLink.className = 'disableAjax';
+ var versionInformationMessageLinkText = document.createTextNode(data.version);
+ versionInformationMessageLink.appendChild(versionInformationMessageLinkText);
+ var prefixMessage = document.createTextNode(Messages.strLatestAvailable + ' ');
+ versionInformationMessage.appendChild(prefixMessage);
+ versionInformationMessage.appendChild(versionInformationMessageLink);
+ if (latest > current) {
+ var message = Functions.sprintf(
+ Messages.strNewerVersion,
+ Functions.escapeHtml(data.version),
+ Functions.escapeHtml(data.date)
+ );
+ var htmlClass = 'notice';
+ if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
+ /* Security update */
+ htmlClass = 'error';
+ }
+ $('#newer_version_notice').remove();
+ var mainContainerDiv = document.createElement('div');
+ mainContainerDiv.id = 'newer_version_notice';
+ mainContainerDiv.className = htmlClass;
+ var mainContainerDivLink = document.createElement('a');
+ mainContainerDivLink.href = url;
+ mainContainerDivLink.className = 'disableAjax';
+ var mainContainerDivLinkText = document.createTextNode(message);
+ mainContainerDivLink.appendChild(mainContainerDivLinkText);
+ mainContainerDiv.appendChild(mainContainerDivLink);
+ $('#maincontainer').append($(mainContainerDiv));
+ }
+ if (latest === current) {
+ versionInformationMessage = document.createTextNode(' (' + Messages.strUpToDate + ')');
+ }
+ /* Remove extra whitespace */
+ var versionInfo = $('#li_pma_version').contents().get(2);
+ if (typeof versionInfo !== 'undefined') {
+ versionInfo.textContent = $.trim(versionInfo.textContent);
+ }
+ var $liPmaVersion = $('#li_pma_version');
+ $liPmaVersion.find('span.latest').remove();
+ $liPmaVersion.append($(versionInformationMessage));
+ }
+};
+
+/**
+ * Loads Git revision data from ajax for index.php
+ */
+Functions.displayGitRevision = function () {
+ $('#is_git_revision').remove();
+ $('#li_pma_version_git').remove();
+ $.get(
+ 'index.php',
+ {
+ 'server': CommonParams.get('server'),
+ 'git_revision': true,
+ 'ajax_request': true,
+ 'no_debug': true
+ },
+ function (data) {
+ if (typeof data !== 'undefined' && data.success === true) {
+ $(data.message).insertAfter('#li_pma_version');
+ }
+ }
+ );
+};
+
+/**
+ * for PhpMyAdmin\Display\ChangePassword
+ * libraries/user_password.php
+ *
+ */
+Functions.displayPasswordGenerateButton = function () {
+ var generatePwdRow = $('<tr></tr>').addClass('vmiddle');
+ $('<td></td>').html(Messages.strGeneratePassword).appendTo(generatePwdRow);
+ var pwdCell = $('<td></td>').appendTo(generatePwdRow);
+ var pwdButton = $('<input>')
+ .attr({ type: 'button', id: 'button_generate_password', value: Messages.strGenerate })
+ .addClass('btn btn-secondary button')
+ .on('click', function () {
+ Functions.suggestPassword(this.form);
+ });
+ var pwdTextbox = $('<input>')
+ .attr({ type: 'text', name: 'generated_pw', id: 'generated_pw' });
+ pwdCell.append(pwdButton).append(pwdTextbox);
+
+ if (document.getElementById('button_generate_password') === null) {
+ $('#tr_element_before_generate_password').parent().append(generatePwdRow);
+ }
+
+ var generatePwdDiv = $('<div></div>').addClass('item');
+ $('<label></label>').attr({ for: 'button_generate_password' })
+ .html(Messages.strGeneratePassword + ':')
+ .appendTo(generatePwdDiv);
+ var optionsSpan = $('<span></span>').addClass('options')
+ .appendTo(generatePwdDiv);
+ pwdButton.clone(true).appendTo(optionsSpan);
+ pwdTextbox.clone(true).appendTo(generatePwdDiv);
+
+ if (document.getElementById('button_generate_password') === null) {
+ $('#div_element_before_generate_password').parent().append(generatePwdDiv);
+ }
+};
+
+/**
+ * selects the content of a given object, f.e. a textarea
+ *
+ * @param {object} element element of which the content will be selected
+ * @param {var} lock variable which holds the lock for this element or true, if no lock exists
+ * @param {boolean} onlyOnce boolean if true this is only done once f.e. only on first focus
+ */
+Functions.selectContent = function (element, lock, onlyOnce) {
+ if (onlyOnce && onlyOnceElements[element.name]) {
+ return;
+ }
+
+ onlyOnceElements[element.name] = true;
+
+ if (lock) {
+ return;
+ }
+
+ element.select();
+};
+
+/**
+ * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
+ * This function is called while clicking links
+ *
+ * @param theLink object the link
+ * @param theSqlQuery object the sql query to submit
+ *
+ * @return boolean whether to run the query or not
+ */
+Functions.confirmLink = function (theLink, theSqlQuery) {
+ // Confirmation is not required in the configuration file
+ // or browser is Opera (crappy js implementation)
+ if (Messages.strDoYouReally === '' || typeof(window.opera) !== 'undefined') {
+ return true;
+ }
+
+ var isConfirmed = confirm(Functions.sprintf(Messages.strDoYouReally, theSqlQuery));
+ if (isConfirmed) {
+ if (typeof(theLink.href) !== 'undefined') {
+ theLink.href += CommonParams.get('arg_separator') + 'is_js_confirmed=1';
+ } else if (typeof(theLink.form) !== 'undefined') {
+ theLink.form.action += '?is_js_confirmed=1';
+ }
+ }
+
+ return isConfirmed;
+};
+
+/**
+ * Confirms a "DROP/DELETE/ALTER" query before
+ * submitting it if required.
+ * This function is called by the 'Functions.checkSqlQuery()' js function.
+ *
+ * @param theForm1 object the form
+ * @param sqlQuery1 string the sql query string
+ *
+ * @return boolean whether to run the query or not
+ *
+ * @see Functions.checkSqlQuery()
+ */
+Functions.confirmQuery = function (theForm1, sqlQuery1) {
+ // Confirmation is not required in the configuration file
+ if (Messages.strDoYouReally === '') {
+ return true;
+ }
+
+ // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
+ //
+ // TODO: find a way (if possible) to use the parser-analyser
+ // for this kind of verification
+ // For now, I just added a ^ to check for the statement at
+ // beginning of expression
+
+ var doConfirmRegExp0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|PROCEDURE)\\s', 'i');
+ var doConfirmRegExp1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
+ var doConfirmRegExp2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
+ var doConfirmRegExp3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
+ var doConfirmRegExp4 = new RegExp('^(?=.*UPDATE\\b)^((?!WHERE).)*$', 'i');
+
+ if (doConfirmRegExp0.test(sqlQuery1) ||
+ doConfirmRegExp1.test(sqlQuery1) ||
+ doConfirmRegExp2.test(sqlQuery1) ||
+ doConfirmRegExp3.test(sqlQuery1) ||
+ doConfirmRegExp4.test(sqlQuery1)) {
+ var message;
+ if (sqlQuery1.length > 100) {
+ message = sqlQuery1.substr(0, 100) + '\n ...';
+ } else {
+ message = sqlQuery1;
+ }
+ var isConfirmed = confirm(Functions.sprintf(Messages.strDoYouReally, message));
+ // statement is confirmed -> update the
+ // "is_js_confirmed" form field so the confirm test won't be
+ // run on the server side and allows to submit the form
+ if (isConfirmed) {
+ theForm1.elements.is_js_confirmed.value = 1;
+ return true;
+ } else {
+ // statement is rejected -> do not submit the form
+ window.focus();
+ return false;
+ } // end if (handle confirm box result)
+ } // end if (display confirm box)
+
+ return true;
+};
+
+/**
+ * Displays an error message if the user submitted the sql query form with no
+ * sql query, else checks for "DROP/DELETE/ALTER" statements
+ *
+ * @param theForm object the form
+ *
+ * @return boolean always false
+ *
+ * @see Functions.confirmQuery()
+ */
+Functions.checkSqlQuery = function (theForm) {
+ // get the textarea element containing the query
+ var sqlQuery;
+ if (codeMirrorEditor) {
+ codeMirrorEditor.save();
+ sqlQuery = codeMirrorEditor.getValue();
+ } else {
+ sqlQuery = theForm.elements.sql_query.value;
+ }
+ var spaceRegExp = new RegExp('\\s+');
+ if (typeof(theForm.elements.sql_file) !== 'undefined' &&
+ theForm.elements.sql_file.value.replace(spaceRegExp, '') !== '') {
+ return true;
+ }
+ if (typeof(theForm.elements.id_bookmark) !== 'undefined' &&
+ (theForm.elements.id_bookmark.value !== null || theForm.elements.id_bookmark.value !== '') &&
+ theForm.elements.id_bookmark.selectedIndex !== 0) {
+ return true;
+ }
+ var result = false;
+ // Checks for "DROP/DELETE/ALTER" statements
+ if (sqlQuery.replace(spaceRegExp, '') !== '') {
+ result = Functions.confirmQuery(theForm, sqlQuery);
+ } else {
+ alert(Messages.strFormEmpty);
+ }
+
+ if (codeMirrorEditor) {
+ codeMirrorEditor.focus();
+ } else if (codeMirrorInlineEditor) {
+ codeMirrorInlineEditor.focus();
+ }
+ return result;
+};
+
+/**
+ * Check if a form's element is empty.
+ * An element containing only spaces is also considered empty
+ *
+ * @param {object} theForm the form
+ * @param {string} theFieldName the name of the form field to put the focus on
+ *
+ * @return {boolean} whether the form field is empty or not
+ */
+Functions.emptyCheckTheField = function (theForm, theFieldName) {
+ var theField = theForm.elements[theFieldName];
+ var spaceRegExp = new RegExp('\\s+');
+ return theField.value.replace(spaceRegExp, '') === '';
+};
+
+/**
+ * Ensures a value submitted in a form is numeric and is in a range
+ *
+ * @param object the form
+ * @param string the name of the form field to check
+ * @param integer the minimum authorized value
+ * @param integer the maximum authorized value
+ *
+ * @return boolean whether a valid number has been submitted or not
+ */
+Functions.checkFormElementInRange = function (theForm, theFieldName, message, minimum, maximum) {
+ var theField = theForm.elements[theFieldName];
+ var val = parseInt(theField.value, 10);
+ var min = minimum;
+ var max = maximum;
+
+ if (typeof(min) === 'undefined') {
+ min = 0;
+ }
+ if (typeof(max) === 'undefined') {
+ max = Number.MAX_VALUE;
+ }
+
+ if (isNaN(val)) {
+ theField.select();
+ alert(Messages.strEnterValidNumber);
+ theField.focus();
+ return false;
+ } else if (val < min || val > max) {
+ theField.select();
+ alert(Functions.sprintf(message, val));
+ theField.focus();
+ return false;
+ } else {
+ theField.value = val;
+ }
+ return true;
+};
+
+Functions.checkTableEditForm = function (theForm, fieldsCnt) {
+ // TODO: avoid sending a message if user just wants to add a line
+ // on the form but has not completed at least one field name
+
+ var atLeastOneField = 0;
+ var i;
+ var elm;
+ var elm2;
+ var elm3;
+ var val;
+ var id;
+
+ for (i = 0; i < fieldsCnt; i++) {
+ id = '#field_' + i + '_2';
+ elm = $(id);
+ val = elm.val();
+ if (val === 'VARCHAR' || val === 'CHAR' || val === 'BIT' || val === 'VARBINARY' || val === 'BINARY') {
+ elm2 = $('#field_' + i + '_3');
+ val = parseInt(elm2.val(), 10);
+ elm3 = $('#field_' + i + '_1');
+ if (isNaN(val) && elm3.val() !== '') {
+ elm2.select();
+ alert(Messages.strEnterValidLength);
+ elm2.focus();
+ return false;
+ }
+ }
+
+ if (atLeastOneField === 0) {
+ id = 'field_' + i + '_1';
+ if (!Functions.emptyCheckTheField(theForm, id)) {
+ atLeastOneField = 1;
+ }
+ }
+ }
+ if (atLeastOneField === 0) {
+ var theField = theForm.elements.field_0_1;
+ alert(Messages.strFormEmpty);
+ theField.focus();
+ return false;
+ }
+
+ // at least this section is under jQuery
+ var $input = $('input.textfield[name=\'table\']');
+ if ($input.val() === '') {
+ alert(Messages.strFormEmpty);
+ $input.focus();
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * True if last click is to check a row.
+ */
+var lastClickChecked = false;
+
+/**
+ * Zero-based index of last clicked row.
+ * Used to handle the shift + click event in the code above.
+ */
+var lastClickedRow = -1;
+
+/**
+ * Zero-based index of last shift clicked row.
+ */
+var lastShiftClickedRow = -1;
+
+var idleSecondsCounter = 0;
+var incInterval;
+var updateTimeout;
+AJAX.registerTeardown('functions.js', function () {
+ clearTimeout(updateTimeout);
+ clearInterval(incInterval);
+ $(document).off('mousemove');
+});
+
+AJAX.registerOnload('functions.js', function () {
+ document.onclick = function () {
+ idleSecondsCounter = 0;
+ };
+ $(document).on('mousemove',function () {
+ idleSecondsCounter = 0;
+ });
+ document.onkeypress = function () {
+ idleSecondsCounter = 0;
+ };
+ function guid () {
+ function s4 () {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+ s4() + '-' + s4() + s4() + s4();
+ }
+
+ function SetIdleTime () {
+ idleSecondsCounter++;
+ }
+ function UpdateIdleTime () {
+ var href = 'index.php';
+ var guid = 'default';
+ if (isStorageSupported('sessionStorage')) {
+ guid = window.sessionStorage.guid;
+ }
+ var params = {
+ 'ajax_request' : true,
+ 'server' : CommonParams.get('server'),
+ 'db' : CommonParams.get('db'),
+ 'guid': guid,
+ 'access_time': idleSecondsCounter,
+ 'check_timeout': 1
+ };
+ $.ajax({
+ type: 'POST',
+ url: href,
+ data: params,
+ success: function (data) {
+ if (data.success) {
+ if (CommonParams.get('LoginCookieValidity') - idleSecondsCounter < 0) {
+ /* There is other active window, let's reset counter */
+ idleSecondsCounter = 0;
+ }
+ var remaining = Math.min(
+ /* Remaining login validity */
+ CommonParams.get('LoginCookieValidity') - idleSecondsCounter,
+ /* Remaining time till session GC */
+ CommonParams.get('session_gc_maxlifetime')
+ );
+ var interval = 1000;
+ if (remaining > 5) {
+ // max value for setInterval() function
+ interval = Math.min((remaining - 1) * 1000, Math.pow(2, 31) - 1);
+ }
+ updateTimeout = window.setTimeout(UpdateIdleTime, interval);
+ } else { // timeout occurred
+ clearInterval(incInterval);
+ if (isStorageSupported('sessionStorage')) {
+ window.sessionStorage.clear();
+ }
+ // append the login form on the page, disable all the forms which were not disabled already, close all the open jqueryui modal boxes
+ if (!$('#modalOverlay').length) {
+ $('fieldset').not(':disabled').attr('disabled', 'disabled').addClass('disabled_for_expiration');
+ $('body').append(data.error);
+ $('.ui-dialog').each(function () {
+ $('#' + $(this).attr('aria-describedby')).dialog('close');
+ });
+ $('#input_username').t