PageSourceSearch

http://3plus2five.com/phpmyadmin/js/dist/functions.js?v=5.2.1

js 3plus2five.com collected 2026-10-05 01:10:37 UTC 148,069 bytes, 4,333 lines download raw bytes

1/* global isStorageSupported */ // js/config.js
2/* global ChartType, ColumnType, DataTable, JQPlotChartFactory */ // js/chart.js
3/* global DatabaseStructure */ // js/database/structure.js
4/* global mysqlDocBuiltin, mysqlDocKeyword */ // js/doclinks.js
5/* global Indexes */ // js/indexes.js
6/* global firstDayOfCalendar, maxInputVars, mysqlDocTemplate, themeImagePath */ // templates/javascript/variables.twig
7/* global sprintf */ // js/vendor/sprintf.js
8/* global zxcvbnts */ // js/vendor/zxcvbn-ts.js
9
10/**
11 * general function, usually for data manipulation pages
12 * @test-module Functions
13 */
14var Functions = {};
15
16/**
17 * @var sqlBoxLocked lock for the sqlbox textarea in the querybox
18 */
19// eslint-disable-next-line no-unused-vars
20var sqlBoxLocked = false;
21
22/**
23 * @var {array}, holds elements which content should only selected once
24 */
25var onlyOnceElements = [];
26
27/**
28 * @var {number} ajaxMessageCount Number of AJAX messages shown since page load
29 */
30var ajaxMessageCount = 0;
31
32/**
33 * @var codeMirrorEditor object containing CodeMirror editor of the query editor in SQL tab
34 */
35var codeMirrorEditor = false;
36
37/**
38 * @var codeMirrorInlineEditor object containing CodeMirror editor of the inline query editor
39 */
40var codeMirrorInlineEditor = false;
41
42/**
43 * @var {boolean} sqlAutoCompleteInProgress shows if Table/Column name autocomplete AJAX is in progress
44 */
45var sqlAutoCompleteInProgress = false;
46
47/**
48 * @var sqlAutoComplete object containing list of columns in each table
49 */
50var sqlAutoComplete = false;
51
52/**
53 * @var {string} sqlAutoCompleteDefaultTable string containing default table to autocomplete columns
54 */
55var sqlAutoCompleteDefaultTable = '';
56
57/**
58 * @var {array} centralColumnList array to hold the columns in central list per db.
59 */
60var centralColumnList = [];
61
62/**
63 * @var {array} primaryIndexes array to hold 'Primary' index columns.
64 */
65// eslint-disable-next-line no-unused-vars
66var primaryIndexes = [];
67
68/**
69 * @var {array} uniqueIndexes array to hold 'Unique' index columns.
70 */
71// eslint-disable-next-line no-unused-vars
72var uniqueIndexes = [];
73
74/**
75 * @var {array} indexes array to hold 'Index' columns.
76 */
77// eslint-disable-next-line no-unused-vars
78var indexes = [];
79
80/**
81 * @var {array} fulltextIndexes array to hold 'Fulltext' columns.
82 */
83// eslint-disable-next-line no-unused-vars
84var fulltextIndexes = [];
85
86/**
87 * @var {array} spatialIndexes array to hold 'Spatial' columns.
88 */
89// eslint-disable-next-line no-unused-vars
90var spatialIndexes = [];
91
92/**
93 * Make sure that ajax requests will not be cached
94 * by appending a random variable to their parameters
95 */
96$.ajaxPrefilter(function (options, originalOptions) {
97  var nocache = new Date().getTime() + '' + Math.floor(Math.random() * 1000000);
98  if (typeof options.data === 'string') {
99    options.data += '&_nocache=' + nocache + '&token=' + encodeURIComponent(CommonParams.get('token'));
100  } else if (typeof options.data === 'object') {
101    options.data = $.extend(originalOptions.data, {
102      '_nocache': nocache,
103      'token': CommonParams.get('token')
104    });
105  }
106});
107
108/**
109 * Adds a date/time picker to an element
110 *
111 * @param {object} $thisElement a jQuery object pointing to the element
112 * @param {string} type
113 * @param {object} options
114 */
115Functions.addDatepicker = function ($thisElement, type, options) {
116  if (type !== 'date' && type !== 'time' && type !== 'datetime' && type !== 'timestamp') {
117    return;
118  }
119  var showTimepicker = true;
120  if (type === 'date') {
121    showTimepicker = false;
122  }
123
124  // Getting the current Date and time
125  var currentDateTime = new Date();
126  var defaultOptions = {
127    timeInput: true,
128    hour: currentDateTime.getHours(),
129    minute: currentDateTime.getMinutes(),
130    second: currentDateTime.getSeconds(),
131    showOn: 'button',
132    buttonImage: themeImagePath + 'b_calendar.png',
133    buttonImageOnly: true,
134    stepMinutes: 1,
135    stepHours: 1,
136    showSecond: true,
137    showMillisec: true,
138    showMicrosec: true,
139    showTimepicker: showTimepicker,
140    showButtonPanel: false,
141    changeYear: true,
142    dateFormat: 'yy-mm-dd',
143    // yy means year with four digits
144    timeFormat: 'HH:mm:ss.lc',
145    constrainInput: false,
146    altFieldTimeOnly: false,
147    showAnim: '',
148    beforeShow: function (input, inst) {
149      // Remember that we came from the datepicker; this is used
150      // in table/change.js by verificationsAfterFieldChange()
151      $thisElement.data('comes_from', 'datepicker');
152      if ($(input).closest('.cEdit').length > 0) {
153        setTimeout(function () {
154          inst.dpDiv.css({
155            top: 0,
156            left: 0,
157            position: 'relative'
158          });
159        }, 0);
160      }
161      setTimeout(function () {
162        // Fix wrong timepicker z-index, doesn't work without timeout
163        $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
164        // Integrate tooltip text into dialog
165        var tooltip = $thisElement.uiTooltip('instance');
166        if (typeof tooltip !== 'undefined') {
167          tooltip.disable();
168          var $note = $('<p class="note"></div>');
169          $note.text(tooltip.option('content'));
170          $('div.ui-datepicker').append($note);
171        }
172      }, 0);
173    },
174    onSelect: function () {
175      $thisElement.data('datepicker').inline = true;
176    },
177    onClose: function () {
178      // The value is no more from the date picker
179      $thisElement.data('comes_from', '');
180      if (typeof $thisElement.data('datepicker') !== 'undefined') {
181        $thisElement.data('datepicker').inline = false;
182      }
183      var tooltip = $thisElement.uiTooltip('instance');
184      if (typeof tooltip !== 'undefined') {
185        tooltip.enable();
186      }
187    }
188  };
189  if (type === 'time') {
190    $thisElement.timepicker($.extend(defaultOptions, options));
191    // Add a tip regarding entering MySQL allowed-values for TIME data-type
192    Functions.tooltip($thisElement, 'input', Messages.strMysqlAllowedValuesTipTime);
193  } else {
194    $thisElement.datetimepicker($.extend(defaultOptions, options));
195  }
196};
197
198/**
199 * Add a date/time picker to each element that needs it
200 * (only when jquery-ui-timepicker-addon.js is loaded)
201 */
202Functions.addDateTimePicker = function () {
203  if ($.timepicker !== undefined) {
204    $('input.timefield, input.datefield, input.datetimefield').each(function () {
205      var decimals = $(this).parent().attr('data-decimals');
206      var type = $(this).parent().attr('data-type');
207      var showMillisec = false;
208      var showMicrosec = false;
209      var timeFormat = 'HH:mm:ss';
210      var hourMax = 23;
211      // check for decimal places of seconds
212      if (decimals > 0 && type.indexOf('time') !== -1) {
213        if (decimals > 3) {
214          showMillisec = true;
215          showMicrosec = true;
216          timeFormat = 'HH:mm:ss.lc';
217        } else {
218          showMillisec = true;
219          timeFormat = 'HH:mm:ss.l';
220        }
221      }
222      if (type === 'time') {
223        hourMax = 99;
224      }
225      Functions.addDatepicker($(this), type, {
226        showMillisec: showMillisec,
227        showMicrosec: showMicrosec,
228        timeFormat: timeFormat,
229        hourMax: hourMax,
230        firstDay: firstDayOfCalendar
231      });
232      // Add a tip regarding entering MySQL allowed-values
233      // for TIME and DATE data-type
234      if ($(this).hasClass('timefield')) {
235        Functions.tooltip($(this), 'input', Messages.strMysqlAllowedValuesTipTime);
236      } else if ($(this).hasClass('datefield')) {
237        Functions.tooltip($(this), 'input', Messages.strMysqlAllowedValuesTipDate);
238      }
239    });
240  }
241};
242
243/**
244 * Handle redirect and reload flags sent as part of AJAX requests
245 *
246 * @param data ajax response data
247 */
248Functions.handleRedirectAndReload = function (data) {
249  if (parseInt(data.redirect_flag) === 1) {
250    // add one more GET param to display session expiry msg
251    if (window.location.href.indexOf('?') === -1) {
252      window.location.href += '?session_expired=1';
253    } else {
254      window.location.href += CommonParams.get('arg_separator') + 'session_expired=1';
255    }
256    window.location.reload();
257  } else if (parseInt(data.reload_flag) === 1) {
258    window.location.reload();
259  }
260};
261
262/**
263 * Creates an SQL editor which supports auto completing etc.
264 *
265 * @param $textarea   jQuery object wrapping the textarea to be made the editor
266 * @param options     optional options for CodeMirror
267 * @param {'vertical'|'horizontal'|'both'} resize optional resizing ('vertical', 'horizontal', 'both')
268 * @param lintOptions additional options for lint
269 *
270 * @return {object|null}
271 */
272Functions.getSqlEditor = function ($textarea, options, resize, lintOptions) {
273  var resizeType = resize;
274  if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') {
275    // merge options for CodeMirror
276    var defaults = {
277      lineNumbers: true,
278      matchBrackets: true,
279      extraKeys: {
280        'Ctrl-Space': 'autocomplete'
281      },
282      hintOptions: {
283        'completeSingle': false,
284        'completeOnSingleClick': true
285      },
286      indentUnit: 4,
287      mode: 'text/x-mysql',
288      lineWrapping: true
289    };
290    if (CodeMirror.sqlLint) {
291      $.extend(defaults, {
292        gutters: ['CodeMirror-lint-markers'],
293        lint: {
294          'getAnnotations': CodeMirror.sqlLint,
295          'async': true,
296          'lintOptions': lintOptions
297        }
298      });
299    }
300    $.extend(true, defaults, options);
301
302    // create CodeMirror editor
303    var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults);
304    // allow resizing
305    if (!resizeType) {
306      resizeType = 'vertical';
307    }
308    var handles = '';
309    if (resizeType === 'vertical') {
310      handles = 's';
311    }
312    if (resizeType === 'both') {
313      handles = 'all';
314    }
315    if (resizeType === 'horizontal') {
316      handles = 'e, w';
317    }
318    $(codemirrorEditor.getWrapperElement()).css('resize', resizeType).resizable({
319      handles: handles,
320      resize: function () {
321        codemirrorEditor.setSize($(this).width(), $(this).height());
322      }
323    });
324    // enable autocomplete
325    codemirrorEditor.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
326
327    // page locking
328    codemirrorEditor.on('change', function (e) {
329      e.data = {
330        value: 3,
331        content: codemirrorEditor.isClean()
332      };
333      AJAX.lockPageHandler(e);
334    });
335    return codemirrorEditor;
336  }
337  return null;
338};
339
340/**
341 * Clear text selection
342 */
343Functions.clearSelection = function () {
344  if (document.selection && document.selection.empty) {
345    document.selection.empty();
346  } else if (window.getSelection) {
347    var sel = window.getSelection();
348    if (sel.empty) {
349      sel.empty();
350    }
351    if (sel.removeAllRanges) {
352      sel.removeAllRanges();
353    }
354  }
355};
356
357/**
358 * Create a jQuery UI tooltip
359 *
360 * @param $elements     jQuery object representing the elements
361 * @param item          the item
362 *                      (see https://api.jqueryui.com/tooltip/#option-items)
363 * @param myContent     content of the tooltip
364 * @param additionalOptions to override the default options
365 *
366 */
367Functions.tooltip = function ($elements, item, myContent, additionalOptions) {
368  if ($('#no_hint').length > 0) {
369    return;
370  }
371  var defaultOptions = {
372    content: myContent,
373    items: item,
374    tooltipClass: 'tooltip',
375    track: true,
376    show: false,
377    hide: false
378  };
379  $elements.uiTooltip($.extend(true, defaultOptions, additionalOptions));
380};
381
382/**
383 * HTML escaping
384 *
385 * @param {any} unsafe
386 * @return {string | false}
387 */
388Functions.escapeHtml = function (unsafe) {
389  if (typeof unsafe !== 'undefined') {
390    return unsafe.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
391  } else {
392    return false;
393  }
394};
395
396/**
397 * JavaScript escaping
398 *
399 * @param {any} unsafe
400 * @return {string | false}
401 */
402Functions.escapeJsString = function (unsafe) {
403  if (typeof unsafe !== 'undefined') {
404    return unsafe.toString().replace('\x00', '').replace('\\', '\\\\').replace('\'', '\\\'').replace('&#039;', '\\&#039;').replace('"', '\\"').replace('&quot;', '\\&quot;').replace('\n', '\n').replace('\r', '\r').replace(/<\/script/gi, '</\' + \'script');
405  } else {
406    return false;
407  }
408};
409
410/**
411 * @param {string} s
412 * @return {string}
413 */
414Functions.escapeBacktick = function (s) {
415  return s.replace('`', '``');
416};
417
418/**
419 * @param {string} s
420 * @return {string}
421 */
422Functions.escapeSingleQuote = function (s) {
423  return s.replace('\\', '\\\\').replace('\'', '\\\'');
424};
425Functions.sprintf = function () {
426  return sprintf.apply(this, arguments);
427};
428
429/**
430 * Hides/shows the default value input field, depending on the default type
431 * Ticks the NULL checkbox if NULL is chosen as default value.
432 *
433 * @param {JQuery<HTMLElement>} $defaultType
434 */
435Functions.hideShowDefaultValue = function ($defaultType) {
436  if ($defaultType.val() === 'USER_DEFINED') {
437    $defaultType.siblings('.default_value').show().trigger('focus');
438  } else {
439    $defaultType.siblings('.default_value').hide();
440    if ($defaultType.val() === 'NULL') {
441      var $nullCheckbox = $defaultType.closest('tr').find('.allow_null');
442      $nullCheckbox.prop('checked', true);
443    }
444  }
445};
446
447/**
448 * Hides/shows the input field for column expression based on whether
449 * VIRTUAL/PERSISTENT is selected
450 *
451 * @param $virtuality virtuality dropdown
452 */
453Functions.hideShowExpression = function ($virtuality) {
454  if ($virtuality.val() === '') {
455    $virtuality.siblings('.expression').hide();
456  } else {
457    $virtuality.siblings('.expression').show();
458  }
459};
460
461/**
462 * Show notices for ENUM columns; add/hide the default value
463 *
464 */
465Functions.verifyColumnsProperties = function () {
466  $('select.column_type').each(function () {
467    Functions.showNoticeForEnum($(this));
468    Functions.showWarningForIntTypes();
469  });
470  $('select.default_type').each(function () {
471    Functions.hideShowDefaultValue($(this));
472  });
473  $('select.virtuality').each(function () {
474    Functions.hideShowExpression($(this));
475  });
476};
477
478/**
479 * Add a hidden field to the form to indicate that this will be an
480 * Ajax request (only if this hidden field does not exist)
481 *
482 * @param {object} $form the form
483 */
484Functions.prepareForAjaxRequest = function ($form) {
485  if (!$form.find('input:hidden').is('#ajax_request_hidden')) {
486    $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true">');
487  }
488};
489Functions.checkPasswordStrength = function (value, meterObject, meterObjectLabel, username) {
490  // List of words we don't want to appear in the password
491  var customDict = ['phpmyadmin', 'mariadb', 'mysql', 'php', 'my', 'admin'];
492  if (username !== null) {
493    customDict.push(username);
494  }
495  zxcvbnts.core.zxcvbnOptions.setOptions({
496    dictionary: {
497      userInputs: customDict
498    }
499  });
500  var zxcvbnObject = zxcvbnts.core.zxcvbn(value);
501  var strength = zxcvbnObject.score;
502  strength = parseInt(strength);
503  meterObject.val(strength);
504  switch (strength) {
505    case 0:
506      meterObjectLabel.html(Messages.strExtrWeak);
507      break;
508    case 1:
509      meterObjectLabel.html(Messages.strVeryWeak);
510      break;
511    case 2:
512      meterObjectLabel.html(Messages.strWeak);
513      break;
514    case 3:
515      meterObjectLabel.html(Messages.strGood);
516      break;
517    case 4:
518      meterObjectLabel.html(Messages.strStrong);
519  }
520};
521
522/**
523 * Generate a new password and copy it to the password input areas
524 *
525 * @param {object} passwordForm the form that holds the password fields
526 *
527 * @return {boolean} always true
528 */
529Functions.suggestPassword = function (passwordForm) {
530  // restrict the password to just letters and numbers to avoid problems:
531  // "editors and viewers regard the password as multiple words and
532  // things like double click no longer work"
533  var pwchars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ@!_.*/()[]-';
534  var passwordlength = 16; // do we want that to be dynamic?  no, keep it simple :)
535  var passwd = passwordForm.generated_pw;
536  // eslint-disable-next-line compat/compat
537  var randomWords = new Int32Array(passwordlength);
538  passwd.value = '';
539  var i;
540
541  // First we're going to try to use a built-in CSPRNG
542  // eslint-disable-next-line compat/compat
543  if (window.crypto && window.crypto.getRandomValues) {
544    // eslint-disable-next-line compat/compat
545    window.crypto.getRandomValues(randomWords);
546  } else if (window.msCrypto && window.msCrypto.getRandomValues) {
547    // Because of course IE calls it msCrypto instead of being standard
548    window.msCrypto.getRandomValues(randomWords);
549  } else {
550    // Fallback to Math.random
551    for (i = 0; i < passwordlength; i++) {
552      randomWords[i] = Math.floor(Math.random() * pwchars.length);
553    }
554  }
555  for (i = 0; i < passwordlength; i++) {
556    passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
557  }
558  var $jQueryPasswordForm = $(passwordForm);
559  passwordForm.elements.pma_pw.value = passwd.value;
560  passwordForm.elements.pma_pw2.value = passwd.value;
561  var meterObj = $jQueryPasswordForm.find('meter[name="pw_meter"]').first();
562  var meterObjLabel = $jQueryPasswordForm.find('span[name="pw_strength"]').first();
563  Functions.checkPasswordStrength(passwd.value, meterObj, meterObjLabel);
564  return true;
565};
566
567/**
568 * for PhpMyAdmin\Display\ChangePassword and /user-password
569 */
570Functions.displayPasswordGenerateButton = function () {
571  var generatePwdRow = $('<tr></tr>').addClass('align-middle');
572  $('<td></td>').html(Messages.strGeneratePassword).appendTo(generatePwdRow);
573  var pwdCell = $('<td></td>').appendTo(generatePwdRow);
574  var pwdButton = $('<input>').attr({
575    type: 'button',
576    id: 'button_generate_password',
577    value: Messages.strGenerate
578  }).addClass('btn btn-secondary button').on('click', function () {
579    Functions.suggestPassword(this.form);
580  });
581  var pwdTextbox = $('<input>').attr({
582    type: 'text',
583    name: 'generated_pw',
584    id: 'generated_pw'
585  });
586  pwdCell.append(pwdButton).append(pwdTextbox);
587  if (document.getElementById('button_generate_password') === null) {
588    $('#tr_element_before_generate_password').parent().append(generatePwdRow);
589  }
590  var generatePwdDiv = $('<div></div>').addClass('item');
591  $('<label></label>').attr({
592    for: 'button_generate_password'
593  }).html(Messages.strGeneratePassword + ':').appendTo(generatePwdDiv);
594  var optionsSpan = $('<span></span>').addClass('options').appendTo(generatePwdDiv);
595  pwdButton.clone(true).appendTo(optionsSpan);
596  pwdTextbox.clone(true).appendTo(generatePwdDiv);
597  if (document.getElementById('button_generate_password') === null) {
598    $('#div_element_before_generate_password').parent().append(generatePwdDiv);
599  }
600};
601
602/**
603 * selects the content of a given object, f.e. a textarea
604 *
605 * @param {object} element   element of which the content will be selected
606 * @param {any | true} lock  variable which holds the lock for this element or true, if no lock exists
607 * @param {boolean} onlyOnce boolean if true this is only done once f.e. only on first focus
608 */
609Functions.selectContent = function (element, lock, onlyOnce) {
610  if (onlyOnce && onlyOnceElements[element.name]) {
611    return;
612  }
613  onlyOnceElements[element.name] = true;
614  if (lock) {
615    return;
616  }
617  element.select();
618};
619
620/**
621 * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
622 * This function is called while clicking links
623 *
624 * @param {object} theLink     the link
625 * @param {object} theSqlQuery the sql query to submit
626 *
627 * @return {boolean} whether to run the query or not
628 */
629Functions.confirmLink = function (theLink, theSqlQuery) {
630  // Confirmation is not required in the configuration file
631  // or browser is Opera (crappy js implementation)
632  if (Messages.strDoYouReally === '' || typeof window.opera !== 'undefined') {
633    return true;
634  }
635  var isConfirmed = confirm(Functions.sprintf(Messages.strDoYouReally, theSqlQuery));
636  if (isConfirmed) {
637    if (typeof theLink.href !== 'undefined') {
638      theLink.href += CommonParams.get('arg_separator') + 'is_js_confirmed=1';
639    } else if (typeof theLink.form !== 'undefined') {
640      theLink.form.action += '?is_js_confirmed=1';
641    }
642  }
643  return isConfirmed;
644};
645
646/**
647 * Confirms a "DROP/DELETE/ALTER" query before
648 * submitting it if required.
649 * This function is called by the 'Functions.checkSqlQuery()' js function.
650 *
651 * @param {object} theForm1  the form
652 * @param {string} sqlQuery1 the sql query string
653 *
654 * @return {boolean} whether to run the query or not
655 *
656 * @see Functions.checkSqlQuery()
657 */
658Functions.confirmQuery = function (theForm1, sqlQuery1) {
659  // Confirmation is not required in the configuration file
660  if (Messages.strDoYouReally === '') {
661    return true;
662  }
663
664  // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
665  //
666  // TODO: find a way (if possible) to use the parser-analyser
667  // for this kind of verification
668  // For now, I just added a ^ to check for the statement at
669  // beginning of expression
670
671  var doConfirmRegExp0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|PROCEDURE)\\s', 'i');
672  var doConfirmRegExp1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
673  var doConfirmRegExp2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
674  var doConfirmRegExp3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
675  var doConfirmRegExp4 = new RegExp('^(?=.*UPDATE\\b)^((?!WHERE).)*$', 'i');
676  if (doConfirmRegExp0.test(sqlQuery1) || doConfirmRegExp1.test(sqlQuery1) || doConfirmRegExp2.test(sqlQuery1) || doConfirmRegExp3.test(sqlQuery1) || doConfirmRegExp4.test(sqlQuery1)) {
677    var message;
678    if (sqlQuery1.length > 100) {
679      message = sqlQuery1.substr(0, 100) + '\n    ...';
680    } else {
681      message = sqlQuery1;
682    }
683    var isConfirmed = confirm(Functions.sprintf(Messages.strDoYouReally, message));
684    // statement is confirmed -> update the
685    // "is_js_confirmed" form field so the confirm test won't be
686    // run on the server side and allows to submit the form
687    if (isConfirmed) {
688      theForm1.elements.is_js_confirmed.value = 1;
689      return true;
690    } else {
691      // statement is rejected -> do not submit the form
692      window.focus();
693      return false;
694    } // end if (handle confirm box result)
695  } // end if (display confirm box)
696
697  return true;
698};
699
700/**
701 * Displays an error message if the user submitted the sql query form with no
702 * sql query, else checks for "DROP/DELETE/ALTER" statements
703 *
704 * @param {object} theForm the form
705 *
706 * @return {boolean} always false
707 *
708 * @see Functions.confirmQuery()
709 */
710Functions.checkSqlQuery = function (theForm) {
711  // get the textarea element containing the query
712  var sqlQuery;
713  if (codeMirrorEditor) {
714    codeMirrorEditor.save();
715    sqlQuery = codeMirrorEditor.getValue();
716  } else {
717    sqlQuery = theForm.elements.sql_query.value;
718  }
719  var spaceRegExp = new RegExp('\\s+');
720  if (typeof theForm.elements.sql_file !== 'undefined' && theForm.elements.sql_file.value.replace(spaceRegExp, '') !== '') {
721    return true;
722  }
723  if (typeof theForm.elements.id_bookmark !== 'undefined' && (theForm.elements.id_bookmark.value !== null || theForm.elements.id_bookmark.value !== '') && theForm.elements.id_bookmark.selectedIndex !== 0) {
724    return true;
725  }
726  var result = false;
727  // Checks for "DROP/DELETE/ALTER" statements
728  if (sqlQuery.replace(spaceRegExp, '') !== '') {
729    result = Functions.confirmQuery(theForm, sqlQuery);
730  } else {
731    alert(Messages.strFormEmpty);
732  }
733  if (codeMirrorEditor) {
734    codeMirrorEditor.focus();
735  } else if (codeMirrorInlineEditor) {
736    codeMirrorInlineEditor.focus();
737  }
738  return result;
739};
740
741/**
742 * Check if a form's element is empty.
743 * An element containing only spaces is also considered empty
744 *
745 * @param {object} theForm      the form
746 * @param {string} theFieldName the name of the form field to put the focus on
747 *
748 * @return {boolean} whether the form field is empty or not
749 */
750Functions.emptyCheckTheField = function (theForm, theFieldName) {
751  var theField = theForm.elements[theFieldName];
752  var spaceRegExp = new RegExp('\\s+');
753  return theField.value.replace(spaceRegExp, '') === '';
754};
755
756/**
757 * Ensures a value submitted in a form is numeric and is in a range
758 *
759 * @param {object} theForm the form
760 * @param {string} theFieldName the name of the form field to check
761 * @param {any} message
762 * @param {number} minimum the minimum authorized value
763 * @param {number} maximum the maximum authorized value
764 *
765 * @return {boolean}  whether a valid number has been submitted or not
766 */
767Functions.checkFormElementInRange = function (theForm, theFieldName, message, minimum, maximum) {
768  var theField = theForm.elements[theFieldName];
769  var val = parseInt(theField.value, 10);
770  var min = 0;
771  var max = Number.MAX_VALUE;
772  if (typeof minimum !== 'undefined') {
773    min = minimum;
774  }
775  if (typeof maximum !== 'undefined' && maximum !== null) {
776    max = maximum;
777  }
778  if (isNaN(val)) {
779    theField.select();
780    alert(Messages.strEnterValidNumber);
781    theField.focus();
782    return false;
783  } else if (val < min || val > max) {
784    theField.select();
785    alert(Functions.sprintf(message, val));
786    theField.focus();
787    return false;
788  } else {
789    theField.value = val;
790  }
791  return true;
792};
793Functions.checkTableEditForm = function (theForm, fieldsCnt) {
794  // TODO: avoid sending a message if user just wants to add a line
795  // on the form but has not completed at least one field name
796
797  var atLeastOneField = 0;
798  var i;
799  var elm;
800  var elm2;
801  var elm3;
802  var val;
803  var id;
804  for (i = 0; i < fieldsCnt; i++) {
805    id = '#field_' + i + '_2';
806    elm = $(id);
807    val = elm.val();
808    if (val === 'VARCHAR' || val === 'CHAR' || val === 'BIT' || val === 'VARBINARY' || val === 'BINARY') {
809      elm2 = $('#field_' + i + '_3');
810      val = parseInt(elm2.val(), 10);
811      elm3 = $('#field_' + i + '_1');
812      if (isNaN(val) && elm3.val() !== '') {
813        elm2.select();
814        alert(Messages.strEnterValidLength);
815        elm2.focus();
816        return false;
817      }
818    }
819    if (atLeastOneField === 0) {
820      id = 'field_' + i + '_1';
821      if (!Functions.emptyCheckTheField(theForm, id)) {
822        atLeastOneField = 1;
823      }
824    }
825  }
826  if (atLeastOneField === 0) {
827    var theField = theForm.elements.field_0_1;
828    alert(Messages.strFormEmpty);
829    theField.focus();
830    return false;
831  }
832
833  // at least this section is under jQuery
834  var $input = $('input.textfield[name=\'table\']');
835  if ($input.val() === '') {
836    alert(Messages.strFormEmpty);
837    $input.trigger('focus');
838    return false;
839  }
840  return true;
841};
842
843/**
844 * True if last click is to check a row.
845 */
846var lastClickChecked = false;
847
848/**
849 * Zero-based index of last clicked row.
850 * Used to handle the shift + click event in the code above.
851 */
852var lastClickedRow = -1;
853
854/**
855 * Zero-based index of last shift clicked row.
856 */
857var lastShiftClickedRow = -1;
858var idleSecondsCounter = 0;
859var incInterval;
860var updateTimeout;
861AJAX.registerTeardown('functions.js', function () {
862  clearTimeout(updateTimeout);
863  clearInterval(incInterval);
864  $(document).off('mousemove');
865});
866AJAX.registerOnload('functions.js', function () {
867  document.onclick = function () {
868    idleSecondsCounter = 0;
869  };
870  $(document).on('mousemove', function () {
871    idleSecondsCounter = 0;
872  });
873  document.onkeypress = function () {
874    idleSecondsCounter = 0;
875  };
876  function guid() {
877    function s4() {
878      return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
879    }
880    return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
881  }
882  function SetIdleTime() {
883    idleSecondsCounter++;
884  }
885  function UpdateIdleTime() {
886    var href = 'index.php?route=/';
887    var guid = 'default';
888    if (isStorageSupported('sessionStorage')) {
889      guid = window.sessionStorage.guid;
890    }
891    var params = {
892      'ajax_request': true,
893      'server': CommonParams.get('server'),
894      'db': CommonParams.get('db'),
895      'guid': guid,
896      'access_time': idleSecondsCounter,
897      'check_timeout': 1
898    };
899    $.ajax({
900      type: 'POST',
901      url: href,
902      data: params,
903      success: function (data) {
904        if (data.success) {
905          if (CommonParams.get('LoginCookieValidity') - idleSecondsCounter < 0) {
906            /* There is other active window, let's reset counter */
907            idleSecondsCounter = 0;
908          }
909          var remaining = Math.min( /* Remaining login validity */
910          CommonParams.get('LoginCookieValidity') - idleSecondsCounter, /* Remaining time till session GC */
911          CommonParams.get('session_gc_maxlifetime'));
912          var interval = 1000;
913          if (remaining > 5) {
914            // max value for setInterval() function
915            interval = Math.min((remaining - 1) * 1000, Math.pow(2, 31) - 1);
916          }
917          updateTimeout = window.setTimeout(UpdateIdleTime, interval);
918        } else {
919          // timeout occurred
920          clearInterval(incInterval);
921          if (isStorageSupported('sessionStorage')) {
922            window.sessionStorage.clear();
923          }
924          // append the login form on the page, disable all the forms which were not disabled already, close all the open jqueryui modal boxes
925          if (!$('#modalOverlay').length) {
926            $('fieldset').not(':disabled').attr('disabled', 'disabled').addClass('disabled_for_expiration');
927            $('body').append(data.error);
928            $('.ui-dialog').each(function () {
929              $('#' + $(this).attr('aria-describedby')).dialog('close');
930            });
931            $('#input_username').trigger('focus');
932          } else {
933            CommonParams.set('token', data.new_token);
934            $('input[name=token]').val(data.new_token);
935          }
936          idleSecondsCounter = 0;
937          Functions.handleRedirectAndReload(data);
938        }
939      }
940    });
941  }
942  if (CommonParams.get('logged_in')) {
943    incInterval = window.setInterval(SetIdleTime, 1000);
944    var sessionTimeout = Math.min(CommonParams.get('LoginCookieValidity'), CommonParams.get('session_gc_maxlifetime'));
945    if (isStorageSupported('sessionStorage')) {
946      window.sessionStorage.setItem('guid', guid());
947    }
948    var interval = (sessionTimeout - 5) * 1000;
949    if (interval > Math.pow(2, 31) - 1) {
950      // max value for setInterval() function
951      interval = Math.pow(2, 31) - 1;
952    }
953    updateTimeout = window.setTimeout(UpdateIdleTime, interval);
954  }
955});
956/**
957 * Unbind all event handlers before tearing down a page
958 */
959AJAX.registerTeardown('functions.js', function () {
960  $(document).off('click', 'input:checkbox.checkall');
961});
962AJAX.registerOnload('functions.js', function () {
963  /**
964   * Row marking in horizontal mode (use "on" so that it works also for
965   * next pages reached via AJAX); a tr may have the class noclick to remove
966   * this behavior.
967   */
968
969  $(document).on('click', 'input:checkbox.checkall', function (e) {
970    var $this = $(this);
971    var $tr = $this.closest('tr');
972    var $table = $this.closest('table');
973    if (!e.shiftKey || lastClickedRow === -1) {
974      // usual click
975
976      var $checkbox = $tr.find(':checkbox.checkall');
977      var checked = $this.prop('checked');
978      $checkbox.prop('checked', checked).trigger('change');
979      if (checked) {
980        $tr.addClass('marked table-active');
981      } else {
982        $tr.removeClass('marked table-active');
983      }
984      lastClickChecked = checked;
985
986      // remember the last clicked row
987      lastClickedRow = lastClickChecked ? $table.find('tbody tr:not(.noclick)').index($tr) : -1;
988      lastShiftClickedRow = -1;
989    } else {
990      // handle the shift click
991      Functions.clearSelection();
992      var start;
993      var end;
994
995      // clear last shift click result
996      if (lastShiftClickedRow >= 0) {
997        if (lastShiftClickedRow >= lastClickedRow) {
998          start = lastClickedRow;
999          end = lastShiftClickedRow;
1000        } else {
1001          start = lastShiftClickedRow;
1002          end = lastClickedRow;
1003        }
1004        $tr.parent().find('tr:not(.noclick)').slice(start, end + 1).removeClass('marked table-active').find(':checkbox').prop('checked', false).trigger('change');
1005      }
1006
1007      // handle new shift click
1008      var currRow = $table.find('tbody tr:not(.noclick)').index($tr);
1009      if (currRow >= lastClickedRow) {
1010        start = lastClickedRow;
1011        end = currRow;
1012      } else {
1013        start = currRow;
1014        end = lastClickedRow;
1015      }
1016      $tr.parent().find('tr:not(.noclick)').slice(start, end + 1).addClass('marked table-active').find(':checkbox').prop('checked', true).trigger('change');
1017
1018      // remember the last shift clicked row
1019      lastShiftClickedRow = currRow;
1020    }
1021  });
1022  Functions.addDateTimePicker();
1023
1024  /**
1025   * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
1026   */
1027  if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
1028    $('input[type=text]').attr('autocapitalize', 'off').attr('autocorrect', 'off');
1029  }
1030});
1031
1032/**
1033  * Checks/unchecks all options of a <select> element
1034  *
1035  * @param {string} theForm   the form name
1036  * @param {string} theSelect the element name
1037  * @param {boolean} doCheck  whether to check or to uncheck options
1038  *
1039  * @return {boolean} always true
1040  */
1041Functions.setSelectOptions = function (theForm, theSelect, doCheck) {
1042  $('form[name=\'' + theForm + '\'] select[name=\'' + theSelect + '\']').find('option').prop('selected', doCheck);
1043  return true;
1044};
1045
1046/**
1047 * Sets current value for query box.
1048 * @param {string} query
1049 * @return {void}
1050 */
1051Functions.setQuery = function (query) {
1052  if (codeMirrorEditor) {
1053    codeMirrorEditor.setValue(query);
1054    codeMirrorEditor.focus();
1055  } else if (document.sqlform) {
1056    document.sqlform.sql_query.value = query;
1057    document.sqlform.sql_query.focus();
1058  }
1059};
1060
1061/**
1062 * Handles 'Simulate query' button on SQL query box.
1063 *
1064 * @return {void}
1065 */
1066Functions.handleSimulateQueryButton = function () {
1067  var updateRegExp = new RegExp('^\\s*UPDATE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+SET\\s', 'i');
1068  var deleteRegExp = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
1069  var query = '';
1070  if (codeMirrorEditor) {
1071    query = codeMirrorEditor.getValue();
1072  } else {
1073    query = $('#sqlquery').val();
1074  }
1075  var $simulateDml = $('#simulate_dml');
1076  if (updateRegExp.test(query) || deleteRegExp.test(query)) {
1077    if (!$simulateDml.length) {
1078      $('#button_submit_query').before('<input type="button" id="simulate_dml"' + 'tabindex="199" class="btn btn-primary" value="' + Messages.strSimulateDML + '">');
1079    }
1080  } else {
1081    if ($simulateDml.length) {
1082      $simulateDml.remove();
1083    }
1084  }
1085};
1086
1087/**
1088  * Create quick sql statements.
1089  *
1090  * @param {'clear'|'format'|'saved'|'selectall'|'select'|'insert'|'update'|'delete'} queryType
1091  *
1092  */
1093Functions.insertQuery = function (queryType) {
1094  var table;
1095  if (queryType === 'clear') {
1096    Functions.setQuery('');
1097    return;
1098  } else if (queryType === 'format') {
1099    if (codeMirrorEditor) {
1100      $('#querymessage').html(Messages.strFormatting + '&nbsp;<img class="ajaxIcon" src="' + themeImagePath + 'ajax_clock_small.gif" alt="">');
1101      var params = {
1102        'ajax_request': true,
1103        'sql': codeMirrorEditor.getValue(),
1104        'server': CommonParams.get('server')
1105      };
1106      $.ajax({
1107        type: 'POST',
1108        url: 'index.php?route=/database/sql/format',
1109        data: params,
1110        success: function (data) {
1111          if (data.success) {
1112            codeMirrorEditor.setValue(data.sql);
1113          }
1114          $('#querymessage').html('');
1115        },
1116        error: function () {
1117          $('#querymessage').html('');
1118        }
1119      });
1120    }
1121    return;
1122  } else if (queryType === 'saved') {
1123    var db = $('input[name="db"]').val();
1124    table = $('input[name="table"]').val();
1125    var key = db;
1126    if (table !== undefined) {
1127      key += '.' + table;
1128    }
1129    key = 'autoSavedSql_' + key;
1130    if (isStorageSupported('localStorage') && typeof window.localStorage.getItem(key) === 'string') {
1131      Functions.setQuery(window.localStorage.getItem(key));
1132    } else if (Cookies.get(key)) {
1133      Functions.setQuery(Cookies.get(key));
1134    } else {
1135      Functions.ajaxShowMessage(Messages.strNoAutoSavedQuery);
1136    }
1137    return;
1138  }
1139  var query = '';
1140  var myListBox = document.sqlform.dummy;
1141  table = document.sqlform.table.value;
1142  if (myListBox.options.length > 0) {
1143    sqlBoxLocked = true;
1144    var columnsList = '';
1145    var valDis = '';
1146    var editDis = '';
1147    var NbSelect = 0;
1148    for (var i = 0; i < myListBox.options.length; i++) {
1149      NbSelect++;
1150      if (NbSelect > 1) {
1151        columnsList += ', ';
1152        valDis += ',';
1153        editDis += ',';
1154      }
1155      columnsList += myListBox.options[i].value;
1156      valDis += '\'[value-' + NbSelect + ']\'';
1157      editDis += myListBox.options[i].value + '=\'[value-' + NbSelect + ']\'';
1158    }
1159    if (queryType === 'selectall') {
1160      query = 'SELECT * FROM `' + table + '` WHERE 1';
1161    } else if (queryType === 'select') {
1162      query = 'SELECT ' + columnsList + ' FROM `' + table + '` WHERE 1';
1163    } else if (queryType === 'insert') {
1164      query = 'INSERT INTO `' + table + '`(' + columnsList + ') VALUES (' + valDis + ')';
1165    } else if (queryType === 'update') {
1166      query = 'UPDATE `' + table + '` SET ' + editDis + ' WHERE 1';
1167    } else if (queryType === 'delete') {
1168      query = 'DELETE FROM `' + table + '` WHERE 0';
1169    }
1170    Functions.setQuery(query);
1171    sqlBoxLocked = false;
1172  }
1173};
1174
1175/**
1176  * Inserts multiple fields.
1177  *
1178  */
1179Functions.insertValueQuery = function () {
1180  var myQuery = document.sqlform.sql_query;
1181  var myListBox = document.sqlform.dummy;
1182  if (myListBox.options.length > 0) {
1183    sqlBoxLocked = true;
1184    var columnsList = '';
1185    var NbSelect = 0;
1186    for (var i = 0; i < myListBox.options.length; i++) {
1187      if (myListBox.options[i].selected) {
1188        NbSelect++;
1189        if (NbSelect > 1) {
1190          columnsList += ', ';
1191        }
1192        columnsList += myListBox.options[i].value;
1193      }
1194    }
1195
1196    /* CodeMirror support */
1197    if (codeMirrorEditor) {
1198      codeMirrorEditor.replaceSelection(columnsList);
1199      codeMirrorEditor.focus();
1200      // IE support
1201    } else if (document.selection) {
1202      myQuery.focus();
1203      var sel = document.selection.createRange();
1204      sel.text = columnsList;
1205      // MOZILLA/NETSCAPE support
1206    } else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart === '0') {
1207      var startPos = document.sqlform.sql_query.selectionStart;
1208      var endPos = document.sqlform.sql_query.selectionEnd;
1209      var SqlString = document.sqlform.sql_query.value;
1210      myQuery.value = SqlString.substring(0, startPos) + columnsList + SqlString.substring(endPos, SqlString.length);
1211      myQuery.focus();
1212    } else {
1213      myQuery.value += columnsList;
1214    }
1215
1216    // eslint-disable-next-line no-unused-vars
1217    sqlBoxLocked = false;
1218  }
1219};
1220
1221/**
1222 * Updates the input fields for the parameters based on the query
1223 */
1224Functions.updateQueryParameters = function () {
1225  if ($('#parameterized').is(':checked')) {
1226    var query = codeMirrorEditor ? codeMirrorEditor.getValue() : $('#sqlquery').val();
1227    var allParameters = query.match(/:[a-zA-Z0-9_]+/g);
1228    var parameters = [];
1229    // get unique parameters
1230    if (allParameters) {
1231      $.each(allParameters, function (i, parameter) {
1232        if ($.inArray(parameter, parameters) === -1) {
1233          parameters.push(parameter);
1234        }
1235      });
1236    } else {
1237      $('#parametersDiv').text(Messages.strNoParam);
1238      return;
1239    }
1240    var $temp = $('<div></div>');
1241    $temp.append($('#parametersDiv').children());
1242    $('#parametersDiv').empty();
1243    $.each(parameters, function (i, parameter) {
1244      var paramName = parameter.substring(1);
1245      var $param = $temp.find('#paramSpan_' + paramName);
1246      if (!$param.length) {
1247        $param = $('<span class="parameter" id="paramSpan_' + paramName + '"></span>');
1248        $('<label for="param_' + paramName + '"></label>').text(parameter).appendTo($param);
1249        $('<input type="text" name="parameters[' + parameter + ']" id="param_' + paramName + '">').appendTo($param);
1250      }
1251      $('#parametersDiv').append($param);
1252    });
1253  } else {
1254    $('#parametersDiv').empty();
1255  }
1256};
1257
1258/**
1259 * Get checkbox for foreign key checks
1260 *
1261 * @return {string}
1262 */
1263Functions.getForeignKeyCheckboxLoader = function () {
1264  var html = '';
1265  html += '<div class="mt-1 mb-2">';
1266  html += '<div class="load-default-fk-check-value">';
1267  html += Functions.getImage('ajax_clock_small');
1268  html += '</div>';
1269  html += '</div>';
1270  return html;
1271};
1272Functions.loadForeignKeyCheckbox = function () {
1273  // Load default foreign key check value
1274  var params = {
1275    'ajax_request': true,
1276    'server': CommonParams.get('server')
1277  };
1278  $.get('index.php?route=/sql/get-default-fk-check-value', params, function (data) {
1279    var html = '<input type="hidden" name="fk_checks" value="0">' + '<input type="checkbox" name="fk_checks" id="fk_checks"' + (data.default_fk_check_value ? ' checked="checked"' : '') + '>' + '<label for="fk_checks">' + Messages.strForeignKeyCheck + '</label>';
1280    $('.load-default-fk-check-value').replaceWith(html);
1281  });
1282};
1283Functions.getJsConfirmCommonParam = function (elem, parameters) {
1284  var $elem = $(elem);
1285  var params = parameters;
1286  var sep = CommonParams.get('arg_separator');
1287  if (params) {
1288    // Strip possible leading ?
1289    if (params.substring(0, 1) === '?') {
1290      params = params.substr(1);
1291    }
1292    params += sep;
1293  } else {
1294    params = '';
1295  }
1296  params += 'is_js_confirmed=1' + sep + 'ajax_request=true' + sep + 'fk_checks=' + ($elem.find('#fk_checks').is(':checked') ? 1 : 0);
1297  return params;
1298};
1299
1300/**
1301 * Unbind all event handlers before tearing down a page
1302 */
1303AJAX.registerTeardown('functions.js', function () {
1304  $(document).off('click', 'a.inline_edit_sql');
1305  $(document).off('click', 'input#sql_query_edit_save');
1306  $(document).off('click', 'input#sql_query_edit_discard');
1307  $('input.sqlbutton').off('click');
1308  if (codeMirrorEditor) {
1309    codeMirrorEditor.off('blur');
1310  } else {
1311    $(document).off('blur', '#sqlquery');
1312  }
1313  $(document).off('change', '#parameterized');
1314  $(document).off('click', 'input.sqlbutton');
1315  $('#sqlquery').off('keydown');
1316  $('#sql_query_edit').off('keydown');
1317  if (codeMirrorInlineEditor) {
1318    // Copy the sql query to the text area to preserve it.
1319    $('#sql_query_edit').text(codeMirrorInlineEditor.getValue());
1320    $(codeMirrorInlineEditor.getWrapperElement()).off('keydown');
1321    codeMirrorInlineEditor.toTextArea();
1322    codeMirrorInlineEditor = false;
1323  }
1324  if (codeMirrorEditor) {
1325    $(codeMirrorEditor.getWrapperElement()).off('keydown');
1326  }
1327});
1328
1329/**
1330 * Jquery Coding for inline editing SQL_QUERY
1331 */
1332AJAX.registerOnload('functions.js', function () {
1333  // If we are coming back to the page by clicking forward button
1334  // of the browser, bind the code mirror to inline query editor.
1335  Functions.bindCodeMirrorToInlineEditor();
1336  $(document).on('click', 'a.inline_edit_sql', function () {
1337    if ($('#sql_query_edit').length) {
1338      // An inline query editor is already open,
1339      // we don't want another copy of it
1340      return false;
1341    }
1342    var $form = $(this).prev('form');
1343    var sqlQuery = $form.find('input[name=\'sql_query\']').val().trim();
1344    var $innerSql = $(this).parent().prev().find('code.sql');
1345    var newContent = '<textarea name="sql_query_edit" id="sql_query_edit">' + Functions.escapeHtml(sqlQuery) + '</textarea>\n';
1346    newContent += Functions.getForeignKeyCheckboxLoader();
1347    newContent += '<input type="submit" id="sql_query_edit_save" class="btn btn-secondary button btnSave" value="' + Messages.strGo + '">\n';
1348    newContent += '<input type="button" id="sql_query_edit_discard" class="btn btn-secondary button btnDiscard" value="' + Messages.strCancel + '">\n';
1349    var $editorArea = $('div#inline_editor');
1350    if ($editorArea.length === 0) {
1351      $editorArea = $('<div id="inline_editor_outer"></div>');
1352      $editorArea.insertBefore($innerSql);
1353    }
1354    $editorArea.html(newContent);
1355    Functions.loadForeignKeyCheckbox();
1356    $innerSql.hide();
1357    Functions.bindCodeMirrorToInlineEditor();
1358    return false;
1359  });
1360  $(document).on('click', 'input#sql_query_edit_save', function () {
1361    // hide already existing success message
1362    var sqlQuery;
1363    if (codeMirrorInlineEditor) {
1364      codeMirrorInlineEditor.save();
1365      sqlQuery = codeMirrorInlineEditor.getValue();
1366    } else {
1367      sqlQuery = $(this).parent().find('#sql_query_edit').val();
1368    }
1369    var fkCheck = $(this).parent().find('#fk_checks').is(':checked');
1370    var $form = $('a.inline_edit_sql').prev('form');
1371    var $fakeForm = $('<form>', {
1372      action: 'index.php?route=/import',
1373      method: 'post'
1374    }).append($form.find('input[name=server], input[name=db], input[name=table], input[name=token]').clone()).append($('<input>', {
1375      type: 'hidden',
1376      name: 'show_query',
1377      value: 1
1378    })).append($('<input>', {
1379      type: 'hidden',
1380      name: 'is_js_confirmed',
1381      value: 0
1382    })).append($('<input>', {
1383      type: 'hidden',
1384      name: 'sql_query',
1385      value: sqlQuery
1386    })).append($('<input>', {
1387      type: 'hidden',
1388      name: 'fk_checks',
1389      value: fkCheck ? 1 : 0
1390    }));
1391    if (!Functions.checkSqlQuery($fakeForm[0])) {
1392      return false;
1393    }
1394    $('.alert-success').hide();
1395    $fakeForm.appendTo($('body')).trigger('submit');
1396  });
1397  $(document).on('click', 'input#sql_query_edit_discard', function () {
1398    var $divEditor = $('div#inline_editor_outer');
1399    $divEditor.siblings('code.sql').show();
1400    $divEditor.remove();
1401  });
1402  $(document).on('click', 'input.sqlbutton', function (evt) {
1403    Functions.insertQuery(evt.target.id);
1404    Functions.handleSimulateQueryButton();
1405    return false;
1406  });
1407  $(document).on('change', '#parameterized', Functions.updateQueryParameters);
1408  var $inputUsername = $('#input_username');
1409  if ($inputUsername) {
1410    if ($inputUsername.val() === '') {
1411      $inputUsername.trigger('focus');
1412    } else {
1413      $('#input_password').trigger('focus');
1414    }
1415  }
1416});
1417
1418/**
1419 * "inputRead" event handler for CodeMirror SQL query editors for autocompletion
1420 * @param instance
1421 */
1422Functions.codeMirrorAutoCompleteOnInputRead = function (instance) {
1423  if (!sqlAutoCompleteInProgress && (!instance.options.hintOptions.tables || !sqlAutoComplete)) {
1424    if (!sqlAutoComplete) {
1425      // Reset after teardown
1426      instance.options.hintOptions.tables = false;
1427      instance.options.hintOptions.defaultTable = '';
1428      sqlAutoCompleteInProgress = true;
1429      var params = {
1430        'ajax_request': true,
1431        'server': CommonParams.get('server'),
1432        'db': CommonParams.get('db'),
1433        'no_debug': true
1434      };
1435      var columnHintRender = function (elem, self, data) {
1436        $('<div class="autocomplete-column-name">').text(data.columnName).appendTo(elem);
1437        $('<div class="autocomplete-column-hint">').text(data.columnHint).appendTo(elem);
1438      };
1439      $.ajax({
1440        type: 'POST',
1441        url: 'index.php?route=/database/sql/autocomplete',
1442        data: params,
1443        success: function (data) {
1444          if (data.success) {
1445            var tables = JSON.parse(data.tables);
1446            sqlAutoCompleteDefaultTable = CommonParams.get('table');
1447            sqlAutoComplete = [];
1448            for (var table in tables) {
1449              if (tables.hasOwnProperty(table)) {
1450                var columns = tables[table];
1451                table = {
1452                  text: table,
1453                  columns: []
1454                };
1455                for (var column in columns) {
1456                  if (columns.hasOwnProperty(column)) {
1457                    var displayText = columns[column].Type;
1458                    if (columns[column].Key === 'PRI') {
1459                      displayText += ' | Primary';
1460                    } else if (columns[column].Key === 'UNI') {
1461                      displayText += ' | Unique';
1462                    }
1463                    table.columns.push({
1464                      text: column,
1465                      displayText: column + ' | ' + displayText,
1466                      columnName: column,
1467                      columnHint: displayText,
1468                      render: columnHintRender
1469                    });
1470                  }
1471                }
1472              }
1473              sqlAutoComplete.push(table);
1474            }
1475            instance.options.hintOptions.tables = sqlAutoComplete;
1476            instance.options.hintOptions.defaultTable = sqlAutoCompleteDefaultTable;
1477          }
1478        },
1479        complete: function () {
1480          sqlAutoCompleteInProgress = false;
1481        }
1482      });
1483    } else {
1484      instance.options.hintOptions.tables = sqlAutoComplete;
1485      instance.options.hintOptions.defaultTable = sqlAutoCompleteDefaultTable;
1486    }
1487  }
1488  if (instance.state.completionActive) {
1489    return;
1490  }
1491  var cur = instance.getCursor();
1492  var token = instance.getTokenAt(cur);
1493  var string = '';
1494  if (token.string.match(/^[.`\w@]\w*$/)) {
1495    string = token.string;
1496  }
1497  if (string.length > 0) {
1498    CodeMirror.commands.autocomplete(instance);
1499  }
1500};
1501
1502/**
1503 * Remove autocomplete information before tearing down a page
1504 */
1505AJAX.registerTeardown('functions.js', function () {
1506  sqlAutoComplete = false;
1507  sqlAutoCompleteDefaultTable = '';
1508});
1509
1510/**
1511 * Binds the CodeMirror to the text area used to inline edit a query.
1512 */
1513Functions.bindCodeMirrorToInlineEditor = function () {
1514  var $inlineEditor = $('#sql_query_edit');
1515  if ($inlineEditor.length > 0) {
1516    if (typeof CodeMirror !== 'undefined') {
1517      var height = $inlineEditor.css('height');
1518      codeMirrorInlineEditor = Functions.getSqlEditor($inlineEditor);
1519      codeMirrorInlineEditor.getWrapperElement().style.height = height;
1520      codeMirrorInlineEditor.refresh();
1521      codeMirrorInlineEditor.focus();
1522      $(codeMirrorInlineEditor.getWrapperElement()).on('keydown', Functions.catchKeypressesFromSqlInlineEdit);
1523    } else {
1524      $inlineEditor.trigger('focus').on('keydown', Functions.catchKeypressesFromSqlInlineEdit);
1525    }
1526  }
1527};
1528Functions.catchKeypressesFromSqlInlineEdit = function (event) {
1529  // ctrl-enter is 10 in chrome and ie, but 13 in ff
1530  if ((event.ctrlKey || event.metaKey) && (event.keyCode === 13 || event.keyCode === 10)) {
1531    $('#sql_query_edit_save').trigger('click');
1532  }
1533};
1534
1535/**
1536 * Adds doc link to single highlighted SQL element
1537 *
1538 * @param $elm
1539 * @param params
1540 */
1541Functions.documentationAdd = function ($elm, params) {
1542  if (typeof mysqlDocTemplate === 'undefined') {
1543    return;
1544  }
1545  var url = Functions.sprintf(decodeURIComponent(mysqlDocTemplate), params[0]);
1546  if (params.length > 1) {
1547    // The # needs to be escaped to be part of the destination URL
1548    url += encodeURIComponent('#') + params[1];
1549  }
1550  var content = $elm.text();
1551  $elm.text('');
1552  $elm.append('<a target="mysql_doc" class="cm-sql-doc" href="' + url + '">' + content + '</a>');
1553};
1554
1555/**
1556 * Generates doc links for keywords inside highlighted SQL
1557 *
1558 * @param idx
1559 * @param elm
1560 */
1561Functions.documentationKeyword = function (idx, elm) {
1562  var $elm = $(elm);
1563  /* Skip already processed ones */
1564  if ($elm.find('a').length > 0) {
1565    return;
1566  }
1567  var keyword = $elm.text().toUpperCase();
1568  var $next = $elm.next('.cm-keyword');
1569  if ($next) {
1570    var nextKeyword = $next.text().toUpperCase();
1571    var full = keyword + ' ' + nextKeyword;
1572    var $next2 = $next.next('.cm-keyword');
1573    if ($next2) {
1574      var next2Keyword = $next2.text().toUpperCase();
1575      var full2 = full + ' ' + next2Keyword;
1576      if (full2 in mysqlDocKeyword) {
1577        Functions.documentationAdd($elm, mysqlDocKeyword[full2]);
1578        Functions.documentationAdd($next, mysqlDocKeyword[full2]);
1579        Functions.documentationAdd($next2, mysqlDocKeyword[full2]);
1580        return;
1581      }
1582    }
1583    if (full in mysqlDocKeyword) {
1584      Functions.documentationAdd($elm, mysqlDocKeyword[full]);
1585      Functions.documentationAdd($next, mysqlDocKeyword[full]);
1586      return;
1587    }
1588  }
1589  if (keyword in mysqlDocKeyword) {
1590    Functions.documentationAdd($elm, mysqlDocKeyword[keyword]);
1591  }
1592};
1593
1594/**
1595 * Generates doc links for builtins inside highlighted SQL
1596 *
1597 * @param idx
1598 * @param elm
1599 */
1600Functions.documentationBuiltin = function (idx, elm) {
1601  var $elm = $(elm);
1602  var builtin = $elm.text().toUpperCase();
1603  if (builtin in mysqlDocBuiltin) {
1604    Functions.documentationAdd($elm, mysqlDocBuiltin[builtin]);
1605  }
1606};
1607
1608/**
1609 * Higlights SQL using CodeMirror.
1610 *
1611 * @param $base
1612 */
1613Functions.highlightSql = function ($base) {
1614  var $elm = $base.find('code.sql');
1615  $elm.each(function () {
1616    var $sql = $(this);
1617    var $pre = $sql.find('pre');
1618    /* We only care about visible elements to avoid double processing */
1619    if ($pre.is(':visible')) {
1620      var $highlight = $('<div class="sql-highlight cm-s-default"></div>');
1621      $sql.append($highlight);
1622      if (typeof CodeMirror !== 'undefined') {
1623        CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]);
1624        $pre.hide();
1625        $highlight.find('.cm-keyword').each(Functions.documentationKeyword);
1626        $highlight.find('.cm-builtin').each(Functions.documentationBuiltin);
1627      }
1628    }
1629  });
1630};
1631
1632/**
1633 * Updates an element containing code.
1634 *
1635 * @param {JQuery} $base     base element which contains the raw and the
1636 *                           highlighted code.
1637 *
1638 * @param {string} htmlValue code in HTML format, displayed if code cannot be
1639 *                           highlighted
1640 *
1641 * @param {string} rawValue  raw code, used as a parameter for highlighter
1642 *
1643 * @return {boolean}        whether content was updated or not
1644 */
1645Functions.updateCode = function ($base, htmlValue, rawValue) {
1646  var $code = $base.find('code');
1647  if ($code.length === 0) {
1648    return false;
1649  }
1650
1651  // Determines the type of the content and appropriate CodeMirror mode.
1652  var type = '';
1653  var mode = '';
1654  if ($code.hasClass('json')) {
1655    type = 'json';
1656    mode = 'application/json';
1657  } else if ($code.hasClass('sql')) {
1658    type = 'sql';
1659    mode = 'text/x-mysql';
1660  } else if ($code.hasClass('xml')) {
1661    type = 'xml';
1662    mode = 'application/xml';
1663  } else {
1664    return false;
1665  }
1666
1667  // Element used to display unhighlighted code.
1668  var $notHighlighted = $('<pre>' + htmlValue + '</pre>');
1669
1670  // Tries to highlight code using CodeMirror.
1671  if (typeof CodeMirror !== 'undefined') {
1672    var $highlighted = $('<div class="' + type + '-highlight cm-s-default"></div>');
1673    CodeMirror.runMode(rawValue, mode, $highlighted[0]);
1674    $notHighlighted.hide();
1675    $code.html('').append($notHighlighted, $highlighted[0]);
1676  } else {
1677    $code.html('').append($notHighlighted);
1678  }
1679  return true;
1680};
1681
1682/**
1683 * Show a message on the top of the page for an Ajax request
1684 *
1685 * Sample usage:
1686 *
1687 * 1) var $msg = Functions.ajaxShowMessage();
1688 * This will show a message that reads "Loading...". Such a message will not
1689 * disappear automatically and cannot be dismissed by the user. To remove this
1690 * message either the Functions.ajaxRemoveMessage($msg) function must be called or
1691 * another message must be show with Functions.ajaxShowMessage() function.
1692 *
1693 * 2) var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest);
1694 * This is a special case. The behaviour is same as above,
1695 * just with a different message
1696 *
1697 * 3) var $msg = Functions.ajaxShowMessage('The operation was successful');
1698 * This will show a message that will disappear automatically and it can also
1699 * be dismissed by the user.
1700 *
1701 * 4) var $msg = Functions.ajaxShowMessage('Some error', false);
1702 * This will show a message that will not disappear automatically, but it
1703 * can be dismissed by the user after they have finished reading it.
1704 *
1705 * @param {string} message      string containing the message to be shown.
1706 *                              optional, defaults to 'Loading...'
1707 * @param {any} timeout         number of milliseconds for the message to be visible
1708 *                              optional, defaults to 5000. If set to 'false', the
1709 *                              notification will never disappear
1710 * @param {string} type         string to dictate the type of message shown.
1711 *                              optional, defaults to normal notification.
1712 *                              If set to 'error', the notification will show message
1713 *                              with red background.
1714 *                              If set to 'success', the notification will show with
1715 *                              a green background.
1716 * @return {JQuery<Element>}   jQuery Element that holds the message div
1717 *                              this object can be passed to Functions.ajaxRemoveMessage()
1718 *                              to remove the notification
1719 */
1720Functions.ajaxShowMessage = function (message, timeout, type) {
1721  var msg = message;
1722  var newTimeOut = timeout;
1723  /**
1724   * @var self_closing Whether the notification will automatically disappear
1725   */
1726  var selfClosing = true;
1727  /**
1728   * @var dismissable Whether the user will be able to remove
1729   *                  the notification by clicking on it
1730   */
1731  var dismissable = true;
1732  // Handle the case when a empty data.message is passed.
1733  // We don't want the empty message
1734  if (msg === '') {
1735    return true;
1736  } else if (!msg) {
1737    // If the message is undefined, show the default
1738    msg = Messages.strLoading;
1739    dismissable = false;
1740    selfClosing = false;
1741  } else if (msg === Messages.strProcessingRequest) {
1742    // This is another case where the message should not disappear
1743    dismissable = false;
1744    selfClosing = false;
1745  }
1746  // Figure out whether (or after how long) to remove the notification
1747  if (newTimeOut === undefined || newTimeOut === null) {
1748    newTimeOut = 5000;
1749  } else if (newTimeOut === false) {
1750    selfClosing = false;
1751  }
1752  // Determine type of message, add styling as required
1753  if (type === 'error') {
1754    msg = '<div class="alert alert-danger" role="alert">
1754' + msg + '</div>';
1755  } else if (type === 'success') {
1756    msg = '<div class="alert alert-success" role="alert">' + msg + '</div>';
1757  }
1758  // Create a parent element for the AJAX messages, if necessary
1759  if ($('#loading_parent').length === 0) {
1760    $('<div id="loading_parent"></div>').prependTo('#page_content');
1761  }
1762  // Update message count to create distinct message elements every time
1763  ajaxMessageCount++;
1764  // Remove all old messages, if any
1765  $('span.ajax_notification[id^=ajax_message_num]').remove();
1766  /**
1767   * @var $retval    a jQuery object containing the reference
1768   *                 to the created AJAX message
1769   */
1770  var $retval = $('<span class="ajax_notification" id="ajax_message_num_' + ajaxMessageCount + '"></span>').hide().appendTo('#loading_parent').html(msg).show();
1771  // If the notification is self-closing we should create a callback to remove it
1772  if (selfClosing) {
1773    $retval.delay(newTimeOut).fadeOut('medium', function () {
1774      if ($(this).is(':data(tooltip)')) {
1775        $(this).uiTooltip('destroy');
1776      }
1777      // Remove the notification
1778      $(this).remove();
1779    });
1780  }
1781  // If the notification is dismissable we need to add the relevant class to it
1782  // and add a tooltip so that the users know that it can be removed
1783  if (dismissable) {
1784    $retval.addClass('dismissable').css('cursor', 'pointer');
1785    /**
1786     * Add a tooltip to the notification to let the user know that they
1787     * can dismiss the ajax notification by clicking on it.
1788     */
1789    Functions.tooltip($retval, 'span', Messages.strDismiss);
1790  }
1791  // Hide spinner if this is not a loading message
1792  if (msg !== Messages.strLoading) {
1793    $retval.css('background-image', 'none');
1794  }
1795  Functions.highlightSql($retval);
1796  return $retval;
1797};
1798
1799/**
1800 * Removes the message shown for an Ajax operation when it's completed
1801 *
1802 * @param {JQuery} $thisMessageBox Element that holds the notification
1803 *
1804 * @return {void}
1805 */
1806Functions.ajaxRemoveMessage = function ($thisMessageBox) {
1807  if ($thisMessageBox !== undefined && $thisMessageBox instanceof jQuery) {
1808    $thisMessageBox.stop(true, true).fadeOut('medium');
1809    if ($thisMessageBox.is(':data(tooltip)')) {
1810      $thisMessageBox.uiTooltip('destroy');
1811    } else {
1812      $thisMessageBox.remove();
1813    }
1814  }
1815};
1816
1817/**
1818 * Requests SQL for previewing before executing.
1819 *
1820 * @param {JQuery<HTMLElement>} $form Form containing query data
1821 *
1822 * @return {void}
1823 */
1824Functions.previewSql = function ($form) {
1825  var formUrl = $form.attr('action');
1826  var sep = CommonParams.get('arg_separator');
1827  var formData = $form.serialize() + sep + 'do_save_data=1' + sep + 'preview_sql=1' + sep + 'ajax_request=1';
1828  var $messageBox = Functions.ajaxShowMessage();
1829  $.ajax({
1830    type: 'POST',
1831    url: formUrl,
1832    data: formData,
1833    success: function (response) {
1834      Functions.ajaxRemoveMessage($messageBox);
1835      if (response.success) {
1836        $('#previewSqlModal').modal('show');
1837        $('#previewSqlModal').find('.modal-body').first().html(response.sql_data);
1838        $('#previewSqlModalLabel').first().html(Messages.strPreviewSQL);
1839        $('#previewSqlModal').on('shown.bs.modal', function () {
1840          Functions.highlightSql($('#previewSqlModal'));
1841        });
1842      } else {
1843        Functions.ajaxShowMessage(response.message);
1844      }
1845    },
1846    error: function () {
1847      Functions.ajaxShowMessage(Messages.strErrorProcessingRequest);
1848    }
1849  });
1850};
1851
1852/**
1853 * Callback called when submit/"OK" is clicked on sql preview/confirm modal
1854 *
1855 * @callback onSubmitCallback
1856 * @param {string} url The url
1857 */
1858
1859/**
1860 *
1861 * @param {string}           sqlData  Sql query to preview
1862 * @param {string}           url      Url to be sent to callback
1863 * @param {onSubmitCallback} callback On submit callback function
1864 *
1865 * @return {void}
1866 */
1867Functions.confirmPreviewSql = function (sqlData, url, callback) {
1868  $('#previewSqlConfirmModal').modal('show');
1869  $('#previewSqlConfirmModalLabel').first().html(Messages.strPreviewSQL);
1870  $('#previewSqlConfirmCode').first().text(sqlData);
1871  $('#previewSqlConfirmModal').on('shown.bs.modal', function () {
1872    Functions.highlightSql($('#previewSqlConfirmModal'));
1873  });
1874  $('#previewSQLConfirmOkButton').on('click', function () {
1875    callback(url);
1876    $('#previewSqlConfirmModal').modal('hide');
1877  });
1878};
1879
1880/**
1881 * check for reserved keyword column name
1882 *
1883 * @param {JQuery} $form Form
1884 *
1885 * @return {boolean}
1886 */
1887Functions.checkReservedWordColumns = function ($form) {
1888  var isConfirmed = true;
1889  $.ajax({
1890    type: 'POST',
1891    url: 'index.php?route=/table/structure/reserved-word-check',
1892    data: $form.serialize(),
1893    success: function (data) {
1894      if (typeof data.success !== 'undefined' && data.success === true) {
1895        isConfirmed = confirm(data.message);
1896      }
1897    },
1898    async: false
1899  });
1900  return isConfirmed;
1901};
1902
1903// This event only need to be fired once after the initial page load
1904$(function () {
1905  /**
1906   * Allows the user to dismiss a notification
1907   * created with Functions.ajaxShowMessage()
1908   */
1909  var holdStarter = null;
1910  $(document).on('mousedown', 'span.ajax_notification.dismissable', function () {
1911    holdStarter = setTimeout(function () {
1912      holdStarter = null;
1913    }, 250);
1914  });
1915  $(document).on('mouseup', 'span.ajax_notification.dismissable', function (event) {
1916    if (holdStarter && event.which === 1) {
1917      clearTimeout(holdStarter);
1918      Functions.ajaxRemoveMessage($(this));
1919    }
1920  });
1921  /**
1922   * The below two functions hide the "Dismiss notification" tooltip when a user
1923   * is hovering a link or button that is inside an ajax message
1924   */
1925  $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
1926    if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
1927      $(this).parents('span.ajax_notification').uiTooltip('disable');
1928    }
1929  });
1930  $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
1931    if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
1932      $(this).parents('span.ajax_notification').uiTooltip('enable');
1933    }
1934  });
1935
1936  /**
1937   * Copy text to clipboard
1938   *
1939   * @param {string | number | string[]} text to copy to clipboard
1940   *
1941   * @return {boolean}
1942   */
1943  Functions.copyToClipboard = function (text) {
1944    var $temp = $('<input>');
1945    $temp.css({
1946      'position': 'fixed',
1947      'width': '2em',
1948      'border': 0,
1949      'top': 0,
1950      'left': 0,
1951      'padding': 0,
1952      'background': 'transparent'
1953    });
1954    $('body').append($temp);
1955    $temp.val(text).trigger('select');
1956    try {
1957      var res = document.execCommand('copy');
1958      $temp.remove();
1959      return res;
1960    } catch (e) {
1961      $temp.remove();
1962      return false;
1963    }
1964  };
1965  $(document).on('click', 'a.copyQueryBtn', function (event) {
1966    event.preventDefault();
1967    var res = Functions.copyToClipboard($(this).attr('data-text'));
1968    if (res) {
1969      $(this).after('<span id=\'copyStatus\'> (' + Messages.strCopyQueryButtonSuccess + ')</span>');
1970    } else {
1971      $(this).after('<span id=\'copyStatus\'> (' + Messages.strCopyQueryButtonFailure + ')</span>');
1972    }
1973    setTimeout(function () {
1974      $('#copyStatus').remove();
1975    }, 2000);
1976  });
1977});
1978
1979/**
1980 * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
1981 *
1982 * @param selectElement
1983 */
1984Functions.showNoticeForEnum = function (selectElement) {
1985  var enumNoticeId = selectElement.attr('id').split('_')[1];
1986  enumNoticeId += '_' + (parseInt(selectElement.attr('id').split('_')[2], 10) + 1);
1987  var selectedType = selectElement.val();
1988  if (selectedType === 'ENUM' || selectedType === 'SET') {
1989    $('p#enum_notice_' + enumNoticeId).show();
1990  } else {
1991    $('p#enum_notice_' + enumNoticeId).hide();
1992  }
1993};
1994
1995/**
1996 * Hides/shows a warning message when LENGTH is used with inappropriate integer type
1997 */
1998Functions.showWarningForIntTypes = function () {
1999  if ($('div#length_not_allowed').length) {
2000    var lengthRestrictions = $('select.column_type option').map(function () {
2001      return $(this).filter(':selected').attr('data-length-restricted');
2002    }).get();
2003    var restricationFound = lengthRestrictions.some(restriction => Number(restriction) === 1);
2004    if (restricationFound) {
2005      $('div#length_not_allowed').show();
2006    } else {
2007      $('div#length_not_allowed').hide();
2008    }
2009  }
2010};
2011
2012/**
2013 * Creates a Profiling Chart. Used in sql.js
2014 * and in server/status/monitor.js
2015 *
2016 * @param target
2017 * @param data
2018 *
2019 * @return {object}
2020 */
2021Functions.createProfilingChart = function (target, data) {
2022  // create the chart
2023  var factory = new JQPlotChartFactory();
2024  var chart = factory.createChart(ChartType.PIE, target);
2025
2026  // create the data table and add columns
2027  var dataTable = new DataTable();
2028  dataTable.addColumn(ColumnType.STRING, '');
2029  dataTable.addColumn(ColumnType.NUMBER, '');
2030  dataTable.setData(data);
2031  var windowWidth = $(window).width();
2032  var location = 's';
2033  if (windowWidth > 768) {
2034    location = 'se';
2035  }
2036
2037  // draw the chart and return the chart object
2038  chart.draw(dataTable, {
2039    seriesDefaults: {
2040      rendererOptions: {
2041        showDataLabels: true
2042      }
2043    },
2044    highlighter: {
2045      tooltipLocation: 'se',
2046      sizeAdjust: 0,
2047      tooltipAxes: 'pieref',
2048      formatString: '%s, %.9Ps'
2049    },
2050    legend: {
2051      show: true,
2052      location: location,
2053      rendererOptions: {
2054        numberColumns: 2
2055      }
2056    },
2057    // from https://web.archive.org/web/20190321233412/http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines
2058    seriesColors: ['#fce94f', '#fcaf3e', '#e9b96e', '#8ae234', '#729fcf', '#ad7fa8', '#ef2929', '#888a85', '#c4a000', '#ce5c00', '#8f5902', '#4e9a06', '#204a87', '#5c3566', '#a40000', '#babdb6', '#2e3436']
2059  });
2060  return chart;
2061};
2062
2063/**
2064 * Formats a profiling duration nicely (in us and ms time).
2065 * Used in server/status/monitor.js
2066 *
2067 * @param {number} number   Number to be formatted, should be in the range of microsecond to second
2068 * @param {number} accuracy Accuracy, how many numbers right to the comma should be
2069 * @return {string}        The formatted number
2070 */
2071Functions.prettyProfilingNum = function (number, accuracy) {
2072  var num = number;
2073  var acc = accuracy;
2074  if (!acc) {
2075    acc = 2;
2076  }
2077  acc = Math.pow(10, acc);
2078  if (num * 1000 < 0.1) {
2079    num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
2080  } else if (num < 0.1) {
2081    num = Math.round(acc * (num * 1000)) / acc + 'm';
2082  } else {
2083    num = Math.round(acc * num) / acc;
2084  }
2085  return num + 's';
2086};
2087
2088/**
2089 * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
2090 *
2091 * @param {string} string Query to be formatted
2092 * @return {string}      The formatted query
2093 */
2094Functions.sqlPrettyPrint = function (string) {
2095  if (typeof CodeMirror === 'undefined') {
2096    return string;
2097  }
2098  var mode = CodeMirror.getMode({}, 'text/x-mysql');
2099  var stream = new CodeMirror.StringStream(string);
2100  var state = mode.startState();
2101  var token;
2102  var tokens = [];
2103  var output = '';
2104  var tabs = function (cnt) {
2105    var ret = '';
2106    for (var i = 0; i < 4 * cnt; i++) {
2107      ret += ' ';
2108    }
2109    return ret;
2110  };
2111
2112  // "root-level" statements
2113  var statements = {
2114    'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'],
2115    'update': ['update', 'set', 'where'],
2116    'insert into': ['insert into', 'values']
2117  };
2118  // don't put spaces before these tokens
2119  var spaceExceptionsBefore = {
2120    ';': true,
2121    ',': true,
2122    '.': true,
2123    '(': true
2124  };
2125  // don't put spaces after these tokens
2126  var spaceExceptionsAfter = {
2127    '.': true
2128  };
2129
2130  // Populate tokens array
2131  while (!stream.eol()) {
2132    stream.start = stream.pos;
2133    token = mode.token(stream, state);
2134    if (token !== null) {
2135      tokens.push([token, stream.current().toLowerCase()]);
2136    }
2137  }
2138  var currentStatement = tokens[0][1];
2139  if (!statements[currentStatement]) {
2140    return string;
2141  }
2142  // Holds all currently opened code blocks (statement, function or generic)
2143  var blockStack = [];
2144  // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
2145  var newBlock;
2146  var endBlock;
2147  // How much to indent in the current line
2148  var indentLevel = 0;
2149  // Holds the "root-level" statements
2150  var statementPart;
2151  var lastStatementPart = statements[currentStatement][0];
2152  blockStack.unshift('statement');
2153
2154  // Iterate through every token and format accordingly
2155  for (var i = 0; i < tokens.length; i++) {
2156    // New block => push to stack
2157    if (tokens[i][1] === '(') {
2158      if (i < tokens.length - 1 && tokens[i + 1][0] === 'statement-verb') {
2159        blockStack.unshift(newBlock = 'statement');
2160      } else if (i > 0 && tokens[i - 1][0] === 'builtin') {
2161        blockStack.unshift(newBlock = 'function');
2162      } else {
2163        blockStack.unshift(newBlock = 'generic');
2164      }
2165    } else {
2166      newBlock = null;
2167    }
2168
2169    // Block end => pop from stack
2170    if (tokens[i][1] === ')') {
2171      endBlock = blockStack[0];
2172      blockStack.shift();
2173    } else {
2174      endBlock = null;
2175    }
2176
2177    // A subquery is starting
2178    if (i > 0 && newBlock === 'statement') {
2179      indentLevel++;
2180      output += '\n' + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + '\n' + tabs(indentLevel + 1);
2181      currentStatement = tokens[i + 1][1];
2182      i++;
2183      continue;
2184    }
2185
2186    // A subquery is ending
2187    if (endBlock === 'statement' && indentLevel > 0) {
2188      output += '\n' + tabs(indentLevel);
2189      indentLevel--;
2190    }
2191
2192    // One less indentation for statement parts (from, where, order by, etc.) and a newline
2193    statementPart = statements[currentStatement].indexOf(tokens[i][1]);
2194    if (statementPart !== -1) {
2195      if (i > 0) {
2196        output += '\n';
2197      }
2198      output += tabs(indentLevel) + tokens[i][1].toUpperCase();
2199      output += '\n' + tabs(indentLevel + 1);
2200      lastStatementPart = tokens[i][1];
2201      // Normal indentation and spaces for everything else
2202    } else {
2203      if (!spaceExceptionsBefore[tokens[i][1]] && !(i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) && output.charAt(output.length - 1) !== ' ') {
2204        output += ' ';
2205      }
2206      if (tokens[i][0] === 'keyword') {
2207        output += tokens[i][1].toUpperCase();
2208      } else {
2209        output += tokens[i][1];
2210      }
2211    }
2212
2213    // split columns in select and 'update set' clauses, but only inside statements blocks
2214    if ((lastStatementPart === 'select' || lastStatementPart === 'where' || lastStatementPart === 'set') && tokens[i][1] === ',' && blockStack[0] === 'statement') {
2215      output += '\n' + tabs(indentLevel + 1);
2216    }
2217
2218    // split conditions in where clauses, but only inside statements blocks
2219    if (lastStatementPart === 'where' && (tokens[i][1] === 'and' || tokens[i][1] === 'or' || tokens[i][1] === 'xor')) {
2220      if (blockStack[0] === 'statement') {
2221        output += '\n' + tabs(indentLevel + 1);
2222      }
2223      // Todo: Also split and or blocks in newlines & indentation++
2224      // if (blockStack[0] === 'generic')
2225      //   output += ...
2226    }
2227  }
2228
2229  return output;
2230};
2231
2232/**
2233 * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
2234 * return a jQuery object yet and hence cannot be chained
2235 *
2236 * @param {string}   question
2237 * @param {string}   url          URL to be passed to the callbackFn to make
2238 *                                an Ajax call to
2239 * @param {Function} callbackFn   callback to execute after user clicks on OK
2240 * @param {Function} openCallback optional callback to run when dialog is shown
2241 *
2242 * @return {bool}
2243 */
2244Functions.confirm = function (question, url, callbackFn, openCallback) {
2245  var confirmState = CommonParams.get('confirm');
2246  if (!confirmState) {
2247    // user does not want to confirm
2248    if (typeof callbackFn === 'function') {
2249      callbackFn.call(this, url);
2250      return true;
2251    }
2252  }
2253  if (Messages.strDoYouReally === '') {
2254    return true;
2255  }
2256
2257  /**
2258   * @var button_options Object that stores the options passed to 
2258jQueryUI
2259   *                     dialog
2260   */
2261  var buttonOptions = [{
2262    text: Messages.strOK,
2263    'class': 'btn btn-primary submitOK',
2264    click: function () {
2265      $(this).dialog('close');
2266      if (typeof callbackFn === 'function') {
2267        callbackFn.call(this, url);
2268      }
2269    }
2270  }, {
2271    text: Messages.strCancel,
2272    'class': 'btn btn-secondary submitCancel',
2273    click: function () {
2274      $(this).dialog('close');
2275    }
2276  }];
2277  $('<div></div>', {
2278    'id': 'confirm_dialog',
2279    'title': Messages.strConfirm
2280  }).prepend(question).dialog({
2281    classes: {
2282      'ui-dialog-titlebar-close': 'btn-close'
2283    },
2284    buttons: buttonOptions,
2285    close: function () {
2286      $(this).remove();
2287    },
2288    open: openCallback,
2289    modal: true
2290  });
2291};
2292jQuery.fn.confirm = Functions.confirm;
2293
2294/**
2295 * jQuery function to sort a table's body after a new row has been appended to it.
2296 *
2297 * @param {string} textSelector string to select the sortKey's text
2298 *
2299 * @return {JQuery<HTMLElement>} for chaining purposes
2300 */
2301Functions.sortTable = function (textSelector) {
2302  return this.each(function () {
2303    /**
2304     * @var table_body  Object referring to the table's <tbody> element
2305     */
2306    var tableBody = $(this);
2307    /**
2308     * @var rows    Object referring to the collection of rows in {@link tableBody}
2309     */
2310    var rows = $(this).find('tr').get();
2311
2312    // get the text of the field that we will sort by
2313    $.each(rows, function (index, row) {
2314      row.sortKey = $(row).find(textSelector).text().toLowerCase().trim();
2315    });
2316
2317    // get the sorted order
2318    rows.sort(function (a, b) {
2319      if (a.sortKey < b.sortKey) {
2320        return -1;
2321      }
2322      if (a.sortKey > b.sortKey) {
2323        return 1;
2324      }
2325      return 0;
2326    });
2327
2328    // pull out each row from the table and then append it according to it's order
2329    $.each(rows, function (index, row) {
2330      $(tableBody).append(row);
2331      row.sortKey = null;
2332    });
2333  });
2334};
2335jQuery.fn.sortTable = Functions.sortTable;
2336
2337/**
2338 * Unbind all event handlers before tearing down a page
2339 */
2340AJAX.registerTeardown('functions.js', function () {
2341  $(document).off('submit', 'form.create_table_form.ajax');
2342  $(document).off('click', 'form.create_table_form.ajax input[name=submit_num_fields]');
2343  $(document).off('keyup', 'form.create_table_form.ajax input');
2344  $(document).off('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]');
2345});
2346
2347/**
2348 * jQuery coding for 'Create Table'. Used on /database/operations,
2349 * /database/structure and /database/tracking (i.e., wherever
2350 * PhpMyAdmin\Display\CreateTable is used)
2351 *
2352 * Attach Ajax Event handlers for Create Table
2353 */
2354AJAX.registerOnload('functions.js', function () {
2355  /**
2356   * Attach event handler for submission of create table form (save)
2357   */
2358  $(document).on('submit', 'form.create_table_form.ajax', function (event) {
2359    event.preventDefault();
2360
2361    /**
2362     * @var    the_form    object referring to the create table form
2363     */
2364    var $form = $(this);
2365
2366    /*
2367     * First validate the form; if there is a problem, avoid submitting it
2368     *
2369     * Functions.checkTableEditForm() needs a pure element and not a jQuery object,
2370     * this is why we pass $form[0] as a parameter (the jQuery object
2371     * is actually an array of DOM elements)
2372     */
2373
2374    if (Functions.checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
2375      Functions.prepareForAjaxRequest($form);
2376      if (Functions.checkReservedWordColumns($form)) {
2377        Functions.ajaxShowMessage(Messages.strProcessingRequest);
2378        // User wants to submit the form
2379        $.post($form.attr('action'), $form.serialize() + CommonParams.get('arg_separator') + 'do_save_data=1', function (data) {
2380          if (typeof data !== 'undefined' && data.success === true) {
2381            $('#properties_message').removeClass('alert-danger').html('');
2382            Functions.ajaxShowMessage(data.message);
2383            // Only if the create table dialog (distinct panel) exists
2384            var $createTableDialog = $('#create_table_dialog');
2385            if ($createTableDialog.length > 0) {
2386              $createTableDialog.dialog('close').remove();
2387            }
2388            $('#tableslistcontainer').before(data.formatted_sql);
2389
2390            /**
2391             * @var tables_table    Object referring to the <tbody> element that holds the list of tables
2392             */
2393            var tablesTable = $('#tablesForm').find('tbody').not('#tbl_summary_row');
2394            // this is the first table created in this db
2395            if (tablesTable.length === 0) {
2396              CommonActions.refreshMain(CommonParams.get('opendb_url'));
2397            } else {
2398              /**
2399               * @var curr_last_row   Object referring to the last <tr> element in {@link tablesTable}
2400               */
2401              var currLastRow = $(tablesTable).find('tr').last();
2402              /**
2403               * @var curr_last_row_index_string   String containing the index of {@link currLastRow}
2404               */
2405              var currLastRowIndexString = $(currLastRow).find('input:checkbox').attr('id').match(/\d+/)[0];
2406              /**
2407               * @var curr_last_row_index Index of {@link currLastRow}
2408               */
2409              var currLastRowIndex = parseFloat(currLastRowIndexString);
2410              /**
2411               * @var new_last_row_index   Index of the new row to be appended to {@link tablesTable}
2412               */
2413              var newLastRowIndex = currLastRowIndex + 1;
2414              /**
2415               * @var new_last_row_id String containing the id of the row to be appended to {@link tablesTable}
2416               */
2417              var newLastRowId = 'checkbox_tbl_' + newLastRowIndex;
2418              data.newTableString = data.newTableString.replace(/checkbox_tbl_/, newLastRowId);
2419              // append to table
2420              $(data.newTableString).appendTo(tablesTable);
2421
2422              // Sort the table
2423              $(tablesTable).sortTable('th');
2424
2425              // Adjust summary row
2426              DatabaseStructure.adjustTotals();
2427            }
2428
2429            // Refresh navigation as a new table has been added
2430            Navigation.reload();
2431            // Redirect to table structure page on creation of new table
2432            var argsep = CommonParams.get('arg_separator');
2433            var params12 = 'ajax_request=true' + argsep + 'ajax_page_request=true';
2434            var tableStructureUrl = 'index.php?route=/table/structure' + argsep + 'server=' + data.params.server + argsep + 'db=' + data.params.db + argsep + 'token=' + data.params.token + argsep + 'goto=' + encodeURIComponent('index.php?route=/database/structure') + argsep + 'table=' + data.params.table + '';
2435            $.get(tableStructureUrl, params12, AJAX.responseHandler);
2436          } else {
2437            Functions.ajaxShowMessage('<div class="alert alert-danger" role="alert">' + data.error + '</div>', false);
2438          }
2439        }); // end $.post()
2440      }
2441    }
2442  }); // end create table form (save)
2443
2444  /**
2445   * Submits the intermediate changes in the table creation form
2446   * to refresh the UI accordingly
2447   *
2448   * @param actionParam
2449   */
2450  function submitChangesInCreateTableForm(actionParam) {
2451    /**
2452     * @var    the_form    object referring to the create table form
2453     */
2454    var $form = $('form.create_table_form.ajax');
2455    var $msgbox = Functions.ajaxShowMessage(Messages.strProcessingRequest);
2456    Functions.prepareForAjaxRequest($form);
2457
2458    // User wants to add more fields to the table
2459    $.post($form.attr('action'), $form.serialize() + '&' + actionParam, function (data) {
2460      if (typeof data !== 'undefined' && data.success) {
2461        var $pageContent = $('#page_content');
2462        $pageContent.html(data.message);
2463        Functions.highlightSql($pageContent);
2464        Functions.verifyColumnsProperties();
2465        Functions.hideShowConnection($('.create_table_form select[name=tbl_storage_engine]'));
2466        Functions.ajaxRemoveMessage($msgbox);
2467      } else {
2468        Functions.ajaxShowMessage(data.error);
2469      }
2470    }); // end $.post()
2471  }
2472
2473  /**
2474   * Attach event handler for create table form (add fields)
2475   */
2476  $(document).on('click', 'form.create_table_form.ajax input[name=submit_num_fields]', function (event) {
2477    event.preventDefault();
2478    submitChangesInCreateTableForm('submit_num_fields=1');
2479  }); // end create table form (add fields)
2480
2481  $(document).on('keydown', 'form.create_table_form.ajax input[name=added_fields]', function (event) {
2482    if (event.keyCode === 13) {
2483      event.preventDefault();
2484      event.stopImmediatePropagation();
2485      $(this).closest('form').find('input[name=submit_num_fields]').trigger('click');
2486    }
2487  });
2488
2489  /**
2490   * Attach event handler to manage changes in number of partitions and subpartitions
2491   */
2492  $(document).on('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]', function () {
2493    var $this = $(this);
2494    var $form = $this.parents('form');
2495    if ($form.is('.create_table_form.ajax')) {
2496      submitChangesInCreateTableForm('submit_partition_change=1');
2497    } else {
2498      $form.trigger('submit');
2499    }
2500  });
2501  $(document).on('change', 'input[value=AUTO_INCREMENT]', function () {
2502    if (this.checked) {
2503      var col = /\d/.exec($(this).attr('name'));
2504      col = col[0];
2505      var $selectFieldKey = $('select[name="field_key[' + col + ']"]');
2506      if ($selectFieldKey.val() === 'none_' + col) {
2507        $selectFieldKey.val('primary_' + col).trigger('change', [false]);
2508      }
2509    }
2510  });
2511  $('body').off('click', 'input.preview_sql').on('click', 'input.preview_sql', function () {
2512    var $form = $(this).closest('form');
2513    Functions.previewSql($form);
2514  });
2515});
2516
2517/**
2518 * Validates the password field in a form
2519 *
2520 * @see    Messages.strPasswordEmpty
2521 * @see    Messages.strPasswordNotSame
2522 * @param {object} $theForm The form to be validated
2523 * @return {boolean}
2524 */
2525Functions.checkPassword = function ($theForm) {
2526  // Did the user select 'no password'?
2527  if ($theForm.find('#nopass_1').is(':checked')) {
2528    return true;
2529  } else {
2530    var $pred = $theForm.find('#select_pred_password');
2531    if ($pred.length && ($pred.val() === 'none' || $pred.val() === 'keep')) {
2532      return true;
2533    }
2534  }
2535  var $password = $theForm.find('input[name=pma_pw]');
2536  var $passwordRepeat = $theForm.find('input[name=pma_pw2]');
2537  var alertMessage = false;
2538  if ($password.val() === '') {
2539    alertMessage = Messages.strPasswordEmpty;
2540  } else if ($password.val() !== $passwordRepeat.val()) {
2541    alertMessage = Messages.strPasswordNotSame;
2542  }
2543  if (alertMessage) {
2544    alert(alertMessage);
2545    $password.val('');
2546    $passwordRepeat.val('');
2547    $password.trigger('focus');
2548    return false;
2549  }
2550  return true;
2551};
2552
2553/**
2554 * Attach Ajax event handlers for 'Change Password' on index.php
2555 */
2556AJAX.registerOnload('functions.js', function () {
2557  /* Handler for hostname type */
2558  $(document).on('change', '#select_pred_hostname', function () {
2559    var hostname = $('#pma_hostname');
2560    if (this.value === 'any') {
2561      hostname.val('%');
2562    } else if (this.value === 'localhost') {
2563      hostname.val('localhost');
2564    } else if (this.value === 'thishost' && $(this).data('thishost')) {
2565      hostname.val($(this).data('thishost'));
2566    } else if (this.value === 'hosttable') {
2567      hostname.val('').prop('required', false);
2568    } else if (this.value === 'userdefined') {
2569      hostname.trigger('focus').select().prop('required', true);
2570    }
2571  });
2572
2573  /* Handler for editing hostname */
2574  $(document).on('change', '#pma_hostname', function () {
2575    $('#select_pred_hostname').val('userdefined');
2576    $('#pma_hostname').prop('required', true);
2577  });
2578
2579  /* Handler for username type */
2580  $(document).on('change', '#select_pred_username', function () {
2581    if (this.value === 'any') {
2582      $('#pma_username').val('').prop('required', false);
2583      $('#user_exists_warning').css('display', 'none');
2584    } else if (this.value === 'userdefined') {
2585      $('#pma_username').trigger('focus').trigger('select').prop('required', true);
2586    }
2587  });
2588
2589  /* Handler for editing username */
2590  $(document).on('change', '#pma_username', function () {
2591    $('#select_pred_username').val('userdefined');
2592    $('#pma_username').prop('required', true);
2593  });
2594
2595  /* Handler for password type */
2596  $(document).on('change', '#select_pred_password', function () {
2597    if (this.value === 'none') {
2598      $('#text_pma_pw2').prop('required', false).val('');
2599      $('#text_pma_pw').prop('required', false).val('');
2600    } else if (this.value === 'userdefined') {
2601      $('#text_pma_pw2').prop('required', true);
2602      $('#text_pma_pw').prop('required', true).trigger('focus').trigger('select');
2603    } else {
2604      $('#text_pma_pw2').prop('required', false);
2605      $('#text_pma_pw').prop('required', false);
2606    }
2607  });
2608
2609  /* Handler for editing password */
2610  $(document).on('change', '#text_pma_pw,#text_pma_pw2', function () {
2611    $('#select_pred_password').val('userdefined');
2612    $('#text_pma_pw2').prop('required', true);
2613    $('#text_pma_pw').prop('required', true);
2614  });
2615
2616  /**
2617   * Unbind all event handlers before tearing down a page
2618   */
2619  $(document).off('click', '#change_password_anchor.ajax');
2620
2621  /**
2622   * Attach Ajax event handler on the change password anchor
2623   */
2624
2625  $(document).on('click', '#change_password_anchor.ajax', function (event) {
2626    event.preventDefault();
2627    var $msgbox = Functions.ajaxShowMessage();
2628
2629    /**
2630     * @var buttonOptions Object containing options to be passed to jQueryUI's dialog
2631     */
2632    var buttonOptions = {
2633      [Messages.strGo]: {
2634        text: Messages.strGo,
2635        'class': 'btn btn-primary'
2636      },
2637      [Messages.strCancel]: {
2638        text: Messages.strCancel,
2639        'class': 'btn btn-secondary'
2640      }
2641    };
2642    buttonOptions[Messages.strGo].click = function () {
2643      event.preventDefault();
2644
2645      /**
2646       * @var $the_form    Object referring to the change password form
2647       */
2648      var $theForm = $('#change_password_form');
2649      if (!Functions.checkPassword($theForm)) {
2650        return false;
2651      }
2652
2653      /**
2654       * @var {string} thisValue String containing the value of the submit button.
2655       * Need to append this for the change password form on Server Privileges
2656       * page to work
2657       */
2658      var thisValue = $(this).val();
2659      var $msgbox = Functions.ajaxShowMessage(Messages.strProcessingRequest);
2660      $theForm.append('<input type="hidden" name="ajax_request" value="true">');
2661      $.post($theForm.attr('action'), $theForm.serialize() + CommonParams.get('arg_separator') + 'change_pw=' + thisValue, function (data) {
2662        if (typeof data === 'undefined' || data.success !== true) {
2663          Functions.ajaxShowMessage(data.error, false);
2664          return;
2665        }
2666        var $pageContent = $('#page_content');
2667        $pageContent.prepend(data.message);
2668        Functions.highlightSql($pageContent);
2669        $('#change_password_dialog').hide().remove();
2670        $('#edit_user_dialog').dialog('close').remove();
2671        Functions.ajaxRemoveMessage($msgbox);
2672      }); // end $.post()
2673    };
2674
2675    buttonOptions[Messages.strCancel].click = function () {
2676      $(this).dialog('close');
2677    };
2678    $.get($(this).attr('href'), {
2679      'ajax_request': true
2680    }, function (data) {
2681      if (typeof data === 'undefined' || !data.success) {
2682        Functions.ajaxShowMessage(data.error, false);
2683        return;
2684      }
2685      if (data.scripts) {
2686        AJAX.scriptHandler.load(data.scripts);
2687      }
2688      $('<div id="change_password_dialog"></div>').dialog({
2689        classes: {
2690          'ui-dialog-titlebar-close': 'btn-close'
2691        },
2692        title: Messages.strChangePassword,
2693        width: 600,
2694        close: function () {
2695          $(this).remove();
2696        },
2697        buttons: buttonOptions,
2698        modal: true
2699      }).append(data.message);
2700      // for this dialog, we remove the fieldset wrapping due to double headings
2701      $('fieldset#fieldset_change_password').find('legend').remove().end().find('table.table').unwrap().addClass('m-3').find('input#text_pma_pw').trigger('focus');
2702      $('#fieldset_change_password_footer').hide();
2703      Functions.ajaxRemoveMessage($msgbox);
2704      Functions.displayPasswordGenerateButton();
2705      $('#change_password_form').on('submit', function (e) {
2706        e.preventDefault();
2707        $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane .ui-button').first().trigger('click');
2708      });
2709    }); // end $.get()
2710  }); // end handler for change password anchor
2711}); // end $() for Change Password
2712
2713/**
2714 * Unbind all event handlers before tearing down a page
2715 */
2716AJAX.registerTeardown('functions.js', function () {
2717  $(document).off('change', 'select.column_type');
2718  $(document).off('change', 'select.default_type');
2719  $(document).off('change', 'select.virtuality');
2720  $(document).off('change', 'input.allow_null');
2721  $(document).off('change', '.create_table_form select[name=tbl_storage_engine]');
2722});
2723/**
2724 * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
2725 * the page loads and when the selected data type changes
2726 */
2727AJAX.registerOnload('functions.js', function () {
2728  // is called here for normal page loads and also when opening
2729  // the Create table dialog
2730  Functions.verifyColumnsProperties();
2731  //
2732  // needs on() to work also in the Create Table dialog
2733  $(document).on('change', 'select.column_type', function () {
2734    Functions.showNoticeForEnum($(this));
2735    Functions.showWarningForIntTypes();
2736  });
2737  $(document).on('change', 'select.default_type', function () {
2738    Functions.hideShowDefaultValue($(this));
2739  });
2740  $(document).on('change', 'select.virtuality', function () {
2741    Functions.hideShowExpression($(this));
2742  });
2743  $(document).on('change', 'input.allow_null', function () {
2744    Functions.validateDefaultValue($(this));
2745  });
2746  $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () {
2747    Functions.hideShowConnection($(this));
2748  });
2749});
2750
2751/**
2752 * If the chosen storage engine is FEDERATED show connection field. Hide otherwise
2753 *
2754 * @param $engineSelector storage engine selector
2755 */
2756Functions.hideShowConnection = function ($engineSelector) {
2757  var $connection = $('.create_table_form input[name=connection]');
2758  var $labelTh = $('.create_table_form #storage-engine-connection');
2759  if ($engineSelector.val() !== 'FEDERATED') {
2760    $connection.prop('disabled', true).parent('td').hide();
2761    $labelTh.hide();
2762  } else {
2763    $connection.prop('disabled', false).parent('td').show();
2764    $labelTh.show();
2765  }
2766};
2767
2768/**
2769 * If the column does not allow NULL values, makes sure that default is not NULL
2770 *
2771 * @param $nullCheckbox
2772 */
2773Functions.validateDefaultValue = function ($nullCheckbox) {
2774  if (!$nullCheckbox.prop('checked')) {
2775    var $default = $nullCheckbox.closest('tr').find('.default_type');
2776    if ($default.val() === 'NULL') {
2777      $default.val('NONE');
2778    }
2779  }
2780};
2781
2782/**
2783 * function to populate the input fields on picking a column from central list
2784 *
2785 * @param {string} inputId input id of the name field for the column to be populated
2786 * @param {number} offset of the selected column in central list of columns
2787 */
2788Functions.autoPopulate = function (inputId, offset) {
2789  var db = CommonParams.get('db');
2790  var table = CommonParams.get('table');
2791  var newInputId = inputId.substring(0, inputId.length - 1);
2792  $('#' + newInputId + '1').val(centralColumnList[db + '_' + table][offset].col_name);
2793  var colType = centralColumnList[db + '_' + table][offset].col_type.toUpperCase();
2794  $('#' + newInputId + '2').val(colType);
2795  var $input3 = $('#' + newInputId + '3');
2796  $input3.val(centralColumnList[db + '_' + table][offset].col_length);
2797  if (colType === 'ENUM' || colType === 'SET') {
2798    $input3.next().show();
2799  } else {
2800    $input3.next().hide();
2801  }
2802  var colDefault = centralColumnList[db + '_' + table][offset].col_default.toUpperCase();
2803  var $input4 = $('#' + newInputId + '4');
2804  if (colDefault === 'NULL' || colDefault === 'CURRENT_TIMESTAMP' || colDefault === 'CURRENT_TIMESTAMP()') {
2805    if (colDefault === 'CURRENT_TIMESTAMP()') {
2806      colDefault = 'CURRENT_TIMESTAMP';
2807    }
2808    $input4.val(colDefault);
2809    $input4.siblings('.default_value').hide();
2810  }
2811  if (colDefault === '') {
2812    $input4.val('NONE');
2813    $input4.siblings('.default_value').hide();
2814  } else {
2815    $input4.val('USER_DEFINED');
2816    $input4.siblings('.default_value').show();
2817    $input4.siblings('.default_value').val(centralColumnList[db + '_' + table][offset].col_default);
2818  }
2819  $('#' + newInputId + '5').val(centralColumnList[db + '_' + table][offset].col_collation);
2820  var $input6 = $('#' + newInputId + '6');
2821  $input6.val(centralColumnList[db + '_' + table][offset].col_attribute);
2822  if (centralColumnList[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') {
2823    $input6.val(centralColumnList[db + '_' + table][offset].col_extra);
2824  }
2825  if (centralColumnList[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') {
2826    $('#' + newInputId + '9').prop('checked', true).trigger('change');
2827  } else {
2828    $('#' + newInputId + '9').prop('checked', false);
2829  }
2830  if (centralColumnList[db + '_' + table][offset].col_isNull !== '0') {
2831    $('#' + newInputId + '7').prop('checked', true);
2832  } else {
2833    $('#' + newInputId + '7').prop('checked', false);
2834  }
2835};
2836
2837/**
2838 * Unbind all event handlers before tearing down a page
2839 */
2840AJAX.registerTeardown('functions.js', function () {
2841  $(document).off('click', 'a.open_enum_editor');
2842  $(document).off('click', 'input.add_value');
2843  $(document).off('click', '#enum_editor td.drop');
2844  $(document).off('click', 'a.central_columns_dialog');
2845});
2846
2847/**
2848 * Opens the ENUM/SET editor and controls its functions
2849 */
2850AJAX.registerOnload('functions.js', function () {
2851  $(document).on('click', 'a.open_enum_editor', function () {
2852    // Get the name of the column that is being edited
2853    var colname = $(this).closest('tr').find('input').first().val();
2854    var title;
2855    var i;
2856    // And use it to make up a title for the page
2857    if (colname.length < 1) {
2858      title = Messages.enum_newColumnVals;
2859    } else {
2860      title = Messages.enum_columnVals.replace(/%s/, '"' + Functions.escapeHtml(decodeURIComponent(colname)) + '"');
2861    }
2862    // Get the values as a string
2863    var inputstring = $(this).closest('td').find('input').val();
2864    // Escape html entities
2865    inputstring = $('<div></div>').text(inputstring).html();
2866    // Parse the values, escaping quotes and
2867    // slashes on the fly, into an array
2868    var values = [];
2869    var inString = false;
2870    var curr;
2871    var next;
2872    var buffer = '';
2873    for (i = 0; i < inputstring.length; i++) {
2874      curr = inputstring.charAt(i);
2875      next = i === inputstring.length ? '' : inputstring.charAt(i + 1);
2876      if (!inString && curr === '\'') {
2877        inString = true;
2878      } else if (inString && curr === '\\' && next === '\\') {
2879        buffer += '&#92;';
2880        i++;
2881      } else if (inString && next === '\'' && (curr === '\'' || curr === '\\')) {
2882        buffer += '&#39;';
2883        i++;
2884      } else if (inString && curr === '\'') {
2885        inString = false;
2886        values.push(buffer);
2887        buffer = '';
2888      } else if (inString) {
2889        buffer += curr;
2890      }
2891    }
2892    if (buffer.length > 0) {
2893      // The leftovers in the buffer are the last value (if any)
2894      values.push(buffer);
2895    }
2896    var fields = '';
2897    // If there are no values, maybe the user is about to make a
2898    // new list so we add a few for them to get started with.
2899    if (values.length === 0) {
2900      values.push('', '', '', '');
2901    }
2902    // Add the parsed values to the editor
2903    var dropIcon = Functions.getImage('b_drop');
2904    for (i = 0; i < values.length; i++) {
2905      fields += '<tr><td>' + '<input type=\'text\' value=\'' + values[i] + '\'>' + '</td><td class=\'drop\'>' + dropIcon + '</td></tr>';
2906    }
2907    /**
2908     * @var dialog HTML code for the ENUM/SET dialog
2909     */
2910    var dialog = '<div id=\'enum_editor\'>' + '<fieldset class="pma-fieldset">' + '<legend>' + title + '</legend>' + '<p>' + Functions.getImage('s_notice') + Messages.enum_hint + '</p>' + '<table class="table table-borderless values">' + fields + '</table>' + '</fieldset><fieldset class="pma-fieldset tblFooters">' + '<table class="table table-borderless add"><tr><td>' + '<div class=\'slider\'></div>' + '</td><td>' + '<form><div><input type=\'submit\' class=\'add_value btn btn-primary\' value=\'' + Functions.sprintf(Messages.enum_addValue, 1) + '\'></div></form>' + '</td></tr></table>' + '<input type=\'hidden\' value=\'' +
2911    // So we know which column's data is being edited
2912    $(this).closest('td').find('input').attr('id') + '\'>' + '</fieldset>' + '</div>';
2913    $('#enumEditorGoButton').on('click', function () {
2914      // When the submit button is clicked,
2915      // put the data back into the original form
2916      var valueArray = [];
2917      $('#enumEditorModal').find('.values input').each(function (index, elm) {
2918        var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, '\'\'');
2919        valueArray.push('\'' + val + '\'');
2920      });
2921      // get the Length/Values text field where this value belongs
2922      var valuesId = $('#enumEditorModal').find('input[type=\'hidden\']').val();
2923      $('input#' + valuesId).val(valueArray.join(','));
2924    });
2925    // Show the dialog
2926    var width = parseInt(parseInt($('html').css('font-size'), 10) / 13 * 340, 10);
2927    if (!width) {
2928      width = 340;
2929    }
2930    $('#enumEditorModal').modal('show');
2931    $('#enumEditorModal').find('.modal-body').first().html(dialog);
2932    // slider for choosing how many fields to add
2933    $('#enumEditorModal').find('.slider').slider({
2934      animate: true,
2935      range: 'min',
2936      value: 1,
2937      min: 1,
2938      max: 9,
2939      slide: function (event, ui) {
2940        $(this).closest('table').find('input[type=submit]').val(Functions.sprintf(Messages.enum_addValue, ui.value));
2941      }
2942    });
2943    // Focus the slider, otherwise it looks nearly transparent
2944    $('a.ui-slider-handle').addClass('ui-state-focus');
2945    return false;
2946  });
2947  $(document).on('click', 'a.central_columns_dialog', function () {
2948    var href = 'index.php?route=/database/central-columns';
2949    var db = CommonParams.get('db');
2950    var table = CommonParams.get('table');
2951    var maxRows = $(this).data('maxrows');
2952    var pick = $(this).data('pick');
2953    if (pick !== false) {
2954      pick = true;
2955    }
2956    var params = {
2957      'ajax_request': true,
2958      'server': CommonParams.get('server'),
2959      'db': CommonParams.get('db'),
2960      'cur_table': CommonParams.get('table'),
2961      'getColumnList': true
2962    };
2963    var colid = $(this).closest('td').find('input').attr('id');
2964    var fields = '';
2965    if (!(db + '_' + table in centralColumnList)) {
2966      centralColumnList.push(db + '_' + table);
2967      $.ajax({
2968        type: 'POST',
2969        url: href,
2970        data: params,
2971        success: function (data) {
2972          centralColumnList[db + '_' + table] = data.message;
2973        },
2974        async: false
2975      });
2976    }
2977    var i = 0;
2978    var listSize = centralColumnList[db + '_' + table].length;
2979    var min = listSize <= maxRows ? listSize : maxRows;
2980    for (i = 0; i < min; i++) {
2981      fields += '<tr><td><div><span class="fw-bold">' + Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_name) + '</span><br><span class="color_gray">' + centralColumnList[db + '_' + table][i].col_type;
2982      if (centralColumnList[db + '_' + table][i].col_attribute !== '') {
2983        fields += '(' + Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_attribute) + ') ';
2984      }
2985      if (centralColumnList[db + '_' + table][i].col_length !== '') {
2986        fields += '(' + Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_length) + ') ';
2987      }
2988      fields += Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_extra) + '</span>' + '</div></td>';
2989      if (pick) {
2990        fields += '<td><input class="btn btn-secondary pick w-100" type="submit" value="' + Messages.pickColumn + '" onclick="Functions.autoPopulate(\'' + colid + '\',' + i + ')"></td>';
2991      }
2992      fields += '</tr>';
2993    }
2994    var resultPointer = i;
2995    var searchIn = '<input type="text" class="filter_rows" placeholder="' + Messages.searchList + '">';
2996    if (fields === '') {
2997      fields = Functions.sprintf(Messages.strEmptyCentralList, '\'' + Functions.escapeHtml(db) + '\'');
2998      searchIn = '';
2999    }
3000    var seeMore = '';
3001    if (listSize > maxRows) {
3002      seeMore = '<fieldset class="pma-fieldset tblFooters text-center fw-bold">' + '<a href=\'#\' id=\'seeMore\'>' + Messages.seeMore + '</a></fieldset>';
3003    }
3004    var centralColumnsDialog = '<div class=\'max_height_400\'>' + '<fieldset class="pma-fieldset">' + searchIn + '<table id="col_list" class="table table-borderless values">' + fields + '</table>' + '</fieldset>' + seeMore + '</div>';
3005    var width = parseInt(parseInt($('html').css('font-size'), 10) / 13 * 500, 10);
3006    if (!width) {
3007      width = 500;
3008    }
3009    var buttonOptions = {};
3010    var $centralColumnsDialog = $(centralColumnsDialog).dialog({
3011      classes: {
3012        'ui-dialog-titlebar-close': 'btn-close'
3013      },
3014      minWidth: width,
3015      maxHeight: 450,
3016      modal: true,
3017      title: Messages.pickColumnTitle,
3018      buttons: buttonOptions,
3019      open: function () {
3020        $('#col_list').on('click', '.pick', function () {
3021          $centralColumnsDialog.remove();
3022        });
3023        $('.filter_rows').on('keyup', function () {
3024          $.uiTableFilter($('#col_list'), $(this).val());
3025        });
3026        $('#seeMore').on('click', function () {
3027          fields = '';
3028          min = listSize <= maxRows + resultPointer ? listSize : maxRows + resultPointer;
3029          for (i = resultPointer; i < min; i++) {
3030            fields += '<tr><td><div><span class="fw-bold">' + centralColumnList[db + '_' + table][i].col_name + '</span><br><span class="color_gray">' + centralColumnList[db + '_' + table][i].col_type;
3031            if (centralColumnList[db + '_' + table][i].col_attribute !== '') {
3032              fields += '(' + centralColumnList[db + '_' + table][i].col_attribute + ') ';
3033            }
3034            if (centralColumnList[db + '_' + table][i].col_length !== '') {
3035              fields += '(' + centralColumnList[db + '_' + table][i].col_length + ') ';
3036            }
3037            fields += centralColumnList[db + '_' + table][i].col_extra + '</span>' + '</div></td>';
3038            if (pick) {
3039              fields += '<td><input class="btn btn-secondary pick w-100" type="submit" value="' + Messages.pickColumn + '" onclick="Functions.autoPopulate(\'' + colid + '\',' + i + ')"></td>';
3040            }
3041            fields += '</tr>';
3042          }
3043          $('#col_list').append(fields);
3044          resultPointer = i;
3045          if (resultPointer === listSize) {
3046            $('#seeMore').hide();
3047          }
3048          return false;
3049        });
3050        $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button').first().trigger('focus');
3051      },
3052      close: function () {
3053        $('#col_list').off('click', '.pick');
3054        $('.filter_rows').off('keyup');
3055        $(this).remove();
3056      }
3057    });
3058    return false;
3059  });
3060
3061  // $(document).on('click', 'a.show_central_list',function(e) {
3062
3063  // });
3064  // When "add a new value" is clicked, append an empty text field
3065  $(document).on('click', 'input.add_value', function (e) {
3066    e.preventDefault();
3067    var numNewRows = $('#enumEditorModal').find('div.slider').slider('value');
3068    while (numNewRows--) {
3069      $('#enumEditorModal').find('.values').append('<tr class=\'hide\'><td>' + '<input type=\'text\'>' + '</td><td class=\'drop\'>' + Functions.getImage('b_drop') + '</td></tr>').find('tr').last().show('fast');
3070    }
3071  });
3072
3073  // Removes the specified row from the enum editor
3074  $(document).on('click', '#enum_editor td.drop', function () {
3075    $(this).closest('tr').hide('fast', function () {
3076      $(this).remove();
3077    });
3078  });
3079});
3080
3081/**
3082 * Ensures indexes names are valid according to their type and, for a primary
3083 * key, lock index name to 'PRIMARY'
3084 * @param {string} formId Variable which parses the form name as
3085 *                        the input
3086 * @return {boolean} false if there is no index form, true else
3087 */
3088Functions.checkIndexName = function (formId) {
3089  if ($('#' + formId).length === 0) {
3090    return false;
3091  }
3092
3093  // Gets the elements pointers
3094  var $theIdxName = $('#input_index_name');
3095  var $theIdxChoice = $('#select_index_choice');
3096
3097  // Index is a primary key
3098  if ($theIdxChoice.find('option:selected').val() === 'PRIMARY') {
3099    $theIdxName.val('PRIMARY');
3100    $theIdxName.prop('disabled', true);
3101  } else {
3102    if ($theIdxName.val() === 'PRIMARY') {
3103      $theIdxName.val('');
3104    }
3105    $theIdxName.prop('disabled', false);
3106  }
3107  return true;
3108};
3109AJAX.registerTeardown('functions.js', function () {
3110  $(document).off('click', '#index_frm input[type=submit]');
3111});
3112AJAX.registerOnload('functions.js', function () {
3113  /**
3114   * Handler for adding more columns to an index in the editor
3115   */
3116  $(document).on('click', '#index_frm input[type=submit]', function (event) {
3117    event.preventDefault();
3118    var hadAddButtonHidden = $(this).closest('fieldset').find('.add_fields').hasClass('hide');
3119    if (hadAddButtonHidden === false) {
3120      var rowsToAdd = $(this).closest('fieldset').find('.slider').slider('value');
3121      var tempEmptyVal = function () {
3122        $(this).val('');
3123      };
3124      var tempSetFocus = function () {
3125        if ($(this).find('option:selected').val() === '') {
3126          return true;
3127        }
3128        $(this).closest('tr').find('input').trigger('focus');
3129      };
3130      while (rowsToAdd--) {
3131        var $indexColumns = $('#index_columns');
3132        var $newrow = $indexColumns.find('tbody > tr').first().clone().appendTo($indexColumns.find('tbody'));
3133        $newrow.find(':input').each(tempEmptyVal);
3134        // focus index size input on column picked
3135        $newrow.find('select').on('change', tempSetFocus);
3136      }
3137    }
3138  });
3139});
3140Functions.indexDialogModal = function (routeUrl, url, title, callbackSuccess, callbackFailure) {
3141  /* Remove the hidden dialogs if there are*/
3142  var modal = $('#indexDialogModal');
3143  const indexDialogPreviewModal = document.getElementById('indexDialogPreviewModal');
3144  indexDialogPreviewModal.addEventListener('shown.bs.modal', () => {
3145    const modalBody = indexDialogPreviewModal.querySelector('.modal-body');
3146    const $form = $('#index_frm');
3147    const formUrl = $form.attr('action');
3148    const sep = CommonParams.get('arg_separator');
3149    const formData = $form.serialize() + sep + 'do_save_data=1' + sep + 'preview_sql=1' + sep + 'ajax_request=1';
3150    $.post({
3151      url: formUrl,
3152      data: formData,
3153      success: response => {
3154        if (!response.success) {
3155          modalBody.innerHTML = '<div class="alert alert-danger" role="alert">' + Messages.strErrorProcessingRequest + '</div>';
3156          return;
3157        }
3158        modalBody.innerHTML = response.sql_data;
3159        Functions.highlightSql($('#indexDialogPreviewModal'));
3160      },
3161      error: () => {
3162        modalBody.innerHTML = '<div class="alert alert-danger" role="alert">' + Messages.strErrorProcessingRequest + '</div>';
3163      }
3164    });
3165  });
3166  indexDialogPreviewModal.addEventListener('hidden.bs.modal', () => {
3167    indexDialogPreviewModal.querySelector('.modal-body').innerHTML = '<div class="spinner-border" role="status">' + '<span class="visually-hidden">' + Messages.strLoading + '</span></div>';
3168  });
3169
3170  // Remove previous click listeners from other modal openings (issue: #17892)
3171  $('#indexDialogModalGoButton').off('click');
3172  $('#indexDialogModalGoButton').on('click', function () {
3173    /**
3174     * @var the_form object referring to the export form
3175     */
3176    var $form = $('#index_frm');
3177    Functions.ajaxShowMessage(Messages.strProcessingRequest);
3178    Functions.prepareForAjaxRequest($form);
3179    // User wants to submit the form
3180    $.post($form.attr('action'), $form.serialize() + CommonParams.get('arg_separator') + 'do_save_data=1', function (data) {
3181      var $sqlqueryresults = $('.sqlqueryresults');
3182      if ($sqlqueryresults.length !== 0) {
3183        $sqlqueryresults.remove();
3184      }
3185      if (typeof data !== 'undefined' && data.success === true) {
3186        Functions.ajaxShowMessage(data.message);
3187        Functions.highlightSql($('.result_query'));
3188        $('.result_query .alert').remove();
3189        /* Reload the field form*/
3190        $('#table_index').remove();
3191        $('<div id=\'temp_div\'><div>').append(data.index_table).find('#table_index').insertAfter('#index_header');
3192        var $editIndexDialog = $('#indexDialogModal');
3193        if ($editIndexDialog.length > 0) {
3194          $editIndexDialog.modal('hide');
3195        }
3196        $('div.no_indexes_defined').hide();
3197        if (callbackSuccess) {
3198          callbackSuccess(data);
3199        }
3200        Navigation.reload();
3201      } else {
3202        var $tempDiv = $('<div id=\'temp_div\'><div>').append(data.error);
3203        var $error;
3204        if ($tempDiv.find('.error code').length !== 0) {
3205          $error = $tempDiv.find('.error code').addClass('error');
3206        } else {
3207          $error = $tempDiv;
3208        }
3209        if (callbackFailure) {
3210          callbackFailure();
3211        }
3212        Functions.ajaxShowMessage($error, false);
3213      }
3214    }); // end $.post()
3215  });
3216
3217  var $msgbox = Functions.ajaxShowMessage();
3218  $.post(routeUrl, url, function (data) {
3219    if (typeof data !== 'undefined' && data.success === false) {
3220      // in the case of an error, show the error message returned.
3221      Functions.ajaxShowMessage(data.error, false);
3222    } else {
3223      Functions.ajaxRemoveMessage($msgbox);
3224      // Show dialog if the request was successful
3225      modal.modal('show');
3226      modal.find('.modal-body').first().html(data.message);
3227      $('#indexDialogModalLabel').first().text(title);
3228      Functions.verifyColumnsProperties();
3229      modal.find('.tblFooters').remove();
3230      Functions.showIndexEditDialog(modal);
3231    }
3232  }); // end $.get()
3233};
3234
3235Functions.indexEditorDialog = function (url, title, callbackSuccess, callbackFailure) {
3236  Functions.indexDialogModal('index.php?route=/table/indexes', url, title, callbackSuccess, callbackFailure);
3237};
3238Functions.indexRenameDialog = function (url, title, callbackSuccess, callbackFailure) {
3239  Functions.indexDialogModal('index.php?route=/table/indexes/rename', url, title, callbackSuccess, callbackFailure);
3240};
3241Functions.showIndexEditDialog = function ($outer) {
3242  Indexes.checkIndexType();
3243  Functions.checkIndexName('index_frm');
3244  var $indexColumns = $('#index_columns');
3245  $indexColumns.find('td').each(function () {
3246    $(this).css('width', $(this).width() + 'px');
3247  });
3248  $indexColumns.find('tbody').sortable({
3249    axis: 'y',
3250    containment: $indexColumns.find('tbody'),
3251    tolerance: 'pointer'
3252  });
3253  Functions.showHints($outer);
3254  // Add a slider for selecting how many columns to add to the index
3255  $outer.find('.slider').slider({
3256    animate: true,
3257    value: 1,
3258    min: 1,
3259    max: 16,
3260    slide: function (event, ui) {
3261      $(this).closest('fieldset').find('input[type=submit]').val(Functions.sprintf(Messages.strAddToIndex, ui.value));
3262    }
3263  });
3264  $('div.add_fields').removeClass('hide');
3265  // focus index size input on column picked
3266  $outer.find('table#index_columns select').on('change', function () {
3267    if ($(this).find('option:selected').val() === '') {
3268      return true;
3269    }
3270    $(this).closest('tr').find('input').trigger('focus');
3271  });
3272  // Focus the slider, otherwise it looks nearly transparent
3273  $('a.ui-slider-handle').addClass('ui-state-focus');
3274  // set focus on index name input, if empty
3275  var input = $outer.find('input#input_index_name');
3276  if (!input.val()) {
3277    input.trigger('focus');
3278  }
3279};
3280
3281/**
3282 * Function to display tooltips that were
3283 * generated on the PHP side by PhpMyAdmin\Util::showHint()
3284 *
3285 * @param {object} $div a div jquery object which specifies the
3286 *                    domain for searching for tooltips. If we
3287 *                    omit this parameter the function searches
3288 *                    in the whole body
3289 **/
3290Functions.showHints = function ($div) {
3291  var $newDiv = $div;
3292  if ($newDiv === undefined || !($newDiv instanceof jQuery) || $newDiv.length === 0) {
3293    $newDiv = $('body');
3294  }
3295  $newDiv.find('.pma_hint').each(function () {
3296    Functions.tooltip($(this).children('img'), 'img', $(this).children('span').html());
3297  });
3298};
3299AJAX.registerOnload('functions.js', function () {
3300  Functions.showHints();
3301});
3302Functions.mainMenuResizerCallback = function () {
3303  // 5 px margin for jumping menu in Chrome
3304  // eslint-disable-next-line compat/compat
3305  return $(document.body).width() - 5;
3306};
3307
3308// This must be fired only once after the initial page load
3309$(function () {
3310  // Initialise the menu resize plugin
3311  $('#topmenu').menuResizer(Functions.mainMenuResizerCallback);
3312  // register resize event
3313  $(window).on('resize', function () {
3314    $('#topmenu').menuResizer('resize');
3315  });
3316});
3317
3318/**
3319 * var  toggleButton  This is a function that creates a toggle
3320 *                    sliding button given a jQuery reference
3321 *                    to the correct DOM element
3322 *
3323 * @param $obj
3324 */
3325Functions.toggleButton = function ($obj) {
3326  // In rtl mode the toggle switch is flipped horizontally
3327  // so we need to take that into account
3328  var right;
3329  if ($('span.text_direction', $obj).text() === 'ltr') {
3330    right = 'right';
3331  } else {
3332    right = 'left';
3333  }
3334  /**
3335   * @var  h  Height of the button, used to scale the
3336   *          background image and position the layers
3337   */
3338  var h = $obj.height();
3339  $('img', $obj).height(h);
3340  $('table', $obj).css('bottom', h - 1);
3341  /**
3342   * @var  on   Width of the "ON" part of the toggle switch
3343   * @var  off  Width of the "OFF" part of the toggle switch
3344   */
3345  var on = $('td.toggleOn', $obj).width();
3346  var off = $('td.toggleOff', $obj).width();
3347  // Make the "ON" and "OFF" parts of the switch the same size
3348  // + 2 pixels to avoid overflowed
3349  $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
3350  $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
3351  /**
3352   *  @var  w  Width of the central part of the switch
3353   */
3354  var w = parseInt($('img', $obj).height() / 16 * 22, 10);
3355  // Resize the central part of the switch on the top
3356  // layer to match the background
3357  $($obj).find('table td').eq(1).children('div').width(w);
3358  /**
3359   * @var  imgw    Width of the background image
3360   * @var  tblw    Width of the foreground layer
3361   * @var  offset  By how many pixels to move the background
3362   *               image, so that it matches the top layer
3363   */
3364  var imgw = $('img', $obj).width();
3365  var tblw = $('table', $obj).width();
3366  var offset = parseInt((imgw - tblw) / 2, 10);
3367  // Move the background to match the layout of the top layer
3368  $obj.find('img').css(right, offset);
3369  /**
3370   * @var  offw    Outer width of the "ON" part of the toggle switch
3371   * @var  btnw    Outer width of the central part of the switch
3372   */
3373  var offw = $('td.toggleOff', $obj).outerWidth();
3374  var btnw = $($obj).find('table td').eq(1).outerWidth();
3375  // Resize the main div so that exactly one side of
3376  // the switch plus the central part fit into it.
3377  $obj.width(offw + btnw + 2);
3378  /**
3379   * @var  move  How many pixels to move the
3380   *             switch by when toggling
3381   */
3382  var move = $('td.toggleOff', $obj).outerWidth();
3383  // If the switch is initialized to the
3384  // OFF state we need to move it now.
3385  if ($('div.toggle-container', $obj).hasClass('off')) {
3386    if (right === 'right') {
3387      $('div.toggle-container', $obj).animate({
3388        'left': '-=' + move + 'px'
3389      }, 0);
3390    } else {
3391      $('div.toggle-container', $obj).animate({
3392        'left': '+=' + move + 'px'
3393      }, 0);
3394    }
3395  }
3396  // Attach an 'onclick' event to the switch
3397  $('div.toggle-container', $obj).on('click', function () {
3398    if ($(this).hasClass('isActive')) {
3399      return false;
3400    } else {
3401      $(this).addClass('isActive');
3402    }
3403    var $msg = Functions.ajaxShowMessage();
3404    var $container = $(this);
3405    var callback = $('span.callback', this).text();
3406    var operator;
3407    var url;
3408    var removeClass;
3409    var addClass;
3410    // Perform the actual toggle
3411    if ($(this).hasClass('on')) {
3412      if (right === 'right') {
3413        operator = '-=';
3414      } else {
3415        operator = '+=';
3416      }
3417      url = $(this).find('td.toggleOff > span').text();
3418      removeClass = 'on';
3419      addClass = 'off';
3420    } else {
3421      if (right === 'right') {
3422        operator = '+=';
3423      } else {
3424        operator = '-=';
3425      }
3426      url = $(this).find('td.toggleOn > span').text();
3427      removeClass = 'off';
3428      addClass = 'on';
3429    }
3430    var parts = url.split('?');
3431    $.post(parts[0], parts[1] + '&ajax_request=true', function (data) {
3432      if (typeof data !== 'undefined' && data.success === true) {
3433        Functions.ajaxRemoveMessage($msg);
3434        $container.removeClass(removeClass).addClass(addClass).animate({
3435          'left': operator + move + 'px'
3436        }, function () {
3437          $container.removeClass('isActive');
3438        });
3439        // eslint-disable-next-line no-eval
3440        eval(callback);
3441      } else {
3442        Functions.ajaxShowMessage(data.error, false);
3443        $container.removeClass('isActive');
3444      }
3445    });
3446  });
3447};
3448
3449/**
3450 * Unbind all event handlers before tearing down a page
3451 */
3452AJAX.registerTeardown('functions.js', function () {
3453  $('div.toggle-container').off('click');
3454});
3455/**
3456 * Initialise all toggle buttons
3457 */
3458AJAX.registerOnload('functions.js', function () {
3459  $('div.toggleAjax').each(function () {
3460    var $button = $(this).show();
3461    $button.find('img').each(function () {
3462      if (this.complete) {
3463        Functions.toggleButton($button);
3464      } else {
3465        $(this).on('load', function () {
3466          Functions.toggleButton($button);
3467        });
3468      }
3469    });
3470  });
3471});
3472
3473/**
3474 * Unbind all event handlers before tearing down a page
3475 */
3476AJAX.registerTeardown('functions.js', function () {
3477  $(document).off('change', 'select.pageselector');
3478  $('#update_recent_tables').off('ready');
3479  $('#sync_favorite_tables').off('ready');
3480});
3481AJAX.registerOnload('functions.js', function () {
3482  /**
3483   * Autosubmit page selector
3484   */
3485  $(document).on('change', 'select.pageselector', function (event) {
3486    event.stopPropagation();
3487    // Check where to load the new content
3488    if ($(this).closest('#pma_navigation').length === 0) {
3489      // For the main page we don't need to do anything,
3490      $(this).closest('form').trigger('submit');
3491    } else {
3492      // but for the navigation we need to manually replace the content
3493      Navigation.treePagination($(this));
3494    }
3495  });
3496  var $updateRecentTables = $('#update_recent_tables');
3497  if ($updateRecentTables.length) {
3498    $.get($updateRecentTables.attr('href'), {
3499      'no_debug': true
3500    }, function (data) {
3501      if (typeof data !== 'undefined' && data.success === true) {
3502        $('#pma_recent_list').html(data.list);
3503      }
3504    });
3505  }
3506
3507  // Sync favorite tables from localStorage to pmadb.
3508  if ($('#sync_favorite_tables').length) {
3509    $.ajax({
3510      url: $('#sync_favorite_tables').attr('href'),
3511      cache: false,
3512      type: 'POST',
3513      data: {
3514        'favoriteTables': isStorageSupported('localStorage') && typeof window.localStorage.favoriteTables !== 'undefined' ? window.localStorage.favoriteTables : '',
3515        'server': CommonParams.get('server'),
3516        'no_debug': true
3517      },
3518      success: function (data) {
3519        // Update localStorage.
3520        if (isStorageSupported('localStorage')) {
3521          window.localStorage.favoriteTables = data.favoriteTables;
3522        }
3523        $('#pma_favorite_list').html(data.list);
3524      }
3525    });
3526  }
3527}); // end of $()
3528
3529/**
3530 * Creates a message inside an object with a sliding effect
3531 *
3532 * @param {string} msg    A string containing the text to display
3533 * @param {JQuery}
3533 $object   a jQuery object containing the reference
3534 *                 to the element where to put the message
3535 *                 This is optional, if no element is
3536 *                 provided, one will be created below the
3537 *                 navigation links at the top of the page
3538 *
3539 * @return {boolean} True on success, false on failure
3540 */
3541Functions.slidingMessage = function (msg, $object) {
3542  var $obj = $object;
3543  if (msg === undefined || msg.length === 0) {
3544    // Don't show an empty message
3545    return false;
3546  }
3547  if ($obj === undefined || !($obj instanceof jQuery) || $obj.length === 0) {
3548    // If the second argument was not supplied,
3549    // we might have to create a new DOM node.
3550    if ($('#PMA_slidingMessage').length === 0) {
3551      $('#page_content').prepend('<span id="PMA_slidingMessage" ' + 'class="d-inline-block"></span>');
3552    }
3553    $obj = $('#PMA_slidingMessage');
3554  }
3555  if ($obj.has('div').length > 0) {
3556    // If there already is a message inside the
3557    // target object, we must get rid of it
3558    $obj.find('div').first().fadeOut(function () {
3559      $obj.children().remove();
3560      $obj.append('<div>' + msg + '</div>');
3561      // highlight any sql before taking height;
3562      Functions.highlightSql($obj);
3563      $obj.find('div').first().hide();
3564      $obj.animate({
3565        height: $obj.find('div').first().height()
3566      }).find('div').first().fadeIn();
3567    });
3568  } else {
3569    // Object does not already have a message
3570    // inside it, so we simply slide it down
3571    $obj.width('100%').html('<div>' + msg + '</div>');
3572    // highlight any sql before taking height;
3573    Functions.highlightSql($obj);
3574    var h = $obj.find('div').first().hide().height();
3575    $obj.find('div').first().css('height', 0).show().animate({
3576      height: h
3577    }, function () {
3578      // Set the height of the parent
3579      // to the height of the child
3580      $obj.height($obj.find('div').first().height());
3581    });
3582  }
3583  return true;
3584};
3585
3586/**
3587 * Attach CodeMirror2 editor to SQL edit area.
3588 */
3589AJAX.registerOnload('functions.js', function () {
3590  var $elm = $('#sqlquery');
3591  if ($elm.siblings().filter('.CodeMirror').length > 0) {
3592    return;
3593  }
3594  if ($elm.length > 0) {
3595    if (typeof CodeMirror !== 'undefined') {
3596      codeMirrorEditor = Functions.getSqlEditor($elm);
3597      codeMirrorEditor.focus();
3598      codeMirrorEditor.on('blur', Functions.updateQueryParameters);
3599    } else {
3600      // without codemirror
3601      $elm.trigger('focus').on('blur', Functions.updateQueryParameters);
3602    }
3603  }
3604  Functions.highlightSql($('body'));
3605});
3606AJAX.registerTeardown('functions.js', function () {
3607  if (codeMirrorEditor) {
3608    $('#sqlquery').text(codeMirrorEditor.getValue());
3609    codeMirrorEditor.toTextArea();
3610    codeMirrorEditor = false;
3611  }
3612});
3613AJAX.registerOnload('functions.js', function () {
3614  // initializes all lock-page elements lock-id and
3615  // val-hash data property
3616  $('#page_content form.lock-page textarea, ' + '#page_content form.lock-page input[type="text"], ' + '#page_content form.lock-page input[type="number"], ' + '#page_content form.lock-page select').each(function (i) {
3617    $(this).data('lock-id', i);
3618    // val-hash is the hash of default value of the field
3619    // so that it can be compared with new value hash
3620    // to check whether field was modified or not.
3621    $(this).data('val-hash', AJAX.hash($(this).val()));
3622  });
3623
3624  // initializes lock-page elements (input types checkbox and radio buttons)
3625  // lock-id and val-hash data property
3626  $('#page_content form.lock-page input[type="checkbox"], ' + '#page_content form.lock-page input[type="radio"]').each(function (i) {
3627    $(this).data('lock-id', i);
3628    $(this).data('val-hash', AJAX.hash($(this).is(':checked')));
3629  });
3630});
3631
3632/**
3633 * jQuery plugin to correctly filter input fields by value, needed
3634 * because some nasty values may break selector syntax
3635 */
3636(function ($) {
3637  $.fn.filterByValue = function (value) {
3638    return this.filter(function () {
3639      return $(this).val() === value;
3640    });
3641  };
3642})(jQuery);
3643
3644/**
3645 * Return value of a cell in a table.
3646 *
3647 * @param {string} td
3648 * @return {string}
3649 */
3650Functions.getCellValue = function (td) {
3651  var $td = $(td);
3652  if ($td.is('.null')) {
3653    return '';
3654  } else if ((!$td.is('.to_be_saved') || $td.is('.set')) && $td.data('original_data')) {
3655    return $td.data('original_data');
3656  } else {
3657    return $td.text();
3658  }
3659};
3660
3661/**
3662 * Validate and return stringified JSON inputs, or plain if invalid.
3663 *
3664 * @param json the json input to be validated and stringified
3665 * @param replacer An array of strings and numbers that acts as an approved list for selecting the object properties that will be stringified.
3666 * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
3667 * @return {string}
3668 */
3669Functions.stringifyJSON = function (json) {
3670  let replacer = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
3671  let space = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
3672  try {
3673    return JSON.stringify(JSON.parse(json), replacer, space);
3674  } catch (e) {
3675    return json;
3676  }
3677};
3678
3679/**
3680 * Unbind all event handlers before tearing down a page
3681 */
3682AJAX.registerTeardown('functions.js', function () {
3683  $(document).off('change', '.autosubmit');
3684});
3685AJAX.registerOnload('functions.js', function () {
3686  /**
3687   * Automatic form submission on change.
3688   */
3689  $(document).on('change', '.autosubmit', function () {
3690    $(this).closest('form').trigger('submit');
3691  });
3692});
3693
3694/**
3695 * @implements EventListener
3696 */
3697const PrintPage = {
3698  handleEvent: () => {
3699    window.print();
3700  }
3701};
3702
3703/**
3704 * Unbind all event handlers before tearing down a page
3705 */
3706AJAX.registerTeardown('functions.js', function () {
3707  document.querySelectorAll('.jsPrintButton').forEach(item => {
3708    item.removeEventListener('click', PrintPage);
3709  });
3710  $(document).off('click', 'a.create_view.ajax');
3711  $(document).off('keydown', '#createViewModal input, #createViewModal select');
3712  $(document).off('change', '#fkc_checkbox');
3713});
3714AJAX.registerOnload('functions.js', function () {
3715  document.querySelectorAll('.jsPrintButton').forEach(item => {
3716    item.addEventListener('click', PrintPage);
3717  });
3718  $('.logout').on('click', function () {
3719    var form = $('<form method="POST" action="' + $(this).attr('href') + '" class="disableAjax">' + '<input type="hidden" name="token" value="' + Functions.escapeHtml(CommonParams.get('token')) + '">' + '</form>');
3720    $('body').append(form);
3721    form.submit();
3722    sessionStorage.clear();
3723    return false;
3724  });
3725  /**
3726   * Ajaxification for the "Create View" action
3727   */
3728  $(document).on('click', 'a.create_view.ajax', function (e) {
3729    e.preventDefault();
3730    Functions.createViewModal($(this));
3731  });
3732  /**
3733   * Attach Ajax event handlers for input fields in the editor
3734   * and used to submit the Ajax request when the ENTER key is pressed.
3735   */
3736  if ($('#createViewModal').length !== 0) {
3737    $(document).on('keydown', '#createViewModal input, #createViewModal select', function (e) {
3738      if (e.which === 13) {
3739        // 13 is the ENTER key
3740        e.preventDefault();
3741
3742        // with preventing default, selection by <select> tag
3743        // was also prevented in IE
3744        $(this).trigger('blur');
3745        $(this).closest('.ui-dialog').find('.ui-button').first().trigger('click');
3746      }
3747    }); // end $(document).on()
3748  }
3749
3750  if ($('textarea[name="view[as]"]').length !== 0) {
3751    codeMirrorEditor = Functions.getSqlEditor($('textarea[name="view[as]"]'));
3752  }
3753});
3754Functions.createViewModal = function ($this) {
3755  var $msg = Functions.ajaxShowMessage();
3756  var sep = CommonParams.get('arg_separator');
3757  var params = Functions.getJsConfirmCommonParam(this, $this.getPostData());
3758  params += sep + 'ajax_dialog=1';
3759  $.post($this.attr('href'), params, function (data) {
3760    if (typeof data !== 'undefined' && data.success === true) {
3761      Functions.ajaxRemoveMessage($msg);
3762      $('#createViewModalGoButton').on('click', function () {
3763        if (typeof CodeMirror !== 'undefined') {
3764          codeMirrorEditor.save();
3765        }
3766        $msg = Functions.ajaxShowMessage();
3767        $.post('index.php?route=/view/create', $('#createViewModal').find('form').serialize(), function (data) {
3768          Functions.ajaxRemoveMessage($msg);
3769          if (typeof data !== 'undefined' && data.success === true) {
3770            $('#createViewModal').modal('hide');
3771            $('.result_query').html(data.message);
3772            Navigation.reload();
3773          } else {
3774            Functions.ajaxShowMessage(data.error);
3775          }
3776        });
3777      });
3778      $('#createViewModal').find('.modal-body').first().html(data.message);
3779      // Attach syntax highlighted editor
3780      $('#createViewModal').on('shown.bs.modal', function () {
3781        codeMirrorEditor = Functions.getSqlEditor($('#createViewModal').find('textarea'));
3782        $('input:visible[type=text]', $('#createViewModal')).first().trigger('focus');
3783        $('#createViewModal').off('shown.bs.modal');
3784      });
3785      $('#createViewModal').modal('show');
3786    } else {
3787      Functions.ajaxShowMessage(data.error);
3788    }
3789  });
3790};
3791
3792/**
3793 * Makes the breadcrumbs and the menu bar float at the top of the viewport
3794 */
3795$(function () {
3796  if ($('#floating_menubar').length && $('#PMA_disable_floating_menubar').length === 0) {
3797    var left = $('html').attr('dir') === 'ltr' ? 'left' : 'right';
3798    $('#floating_menubar').css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width()).css(left, 0).css({
3799      'position': 'fixed',
3800      'top': 0,
3801      'width': '100%',
3802      'z-index': 99
3803    }).append($('#server-breadcrumb')).append($('#topmenucontainer'));
3804    // Allow the DOM to render, then adjust the padding on the body
3805    setTimeout(function () {
3806      $('body').css('padding-top', $('#floating_menubar').outerHeight(true));
3807      $('#topmenu').menuResizer('resize');
3808    }, 4);
3809  }
3810});
3811
3812/**
3813 * Scrolls the page to the top if clicking the server-breadcrumb bar
3814 */
3815$(function () {
3816  $(document).on('click', '#server-breadcrumb, #goto_pagetop', function (event) {
3817    event.preventDefault();
3818    $('html, body').animate({
3819      scrollTop: 0
3820    }, 'fast');
3821  });
3822});
3823var checkboxesSel = 'input.checkall:checkbox:enabled';
3824Functions.checkboxesSel = checkboxesSel;
3825
3826/**
3827 * Watches checkboxes in a form to set the checkall box accordingly
3828 */
3829Functions.checkboxesChanged = function () {
3830  var $form = $(this.form);
3831  // total number of checkboxes in current form
3832  var totalBoxes = $form.find(checkboxesSel).length;
3833  // number of checkboxes checked in current form
3834  var checkedBoxes = $form.find(checkboxesSel + ':checked').length;
3835  var $checkall = $form.find('input.checkall_box');
3836  if (totalBoxes === checkedBoxes) {
3837    $checkall.prop({
3838      checked: true,
3839      indeterminate: false
3840    });
3841  } else if (checkedBoxes > 0) {
3842    $checkall.prop({
3843      checked: true,
3844      indeterminate: true
3845    });
3846  } else {
3847    $checkall.prop({
3848      checked: false,
3849      indeterminate: false
3850    });
3851  }
3852};
3853$(document).on('change', checkboxesSel, Functions.checkboxesChanged);
3854$(document).on('change', 'input.checkall_box', function () {
3855  var isChecked = $(this).is(':checked');
3856  $(this.form).find(checkboxesSel).not('.row-hidden').prop('checked', isChecked).parents('tr').toggleClass('marked table-active', isChecked);
3857});
3858$(document).on('click', '.checkall-filter', function () {
3859  var $this = $(this);
3860  var selector = $this.data('checkall-selector');
3861  $('input.checkall_box').prop('checked', false);
3862  $this.parents('form').find(checkboxesSel).filter(selector).prop('checked', true).trigger('change').parents('tr').toggleClass('marked', true);
3863  return false;
3864});
3865
3866/**
3867 * Watches checkboxes in a sub form to set the sub checkall box accordingly
3868 */
3869Functions.subCheckboxesChanged = function () {
3870  var $form = $(this).parent().parent();
3871  // total number of checkboxes in current sub form
3872  var totalBoxes = $form.find(checkboxesSel).length;
3873  // number of checkboxes checked in current sub form
3874  var checkedBoxes = $form.find(checkboxesSel + ':checked').length;
3875  var $checkall = $form.find('input.sub_checkall_box');
3876  if (totalBoxes === checkedBoxes) {
3877    $checkall.prop({
3878      checked: true,
3879      indeterminate: false
3880    });
3881  } else if (checkedBoxes > 0) {
3882    $checkall.prop({
3883      checked: true,
3884      indeterminate: true
3885    });
3886  } else {
3887    $checkall.prop({
3888      checked: false,
3889      indeterminate: false
3890    });
3891  }
3892};
3893$(document).on('change', checkboxesSel + ', input.checkall_box:checkbox:enabled', Functions.subCheckboxesChanged);
3894$(document).on('change', 'input.sub_checkall_box', function () {
3895  var isChecked = $(this).is(':checked');
3896  var $form = $(this).parent().parent();
3897  $form.find(checkboxesSel).prop('checked', isChecked).parents('tr').toggleClass('marked', isChecked);
3898});
3899
3900/**
3901 * Rows filtering
3902 *
3903 * - rows to filter are identified by data-filter-row attribute
3904 *   which contains uppercase string to filter
3905 * - it is simple substring case insensitive search
3906 * - optionally number of matching rows is written to element with
3907 *   id filter-rows-count
3908 */
3909$(document).on('keyup', '#filterText', function () {
3910  var filterInput = $(this).val().toUpperCase().replace(/ /g, '_');
3911  var count = 0;
3912  $('[data-filter-row]').each(function () {
3913    var $row = $(this);
3914    /* Can not use data() here as it does magic conversion to int for numeric values */
3915    if ($row.attr('data-filter-row').indexOf(filterInput) > -1) {
3916      count += 1;
3917      $row.show();
3918      $row.find('input.checkall').removeClass('row-hidden');
3919    } else {
3920      $row.hide();
3921      $row.find('input.checkall').addClass('row-hidden').prop('checked', false);
3922      $row.removeClass('marked');
3923    }
3924  });
3925  setTimeout(function () {
3926    $(checkboxesSel).trigger('change');
3927  }, 300);
3928  $('#filter-rows-count').html(count);
3929});
3930AJAX.registerOnload('functions.js', function () {
3931  /* Trigger filtering of the list based on incoming database name */
3932  var $filter = $('#filterText');
3933  if ($filter.val()) {
3934    $filter.trigger('keyup').trigger('select');
3935  }
3936});
3937
3938/**
3939 * Formats a byte number to human-readable form
3940 *
3941 * @param bytesToFormat the bytes to format
3942 * @param subDecimals optional subdecimals the number of digits after the point
3943 * @param pointChar optional pointchar the char to use as decimal point
3944 *
3945 * @return {string}
3946 */
3947Functions.formatBytes = function (bytesToFormat, subDecimals, pointChar) {
3948  var bytes = bytesToFormat;
3949  var decimals = subDecimals;
3950  var point = pointChar;
3951  if (!decimals) {
3952    decimals = 0;
3953  }
3954  if (!point) {
3955    point = '.';
3956  }
3957  var units = ['B', 'KiB', 'MiB', 'GiB'];
3958  for (var i = 0; bytes > 1024 && i < units.length; i++) {
3959    bytes /= 1024;
3960  }
3961  var factor = Math.pow(10, decimals);
3962  bytes = Math.round(bytes * factor) / factor;
3963  bytes = bytes.toString().split('.').join(point);
3964  return bytes + ' ' + units[i];
3965};
3966AJAX.registerOnload('functions.js', function () {
3967  /**
3968   * Reveal the login form to users with JS enabled
3969   * and focus the appropriate input field
3970   */
3971  var $loginform = $('#loginform');
3972  if ($loginform.length) {
3973    $loginform.find('.js-show').show();
3974    if ($('#input_username').val()) {
3975      $('#input_password').trigger('focus');
3976    } else {
3977      $('#input_username').trigger('focus');
3978    }
3979  }
3980  var $httpsWarning = $('#js-https-mismatch');
3981  if ($httpsWarning.length) {
3982    if (window.location.protocol === 'https:' !== CommonParams.get('is_https')) {
3983      $httpsWarning.show();
3984    }
3985  }
3986});
3987
3988/**
3989 * Formats timestamp for display
3990 *
3991 * @param {string} date
3992 * @param {bool} seconds
3993 * @return {string}
3994 */
3995Functions.formatDateTime = function (date, seconds) {
3996  var result = $.datepicker.formatDate('yy-mm-dd', date);
3997  var timefmt = 'HH:mm';
3998  if (seconds) {
3999    timefmt = 'HH:mm:ss';
4000  }
4001  return result + ' ' + $.datepicker.formatTime(timefmt, {
4002    hour: date.getHours(),
4003    minute: date.getMinutes(),
4004    second: date.getSeconds()
4005  });
4006};
4007
4008/**
4009 * Check than forms have less fields than max allowed by PHP.
4010 * @return {boolean}
4011 */
4012Functions.checkNumberOfFields = function () {
4013  if (typeof maxInputVars === 'undefined') {
4014    return false;
4015  }
4016  if (false === maxInputVars) {
4017    return false;
4018  }
4019  $('form').each(function () {
4020    var nbInputs = $(this).find(':input').length;
4021    if (nbInputs > maxInputVars) {
4022      var warning = Functions.sprintf(Messages.strTooManyInputs, maxInputVars);
4023      Functions.ajaxShowMessage(warning);
4024      return false;
4025    }
4026    return true;
4027  });
4028  return true;
4029};
4030
4031/**
4032 * Ignore the displayed php errors.
4033 * Simply removes the displayed errors.
4034 *
4035 * @param clearPrevErrors whether to clear errors stored
4036 *             in $_SESSION['prev_errors'] at server
4037 *
4038 */
4039Functions.ignorePhpErrors = function (clearPrevErrors) {
4040  var clearPrevious = clearPrevErrors;
4041  if (typeof clearPrevious === 'undefined' || clearPrevious === null) {
4042    clearPrevious = false;
4043  }
4044  // send AJAX request to /error-report with send_error_report=0, exception_type=php & token.
4045  // It clears the prev_errors stored in session.
4046  if (clearPrevious) {
4047    var $pmaReportErrorsForm = $('#pma_report_errors_form');
4048    $pmaReportErrorsForm.find('input[name="send_error_report"]').val(0); // change send_error_report to '0'
4049    $pmaReportErrorsForm.trigger('submit');
4050  }
4051
4052  // remove displayed errors
4053  var $pmaErrors = $('#pma_errors');
4054  $pmaErrors.fadeOut('slow');
4055  $pmaErrors.remove();
4056};
4057
4058/**
4059 * Toggle the Datetimepicker UI if the date value entered
4060 * by the user in the 'text box' is not going to be accepted
4061 * by the Datetimepicker plugin (but is accepted by MySQL)
4062 *
4063 * @param $td
4064 * @param $inputField
4065 */
4066Functions.toggleDatepickerIfInvalid = function ($td, $inputField) {
4067  // Regex allowed by the Datetimepicker UI
4068  var dtexpDate = new RegExp(['^([0-9]{4})', '-(((01|03|05|07|08|10|12)-((0[1-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)', '-((0[1-9])|([1-2][0-9])|30)))$'].join(''));
4069  var dtexpTime = new RegExp(['^(([0-1][0-9])|(2[0-3]))', ':((0[0-9])|([1-5][0-9]))', ':((0[0-9])|([1-5][0-9]))(.[0-9]{1,6}){0,1}$'].join(''));
4070
4071  // If key-ed in Time or Date values are unsupported by the UI, close it
4072  if ($td.attr('data-type') === 'date' && !dtexpDate.test($inputField.val())) {
4073    $inputField.datepicker('hide');
4074  } else if ($td.attr('data-type') === 'time' && !dtexpTime.test($inputField.val())) {
4075    $inputField.datepicker('hide');
4076  } else {
4077    $inputField.datepicker('show');
4078  }
4079};
4080
4081/**
4082 * Function to submit the login form after validation is done.
4083 * NOTE: do NOT use a module or it will break the callback, issue #15435
4084 */
4085// eslint-disable-next-line no-unused-vars, camelcase
4086var Functions_recaptchaCallback = function () {
4087  $('#login_form').trigger('submit');
4088};
4089
4090/**
4091 * Unbind all event handlers before tearing down a page
4092 */
4093AJAX.registerTeardown('functions.js', function () {
4094  $(document).off('keydown', 'form input, form textarea, form select');
4095});
4096AJAX.registerOnload('functions.js', function () {
4097  /**
4098   * Handle 'Ctrl/Alt + Enter' form submits
4099   */
4100  $('form input, form textarea, form select').on('keydown', function (e) {
4101    if (e.ctrlKey && e.which === 13 || e.altKey && e.which === 13) {
4102      var $form = $(this).closest('form');
4103
4104      // There could be multiple submit buttons on the same form,
4105      // we assume all of them behave identical and just click one.
4106      if (!$form.find('input[type="submit"]').first() || !$form.find('input[type="submit"]').first().trigger('click')) {
4107        $form.trigger('submit');
4108      }
4109    }
4110  });
4111});
4112
4113/**
4114 * Unbind all event handlers before tearing down a page
4115 */
4116AJAX.registerTeardown('functions.js', function () {
4117  $(document).off('change', 'input[type=radio][name="pw_hash"]');
4118  $(document).off('mouseover', '.sortlink');
4119  $(document).off('mouseout', '.sortlink');
4120});
4121AJAX.registerOnload('functions.js', function () {
4122  /*
4123   * Display warning regarding SSL when sha256_password
4124   * method is selected
4125   * Used in /user-password (Change Password link on index.php)
4126   */
4127  $(document).on('change', 'select#select_authentication_plugin_cp', function () {
4128    if (this.value === 'sha256_password') {
4129      $('#ssl_reqd_warning_cp').show();
4130    } else {
4131      $('#ssl_reqd_warning_cp').hide();
4132    }
4133  });
4134  Cookies.defaults.path = CommonParams.get('rootPath');
4135
4136  // Bind event handlers for toggling sort icons
4137  $(document).on('mouseover', '.sortlink', function () {
4138    $(this).find('.soimg').toggle();
4139  });
4140  $(document).on('mouseout', '.sortlink', function () {
4141    $(this).find('.soimg').toggle();
4142  });
4143});
4144
4145/**
4146 * Returns an HTML IMG tag for a particular image from a theme,
4147 * which may be an actual file or an icon from a sprite
4148 *
4149 * @param {string} image      The name of the file to get
4150 * @param {string} alternate  Used to set 'alt' and 'title' attributes of the image
4151 * @param {object} attributes An associative array of other attributes
4152 *
4153 * @return {object} The requested image, this object has two methods:
4154 *                  .toString()        - Returns the IMG tag for the requested image
4155 *                  .attr(name)        - Returns a particular attribute of the IMG
4156 *                                       tag given it's name
4157 *                  .attr(name, value) - Sets a particular attribute of the IMG
4158 *                                       tag to the given value
4159 */
4160Functions.getImage = function (image, alternate, attributes) {
4161  var alt = alternate;
4162  var attr = attributes;
4163  // custom image object, it will eventually be returned by this functions
4164  var retval = {
4165    data: {
4166      // this is private
4167      alt: '',
4168      title: '',
4169      src: 'themes/dot.gif'
4170    },
4171    attr: function (name, value) {
4172      if (value === undefined) {
4173        if (this.data[name] === undefined) {
4174          return '';
4175        } else {
4176          return this.data[name];
4177        }
4178      } else {
4179        this.data[name] = value;
4180      }
4181    },
4182    toString: function () {
4183      var retval = '<' + 'img';
4184      for (var i in this.data) {
4185        retval += ' ' + i + '="' + this.data[i] + '"';
4186      }
4187      retval += ' /' + '>';
4188      return retval;
4189    }
4190  };
4191  // initialise missing parameters
4192  if (attr === undefined) {
4193    attr = {};
4194  }
4195  if (alt === undefined) {
4196    alt = '';
4197  }
4198  // set alt
4199  if (attr.alt !== undefined) {
4200    retval.attr('alt', Functions.escapeHtml(attr.alt));
4201  } else {
4202    retval.attr('alt', Functions.escapeHtml(alt));
4203  }
4204  // set title
4205  if (attr.title !== undefined) {
4206    retval.attr('title', Functions.escapeHtml(attr.title));
4207  } else {
4208    retval.attr('title', Functions.escapeHtml(alt));
4209  }
4210  // set css classes
4211  retval.attr('class', 'icon ic_' + image);
4212  // set all other attributes
4213  for (var i in attr) {
4214    if (i === 'src') {
4215      // do not allow to override the 'src' attribute
4216      continue;
4217    }
4218    retval.attr(i, attr[i]);
4219  }
4220  return retval;
4221};
4222
4223/**
4224 * Sets a configuration value.
4225 *
4226 * A configuration value may be set in both browser's local storage and
4227 * remotely in server's configuration table.
4228 *
4229 * NOTE: Depending on server's configuration, the configuration table may be or
4230 * not persistent.
4231 *
4232 * @param {string}     key         Configuration key.
4233 * @param {object}     value       Configuration value.
4234 */
4235Functions.configSet = function (key, value) {
4236  // Updating value in local storage.
4237  var serialized = JSON.stringify(value);
4238  localStorage.setItem(key, serialized);
4239  $.ajax({
4240    url: 'index.php?route=/config/set',
4241    type: 'POST',
4242    dataType: 'json',
4243    data: {
4244      'ajax_request': true,
4245      key: key,
4246      server: CommonParams.get('server'),
4247      value: serialized
4248    },
4249    success: function (data) {
4250      if (data.success !== true) {
4251        // Try to find a message to display
4252        if (data.error || data.message || false) {
4253          Functions.ajaxShowMessage(data.error || data.message);
4254        }
4255      }
4256    }
4257  });
4258};
4259
4260/**
4261 * Gets a configuration value. A configuration value will be searched in
4262 * browser's local storage first and if not found, a call to the server will be
4263 * made.
4264 *
4265 * If value should not be cached and the up-to-date configuration value from
4266 * right from the server is required, the third parameter should be `false`.
4267 *
4268 * @param {string}     key             Configuration key.
4269 * @param {boolean}    cached          Configuration type.
4270 * @param {Function}   successCallback The callback to call after the value is successfully received
4271 * @param {Function}   failureCallback The callback to call when the value can not be received
4272 *
4273 * @return {void}
4274 */
4275Functions.configGet = function (key, cached, successCallback, failureCallback) {
4276  var isCached = typeof cached !== 'undefined' ? cached : true;
4277  var value = localStorage.getItem(key);
4278  if (isCached && value !== undefined && value !== null) {
4279    return JSON.parse(value);
4280  }
4281
4282  // Result not found in local storage or ignored.
4283  // Hitting the server.
4284  $.ajax({
4285    url: 'index.php?route=/config/get',
4286    type: 'POST',
4287    dataType: 'json',
4288    data: {
4289      'ajax_request': true,
4290      server: CommonParams.get('server'),
4291      key: key
4292    },
4293    success: function (data) {
4294      if (data.success !== true) {
4295        // Try to find a message to display
4296        if (data.error || data.message || false) {
4297          Functions.ajaxShowMessage(data.error || data.message);
4298        }
4299
4300        // Call the callback if it is defined
4301        if (typeof failureCallback === 'function') {
4302          failureCallback();
4303        }
4304
4305        // return here, exit non success mode
4306        return;
4307      }
4308
4309      // Updating value in local storage.
4310      localStorage.setItem(key, JSON.stringify(data.value));
4311      // Call the callback if it is defined
4312      if (typeof successCallback === 'function') {
4313        // Feed it the value previously saved like on async mode
4314        successCallback(JSON.parse(localStorage.getItem(key)));
4315      }
4316    }
4317  });
4318};
4319
4320/**
4321 * Return POST data as stored by Generator::linkOrButton
4322 *
4323 * @return {string}
4324 */
4325Functions.getPostData = function () {
4326  var dataPost = this.attr('data-post');
4327  // Strip possible leading ?
4328  if (dataPost !== undefined && dataPost.substring(0, 1) === '?') {
4329    dataPost = dataPost.substr(1);
4330  }
4331  return dataPost;
4332};
4333jQuery.fn.getPostData = Functions.getPostData;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.