Translated using Weblate (Czech)
[phpmyadmin.git] / js / functions.js
blob6d4997c6c5d157213977effab907b5a7626253eb
2 /* global isStorageSupported */ // js/config.js
3 /* global ChartType, ColumnType, DataTable, JQPlotChartFactory */ // js/chart.js
4 /* global DatabaseStructure */ // js/database/structure.js
5 /* global mysqlDocBuiltin, mysqlDocKeyword */ // js/doclinks.js
6 /* global Indexes */ // js/indexes.js
7 /* global firstDayOfCalendar, maxInputVars, mysqlDocTemplate, pmaThemeImage */ // templates/javascript/variables.twig
8 /* global MicroHistory */ // js/microhistory.js
9 /* global sprintf */ // js/vendor/sprintf.js
10 /* global Int32Array */ // ES6
11 /* global zxcvbn */ // js/vendor/zxcvbn.js
13 /**
14  * general function, usually for data manipulation pages
15  *
16  */
17 var Functions = {};
19 /**
20  * @var sqlBoxLocked lock for the sqlbox textarea in the querybox
21  */
22 // eslint-disable-next-line no-unused-vars
23 var sqlBoxLocked = false;
25 /**
26  * @var {array} holds elements which content should only selected once
27  */
28 var onlyOnceElements = [];
30 /**
31  * @var {int} ajaxMessageCount Number of AJAX messages shown since page load
32  */
33 var ajaxMessageCount = 0;
35 /**
36  * @var codeMirrorEditor object containing CodeMirror editor of the query editor in SQL tab
37  */
38 var codeMirrorEditor = false;
40 /**
41  * @var codeMirrorInlineEditor object containing CodeMirror editor of the inline query editor
42  */
43 var codeMirrorInlineEditor = false;
45 /**
46  * @var {boolean} sqlAutoCompleteInProgress shows if Table/Column name autocomplete AJAX is in progress
47  */
48 var sqlAutoCompleteInProgress = false;
50 /**
51  * @var sqlAutoComplete object containing list of columns in each table
52  */
53 var sqlAutoComplete = false;
55 /**
56  * @var {string} sqlAutoCompleteDefaultTable string containing default table to autocomplete columns
57  */
58 var sqlAutoCompleteDefaultTable = '';
60 /**
61  * @var {array} centralColumnList array to hold the columns in central list per db.
62  */
63 var centralColumnList = [];
65 /**
66  * @var {array} primaryIndexes array to hold 'Primary' index columns.
67  */
68 // eslint-disable-next-line no-unused-vars
69 var primaryIndexes = [];
71 /**
72  * @var {array} uniqueIndexes array to hold 'Unique' index columns.
73  */
74 // eslint-disable-next-line no-unused-vars
75 var uniqueIndexes = [];
77 /**
78  * @var {array} indexes array to hold 'Index' columns.
79  */
80 // eslint-disable-next-line no-unused-vars
81 var indexes = [];
83 /**
84  * @var {array} fulltextIndexes array to hold 'Fulltext' columns.
85  */
86 // eslint-disable-next-line no-unused-vars
87 var fulltextIndexes = [];
89 /**
90  * @var {array} spatialIndexes array to hold 'Spatial' columns.
91  */
92 // eslint-disable-next-line no-unused-vars
93 var spatialIndexes = [];
95 /**
96  * Make sure that ajax requests will not be cached
97  * by appending a random variable to their parameters
98  */
99 $.ajaxPrefilter(function (options, originalOptions) {
100     var nocache = new Date().getTime() + '' + Math.floor(Math.random() * 1000000);
101     if (typeof options.data === 'string') {
102         options.data += '&_nocache=' + nocache + '&token=' + encodeURIComponent(CommonParams.get('token'));
103     } else if (typeof options.data === 'object') {
104         options.data = $.extend(originalOptions.data, { '_nocache' : nocache, 'token': CommonParams.get('token') });
105     }
109  * Adds a date/time picker to an element
111  * @param {object} $thisElement a jQuery object pointing to the element
112  */
113 Functions.addDatepicker = function ($thisElement, type, options) {
114     if (type !== 'date' && type !== 'time' && type !== 'datetime' && type !== 'timestamp') {
115         return;
116     }
118     var showTimepicker = true;
119     if (type === 'date') {
120         showTimepicker = false;
121     }
123     // Getting the current Date and time
124     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: pmaThemeImage + '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         dateFormat: 'yy-mm-dd', // yy means year with four digits
142         timeFormat: 'HH:mm:ss.lc',
143         constrainInput: false,
144         altFieldTimeOnly: false,
145         showAnim: '',
146         beforeShow: function (input, inst) {
147             // Remember that we came from the datepicker; this is used
148             // in table/change.js by verificationsAfterFieldChange()
149             $thisElement.data('comes_from', 'datepicker');
150             if ($(input).closest('.cEdit').length > 0) {
151                 setTimeout(function () {
152                     inst.dpDiv.css({
153                         top: 0,
154                         left: 0,
155                         position: 'relative'
156                     });
157                 }, 0);
158             }
159             setTimeout(function () {
160                 // Fix wrong timepicker z-index, doesn't work without timeout
161                 $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
162                 // Integrate tooltip text into dialog
163                 var tooltip = $thisElement.tooltip('instance');
164                 if (typeof tooltip !== 'undefined') {
165                     tooltip.disable();
166                     var $note = $('<p class="note"></div>');
167                     $note.text(tooltip.option('content'));
168                     $('div.ui-datepicker').append($note);
169                 }
170             }, 0);
171         },
172         onSelect: function () {
173             $thisElement.data('datepicker').inline = true;
174         },
175         onClose: function () {
176             // The value is no more from the date picker
177             $thisElement.data('comes_from', '');
178             if (typeof $thisElement.data('datepicker') !== 'undefined') {
179                 $thisElement.data('datepicker').inline = false;
180             }
181             var tooltip = $thisElement.tooltip('instance');
182             if (typeof tooltip !== 'undefined') {
183                 tooltip.enable();
184             }
185         }
186     };
187     if (type === 'time') {
188         $thisElement.timepicker($.extend(defaultOptions, options));
189         // Add a tip regarding entering MySQL allowed-values for TIME data-type
190         Functions.tooltip($thisElement, 'input', Messages.strMysqlAllowedValuesTipTime);
191     } else {
192         $thisElement.datetimepicker($.extend(defaultOptions, options));
193     }
197  * Add a date/time picker to each element that needs it
198  * (only when jquery-ui-timepicker-addon.js is loaded)
199  */
200 Functions.addDateTimePicker = function () {
201     if ($.timepicker !== undefined) {
202         $('input.timefield, input.datefield, input.datetimefield').each(function () {
203             var decimals = $(this).parent().attr('data-decimals');
204             var type = $(this).parent().attr('data-type');
206             var showMillisec = false;
207             var showMicrosec = false;
208             var timeFormat = 'HH:mm:ss';
209             var hourMax = 23;
210             // check for decimal places of seconds
211             if (decimals > 0 && type.indexOf('time') !== -1) {
212                 if (decimals > 3) {
213                     showMillisec = true;
214                     showMicrosec = true;
215                     timeFormat = 'HH:mm:ss.lc';
216                 } else {
217                     showMillisec = true;
218                     timeFormat = 'HH:mm:ss.l';
219                 }
220             }
221             if (type === 'time') {
222                 hourMax = 99;
223             }
224             Functions.addDatepicker($(this), type, {
225                 showMillisec: showMillisec,
226                 showMicrosec: showMicrosec,
227                 timeFormat: timeFormat,
228                 hourMax: hourMax,
229                 firstDay: firstDayOfCalendar
230             });
231             // Add a tip regarding entering MySQL allowed-values
232             // for TIME and DATE data-type
233             if ($(this).hasClass('timefield')) {
234                 Functions.tooltip($(this), 'input', Messages.strMysqlAllowedValuesTipTime);
235             } else if ($(this).hasClass('datefield')) {
236                 Functions.tooltip($(this), 'input', Messages.strMysqlAllowedValuesTipDate);
237             }
238         });
239     }
243  * Handle redirect and reload flags sent as part of AJAX requests
245  * @param data ajax response data
246  */
247 Functions.handleRedirectAndReload = function (data) {
248     if (parseInt(data.redirect_flag) === 1) {
249         // add one more GET param to display session expiry msg
250         if (window.location.href.indexOf('?') === -1) {
251             window.location.href += '?session_expired=1';
252         } else {
253             window.location.href += CommonParams.get('arg_separator') + 'session_expired=1';
254         }
255         window.location.reload();
256     } else if (parseInt(data.reload_flag) === 1) {
257         window.location.reload();
258     }
262  * Creates an SQL editor which supports auto completing etc.
264  * @param $textarea   jQuery object wrapping the textarea to be made the editor
265  * @param options     optional options for CodeMirror
266  * @param resize      optional resizing ('vertical', 'horizontal', 'both')
267  * @param lintOptions additional options for lint
268  */
269 Functions.getSqlEditor = function ($textarea, options, resize, lintOptions) {
270     var resizeType = resize;
271     if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') {
272         // merge options for CodeMirror
273         var defaults = {
274             lineNumbers: true,
275             matchBrackets: true,
276             extraKeys: { 'Ctrl-Space': 'autocomplete' },
277             hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
278             indentUnit: 4,
279             mode: 'text/x-mysql',
280             lineWrapping: true
281         };
283         if (CodeMirror.sqlLint) {
284             $.extend(defaults, {
285                 gutters: ['CodeMirror-lint-markers'],
286                 lint: {
287                     'getAnnotations': CodeMirror.sqlLint,
288                     'async': true,
289                     'lintOptions': lintOptions
290                 }
291             });
292         }
294         $.extend(true, defaults, options);
296         // create CodeMirror editor
297         var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults);
298         // allow resizing
299         if (! resizeType) {
300             resizeType = 'vertical';
301         }
302         var handles = '';
303         if (resizeType === 'vertical') {
304             handles = 's';
305         }
306         if (resizeType === 'both') {
307             handles = 'all';
308         }
309         if (resizeType === 'horizontal') {
310             handles = 'e, w';
311         }
312         $(codemirrorEditor.getWrapperElement())
313             .css('resize', resizeType)
314             .resizable({
315                 handles: handles,
316                 resize: function () {
317                     codemirrorEditor.setSize($(this).width(), $(this).height());
318                 }
319             });
320         // enable autocomplete
321         codemirrorEditor.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
323         // page locking
324         codemirrorEditor.on('change', function (e) {
325             e.data = {
326                 value: 3,
327                 content: codemirrorEditor.isClean(),
328             };
329             AJAX.lockPageHandler(e);
330         });
332         return codemirrorEditor;
333     }
334     return null;
338  * Clear text selection
339  */
340 Functions.clearSelection = function () {
341     if (document.selection && document.selection.empty) {
342         document.selection.empty();
343     } else if (window.getSelection) {
344         var sel = window.getSelection();
345         if (sel.empty) {
346             sel.empty();
347         }
348         if (sel.removeAllRanges) {
349             sel.removeAllRanges();
350         }
351     }
355  * Create a jQuery UI tooltip
357  * @param $elements     jQuery object representing the elements
358  * @param item          the item
359  *                      (see https://api.jqueryui.com/tooltip/#option-items)
360  * @param myContent     content of the tooltip
361  * @param additionalOptions to override the default options
363  */
364 Functions.tooltip = function ($elements, item, myContent, additionalOptions) {
365     if ($('#no_hint').length > 0) {
366         return;
367     }
369     var defaultOptions = {
370         content: myContent,
371         items:  item,
372         tooltipClass: 'tooltip',
373         track: true,
374         show: false,
375         hide: false
376     };
378     $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
382  * HTML escaping
383  */
384 Functions.escapeHtml = function (unsafe) {
385     if (typeof(unsafe) !== 'undefined') {
386         return unsafe
387             .toString()
388             .replace(/&/g, '&amp;')
389             .replace(/</g, '&lt;')
390             .replace(/>/g, '&gt;')
391             .replace(/"/g, '&quot;')
392             .replace(/'/g, '&#039;');
393     } else {
394         return false;
395     }
398 Functions.escapeJsString = function (unsafe) {
399     if (typeof(unsafe) !== 'undefined') {
400         return unsafe
401             .toString()
402             .replace('\x00', '')
403             .replace('\\', '\\\\')
404             .replace('\'', '\\\'')
405             .replace('&#039;', '\\&#039;')
406             .replace('"', '\\"')
407             .replace('&quot;', '\\&quot;')
408             .replace('\n', '\n')
409             .replace('\r', '\r')
410             .replace(/<\/script/gi, '</\' + \'script');
411     } else {
412         return false;
413     }
416 Functions.escapeBacktick = function (s) {
417     return s.replace('`', '``');
420 Functions.escapeSingleQuote = function (s) {
421     return s.replace('\\', '\\\\').replace('\'', '\\\'');
424 Functions.sprintf = function () {
425     return sprintf.apply(this, arguments);
429  * Hides/shows the default value input field, depending on the default type
430  * Ticks the NULL checkbox if NULL is chosen as default value.
431  */
432 Functions.hideShowDefaultValue = function ($defaultType) {
433     if ($defaultType.val() === 'USER_DEFINED') {
434         $defaultType.siblings('.default_value').show().trigger('focus');
435     } else {
436         $defaultType.siblings('.default_value').hide();
437         if ($defaultType.val() === 'NULL') {
438             var $nullCheckbox = $defaultType.closest('tr').find('.allow_null');
439             $nullCheckbox.prop('checked', true);
440         }
441     }
445  * Hides/shows the input field for column expression based on whether
446  * VIRTUAL/PERSISTENT is selected
448  * @param $virtuality virtuality dropdown
449  */
450 Functions.hideShowExpression = function ($virtuality) {
451     if ($virtuality.val() === '') {
452         $virtuality.siblings('.expression').hide();
453     } else {
454         $virtuality.siblings('.expression').show();
455     }
459  * Show notices for ENUM columns; add/hide the default value
461  */
462 Functions.verifyColumnsProperties = function () {
463     $('select.column_type').each(function () {
464         Functions.showNoticeForEnum($(this));
465     });
466     $('select.default_type').each(function () {
467         Functions.hideShowDefaultValue($(this));
468     });
469     $('select.virtuality').each(function () {
470         Functions.hideShowExpression($(this));
471     });
475  * Add a hidden field to the form to indicate that this will be an
476  * Ajax request (only if this hidden field does not exist)
478  * @param $form object   the form
479  */
480 Functions.prepareForAjaxRequest = function ($form) {
481     if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
482         $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true">');
483     }
486 Functions.checkPasswordStrength = function (value, meterObject, meterObjectLabel, username) {
487     // List of words we don't want to appear in the password
488     var customDict = [
489         'phpmyadmin',
490         'mariadb',
491         'mysql',
492         'php',
493         'my',
494         'admin',
495     ];
496     if (username !== null) {
497         customDict.push(username);
498     }
499     var zxcvbnObject = zxcvbn(value, customDict);
500     var strength = zxcvbnObject.score;
501     strength = parseInt(strength);
502     meterObject.val(strength);
503     switch (strength) {
504     case 0: meterObjectLabel.html(Messages.strExtrWeak);
505         break;
506     case 1: meterObjectLabel.html(Messages.strVeryWeak);
507         break;
508     case 2: meterObjectLabel.html(Messages.strWeak);
509         break;
510     case 3: meterObjectLabel.html(Messages.strGood);
511         break;
512     case 4: meterObjectLabel.html(Messages.strStrong);
513     }
517  * Generate a new password and copy it to the password input areas
519  * @param {object} passwordForm the form that holds the password fields
521  * @return {boolean} always true
522  */
523 Functions.suggestPassword = function (passwordForm) {
524     // restrict the password to just letters and numbers to avoid problems:
525     // "editors and viewers regard the password as multiple words and
526     // things like double click no longer work"
527     var pwchars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ@!_.*/()[]-';
528     var passwordlength = 16;    // do we want that to be dynamic?  no, keep it simple :)
529     var passwd = passwordForm.generated_pw;
530     // eslint-disable-next-line compat/compat
531     var randomWords = new Int32Array(passwordlength);
533     passwd.value = '';
535     var i;
537     // First we're going to try to use a built-in CSPRNG
538     // eslint-disable-next-line compat/compat
539     if (window.crypto && window.crypto.getRandomValues) {
540         // eslint-disable-next-line compat/compat
541         window.crypto.getRandomValues(randomWords);
542     } else if (window.msCrypto && window.msCrypto.getRandomValues) {
543         // Because of course IE calls it msCrypto instead of being standard
544         window.msCrypto.getRandomValues(randomWords);
545     } else {
546         // Fallback to Math.random
547         for (i = 0; i < passwordlength; i++) {
548             randomWords[i] = Math.floor(Math.random() * pwchars.length);
549         }
550     }
552     for (i = 0; i < passwordlength; i++) {
553         passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
554     }
556     var $jQueryPasswordForm = $(passwordForm);
558     passwordForm.elements.pma_pw.value = passwd.value;
559     passwordForm.elements.pma_pw2.value = passwd.value;
560     var meterObj = $jQueryPasswordForm.find('meter[name="pw_meter"]').first();
561     var meterObjLabel = $jQueryPasswordForm.find('span[name="pw_strength"]').first();
562     Functions.checkPasswordStrength(passwd.value, meterObj, meterObjLabel);
563     return true;
567  * Version string to integer conversion.
568  */
569 Functions.parseVersionString = function (str) {
570     if (typeof(str) !== 'string') {
571         return false;
572     }
573     var add = 0;
574     // Parse possible alpha/beta/rc/
575     var state = str.split('-');
576     if (state.length >= 2) {
577         if (state[1].substr(0, 2) === 'rc') {
578             add = - 20 - parseInt(state[1].substr(2), 10);
579         } else if (state[1].substr(0, 4) === 'beta') {
580             add =  - 40 - parseInt(state[1].substr(4), 10);
581         } else if (state[1].substr(0, 5) === 'alpha') {
582             add =  - 60 - parseInt(state[1].substr(5), 10);
583         } else if (state[1].substr(0, 3) === 'dev') {
584             /* We don't handle dev, it's git snapshot */
585             add = 0;
586         }
587     }
588     // Parse version
589     var x = str.split('.');
590     // Use 0 for non existing parts
591     var maj = parseInt(x[0], 10) || 0;
592     var min = parseInt(x[1], 10) || 0;
593     var pat = parseInt(x[2], 10) || 0;
594     var hotfix = parseInt(x[3], 10) || 0;
595     return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
599  * Indicates current available version on main page.
600  */
601 Functions.currentVersion = function (data) {
602     if (data && data.version && data.date) {
603         var current = Functions.parseVersionString($('span.version').text());
604         var latest = Functions.parseVersionString(data.version);
605         var url = 'https://www.phpmyadmin.net/files/' + Functions.escapeHtml(encodeURIComponent(data.version)) + '/';
606         var versionInformationMessage = document.createElement('span');
607         versionInformationMessage.className = 'latest';
608         var versionInformationMessageLink = document.createElement('a');
609         versionInformationMessageLink.href = url;
610         versionInformationMessageLink.className = 'disableAjax';
611         var versionInformationMessageLinkText = document.createTextNode(data.version);
612         versionInformationMessageLink.appendChild(versionInformationMessageLinkText);
613         var prefixMessage = document.createTextNode(Messages.strLatestAvailable + ' ');
614         versionInformationMessage.appendChild(prefixMessage);
615         versionInformationMessage.appendChild(versionInformationMessageLink);
616         if (latest > current) {
617             var message = Functions.sprintf(
618                 Messages.strNewerVersion,
619                 Functions.escapeHtml(data.version),
620                 Functions.escapeHtml(data.date)
621             );
622             var htmlClass = 'alert alert-primary';
623             if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
624                 /* Security update */
625                 htmlClass = 'alert alert-danger';
626             }
627             $('#newer_version_notice').remove();
628             var mainContainerDiv = document.createElement('div');
629             mainContainerDiv.id = 'newer_version_notice';
630             mainContainerDiv.className = htmlClass;
631             var mainContainerDivLink = document.createElement('a');
632             mainContainerDivLink.href = url;
633             mainContainerDivLink.className = 'disableAjax';
634             var mainContainerDivLinkText = document.createTextNode(message);
635             mainContainerDivLink.appendChild(mainContainerDivLinkText);
636             mainContainerDiv.appendChild(mainContainerDivLink);
637             $('#maincontainer').append($(mainContainerDiv));
638         }
639         if (latest === current) {
640             versionInformationMessage = document.createTextNode(' (' + Messages.strUpToDate + ')');
641         }
642         /* Remove extra whitespace */
643         var versionInfo = $('#li_pma_version').contents().get(2);
644         if (typeof versionInfo !== 'undefined') {
645             versionInfo.textContent = $.trim(versionInfo.textContent);
646         }
647         var $liPmaVersion = $('#li_pma_version');
648         $liPmaVersion.find('span.latest').remove();
649         $liPmaVersion.append($(versionInformationMessage));
650     }
654  * Loads Git revision data from ajax for index.php
655  */
656 Functions.displayGitRevision = function () {
657     $('#is_git_revision').remove();
658     $('#li_pma_version_git').remove();
659     $.get(
660         'index.php?route=/git-revision',
661         {
662             'server': CommonParams.get('server'),
663             'ajax_request': true,
664             'no_debug': true
665         },
666         function (data) {
667             if (typeof data !== 'undefined' && data.success === true) {
668                 $(data.message).insertAfter('#li_pma_version');
669             }
670         }
671     );
675  * for PhpMyAdmin\Display\ChangePassword and /user-password
676  */
677 Functions.displayPasswordGenerateButton = function () {
678     var generatePwdRow = $('<tr></tr>').addClass('vmiddle');
679     $('<td></td>').html(Messages.strGeneratePassword).appendTo(generatePwdRow);
680     var pwdCell = $('<td></td>').appendTo(generatePwdRow);
681     var pwdButton = $('<input>')
682         .attr({ type: 'button', id: 'button_generate_password', value: Messages.strGenerate })
683         .addClass('btn btn-secondary button')
684         .on('click', function () {
685             Functions.suggestPassword(this.form);
686         });
687     var pwdTextbox = $('<input>')
688         .attr({ type: 'text', name: 'generated_pw', id: 'generated_pw' });
689     pwdCell.append(pwdButton).append(pwdTextbox);
691     if (document.getElementById('button_generate_password') === null) {
692         $('#tr_element_before_generate_password').parent().append(generatePwdRow);
693     }
695     var generatePwdDiv = $('<div></div>').addClass('item');
696     $('<label></label>').attr({ for: 'button_generate_password' })
697         .html(Messages.strGeneratePassword + ':')
698         .appendTo(generatePwdDiv);
699     var optionsSpan = $('<span></span>').addClass('options')
700         .appendTo(generatePwdDiv);
701     pwdButton.clone(true).appendTo(optionsSpan);
702     pwdTextbox.clone(true).appendTo(generatePwdDiv);
704     if (document.getElementById('button_generate_password') === null) {
705         $('#div_element_before_generate_password').parent().append(generatePwdDiv);
706     }
710  * selects the content of a given object, f.e. a textarea
712  * @param {object}  element  element of which the content will be selected
713  * @param {var}     lock     variable which holds the lock for this element or true, if no lock exists
714  * @param {boolean} onlyOnce boolean if true this is only done once f.e. only on first focus
715  */
716 Functions.selectContent = function (element, lock, onlyOnce) {
717     if (onlyOnce && onlyOnceElements[element.name]) {
718         return;
719     }
721     onlyOnceElements[element.name] = true;
723     if (lock) {
724         return;
725     }
727     element.select();
731  * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
732  * This function is called while clicking links
734  * @param theLink     object the link
735  * @param theSqlQuery object the sql query to submit
737  * @return boolean  whether to run the query or not
738  */
739 Functions.confirmLink = function (theLink, theSqlQuery) {
740     // Confirmation is not required in the configuration file
741     // or browser is Opera (crappy js implementation)
742     if (Messages.strDoYouReally === '' || typeof(window.opera) !== 'undefined') {
743         return true;
744     }
746     var isConfirmed = confirm(Functions.sprintf(Messages.strDoYouReally, theSqlQuery));
747     if (isConfirmed) {
748         if (typeof(theLink.href) !== 'undefined') {
749             theLink.href += CommonParams.get('arg_separator') + 'is_js_confirmed=1';
750         } else if (typeof(theLink.form) !== 'undefined') {
751             theLink.form.action += '?is_js_confirmed=1';
752         }
753     }
755     return isConfirmed;
759  * Confirms a "DROP/DELETE/ALTER" query before
760  * submitting it if required.
761  * This function is called by the 'Functions.checkSqlQuery()' js function.
763  * @param theForm1 object   the form
764  * @param sqlQuery1 string  the sql query string
766  * @return boolean  whether to run the query or not
768  * @see     Functions.checkSqlQuery()
769  */
770 Functions.confirmQuery = function (theForm1, sqlQuery1) {
771     // Confirmation is not required in the configuration file
772     if (Messages.strDoYouReally === '') {
773         return true;
774     }
776     // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
777     //
778     // TODO: find a way (if possible) to use the parser-analyser
779     // for this kind of verification
780     // For now, I just added a ^ to check for the statement at
781     // beginning of expression
783     var doConfirmRegExp0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|PROCEDURE)\\s', 'i');
784     var doConfirmRegExp1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
785     var doConfirmRegExp2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
786     var doConfirmRegExp3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
787     var doConfirmRegExp4 = new RegExp('^(?=.*UPDATE\\b)^((?!WHERE).)*$', 'i');
789     if (doConfirmRegExp0.test(sqlQuery1) ||
790         doConfirmRegExp1.test(sqlQuery1) ||
791         doConfirmRegExp2.test(sqlQuery1) ||
792         doConfirmRegExp3.test(sqlQuery1) ||
793         doConfirmRegExp4.test(sqlQuery1)) {
794         var message;
795         if (sqlQuery1.length > 100) {
796             message = sqlQuery1.substr(0, 100) + '\n    ...';
797         } else {
798             message = sqlQuery1;
799         }
800         var isConfirmed = confirm(Functions.sprintf(Messages.strDoYouReally, message));
801         // statement is confirmed -> update the
802         // "is_js_confirmed" form field so the confirm test won't be
803         // run on the server side and allows to submit the form
804         if (isConfirmed) {
805             theForm1.elements.is_js_confirmed.value = 1;
806             return true;
807         } else {
808             // statement is rejected -> do not submit the form
809             window.focus();
810             return false;
811         } // end if (handle confirm box result)
812     } // end if (display confirm box)
814     return true;
818  * Displays an error message if the user submitted the sql query form with no
819  * sql query, else checks for "DROP/DELETE/ALTER" statements
821  * @param theForm object the form
823  * @return boolean  always false
825  * @see     Functions.confirmQuery()
826  */
827 Functions.checkSqlQuery = function (theForm) {
828     // get the textarea element containing the query
829     var sqlQuery;
830     if (codeMirrorEditor) {
831         codeMirrorEditor.save();
832         sqlQuery = codeMirrorEditor.getValue();
833     } else {
834         sqlQuery = theForm.elements.sql_query.value;
835     }
836     var spaceRegExp = new RegExp('\\s+');
837     if (typeof(theForm.elements.sql_file) !== 'undefined' &&
838             theForm.elements.sql_file.value.replace(spaceRegExp, '') !== '') {
839         return true;
840     }
841     if (typeof(theForm.elements.id_bookmark) !== 'undefined' &&
842             (theForm.elements.id_bookmark.value !== null || theForm.elements.id_bookmark.value !== '') &&
843             theForm.elements.id_bookmark.selectedIndex !== 0) {
844         return true;
845     }
846     var result = false;
847     // Checks for "DROP/DELETE/ALTER" statements
848     if (sqlQuery.replace(spaceRegExp, '') !== '') {
849         result = Functions.confirmQuery(theForm, sqlQuery);
850     } else {
851         alert(Messages.strFormEmpty);
852     }
854     if (codeMirrorEditor) {
855         codeMirrorEditor.focus();
856     } else if (codeMirrorInlineEditor) {
857         codeMirrorInlineEditor.focus();
858     }
859     return result;
863  * Check if a form's element is empty.
864  * An element containing only spaces is also considered empty
866  * @param {object} theForm      the form
867  * @param {string} theFieldName the name of the form field to put the focus on
869  * @return {boolean} whether the form field is empty or not
870  */
871 Functions.emptyCheckTheField = function (theForm, theFieldName) {
872     var theField = theForm.elements[theFieldName];
873     var spaceRegExp = new RegExp('\\s+');
874     return theField.value.replace(spaceRegExp, '') === '';
878  * Ensures a value submitted in a form is numeric and is in a range
880  * @param object   the form
881  * @param string   the name of the form field to check
882  * @param integer  the minimum authorized value
883  * @param integer  the maximum authorized value
885  * @return boolean  whether a valid number has been submitted or not
886  */
887 Functions.checkFormElementInRange = function (theForm, theFieldName, message, minimum, maximum) {
888     var theField         = theForm.elements[theFieldName];
889     var val              = parseInt(theField.value, 10);
890     var min = 0;
891     var max = Number.MAX_VALUE;
893     if (typeof(minimum) !== 'undefined') {
894         min = minimum;
895     }
896     if (typeof(maximum) !== 'undefined' && maximum !== null) {
897         max = maximum;
898     }
900     if (isNaN(val)) {
901         theField.select();
902         alert(Messages.strEnterValidNumber);
903         theField.focus();
904         return false;
905     } else if (val < min || val > max) {
906         theField.select();
907         alert(Functions.sprintf(message, val));
908         theField.focus();
909         return false;
910     } else {
911         theField.value = val;
912     }
913     return true;
916 Functions.checkTableEditForm = function (theForm, fieldsCnt) {
917     // TODO: avoid sending a message if user just wants to add a line
918     // on the form but has not completed at least one field name
920     var atLeastOneField = 0;
921     var i;
922     var elm;
923     var elm2;
924     var elm3;
925     var val;
926     var id;
928     for (i = 0; i < fieldsCnt; i++) {
929         id = '#field_' + i + '_2';
930         elm = $(id);
931         val = elm.val();
932         if (val === 'VARCHAR' || val === 'CHAR' || val === 'BIT' || val === 'VARBINARY' || val === 'BINARY') {
933             elm2 = $('#field_' + i + '_3');
934             val = parseInt(elm2.val(), 10);
935             elm3 = $('#field_' + i + '_1');
936             if (isNaN(val) && elm3.val() !== '') {
937                 elm2.select();
938                 alert(Messages.strEnterValidLength);
939                 elm2.focus();
940                 return false;
941             }
942         }
944         if (atLeastOneField === 0) {
945             id = 'field_' + i + '_1';
946             if (!Functions.emptyCheckTheField(theForm, id)) {
947                 atLeastOneField = 1;
948             }
949         }
950     }
951     if (atLeastOneField === 0) {
952         var theField = theForm.elements.field_0_1;
953         alert(Messages.strFormEmpty);
954         theField.focus();
955         return false;
956     }
958     // at least this section is under jQuery
959     var $input = $('input.textfield[name=\'table\']');
960     if ($input.val() === '') {
961         alert(Messages.strFormEmpty);
962         $input.trigger('focus');
963         return false;
964     }
966     return true;
970  * True if last click is to check a row.
971  */
972 var lastClickChecked = false;
975  * Zero-based index of last clicked row.
976  * Used to handle the shift + click event in the code above.
977  */
978 var lastClickedRow = -1;
981  * Zero-based index of last shift clicked row.
982  */
983 var lastShiftClickedRow = -1;
985 var idleSecondsCounter = 0;
986 var incInterval;
987 var updateTimeout;
988 AJAX.registerTeardown('functions.js', function () {
989     clearTimeout(updateTimeout);
990     clearInterval(incInterval);
991     $(document).off('mousemove');
994 AJAX.registerOnload('functions.js', function () {
995     document.onclick = function () {
996         idleSecondsCounter = 0;
997     };
998     $(document).on('mousemove',function () {
999         idleSecondsCounter = 0;
1000     });
1001     document.onkeypress = function () {
1002         idleSecondsCounter = 0;
1003     };
1004     function guid () {
1005         function s4 () {
1006             return Math.floor((1 + Math.random()) * 0x10000)
1007                 .toString(16)
1008                 .substring(1);
1009         }
1010         return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
1011             s4() + '-' + s4() + s4() + s4();
1012     }
1014     function SetIdleTime () {
1015         idleSecondsCounter++;
1016     }
1017     function UpdateIdleTime () {
1018         var href = 'index.php?route=/';
1019         var guid = 'default';
1020         if (isStorageSupported('sessionStorage')) {
1021             guid = window.sessionStorage.guid;
1022         }
1023         var params = {
1024             'ajax_request' : true,
1025             'server' : CommonParams.get('server'),
1026             'db' : CommonParams.get('db'),
1027             'guid': guid,
1028             'access_time': idleSecondsCounter,
1029             'check_timeout': 1
1030         };
1031         $.ajax({
1032             type: 'POST',
1033             url: href,
1034             data: params,
1035             success: function (data) {
1036                 if (data.success) {
1037                     if (CommonParams.get('LoginCookieValidity') - idleSecondsCounter < 0) {
1038                         /* There is other active window, let's reset counter */
1039                         idleSecondsCounter = 0;
1040                     }
1041                     var remaining = Math.min(
1042                         /* Remaining login validity */
1043                         CommonParams.get('LoginCookieValidity') - idleSecondsCounter,
1044                         /* Remaining time till session GC */
1045                         CommonParams.get('session_gc_maxlifetime')
1046                     );
1047                     var interval = 1000;
1048                     if (remaining > 5) {
1049                         // max value for setInterval() function
1050                         interval = Math.min((remaining - 1) * 1000, Math.pow(2, 31) - 1);
1051                     }
1052                     updateTimeout = window.setTimeout(UpdateIdleTime, interval);
1053                 } else { // timeout occurred
1054                     clearInterval(incInterval);
1055                     if (isStorageSupported('sessionStorage')) {
1056                         window.sessionStorage.clear();
1057                     }
1058                     // append the login form on the page, disable all the forms which were not disabled already, close all the open jqueryui modal boxes
1059                     if (!$('#modalOverlay').length) {
1060                         $('fieldset').not(':disabled').attr('disabled', 'disabled').addClass('disabled_for_expiration');
1061                         $('body').append(data.error);
1062                         $('.ui-dialog').each(function () {
1063                             $('#' + $(this).attr('aria-describedby')).dialog('close');
1064                         });
1065                         $('#input_username').trigger('focus');
1066                     } else {
1067                         CommonParams.set('token', data.new_token);
1068                         $('input[name=token]').val(data.new_token);
1069                     }
1070                     idleSecondsCounter = 0;
1071                     Functions.handleRedirectAndReload(data);
1072                 }
1073             }
1074         });
1075     }
1076     if (CommonParams.get('logged_in')) {
1077         incInterval = window.setInterval(SetIdleTime, 1000);
1078         var sessionTimeout = Math.min(
1079             CommonParams.get('LoginCookieValidity'),
1080             CommonParams.get('session_gc_maxlifetime')
1081         );
1082         if (isStorageSupported('sessionStorage')) {
1083             window.sessionStorage.setItem('guid', guid());
1084         }
1085         var interval = (sessionTimeout - 5) * 1000;
1086         if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function
1087             interval = Math.pow(2, 31) - 1;
1088         }
1089         updateTimeout = window.setTimeout(UpdateIdleTime, interval);
1090     }
1093  * Unbind all event handlers before tearing down a page
1094  */
1095 AJAX.registerTeardown('functions.js', function () {
1096     $(document).off('click', 'input:checkbox.checkall');
1099 AJAX.registerOnload('functions.js', function () {
1100     /**
1101      * Row marking in horizontal mode (use "on" so that it works also for
1102      * next pages reached via AJAX); a tr may have the class noclick to remove
1103      * this behavior.
1104      */
1106     $(document).on('click', 'input:checkbox.checkall', function (e) {
1107         var $this = $(this);
1108         var $tr = $this.closest('tr');
1109         var $table = $this.closest('table');
1111         if (!e.shiftKey || lastClickedRow === -1) {
1112             // usual click
1114             var $checkbox = $tr.find(':checkbox.checkall');
1115             var checked = $this.prop('checked');
1116             $checkbox.prop('checked', checked).trigger('change');
1117             if (checked) {
1118                 $tr.addClass('marked');
1119             } else {
1120                 $tr.removeClass('marked');
1121             }
1122             lastClickChecked = checked;
1124             // remember the last clicked row
1125             lastClickedRow = lastClickChecked ? $table.find('tbody tr:not(.noclick)').index($tr) : -1;
1126             lastShiftClickedRow = -1;
1127         } else {
1128             // handle the shift click
1129             Functions.clearSelection();
1130             var start;
1131             var end;
1133             // clear last shift click result
1134             if (lastShiftClickedRow >= 0) {
1135                 if (lastShiftClickedRow >= lastClickedRow) {
1136                     start = lastClickedRow;
1137                     end = lastShiftClickedRow;
1138                 } else {
1139                     start = lastShiftClickedRow;
1140                     end = lastClickedRow;
1141                 }
1142                 $tr.parent().find('tr:not(.noclick)')
1143                     .slice(start, end + 1)
1144                     .removeClass('marked')
1145                     .find(':checkbox')
1146                     .prop('checked', false)
1147                     .trigger('change');
1148             }
1150             // handle new shift click
1151             var currRow = $table.find('tbody tr:not(.noclick)').index($tr);
1152             if (currRow >= lastClickedRow) {
1153                 start = lastClickedRow;
1154                 end = currRow;
1155             } else {
1156                 start = currRow;
1157                 end = lastClickedRow;
1158             }
1159             $tr.parent().find('tr:not(.noclick)')
1160                 .slice(start, end + 1)
1161                 .addClass('marked')
1162                 .find(':checkbox')
1163                 .prop('checked', true)
1164                 .trigger('change');
1166             // remember the last shift clicked row
1167             lastShiftClickedRow = currRow;
1168         }
1169     });
1171     Functions.addDateTimePicker();
1173     /**
1174      * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
1175      */
1176     if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
1177         $('input[type=text]').attr('autocapitalize', 'off').attr('autocorrect', 'off');
1178     }
1182   * Checks/unchecks all options of a <select> element
1183   *
1184   * @param string   the form name
1185   * @param string   the element name
1186   * @param boolean  whether to check or to uncheck options
1187   *
1188   * @return boolean  always true
1189   */
1190 Functions.setSelectOptions = function (theForm, theSelect, doCheck) {
1191     $('form[name=\'' + theForm + '\'] select[name=\'' + theSelect + '\']').find('option').prop('selected', doCheck);
1192     return true;
1196  * Sets current value for query box.
1197  */
1198 Functions.setQuery = function (query) {
1199     if (codeMirrorEditor) {
1200         codeMirrorEditor.setValue(query);
1201         codeMirrorEditor.focus();
1202     } else if (document.sqlform) {
1203         document.sqlform.sql_query.value = query;
1204         document.sqlform.sql_query.focus();
1205     }
1209  * Handles 'Simulate query' button on SQL query box.
1211  * @return void
1212  */
1213 Functions.handleSimulateQueryButton = function () {
1214     var updateRegExp = new RegExp('^\\s*UPDATE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+SET\\s', 'i');
1215     var deleteRegExp = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
1216     var query = '';
1218     if (codeMirrorEditor) {
1219         query = codeMirrorEditor.getValue();
1220     } else {
1221         query = $('#sqlquery').val();
1222     }
1224     var $simulateDml = $('#simulate_dml');
1225     if (updateRegExp.test(query) || deleteRegExp.test(query)) {
1226         if (! $simulateDml.length) {
1227             $('#button_submit_query')
1228                 .before('<input type="button" id="simulate_dml"' +
1229                 'tabindex="199" class="btn btn-primary" value="' +
1230                 Messages.strSimulateDML +
1231                 '">');
1232         }
1233     } else {
1234         if ($simulateDml.length) {
1235             $simulateDml.remove();
1236         }
1237     }
1241   * Create quick sql statements.
1242   *
1243   */
1244 Functions.insertQuery = function (queryType) {
1245     if (queryType === 'clear') {
1246         Functions.setQuery('');
1247         return;
1248     } else if (queryType === 'format') {
1249         if (codeMirrorEditor) {
1250             $('#querymessage').html(Messages.strFormatting +
1251                 '&nbsp;<img class="ajaxIcon" src="' +
1252                 pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1253             var params = {
1254                 'ajax_request': true,
1255                 'sql': codeMirrorEditor.getValue(),
1256                 'server': CommonParams.get('server')
1257             };
1258             $.ajax({
1259                 type: 'POST',
1260                 url: 'index.php?route=/database/sql/format',
1261                 data: params,
1262                 success: function (data) {
1263                     if (data.success) {
1264                         codeMirrorEditor.setValue(data.sql);
1265                     }
1266                     $('#querymessage').html('');
1267                 }
1268             });
1269         }
1270         return;
1271     } else if (queryType === 'saved') {
1272         if (isStorageSupported('localStorage') && typeof window.localStorage.autoSavedSql !== 'undefined') {
1273             Functions.setQuery(window.localStorage.autoSavedSql);
1274         } else if (Cookies.get('autoSavedSql')) {
1275             Functions.setQuery(Cookies.get('autoSavedSql'));
1276         } else {
1277             Functions.ajaxShowMessage(Messages.strNoAutoSavedQuery);
1278         }
1279         return;
1280     }
1282     var query = '';
1283     var myListBox = document.sqlform.dummy;
1284     var table = document.sqlform.table.value;
1286     if (myListBox.options.length > 0) {
1287         sqlBoxLocked = true;
1288         var columnsList = '';
1289         var valDis = '';
1290         var editDis = '';
1291         var NbSelect = 0;
1292         for (var i = 0; i < myListBox.options.length; i++) {
1293             NbSelect++;
1294             if (NbSelect > 1) {
1295                 columnsList += ', ';
1296                 valDis += ',';
1297                 editDis += ',';
1298             }
1299             columnsList += myListBox.options[i].value;
1300             valDis += '[value-' + NbSelect + ']';
1301             editDis += myListBox.options[i].value + '=[value-' + NbSelect + ']';
1302         }
1303         if (queryType === 'selectall') {
1304             query = 'SELECT * FROM `' + table + '` WHERE 1';
1305         } else if (queryType === 'select') {
1306             query = 'SELECT ' + columnsList + ' FROM `' + table + '` WHERE 1';
1307         } else if (queryType === 'insert') {
1308             query = 'INSERT INTO `' + table + '`(' + columnsList + ') VALUES (' + valDis + ')';
1309         } else if (queryType === 'update') {
1310             query = 'UPDATE `' + table + '` SET ' + editDis + ' WHERE 1';
1311         } else if (queryType === 'delete') {
1312             query = 'DELETE FROM `' + table + '` WHERE 0';
1313         }
1314         Functions.setQuery(query);
1315         sqlBoxLocked = false;
1316     }
1320   * Inserts multiple fields.
1321   *
1322   */
1323 Functions.insertValueQuery = function () {
1324     var myQuery = document.sqlform.sql_query;
1325     var myListBox = document.sqlform.dummy;
1327     if (myListBox.options.length > 0) {
1328         sqlBoxLocked = true;
1329         var columnsList = '';
1330         var NbSelect = 0;
1331         for (var i = 0; i < myListBox.options.length; i++) {
1332             if (myListBox.options[i].selected) {
1333                 NbSelect++;
1334                 if (NbSelect > 1) {
1335                     columnsList += ', ';
1336                 }
1337                 columnsList += myListBox.options[i].value;
1338             }
1339         }
1341         /* CodeMirror support */
1342         if (codeMirrorEditor) {
1343             codeMirrorEditor.replaceSelection(columnsList);
1344             codeMirrorEditor.focus();
1345         // IE support
1346         } else if (document.selection) {
1347             myQuery.focus();
1348             var sel = document.selection.createRange();
1349             sel.text = columnsList;
1350         // MOZILLA/NETSCAPE support
1351         } else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart === '0') {
1352             var startPos = document.sqlform.sql_query.selectionStart;
1353             var endPos = document.sqlform.sql_query.selectionEnd;
1354             var SqlString = document.sqlform.sql_query.value;
1356             myQuery.value = SqlString.substring(0, startPos) + columnsList + SqlString.substring(endPos, SqlString.length);
1357             myQuery.focus();
1358         } else {
1359             myQuery.value += columnsList;
1360         }
1361         sqlBoxLocked = false;
1362     }
1366  * Updates the input fields for the parameters based on the query
1367  */
1368 Functions.updateQueryParameters = function () {
1369     if ($('#parameterized').is(':checked')) {
1370         var query = codeMirrorEditor ? codeMirrorEditor.getValue() : $('#sqlquery').val();
1372         var allParameters = query.match(/:[a-zA-Z0-9_]+/g);
1373         var parameters = [];
1374         // get unique parameters
1375         if (allParameters) {
1376             $.each(allParameters, function (i, parameter) {
1377                 if ($.inArray(parameter, parameters) === -1) {
1378                     parameters.push(parameter);
1379                 }
1380             });
1381         } else {
1382             $('#parametersDiv').text(Messages.strNoParam);
1383             return;
1384         }
1386         var $temp = $('<div></div>');
1387         $temp.append($('#parametersDiv').children());
1388         $('#parametersDiv').empty();
1390         $.each(parameters, function (i, parameter) {
1391             var paramName = parameter.substring(1);
1392             var $param = $temp.find('#paramSpan_' + paramName);
1393             if (! $param.length) {
1394                 $param = $('<span class="parameter" id="paramSpan_' + paramName + '"></span>');
1395                 $('<label for="param_' + paramName + '"></label>').text(parameter).appendTo($param);
1396                 $('<input type="text" name="parameters[' + parameter + ']" id="param_' + paramName + '">').appendTo($param);
1397             }
1398             $('#parametersDiv').append($param);
1399         });
1400     } else {
1401         $('#parametersDiv').empty();
1402     }
1406   * Refresh/resize the WYSIWYG scratchboard
1407   */
1408 Functions.refreshLayout = function () {
1409     var $elm = $('#pdflayout');
1410     var orientation = $('#orientation_opt').val();
1411     var paper = 'A4';
1412     var $paperOpt = $('#paper_opt');
1413     if ($paperOpt.length === 1) {
1414         paper = $paperOpt.val();
1415     }
1416     var posa = 'y';
1417     var posb = 'x';
1418     if (orientation === 'P') {
1419         posa = 'x';
1420         posb = 'y';
1421     }
1422     $elm.css('width', Functions.pdfPaperSize(paper, posa) + 'px');
1423     $elm.css('height', Functions.pdfPaperSize(paper, posb) + 'px');
1427  * Initializes positions of elements.
1428  */
1429 Functions.tableDragInit = function () {
1430     $('.pdflayout_table').each(function () {
1431         var $this = $(this);
1432         var number = $this.data('number');
1433         var x = $('#c_table_' + number + '_x').val();
1434         var y = $('#c_table_' + number + '_y').val();
1435         $this.css('left', x + 'px');
1436         $this.css('top', y + 'px');
1437         /* Make elements draggable */
1438         $this.draggable({
1439             containment: 'parent',
1440             drag: function (evt, ui) {
1441                 var number = $this.data('number');
1442                 $('#c_table_' + number + '_x').val(parseInt(ui.position.left, 10));
1443                 $('#c_table_' + number + '_y').val(parseInt(ui.position.top, 10));
1444             }
1445         });
1446     });
1450  * Resets drag and drop positions.
1451  */
1452 Functions.resetDrag = function () {
1453     $('.pdflayout_table').each(function () {
1454         var $this = $(this);
1455         var x = $this.data('x');
1456         var y = $this.data('y');
1457         $this.css('left', x + 'px');
1458         $this.css('top', y + 'px');
1459     });
1463  * User schema handlers.
1464  */
1465 $(function () {
1466     /* Move in scratchboard on manual change */
1467     $(document).on('change', '.position-change', function () {
1468         var $this = $(this);
1469         var $elm = $('#table_' + $this.data('number'));
1470         $elm.css($this.data('axis'), $this.val() + 'px');
1471     });
1472     /* Refresh on paper size/orientation change */
1473     $(document).on('change', '.paper-change', function () {
1474         var $elm = $('#pdflayout');
1475         if ($elm.css('visibility') === 'visible') {
1476             Functions.refreshLayout();
1477             Functions.tableDragInit();
1478         }
1479     });
1480     /* Show/hide the WYSIWYG scratchboard */
1481     $(document).on('click', '#toggle-dragdrop', function () {
1482         var $elm = $('#pdflayout');
1483         if ($elm.css('visibility') === 'hidden') {
1484             Functions.refreshLayout();
1485             Functions.tableDragInit();
1486             $elm.css('visibility', 'visible');
1487             $elm.css('display', 'block');
1488             $('#showwysiwyg').val('1');
1489         } else {
1490             $elm.css('visibility', 'hidden');
1491             $elm.css('display', 'none');
1492             $('#showwysiwyg').val('0');
1493         }
1494     });
1495     /* Reset scratchboard */
1496     $(document).on('click', '#reset-dragdrop', function () {
1497         Functions.resetDrag();
1498     });
1502  * Returns paper sizes for a given format
1503  */
1504 Functions.pdfPaperSize = function (format, axis) {
1505     switch (format.toUpperCase()) {
1506     case '4A0':
1507         if (axis === 'x') {
1508             return 4767.87;
1509         }
1510         return 6740.79;
1511     case '2A0':
1512         if (axis === 'x') {
1513             return 3370.39;
1514         }
1515         return 4767.87;
1516     case 'A0':
1517         if (axis === 'x') {
1518             return 2383.94;
1519         }
1520         return 3370.39;
1521     case 'A1':
1522         if (axis === 'x') {
1523             return 1683.78;
1524         }
1525         return 2383.94;
1526     case 'A2':
1527         if (axis === 'x') {
1528             return 1190.55;
1529         }
1530         return 1683.78;
1531     case 'A3':
1532         if (axis === 'x') {
1533             return 841.89;
1534         }
1535         return 1190.55;
1536     case 'A4':
1537         if (axis === 'x') {
1538             return 595.28;
1539         }
1540         return 841.89;
1541     case 'A5':
1542         if (axis === 'x') {
1543             return 419.53;
1544         }
1545         return 595.28;
1546     case 'A6':
1547         if (axis === 'x') {
1548             return 297.64;
1549         }
1550         return 419.53;
1551     case 'A7':
1552         if (axis === 'x') {
1553             return 209.76;
1554         }
1555         return 297.64;
1556     case 'A8':
1557         if (axis === 'x') {
1558             return 147.40;
1559         }
1560         return 209.76;
1561     case 'A9':
1562         if (axis === 'x') {
1563             return 104.88;
1564         }
1565         return 147.40;
1566     case 'A10':
1567         if (axis === 'x') {
1568             return 73.70;
1569         }
1570         return 104.88;
1571     case 'B0':
1572         if (axis === 'x') {
1573             return 2834.65;
1574         }
1575         return 4008.19;
1576     case 'B1':
1577         if (axis === 'x') {
1578             return 2004.09;
1579         }
1580         return 2834.65;
1581     case 'B2':
1582         if (axis === 'x') {
1583             return 1417.32;
1584         }
1585         return 2004.09;
1586     case 'B3':
1587         if (axis === 'x') {
1588             return 1000.63;
1589         }
1590         return 1417.32;
1591     case 'B4':
1592         if (axis === 'x') {
1593             return 708.66;
1594         }
1595         return 1000.63;
1596     case 'B5':
1597         if (axis === 'x') {
1598             return 498.90;
1599         }
1600         return 708.66;
1601     case 'B6':
1602         if (axis === 'x') {
1603             return 354.33;
1604         }
1605         return 498.90;
1606     case 'B7':
1607         if (axis === 'x') {
1608             return 249.45;
1609         }
1610         return 354.33;
1611     case 'B8':
1612         if (axis === 'x') {
1613             return 175.75;
1614         }
1615         return 249.45;
1616     case 'B9':
1617         if (axis === 'x') {
1618             return 124.72;
1619         }
1620         return 175.75;
1621     case 'B10':
1622         if (axis === 'x') {
1623             return 87.87;
1624         }
1625         return 124.72;
1626     case 'C0':
1627         if (axis === 'x') {
1628             return 2599.37;
1629         }
1630         return 3676.54;
1631     case 'C1':
1632         if (axis === 'x') {
1633             return 1836.85;
1634         }
1635         return 2599.37;
1636     case 'C2':
1637         if (axis === 'x') {
1638             return 1298.27;
1639         }
1640         return 1836.85;
1641     case 'C3':
1642         if (axis === 'x') {
1643             return 918.43;
1644         }
1645         return 1298.27;
1646     case 'C4':
1647         if (axis === 'x') {
1648             return 649.13;
1649         }
1650         return 918.43;
1651     case 'C5':
1652         if (axis === 'x') {
1653             return 459.21;
1654         }
1655         return 649.13;
1656     case 'C6':
1657         if (axis === 'x') {
1658             return 323.15;
1659         }
1660         return 459.21;
1661     case 'C7':
1662         if (axis === 'x') {
1663             return 229.61;
1664         }
1665         return 323.15;
1666     case 'C8':
1667         if (axis === 'x') {
1668             return 161.57;
1669         }
1670         return 229.61;
1671     case 'C9':
1672         if (axis === 'x') {
1673             return 113.39;
1674         }
1675         return 161.57;
1676     case 'C10':
1677         if (axis === 'x') {
1678             return 79.37;
1679         }
1680         return 113.39;
1681     case 'RA0':
1682         if (axis === 'x') {
1683             return 2437.80;
1684         }
1685         return 3458.27;
1686     case 'RA1':
1687         if (axis === 'x') {
1688             return 1729.13;
1689         }
1690         return 2437.80;
1691     case 'RA2':
1692         if (axis === 'x') {
1693             return 1218.90;
1694         }
1695         return 1729.13;
1696     case 'RA3':
1697         if (axis === 'x') {
1698             return 864.57;
1699         }
1700         return 1218.90;
1701     case 'RA4':
1702         if (axis === 'x') {
1703             return 609.45;
1704         }
1705         return 864.57;
1706     case 'SRA0':
1707         if (axis === 'x') {
1708             return 2551.18;
1709         }
1710         return 3628.35;
1711     case 'SRA1':
1712         if (axis === 'x') {
1713             return 1814.17;
1714         }
1715         return 2551.18;
1716     case 'SRA2':
1717         if (axis === 'x') {
1718             return 1275.59;
1719         }
1720         return 1814.17;
1721     case 'SRA3':
1722         if (axis === 'x') {
1723             return 907.09;
1724         }
1725         return 1275.59;
1726     case 'SRA4':
1727         if (axis === 'x') {
1728             return 637.80;
1729         }
1730         return 907.09;
1731     case 'LETTER':
1732         if (axis === 'x') {
1733             return 612.00;
1734         }
1735         return 792.00;
1736     case 'LEGAL':
1737         if (axis === 'x') {
1738             return 612.00;
1739         }
1740         return 1008.00;
1741     case 'EXECUTIVE':
1742         if (axis === 'x') {
1743             return 521.86;
1744         }
1745         return 756.00;
1746     case 'FOLIO':
1747         if (axis === 'x') {
1748             return 612.00;
1749         }
1750         return 936.00;
1751     }
1752     return 0;
1756  * Get checkbox for foreign key checks
1758  * @return string
1759  */
1760 Functions.getForeignKeyCheckboxLoader = function () {
1761     var html = '';
1762     html    += '<div>';
1763     html    += '<div class="load-default-fk-check-value">';
1764     html    += Functions.getImage('ajax_clock_small');
1765     html    += '</div>';
1766     html    += '</div>';
1767     return html;
1770 Functions.loadForeignKeyCheckbox = function () {
1771     // Load default foreign key check value
1772     var params = {
1773         'ajax_request': true,
1774         'server': CommonParams.get('server'),
1775     };
1776     $.get('index.php?route=/sql/get-default-fk-check-value', params, function (data) {
1777         var html = '<input type="hidden" name="fk_checks" value="0">' +
1778             '<input type="checkbox" name="fk_checks" id="fk_checks"' +
1779             (data.default_fk_check_value ? ' checked="checked"' : '') + '>' +
1780             '<label for="fk_checks">' + Messages.strForeignKeyCheck + '</label>';
1781         $('.load-default-fk-check-value').replaceWith(html);
1782     });
1785 Functions.getJsConfirmCommonParam = function (elem, parameters) {
1786     var $elem = $(elem);
1787     var params = parameters;
1788     var sep = CommonParams.get('arg_separator');
1789     if (params) {
1790         // Strip possible leading ?
1791         if (params.substring(0,1) === '?') {
1792             params = params.substr(1);
1793         }
1794         params += sep;
1795     } else {
1796         params = '';
1797     }
1798     params += 'is_js_confirmed=1' + sep + 'ajax_request=true' + sep + 'fk_checks=' + ($elem.find('#fk_checks').is(':checked') ? 1 : 0);
1799     return params;
1803  * Unbind all event handlers before tearing down a page
1804  */
1805 AJAX.registerTeardown('functions.js', function () {
1806     $(document).off('click', 'a.inline_edit_sql');
1807     $(document).off('click', 'input#sql_query_edit_save');
1808     $(document).off('click', 'input#sql_query_edit_discard');
1809     $('input.sqlbutton').off('click');
1810     if (codeMirrorEditor) {
1811         codeMirrorEditor.off('blur');
1812     } else {
1813         $(document).off('blur', '#sqlquery');
1814     }
1815     $(document).off('change', '#parameterized');
1816     $(document).off('click', 'input.sqlbutton');
1817     $('#sqlquery').off('keydown');
1818     $('#sql_query_edit').off('keydown');
1820     if (codeMirrorInlineEditor) {
1821         // Copy the sql query to the text area to preserve it.
1822         $('#sql_query_edit').text(codeMirrorInlineEditor.getValue());
1823         $(codeMirrorInlineEditor.getWrapperElement()).off('keydown');
1824         codeMirrorInlineEditor.toTextArea();
1825         codeMirrorInlineEditor = false;
1826     }
1827     if (codeMirrorEditor) {
1828         $(codeMirrorEditor.getWrapperElement()).off('keydown');
1829     }
1833  * Jquery Coding for inline editing SQL_QUERY
1834  */
1835 AJAX.registerOnload('functions.js', function () {
1836     // If we are coming back to the page by clicking forward button
1837     // of the browser, bind the code mirror to inline query editor.
1838     Functions.bindCodeMirrorToInlineEditor();
1839     $(document).on('click', 'a.inline_edit_sql', function () {
1840         if ($('#sql_query_edit').length) {
1841             // An inline query editor is already open,
1842             // we don't want another copy of it
1843             return false;
1844         }
1846         var $form = $(this).prev('form');
1847         var sqlQuery  = $form.find('input[name=\'sql_query\']').val().trim();
1848         var $innerSql = $(this).parent().prev().find('code.sql');
1850         var newContent = '<textarea name="sql_query_edit" id="sql_query_edit">' + Functions.escapeHtml(sqlQuery) + '</textarea>\n';
1851         newContent    += Functions.getForeignKeyCheckboxLoader();
1852         newContent    += '<input type="submit" id="sql_query_edit_save" class="btn btn-secondary button btnSave" value="' + Messages.strGo + '">\n';
1853         newContent    += '<input type="button" id="sql_query_edit_discard" class="btn btn-secondary button btnDiscard" value="' + Messages.strCancel + '">\n';
1854         var $editorArea = $('div#inline_editor');
1855         if ($editorArea.length === 0) {
1856             $editorArea = $('<div id="inline_editor_outer"></div>');
1857             $editorArea.insertBefore($innerSql);
1858         }
1859         $editorArea.html(newContent);
1860         Functions.loadForeignKeyCheckbox();
1861         $innerSql.hide();
1863         Functions.bindCodeMirrorToInlineEditor();
1864         return false;
1865     });
1867     $(document).on('click', 'input#sql_query_edit_save', function () {
1868         // hide already existing success message
1869         var sqlQuery;
1870         if (codeMirrorInlineEditor) {
1871             codeMirrorInlineEditor.save();
1872             sqlQuery = codeMirrorInlineEditor.getValue();
1873         } else {
1874             sqlQuery = $(this).parent().find('#sql_query_edit').val();
1875         }
1876         var fkCheck = $(this).parent().find('#fk_checks').is(':checked');
1878         var $form = $('a.inline_edit_sql').prev('form');
1879         var $fakeForm = $('<form>', { action: 'index.php?route=/import', method: 'post' })
1880             .append($form.find('input[name=server], input[name=db], input[name=table], input[name=token]').clone())
1881             .append($('<input>', { type: 'hidden', name: 'show_query', value: 1 }))
1882             .append($('<input>', { type: 'hidden', name: 'is_js_confirmed', value: 0 }))
1883             .append($('<input>', { type: 'hidden', name: 'sql_query', value: sqlQuery }))
1884             .append($('<input>', { type: 'hidden', name: 'fk_checks', value: fkCheck ? 1 : 0 }));
1885         if (! Functions.checkSqlQuery($fakeForm[0])) {
1886             return false;
1887         }
1888         $('.alert-success').hide();
1889         $fakeForm.appendTo($('body')).trigger('submit');
1890     });
1892     $(document).on('click', 'input#sql_query_edit_discard', function () {
1893         var $divEditor = $('div#inline_editor_outer');
1894         $divEditor.siblings('code.sql').show();
1895         $divEditor.remove();
1896     });
1898     $(document).on('click', 'input.sqlbutton', function (evt) {
1899         Functions.insertQuery(evt.target.id);
1900         Functions.handleSimulateQueryButton();
1901         return false;
1902     });
1904     $(document).on('change', '#parameterized', Functions.updateQueryParameters);
1906     var $inputUsername = $('#input_username');
1907     if ($inputUsername) {
1908         if ($inputUsername.val() === '') {
1909             $inputUsername.trigger('focus');
1910         } else {
1911             $('#input_password').trigger('focus');
1912         }
1913     }
1917  * "inputRead" event handler for CodeMirror SQL query editors for autocompletion
1918  */
1919 Functions.codeMirrorAutoCompleteOnInputRead = function (instance) {
1920     if (!sqlAutoCompleteInProgress
1921         && (!instance.options.hintOptions.tables || !sqlAutoComplete)) {
1922         if (!sqlAutoComplete) {
1923             // Reset after teardown
1924             instance.options.hintOptions.tables = false;
1925             instance.options.hintOptions.defaultTable = '';
1927             sqlAutoCompleteInProgress = true;
1929             var params = {
1930                 'ajax_request': true,
1931                 'server': CommonParams.get('server'),
1932                 'db': CommonParams.get('db'),
1933                 'no_debug': true
1934             };
1936             var columnHintRender = function (elem, self, data) {
1937                 $('<div class="autocomplete-column-name">')
1938                     .text(data.columnName)
1939                     .appendTo(elem);
1940                 $('<div class="autocomplete-column-hint">')
1941                     .text(data.columnHint)
1942                     .appendTo(elem);
1943             };
1945             $.ajax({
1946                 type: 'POST',
1947                 url: 'index.php?route=/database/sql/autocomplete',
1948                 data: params,
1949                 success: function (data) {
1950                     if (data.success) {
1951                         var tables = JSON.parse(data.tables);
1952                         sqlAutoCompleteDefaultTable = CommonParams.get('table');
1953                         sqlAutoComplete = [];
1954                         for (var table in tables) {
1955                             if (tables.hasOwnProperty(table)) {
1956                                 var columns = tables[table];
1957                                 table = {
1958                                     text: table,
1959                                     columns: []
1960                                 };
1961                                 for (var column in columns) {
1962                                     if (columns.hasOwnProperty(column)) {
1963                                         var displayText = columns[column].Type;
1964                                         if (columns[column].Key === 'PRI') {
1965                                             displayText += ' | Primary';
1966                                         } else if (columns[column].Key === 'UNI') {
1967                                             displayText += ' | Unique';
1968                                         }
1969                                         table.columns.push({
1970                                             text: column,
1971                                             displayText: column + ' | ' +  displayText,
1972                                             columnName: column,
1973                                             columnHint: displayText,
1974                                             render: columnHintRender
1975                                         });
1976                                     }
1977                                 }
1978                             }
1979                             sqlAutoComplete.push(table);
1980                         }
1981                         instance.options.hintOptions.tables = sqlAutoComplete;
1982                         instance.options.hintOptions.defaultTable = sqlAutoCompleteDefaultTable;
1983                     }
1984                 },
1985                 complete: function () {
1986                     sqlAutoCompleteInProgress = false;
1987                 }
1988             });
1989         } else {
1990             instance.options.hintOptions.tables = sqlAutoComplete;
1991             instance.options.hintOptions.defaultTable = sqlAutoCompleteDefaultTable;
1992         }
1993     }
1994     if (instance.state.completionActive) {
1995         return;
1996     }
1997     var cur = instance.getCursor();
1998     var token = instance.getTokenAt(cur);
1999     var string = '';
2000     if (token.string.match(/^[.`\w@]\w*$/)) {
2001         string = token.string;
2002     }
2003     if (string.length > 0) {
2004         CodeMirror.commands.autocomplete(instance);
2005     }
2009  * Remove autocomplete information before tearing down a page
2010  */
2011 AJAX.registerTeardown('functions.js', function () {
2012     sqlAutoComplete = false;
2013     sqlAutoCompleteDefaultTable = '';
2017  * Binds the CodeMirror to the text area used to inline edit a query.
2018  */
2019 Functions.bindCodeMirrorToInlineEditor = function () {
2020     var $inlineEditor = $('#sql_query_edit');
2021     if ($inlineEditor.length > 0) {
2022         if (typeof CodeMirror !== 'undefined') {
2023             var height = $inlineEditor.css('height');
2024             codeMirrorInlineEditor = Functions.getSqlEditor($inlineEditor);
2025             codeMirrorInlineEditor.getWrapperElement().style.height = height;
2026             codeMirrorInlineEditor.refresh();
2027             codeMirrorInlineEditor.focus();
2028             $(codeMirrorInlineEditor.getWrapperElement())
2029                 .on('keydown', Functions.catchKeypressesFromSqlInlineEdit);
2030         } else {
2031             $inlineEditor
2032                 .trigger('focus')
2033                 .on('keydown', Functions.catchKeypressesFromSqlInlineEdit);
2034         }
2035     }
2038 Functions.catchKeypressesFromSqlInlineEdit = function (event) {
2039     // ctrl-enter is 10 in chrome and ie, but 13 in ff
2040     if ((event.ctrlKey || event.metaKey) && (event.keyCode === 13 || event.keyCode === 10)) {
2041         $('#sql_query_edit_save').trigger('click');
2042     }
2046  * Adds doc link to single highlighted SQL element
2047  */
2048 Functions.documentationAdd = function ($elm, params) {
2049     if (typeof mysqlDocTemplate === 'undefined') {
2050         return;
2051     }
2053     var url = Functions.sprintf(
2054         decodeURIComponent(mysqlDocTemplate),
2055         params[0]
2056     );
2057     if (params.length > 1) {
2058         url += '#' + params[1];
2059     }
2060     var content = $elm.text();
2061     $elm.text('');
2062     $elm.append('<a target="mysql_doc" class="cm-sql-doc" href="' + url + '">' + content + '</a>');
2066  * Generates doc links for keywords inside highlighted SQL
2067  */
2068 Functions.documentationKeyword = function (idx, elm) {
2069     var $elm = $(elm);
2070     /* Skip already processed ones */
2071     if ($elm.find('a').length > 0) {
2072         return;
2073     }
2074     var keyword = $elm.text().toUpperCase();
2075     var $next = $elm.next('.cm-keyword');
2076     if ($next) {
2077         var nextKeyword = $next.text().toUpperCase();
2078         var full = keyword + ' ' + nextKeyword;
2080         var $next2 = $next.next('.cm-keyword');
2081         if ($next2) {
2082             var next2Keyword = $next2.text().toUpperCase();
2083             var full2 = full + ' ' + next2Keyword;
2084             if (full2 in mysqlDocKeyword) {
2085                 Functions.documentationAdd($elm, mysqlDocKeyword[full2]);
2086                 Functions.documentationAdd($next, mysqlDocKeyword[full2]);
2087                 Functions.documentationAdd($next2, mysqlDocKeyword[full2]);
2088                 return;
2089             }
2090         }
2091         if (full in mysqlDocKeyword) {
2092             Functions.documentationAdd($elm, mysqlDocKeyword[full]);
2093             Functions.documentationAdd($next, mysqlDocKeyword[full]);
2094             return;
2095         }
2096     }
2097     if (keyword in mysqlDocKeyword) {
2098         Functions.documentationAdd($elm, mysqlDocKeyword[keyword]);
2099     }
2103  * Generates doc links for builtins inside highlighted SQL
2104  */
2105 Functions.documentationBuiltin = function (idx, elm) {
2106     var $elm = $(elm);
2107     var builtin = $elm.text().toUpperCase();
2108     if (builtin in mysqlDocBuiltin) {
2109         Functions.documentationAdd($elm, mysqlDocBuiltin[builtin]);
2110     }
2114  * Higlights SQL using CodeMirror.
2115  */
2116 Functions.highlightSql = function ($base) {
2117     var $elm = $base.find('code.sql');
2118     $elm.each(function () {
2119         var $sql = $(this);
2120         var $pre = $sql.find('pre');
2121         /* We only care about visible elements to avoid double processing */
2122         if ($pre.is(':visible')) {
2123             var $highlight = $('<div class="sql-highlight cm-s-default"></div>');
2124             $sql.append($highlight);
2125             if (typeof CodeMirror !== 'undefined') {
2126                 CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]);
2127                 $pre.hide();
2128                 $highlight.find('.cm-keyword').each(Functions.documentationKeyword);
2129                 $highlight.find('.cm-builtin').each(Functions.documentationBuiltin);
2130             }
2131         }
2132     });
2136  * Updates an element containing code.
2138  * @param jQuery Object $base base element which contains the raw and the
2139  *                            highlighted code.
2141  * @param string htmlValue    code in HTML format, displayed if code cannot be
2142  *                            highlighted
2144  * @param string rawValue     raw code, used as a parameter for highlighter
2146  * @return bool               whether content was updated or not
2147  */
2148 Functions.updateCode = function ($base, htmlValue, rawValue) {
2149     var $code = $base.find('code');
2150     if ($code.length === 0) {
2151         return false;
2152     }
2154     // Determines the type of the content and appropriate CodeMirror mode.
2155     var type = '';
2156     var mode = '';
2157     if  ($code.hasClass('json')) {
2158         type = 'json';
2159         mode = 'application/json';
2160     } else if ($code.hasClass('sql')) {
2161         type = 'sql';
2162         mode = 'text/x-mysql';
2163     } else if ($code.hasClass('xml')) {
2164         type = 'xml';
2165         mode = 'application/xml';
2166     } else {
2167         return false;
2168     }
2170     // Element used to display unhighlighted code.
2171     var $notHighlighted = $('<pre>' + htmlValue + '</pre>');
2173     // Tries to highlight code using CodeMirror.
2174     if (typeof CodeMirror !== 'undefined') {
2175         var $highlighted = $('<div class="' + type + '-highlight cm-s-default"></div>');
2176         CodeMirror.runMode(rawValue, mode, $highlighted[0]);
2177         $notHighlighted.hide();
2178         $code.html('').append($notHighlighted, $highlighted[0]);
2179     } else {
2180         $code.html('').append($notHighlighted);
2181     }
2183     return true;
2187  * Show a message on the top of the page for an Ajax request
2189  * Sample usage:
2191  * 1) var $msg = Functions.ajaxShowMessage();
2192  * This will show a message that reads "Loading...". Such a message will not
2193  * disappear automatically and cannot be dismissed by the user. To remove this
2194  * message either the Functions.ajaxRemoveMessage($msg) function must be called or
2195  * another message must be show with Functions.ajaxShowMessage() function.
2197  * 2) var $msg = Functions.ajaxShowMessage(Messages.strProcessingRequest);
2198  * This is a special case. The behaviour is same as above,
2199  * just with a different message
2201  * 3) var $msg = Functions.ajaxShowMessage('The operation was successful');
2202  * This will show a message that will disappear automatically and it can also
2203  * be dismissed by the user.
2205  * 4) var $msg = Functions.ajaxShowMessage('Some error', false);
2206  * This will show a message that will not disappear automatically, but it
2207  * can be dismissed by the user after they have finished reading it.
2209  * @param string  message     string containing the message to be shown.
2210  *                              optional, defaults to 'Loading...'
2211  * @param mixed   timeout     number of milliseconds for the message to be visible
2212  *                              optional, defaults to 5000. If set to 'false', the
2213  *                              notification will never disappear
2214  * @param string  type        string to dictate the type of message shown.
2215  *                              optional, defaults to normal notification.
2216  *                              If set to 'error', the notification will show message
2217  *                              with red background.
2218  *                              If set to 'success', the notification will show with
2219  *                              a green background.
2220  * @return jQuery object       jQuery Element that holds the message div
2221  *                              this object can be passed to Functions.ajaxRemoveMessage()
2222  *                              to remove the notification
2223  */
2224 Functions.ajaxShowMessage = function (message, timeout, type) {
2225     var msg = message;
2226     var newTimeOut = timeout;
2227     /**
2228      * @var self_closing Whether the notification will automatically disappear
2229      */
2230     var selfClosing = true;
2231     /**
2232      * @var dismissable Whether the user will be able to remove
2233      *                  the notification by clicking on it
2234      */
2235     var dismissable = true;
2236     // Handle the case when a empty data.message is passed.
2237     // We don't want the empty message
2238     if (msg === '') {
2239         return true;
2240     } else if (! msg) {
2241         // If the message is undefined, show the default
2242         msg = Messages.strLoading;
2243         dismissable = false;
2244         selfClosing = false;
2245     } else if (msg === Messages.strProcessingRequest) {
2246         // This is another case where the message should not disappear
2247         dismissable = false;
2248         selfClosing = false;
2249     }
2250     // Figure out whether (or after how long) to remove the notification
2251     if (newTimeOut === undefined) {
2252         newTimeOut = 5000;
2253     } else if (newTimeOut === false) {
2254         selfClosing = false;
2255     }
2256     // Determine type of message, add styling as required
2257     if (type === 'error') {
2258         msg = '<div class="alert alert-danger" role="alert">' + msg + '</div>';
2259     } else if (type === 'success') {
2260         msg = '<div class="alert alert-success" role="alert">' + msg + '</div>';
2261     }
2262     // Create a parent element for the AJAX messages, if necessary
2263     if ($('#loading_parent').length === 0) {
2264         $('<div id="loading_parent"></div>')
2265             .prependTo('#page_content');
2266     }
2267     // Update message count to create distinct message elements every time
2268     ajaxMessageCount++;
2269     // Remove all old messages, if any
2270     $('span.ajax_notification[id^=ajax_message_num]').remove();
2271     /**
2272      * @var $retval    a jQuery object containing the reference
2273      *                 to the created AJAX message
2274      */
2275     var $retval = $(
2276         '<span class="ajax_notification" id="ajax_message_num_' +
2277             ajaxMessageCount +
2278             '"></span>'
2279     )
2280         .hide()
2281         .appendTo('#loading_parent')
2282         .html(msg)
2283         .show();
2284     // If the notification is self-closing we should create a callback to remove it
2285     if (selfClosing) {
2286         $retval
2287             .delay(newTimeOut)
2288             .fadeOut('medium', function () {
2289                 if ($(this).is(':data(tooltip)')) {
2290                     $(this).tooltip('destroy');
2291                 }
2292                 // Remove the notification
2293                 $(this).remove();
2294             });
2295     }
2296     // If the notification is dismissable we need to add the relevant class to it
2297     // and add a tooltip so that the users know that it can be removed
2298     if (dismissable) {
2299         $retval.addClass('dismissable').css('cursor', 'pointer');
2300         /**
2301          * Add a tooltip to the notification to let the user know that they
2302          * can dismiss the ajax notification by clicking on it.
2303          */
2304         Functions.tooltip(
2305             $retval,
2306             'span',
2307             Messages.strDismiss
2308         );
2309     }
2310     // Hide spinner if this is not a loading message
2311     if (msg !== Messages.strLoading) {
2312         $retval.css('background-image', 'none');
2313     }
2314     Functions.highlightSql($retval);
2316     return $retval;
2320  * Removes the message shown for an Ajax operation when it's completed
2322  * @param jQuery object   jQuery Element that holds the notification
2324  * @return nothing
2325  */
2326 Functions.ajaxRemoveMessage = function ($thisMessageBox) {
2327     if ($thisMessageBox !== undefined && $thisMessageBox instanceof jQuery) {
2328         $thisMessageBox
2329             .stop(true, true)
2330             .fadeOut('medium');
2331         if ($thisMessageBox.is(':data(tooltip)')) {
2332             $thisMessageBox.tooltip('destroy');
2333         } else {
2334             $thisMessageBox.remove();
2335         }
2336     }
2340  * Requests SQL for previewing before executing.
2342  * @param jQuery Object $form Form containing query data
2344  * @return void
2345  */
2346 Functions.previewSql = function ($form) {
2347     var formUrl = $form.attr('action');
2348     var sep = CommonParams.get('arg_separator');
2349     var formData = $form.serialize() +
2350         sep + 'do_save_data=1' +
2351         sep + 'preview_sql=1' +
2352         sep + 'ajax_request=1';
2353     var $messageBox = Functions.ajaxShowMessage();
2354     $.ajax({
2355         type: 'POST',
2356         url: formUrl,
2357         data: formData,
2358         success: function (response) {
2359             Functions.ajaxRemoveMessage($messageBox);
2360             if (response.success) {
2361                 var $dialogContent = $('<div></div>')
2362                     .append(response.sql_data);
2363                 var buttonOptions = {};
2364                 buttonOptions[Messages.strClose] = function () {
2365                     $(this).dialog('close');
2366                 };
2367                 $dialogContent.dialog({
2368                     minWidth: 550,
2369                     maxHeight: 400,
2370                     modal: true,
2371                     buttons: buttonOptions,
2372                     title: Messages.strPreviewSQL,
2373                     close: function () {
2374                         $(this).remove();
2375                     },
2376                     open: function () {
2377                         // Pretty SQL printing.
2378                         Functions.highlightSql($(this));
2379                     }
2380                 });
2381             } else {
2382                 Functions.ajaxShowMessage(response.message);
2383             }
2384         },
2385         error: function () {
2386             Functions.ajaxShowMessage(Messages.strErrorProcessingRequest);
2387         }
2388     });
2392  * Callback called when submit/"OK" is clicked on sql preview/confirm modal
2394  * @callback onSubmitCallback
2395  * @param {string} url The url
2396  */
2400  * @param {string}           sqlData  Sql query to preview
2401  * @param {string}           url       Url to be sent to callback
2402  * @param {onSubmitCallback} callback  On submit callback function
2404  * @return void
2405  */
2406 Functions.confirmPreviewSql = function (sqlData, url, callback) {
2407     var $dialogContent = $('<div class="preview_sql"><code class="sql"><pre>'
2408         + sqlData
2409         + '</pre></code></div>'
2410     );
2411     var buttonOptions = [
2412         {
2413             text: Messages.strOK,
2414             class: 'submitOK',
2415             click: function () {
2416                 callback(url);
2417             }
2418         },
2419         {
2420             text: Messages.strCancel,
2421             class: 'submitCancel',
2422             click: function () {
2423                 $(this).dialog('close');
2424             }
2425         }
2426     ];
2427     $dialogContent.dialog({
2428         minWidth: 550,
2429         maxHeight: 400,
2430         modal: true,
2431         buttons: buttonOptions,
2432         title: Messages.strPreviewSQL,
2433         close: function () {
2434             $(this).remove();
2435         },
2436         open: function () {
2437             // Pretty SQL printing.
2438             Functions.highlightSql($(this));
2439         }
2440     });
2444  * check for reserved keyword column name
2446  * @param jQuery Object $form Form
2448  * @returns true|false
2449  */
2450 Functions.checkReservedWordColumns = function ($form) {
2451     var isConfirmed = true;
2452     $.ajax({
2453         type: 'POST',
2454         url: 'index.php?route=/table/structure/reserved-word-check',
2455         data: $form.serialize(),
2456         success: function (data) {
2457             if (typeof data.success !== 'undefined' && data.success === true) {
2458                 isConfirmed = confirm(data.message);
2459             }
2460         },
2461         async:false
2462     });
2463     return isConfirmed;
2466 // This event only need to be fired once after the initial page load
2467 $(function () {
2468     /**
2469      * Allows the user to dismiss a notification
2470      * created with Functions.ajaxShowMessage()
2471      */
2472     var holdStarter = null;
2473     $(document).on('mousedown', 'span.ajax_notification.dismissable', function () {
2474         holdStarter = setTimeout(function () {
2475             holdStarter = null;
2476         }, 250);
2477     });
2479     $(document).on('mouseup', 'span.ajax_notification.dismissable', function () {
2480         if (holdStarter && event.which === 1) {
2481             clearTimeout(holdStarter);
2482             Functions.ajaxRemoveMessage($(this));
2483         }
2484     });
2485     /**
2486      * The below two functions hide the "Dismiss notification" tooltip when a user
2487      * is hovering a link or button that is inside an ajax message
2488      */
2489     $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
2490         if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
2491             $(this).parents('span.ajax_notification').tooltip('disable');
2492         }
2493     });
2494     $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
2495         if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
2496             $(this).parents('span.ajax_notification').tooltip('enable');
2497         }
2498     });
2500     /**
2501      * Copy text to clipboard
2502      *
2503      * @param text to copy to clipboard
2504      *
2505      * @returns bool true|false
2506      */
2507     function copyToClipboard (text) {
2508         var $temp = $('<input>');
2509         $temp.css({ 'position': 'fixed', 'width': '2em', 'border': 0, 'top': 0, 'left': 0, 'padding': 0, 'background': 'transparent' });
2510         $('body').append($temp);
2511         $temp.val(text).trigger('select');
2512         try {
2513             var res = document.execCommand('copy');
2514             $temp.remove();
2515             return res;
2516         } catch (e) {
2517             $temp.remove();
2518             return false;
2519         }
2520     }
2522     $(document).on('click', 'a.copyQueryBtn', function (event) {
2523         event.preventDefault();
2524         var res = copyToClipboard($(this).attr('data-text'));
2525         if (res) {
2526             $(this).after('<span id=\'copyStatus\'> (' + Messages.strCopyQueryButtonSuccess + ')</span>');
2527         } else {
2528             $(this).after('<span id=\'copyStatus\'> (' + Messages.strCopyQueryButtonFailure + ')</span>');
2529         }
2530         setTimeout(function () {
2531             $('#copyStatus').remove();
2532         }, 2000);
2533     });
2537  * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
2538  */
2539 Functions.showNoticeForEnum = function (selectElement) {
2540     var enumNoticeId = selectElement.attr('id').split('_')[1];
2541     enumNoticeId += '_' + (parseInt(selectElement.attr('id').split('_')[2], 10) + 1);
2542     var selectedType = selectElement.val();
2543     if (selectedType === 'ENUM' || selectedType === 'SET') {
2544         $('p#enum_notice_' + enumNoticeId).show();
2545     } else {
2546         $('p#enum_notice_' + enumNoticeId).hide();
2547     }
2551  * Creates a Profiling Chart. Used in sql.js
2552  * and in server/status/monitor.js
2553  */
2554 Functions.createProfilingChart = function (target, data) {
2555     // create the chart
2556     var factory = new JQPlotChartFactory();
2557     var chart = factory.createChart(ChartType.PIE, target);
2559     // create the data table and add columns
2560     var dataTable = new DataTable();
2561     dataTable.addColumn(ColumnType.STRING, '');
2562     dataTable.addColumn(ColumnType.NUMBER, '');
2563     dataTable.setData(data);
2565     var windowWidth = $(window).width();
2566     var location = 's';
2567     if (windowWidth > 768) {
2568         location = 'se';
2569     }
2571     // draw the chart and return the chart object
2572     chart.draw(dataTable, {
2573         seriesDefaults: {
2574             rendererOptions: {
2575                 showDataLabels:  true
2576             }
2577         },
2578         highlighter: {
2579             tooltipLocation: 'se',
2580             sizeAdjust: 0,
2581             tooltipAxes: 'pieref',
2582             formatString: '%s, %.9Ps'
2583         },
2584         legend: {
2585             show: true,
2586             location: location,
2587             rendererOptions: {
2588                 numberColumns: 2
2589             }
2590         },
2591         // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
2592         seriesColors: [
2593             '#fce94f',
2594             '#fcaf3e',
2595             '#e9b96e',
2596             '#8ae234',
2597             '#729fcf',
2598             '#ad7fa8',
2599             '#ef2929',
2600             '#888a85',
2601             '#c4a000',
2602             '#ce5c00',
2603             '#8f5902',
2604             '#4e9a06',
2605             '#204a87',
2606             '#5c3566',
2607             '#a40000',
2608             '#babdb6',
2609             '#2e3436'
2610         ]
2611     });
2612     return chart;
2616  * Formats a profiling duration nicely (in us and ms time).
2617  * Used in server/status/monitor.js
2619  * @param  integer    Number to be formatted, should be in the range of microsecond to second
2620  * @param  integer    Accuracy, how many numbers right to the comma should be
2621  * @return string     The formatted number
2622  */
2623 Functions.prettyProfilingNum = function (number, accuracy) {
2624     var num = number;
2625     var acc = accuracy;
2626     if (!acc) {
2627         acc = 2;
2628     }
2629     acc = Math.pow(10, acc);
2630     if (num * 1000 < 0.1) {
2631         num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
2632     } else if (num < 0.1) {
2633         num = Math.round(acc * (num * 1000)) / acc + 'm';
2634     } else {
2635         num = Math.round(acc * num) / acc;
2636     }
2638     return num + 's';
2642  * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
2644  * @param string      Query to be formatted
2645  * @return string      The formatted query
2646  */
2647 Functions.sqlPrettyPrint = function (string) {
2648     if (typeof CodeMirror === 'undefined') {
2649         return string;
2650     }
2652     var mode = CodeMirror.getMode({}, 'text/x-mysql');
2653     var stream = new CodeMirror.StringStream(string);
2654     var state = mode.startState();
2655     var token;
2656     var tokens = [];
2657     var output = '';
2658     var tabs = function (cnt) {
2659         var ret = '';
2660         for (var i = 0; i < 4 * cnt; i++) {
2661             ret += ' ';
2662         }
2663         return ret;
2664     };
2666     // "root-level" statements
2667     var statements = {
2668         'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'],
2669         'update': ['update', 'set', 'where'],
2670         'insert into': ['insert into', 'values']
2671     };
2672     // don't put spaces before these tokens
2673     var spaceExceptionsBefore = { ';': true, ',': true, '.': true, '(': true };
2674     // don't put spaces after these tokens
2675     var spaceExceptionsAfter = { '.': true };
2677     // Populate tokens array
2678     while (! stream.eol()) {
2679         stream.start = stream.pos;
2680         token = mode.token(stream, state);
2681         if (token !== null) {
2682             tokens.push([token, stream.current().toLowerCase()]);
2683         }
2684     }
2686     var currentStatement = tokens[0][1];
2688     if (! statements[currentStatement]) {
2689         return string;
2690     }
2691     // Holds all currently opened code blocks (statement, function or generic)
2692     var blockStack = [];
2693     // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
2694     var newBlock;
2695     var endBlock;
2696     // How much to indent in the current line
2697     var indentLevel = 0;
2698     // Holds the "root-level" statements
2699     var statementPart;
2700     var lastStatementPart = statements[currentStatement][0];
2702     blockStack.unshift('statement');
2704     // Iterate through every token and format accordingly
2705     for (var i = 0; i < tokens.length; i++) {
2706         // New block => push to stack
2707         if (tokens[i][1] === '(') {
2708             if (i < tokens.length - 1 && tokens[i + 1][0] === 'statement-verb') {
2709                 blockStack.unshift(newBlock = 'statement');
2710             } else if (i > 0 && tokens[i - 1][0] === 'builtin') {
2711                 blockStack.unshift(newBlock = 'function');
2712             } else {
2713                 blockStack.unshift(newBlock = 'generic');
2714             }
2715         } else {
2716             newBlock = null;
2717         }
2719         // Block end => pop from stack
2720         if (tokens[i][1] === ')') {
2721             endBlock = blockStack[0];
2722             blockStack.shift();
2723         } else {
2724             endBlock = null;
2725         }
2727         // A subquery is starting
2728         if (i > 0 && newBlock === 'statement') {
2729             indentLevel++;
2730             output += '\n' + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + '\n' + tabs(indentLevel + 1);
2731             currentStatement = tokens[i + 1][1];
2732             i++;
2733             continue;
2734         }
2736         // A subquery is ending
2737         if (endBlock === 'statement' && indentLevel > 0) {
2738             output += '\n' + tabs(indentLevel);
2739             indentLevel--;
2740         }
2742         // One less indentation for statement parts (from, where, order by, etc.) and a newline
2743         statementPart = statements[currentStatement].indexOf(tokens[i][1]);
2744         if (statementPart !== -1) {
2745             if (i > 0) {
2746                 output += '\n';
2747             }
2748             output += tabs(indentLevel) + tokens[i][1].toUpperCase();
2749             output += '\n' + tabs(indentLevel + 1);
2750             lastStatementPart = tokens[i][1];
2751         // Normal indentation and spaces for everything else
2752         } else {
2753             if (! spaceExceptionsBefore[tokens[i][1]] &&
2754                ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) &&
2755                output.charAt(output.length - 1) !== ' ') {
2756                 output += ' ';
2757             }
2758             if (tokens[i][0] === 'keyword') {
2759                 output += tokens[i][1].toUpperCase();
2760             } else {
2761                 output += tokens[i][1];
2762             }
2763         }
2765         // split columns in select and 'update set' clauses, but only inside statements blocks
2766         if ((lastStatementPart === 'select' || lastStatementPart === 'where'  || lastStatementPart === 'set') &&
2767             tokens[i][1] === ',' && blockStack[0] === 'statement') {
2768             output += '\n' + tabs(indentLevel + 1);
2769         }
2771         // split conditions in where clauses, but only inside statements blocks
2772         if (lastStatementPart === 'where' &&
2773             (tokens[i][1] === 'and' || tokens[i][1] === 'or' || tokens[i][1] === 'xor')) {
2774             if (blockStack[0] === 'statement') {
2775                 output += '\n' + tabs(indentLevel + 1);
2776             }
2777             // Todo: Also split and or blocks in newlines & indentation++
2778             // if (blockStack[0] === 'generic')
2779             //   output += ...
2780         }
2781     }
2782     return output;
2786  * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
2787  *  return a jQuery object yet and hence cannot be chained
2789  * @param string      question
2790  * @param string      url           URL to be passed to the callbackFn to make
2791  *                                  an Ajax call to
2792  * @param function    callbackFn    callback to execute after user clicks on OK
2793  * @param function    openCallback  optional callback to run when dialog is shown
2794  */
2795 Functions.confirm = function (question, url, callbackFn, openCallback) {
2796     var confirmState = CommonParams.get('confirm');
2797     if (! confirmState) {
2798         // user does not want to confirm
2799         if (typeof callbackFn === 'function') {
2800             callbackFn.call(this, url);
2801             return true;
2802         }
2803     }
2804     if (Messages.strDoYouReally === '') {
2805         return true;
2806     }
2808     /**
2809      * @var    button_options  Object that stores the options passed to jQueryUI
2810      *                          dialog
2811      */
2812     var buttonOptions = [
2813         {
2814             text: Messages.strOK,
2815             'class': 'submitOK',
2816             click: function () {
2817                 $(this).dialog('close');
2818                 if (typeof callbackFn === 'function') {
2819                     callbackFn.call(this, url);
2820                 }
2821             }
2822         },
2823         {
2824             text: Messages.strCancel,
2825             'class': 'submitCancel',
2826             click: function () {
2827                 $(this).dialog('close');
2828             }
2829         }
2830     ];
2832     $('<div></div>', { 'id': 'confirm_dialog', 'title': Messages.strConfirm })
2833         .prepend(question)
2834         .dialog({
2835             buttons: buttonOptions,
2836             close: function () {
2837                 $(this).remove();
2838             },
2839             open: openCallback,
2840             modal: true
2841         });
2843 jQuery.fn.confirm = Functions.confirm;
2846  * jQuery function to sort a table's body after a new row has been appended to it.
2848  * @param string      text_selector   string to select the sortKey's text
2850  * @return jQuery Object for chaining purposes
2851  */
2852 Functions.sortTable = function (textSelector) {
2853     return this.each(function () {
2854         /**
2855          * @var table_body  Object referring to the table's <tbody> element
2856          */
2857         var tableBody = $(this);
2858         /**
2859          * @var rows    Object referring to the collection of rows in {@link tableBody}
2860          */
2861         var rows = $(this).find('tr').get();
2863         // get the text of the field that we will sort by
2864         $.each(rows, function (index, row) {
2865             row.sortKey = $.trim($(row).find(textSelector).text().toLowerCase());
2866         });
2868         // get the sorted order
2869         rows.sort(function (a, b) {
2870             if (a.sortKey < b.sortKey) {
2871                 return -1;
2872             }
2873             if (a.sortKey > b.sortKey) {
2874                 return 1;
2875             }
2876             return 0;
2877         });
2879         // pull out each row from the table and then append it according to it's order
2880         $.each(rows, function (index, row) {
2881             $(tableBody).append(row);
2882             row.sortKey = null;
2883         });
2884     });
2886 jQuery.fn.sortTable = Functions.sortTable;
2889  * Unbind all event handlers before tearing down a page
2890  */
2891 AJAX.registerTeardown('functions.js', function () {
2892     $(document).off('submit', '#create_table_form_minimal.ajax');
2893     $(document).off('submit', 'form.create_table_form.ajax');
2894     $(document).off('click', 'form.create_table_form.ajax input[name=submit_num_fields]');
2895     $(document).off('keyup', 'form.create_table_form.ajax input');
2896     $(document).off('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]');
2900  * jQuery coding for 'Create Table'. Used on /database/operations,
2901  * /database/structure and /database/tracking (i.e., wherever
2902  * PhpMyAdmin\Display\CreateTable is used)
2904  * Attach Ajax Event handlers for Create Table
2905  */
2906 AJAX.registerOnload('functions.js', function () {
2907     /**
2908      * Attach event handler for submission of create table form (save)
2909      */
2910     $(document).on('submit', 'form.create_table_form.ajax', function (event) {
2911         event.preventDefault();
2913         /**
2914          * @var    the_form    object referring to the create table form
2915          */
2916         var $form = $(this);
2918         /*
2919          * First validate the form; if there is a problem, avoid submitting it
2920          *
2921          * Functions.checkTableEditForm() needs a pure element and not a jQuery object,
2922          * this is why we pass $form[0] as a parameter (the jQuery object
2923          * is actually an array of DOM elements)
2924          */
2926         if (Functions.checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
2927             Functions.prepareForAjaxRequest($form);
2928             if (Functions.checkReservedWordColumns($form)) {
2929                 Functions.ajaxShowMessage(Messages.strProcessingRequest);
2930                 // User wants to submit the form
2931                 $.post($form.attr('action'), $form.serialize() + CommonParams.get('arg_separator') + 'do_save_data=1', function (data) {
2932                     if (typeof data !== 'undefined' && data.success === true) {
2933                         $('#properties_message')
2934                             .removeClass('alert-danger')
2935                             .html('');
2936                         Functions.ajaxShowMessage(data.message);
2937                         // Only if the create table dialog (distinct panel) exists
2938                         var $createTableDialog = $('#create_table_dialog');
2939                         if ($createTableDialog.length > 0) {
2940                             $createTableDialog.dialog('close').remove();
2941                         }
2942                         $('#tableslistcontainer').before(data.formatted_sql);
2944                         /**
2945                          * @var tables_table    Object referring to the <tbody> element that holds the list of tables
2946                          */
2947                         var tablesTable = $('#tablesForm').find('tbody').not('#tbl_summary_row');
2948                         // this is the first table created in this db
2949                         if (tablesTable.length === 0) {
2950                             CommonActions.refreshMain(
2951                                 CommonParams.get('opendb_url')
2952                             );
2953                         } else {
2954                             /**
2955                              * @var curr_last_row   Object referring to the last <tr> element in {@link tablesTable}
2956                              */
2957                             var currLastRow = $(tablesTable).find('tr').last();
2958                             /**
2959                              * @var curr_last_row_index_string   String containing the index of {@link currLastRow}
2960                              */
2961                             var currLastRowIndexString = $(currLastRow).find('input:checkbox').attr('id').match(/\d+/)[0];
2962                             /**
2963                              * @var curr_last_row_index Index of {@link currLastRow}
2964                              */
2965                             var currLastRowIndex = parseFloat(currLastRowIndexString);
2966                             /**
2967                              * @var new_last_row_index   Index of the new row to be appended to {@link tablesTable}
2968                              */
2969                             var newLastRowIndex = currLastRowIndex + 1;
2970                             /**
2971                              * @var new_last_row_id String containing the id of the row to be appended to {@link tablesTable}
2972                              */
2973                             var newLastRowId = 'checkbox_tbl_' + newLastRowIndex;
2975                             data.newTableString = data.newTableString.replace(/checkbox_tbl_/, newLastRowId);
2976                             // append to table
2977                             $(data.newTableString)
2978                                 .appendTo(tablesTable);
2980                             // Sort the table
2981                             $(tablesTable).sortTable('th');
2983                             // Adjust summary row
2984                             DatabaseStructure.adjustTotals();
2985                         }
2987                         // Refresh navigation as a new table has been added
2988                         Navigation.reload();
2989                         // Redirect to table structure page on creation of new table
2990                         var argsep = CommonParams.get('arg_separator');
2991                         var params12 = 'ajax_request=true' + argsep + 'ajax_page_request=true';
2992                         if (! (history && history.pushState)) {
2993                             params12 += MicroHistory.menus.getRequestParam();
2994                         }
2995                         var tableStructureUrl = 'index.php?route=/table/structure' + argsep + 'server=' + data.params.server +
2996                             argsep + 'db=' + data.params.db + argsep + 'token=' + data.params.token +
2997                             argsep + 'goto=' + encodeURIComponent('index.php?route=/database/structure') + argsep + 'table=' + data.params.table + '';
2998                         $.get(tableStructureUrl, params12, AJAX.responseHandler);
2999                     } else {
3000                         Functions.ajaxShowMessage(
3001                             '<div class="alert alert-danger" role="alert">' + data.error + '</div>',
3002                             false
3003                         );
3004                     }
3005                 }); // end $.post()
3006             }
3007         }
3008     }); // end create table form (save)
3010     /**
3011      * Submits the intermediate changes in the table creation form
3012      * to refresh the UI accordingly
3013      */
3014     function submitChangesInCreateTableForm (actionParam) {
3015         /**
3016          * @var    the_form    object referring to the create table form
3017          */
3018         var $form = $('form.create_table_form.ajax');
3020         var $msgbox = Functions.ajaxShowMessage(Messages.strProcessingRequest);
3021         Functions.prepareForAjaxRequest($form);
3023         // User wants to add more fields to the table
3024         $.post($form.attr('action'), $form.serialize() + '&' + actionParam, function (data) {
3025             if (typeof data !== 'undefined' && data.success) {
3026                 var $pageContent = $('#page_content');
3027                 $pageContent.html(data.message);
3028                 Functions.highlightSql($pageContent);
3029                 Functions.verifyColumnsProperties();
3030                 Functions.hideShowConnection($('.create_table_form select[name=tbl_storage_engine]'));
3031                 Functions.ajaxRemoveMessage($msgbox);
3032             } else {
3033                 Functions.ajaxShowMessage(data.error);
3034             }
3035         }); // end $.post()
3036     }
3038     /**
3039      * Attach event handler for create table form (add fields)
3040      */
3041     $(document).on('click', 'form.create_table_form.ajax input[name=submit_num_fields]', function (event) {
3042         event.preventDefault();
3043         submitChangesInCreateTableForm('submit_num_fields=1');
3044     }); // end create table form (add fields)
3046     $(document).on('keydown', 'form.create_table_form.ajax input[name=added_fields]', function (event) {
3047         if (event.keyCode === 13) {
3048             event.preventDefault();
3049             event.stopImmediatePropagation();
3050             $(this)
3051                 .closest('form')
3052                 .find('input[name=submit_num_fields]')
3053                 .trigger('click');
3054         }
3055     });
3057     /**
3058      * Attach event handler to manage changes in number of partitions and subpartitions
3059      */
3060     $(document).on('change', 'input[name=partition_count],input[name=subpartition_count],select[name=partition_by]', function () {
3061         var $this = $(this);
3062         var $form = $this.parents('form');
3063         if ($form.is('.create_table_form.ajax')) {
3064             submitChangesInCreateTableForm('submit_partition_change=1');
3065         } else {
3066             $form.trigger('submit');
3067         }
3068     });
3070     $(document).on('change', 'input[value=AUTO_INCREMENT]', function () {
3071         if (this.checked) {
3072             var col = /\d/.exec($(this).attr('name'));
3073             col = col[0];
3074             var $selectFieldKey = $('select[name="field_key[' + col + ']"]');
3075             if ($selectFieldKey.val() === 'none_' + col) {
3076                 $selectFieldKey.val('primary_' + col).trigger('change', [false]);
3077             }
3078         }
3079     });
3080     $('body')
3081         .off('click', 'input.preview_sql')
3082         .on('click', 'input.preview_sql', function () {
3083             var $form = $(this).closest('form');
3084             Functions.previewSql($form);
3085         });
3090  * Validates the password field in a form
3092  * @see    Messages.strPasswordEmpty
3093  * @see    Messages.strPasswordNotSame
3094  * @param {object} $theForm The form to be validated
3095  * @return bool
3096  */
3097 Functions.checkPassword = function ($theForm) {
3098     // Did the user select 'no password'?
3099     if ($theForm.find('#nopass_1').is(':checked')) {
3100         return true;
3101     } else {
3102         var $pred = $theForm.find('#select_pred_password');
3103         if ($pred.length && ($pred.val() === 'none' || $pred.val() === 'keep')) {
3104             return true;
3105         }
3106     }
3108     var $password = $theForm.find('input[name=pma_pw]');
3109     var $passwordRepeat = $theForm.find('input[name=pma_pw2]');
3110     var alertMessage = false;
3112     if ($password.val() === '') {
3113         alertMessage = Messages.strPasswordEmpty;
3114     } else if ($password.val() !== $passwordRepeat.val()) {
3115         alertMessage = Messages.strPasswordNotSame;
3116     }
3118     if (alertMessage) {
3119         alert(alertMessage);
3120         $password.val('');
3121         $passwordRepeat.val('');
3122         $password.trigger('focus');
3123         return false;
3124     }
3125     return true;
3129  * Attach Ajax event handlers for 'Change Password' on index.php
3130  */
3131 AJAX.registerOnload('functions.js', function () {
3132     /* Handler for hostname type */
3133     $(document).on('change', '#select_pred_hostname', function () {
3134         var hostname = $('#pma_hostname');
3135         if (this.value === 'any') {
3136             hostname.val('%');
3137         } else if (this.value === 'localhost') {
3138             hostname.val('localhost');
3139         } else if (this.value === 'thishost' && $(this).data('thishost')) {
3140             hostname.val($(this).data('thishost'));
3141         } else if (this.value === 'hosttable') {
3142             hostname.val('').prop('required', false);
3143         } else if (this.value === 'userdefined') {
3144             hostname.trigger('focus').select().prop('required', true);
3145         }
3146     });
3148     /* Handler for editing hostname */
3149     $(document).on('change', '#pma_hostname', function () {
3150         $('#select_pred_hostname').val('userdefined');
3151         $('#pma_hostname').prop('required', true);
3152     });
3154     /* Handler for username type */
3155     $(document).on('change', '#select_pred_username', function () {
3156         if (this.value === 'any') {
3157             $('#pma_username').val('').prop('required', false);
3158             $('#user_exists_warning').css('display', 'none');
3159         } else if (this.value === 'userdefined') {
3160             $('#pma_username').trigger('focus').trigger('select').prop('required', true);
3161         }
3162     });
3164     /* Handler for editing username */
3165     $(document).on('change', '#pma_username', function () {
3166         $('#select_pred_username').val('userdefined');
3167         $('#pma_username').prop('required', true);
3168     });
3170     /* Handler for password type */
3171     $(document).on('change', '#select_pred_password', function () {
3172         if (this.value === 'none') {
3173             $('#text_pma_pw2').prop('required', false).val('');
3174             $('#text_pma_pw').prop('required', false).val('');
3175         } else if (this.value === 'userdefined') {
3176             $('#text_pma_pw2').prop('required', true);
3177             $('#text_pma_pw').prop('required', true).trigger('focus').trigger('select');
3178         } else {
3179             $('#text_pma_pw2').prop('required', false);
3180             $('#text_pma_pw').prop('required', false);
3181         }
3182     });
3184     /* Handler for editing password */
3185     $(document).on('change', '#text_pma_pw,#text_pma_pw2', function () {
3186         $('#select_pred_password').val('userdefined');
3187         $('#text_pma_pw2').prop('required', true);
3188         $('#text_pma_pw').prop('required', true);
3189     });
3191     /**
3192      * Unbind all event handlers before tearing down a page
3193      */
3194     $(document).off('click', '#change_password_anchor.ajax');
3196     /**
3197      * Attach Ajax event handler on the change password anchor
3198      */
3200     $(document).on('click', '#change_password_anchor.ajax', function (event) {
3201         event.preventDefault();
3203         var $msgbox = Functions.ajaxShowMessage();
3205         /**
3206          * @var button_options  Object containing options to be passed to jQueryUI's dialog
3207          */
3208         var buttonOptions = {};
3209         buttonOptions[Messages.strGo] = function () {
3210             event.preventDefault();
3212             /**
3213              * @var $the_form    Object referring to the change password form
3214              */
3215             var $theForm = $('#change_password_form');
3217             if (! Functions.checkPassword($theForm)) {
3218                 return false;
3219             }
3221             /**
3222              * @var this_value  String containing the value of the submit button.
3223              * Need to append this for the change password form on Server Privileges
3224              * page to work
3225              */
3226             var thisValue = $(this).val();
3228             var $msgbox = Functions.ajaxShowMessage(Messages.strProcessingRequest);
3229             $theForm.append('<input type="hidden" name="ajax_request" value="true">');
3231             $.post($theForm.attr('action'), $theForm.serialize() + CommonParams.get('arg_separator') + 'change_pw=' + thisValue, function (data) {
3232                 if (typeof data === 'undefined' || data.success !== true) {
3233                     Functions.ajaxShowMessage(data.error, false);
3234                     return;
3235                 }
3237                 var $pageContent = $('#page_content');
3238                 $pageContent.prepend(data.message);
3239                 Functions.highlightSql($pageContent);
3240                 $('#change_password_dialog').hide().remove();
3241                 $('#edit_user_dialog').dialog('close').remove();
3242                 Functions.ajaxRemoveMessage($msgbox);
3243             }); // end $.post()
3244         };
3246         buttonOptions[Messages.strCancel] = function () {
3247             $(this).dialog('close');
3248         };
3249         $.get($(this).attr('href'), { 'ajax_request': true }, function (data) {
3250             if (typeof data === 'undefined' || !data.success) {
3251                 Functions.ajaxShowMessage(data.error, false);
3252                 return;
3253             }
3255             if (data.scripts) {
3256                 AJAX.scriptHandler.load(data.scripts);
3257             }
3259             $('<div id="change_password_dialog"></div>')
3260                 .dialog({
3261                     title: Messages.strChangePassword,
3262                     width: 600,
3263                     close: function () {
3264                         $(this).remove();
3265                     },
3266                     buttons: buttonOptions,
3267                     modal: true
3268                 })
3269                 .append(data.message);
3270             // for this dialog, we remove the fieldset wrapping due to double headings
3271             $('fieldset#fieldset_change_password')
3272                 .find('legend').remove().end()
3273                 .find('table.noclick').unwrap().addClass('some-margin')
3274                 .find('input#text_pma_pw').trigger('focus');
3275             $('#fieldset_change_password_footer').hide();
3276             Functions.ajaxRemoveMessage($msgbox);
3277             Functions.displayPasswordGenerateButton();
3278             $('#change_password_form').on('submit', function (e) {
3279                 e.preventDefault();
3280                 $(this)
3281                     .closest('.ui-dialog')
3282                     .find('.ui-dialog-buttonpane .ui-button')
3283                     .first()
3284                     .trigger('click');
3285             });
3286         }); // end $.get()
3287     }); // end handler for change password anchor
3288 }); // end $() for Change Password
3291  * Unbind all event handlers before tearing down a page
3292  */
3293 AJAX.registerTeardown('functions.js', function () {
3294     $(document).off('change', 'select.column_type');
3295     $(document).off('change', 'select.default_type');
3296     $(document).off('change', 'select.virtuality');
3297     $(document).off('change', 'input.allow_null');
3298     $(document).off('change', '.create_table_form select[name=tbl_storage_engine]');
3301  * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
3302  * the page loads and when the selected data type changes
3303  */
3304 AJAX.registerOnload('functions.js', function () {
3305     // is called here for normal page loads and also when opening
3306     // the Create table dialog
3307     Functions.verifyColumnsProperties();
3308     //
3309     // needs on() to work also in the Create Table dialog
3310     $(document).on('change', 'select.column_type', function () {
3311         Functions.showNoticeForEnum($(this));
3312     });
3313     $(document).on('change', 'select.default_type', function () {
3314         Functions.hideShowDefaultValue($(this));
3315     });
3316     $(document).on('change', 'select.virtuality', function () {
3317         Functions.hideShowExpression($(this));
3318     });
3319     $(document).on('change', 'input.allow_null', function () {
3320         Functions.validateDefaultValue($(this));
3321     });
3322     $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () {
3323         Functions.hideShowConnection($(this));
3324     });
3328  * If the chosen storage engine is FEDERATED show connection field. Hide otherwise
3330  * @param $engineSelector storage engine selector
3331  */
3332 Functions.hideShowConnection = function ($engineSelector) {
3333     var $connection = $('.create_table_form input[name=connection]');
3334     var index = $connection.parent('td').index() + 1;
3335     var $labelTh = $connection.parents('tr').prev('tr').children(document.querySelectorAll('th:nth-child(' + index + ')'));
3336     if ($engineSelector.val() !== 'FEDERATED') {
3337         $connection
3338             .prop('disabled', true)
3339             .parent('td').hide();
3340         $labelTh.hide();
3341     } else {
3342         $connection
3343             .prop('disabled', false)
3344             .parent('td').show();
3345         $labelTh.show();
3346     }
3350  * If the column does not allow NULL values, makes sure that default is not NULL
3351  */
3352 Functions.validateDefaultValue = function ($nullCheckbox) {
3353     if (! $nullCheckbox.prop('checked')) {
3354         var $default = $nullCheckbox.closest('tr').find('.default_type');
3355         if ($default.val() === 'NULL') {
3356             $default.val('NONE');
3357         }
3358     }
3362  * function to populate the input fields on picking a column from central list
3364  * @param string  input_id input id of the name field for the column to be populated
3365  * @param integer offset of the selected column in central list of columns
3366  */
3367 Functions.autoPopulate = function (inputId, offset) {
3368     var db = CommonParams.get('db');
3369     var table = CommonParams.get('table');
3370     var newInputId = inputId.substring(0, inputId.length - 1);
3371     $('#' + newInputId + '1').val(centralColumnList[db + '_' + table][offset].col_name);
3372     var colType = centralColumnList[db + '_' + table][offset].col_type.toUpperCase();
3373     $('#' + newInputId + '2').val(colType);
3374     var $input3 = $('#' + newInputId + '3');
3375     $input3.val(centralColumnList[db + '_' + table][offset].col_length);
3376     if (colType === 'ENUM' || colType === 'SET') {
3377         $input3.next().show();
3378     } else {
3379         $input3.next().hide();
3380     }
3381     var colDefault = centralColumnList[db + '_' + table][offset].col_default.toUpperCase();
3382     var $input4 = $('#' + newInputId + '4');
3383     if (colDefault !== '' && colDefault !== 'NULL' && colDefault !== 'CURRENT_TIMESTAMP' && colDefault !== 'CURRENT_TIMESTAMP()') {
3384         $input4.val('USER_DEFINED');
3385         $input4.next().next().show();
3386         $input4.next().next().val(centralColumnList[db + '_' + table][offset].col_default);
3387     } else {
3388         $input4.val(centralColumnList[db + '_' + table][offset].col_default);
3389         $input4.next().next().hide();
3390     }
3391     $('#' + newInputId + '5').val(centralColumnList[db + '_' + table][offset].col_collation);
3392     var $input6 = $('#' + newInputId + '6');
3393     $input6.val(centralColumnList[db + '_' + table][offset].col_attribute);
3394     if (centralColumnList[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') {
3395         $input6.val(centralColumnList[db + '_' + table][offset].col_extra);
3396     }
3397     if (centralColumnList[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') {
3398         $('#' + newInputId + '9').prop('checked',true).trigger('change');
3399     } else {
3400         $('#' + newInputId + '9').prop('checked',false);
3401     }
3402     if (centralColumnList[db + '_' + table][offset].col_isNull !== '0') {
3403         $('#' + newInputId + '7').prop('checked',true);
3404     } else {
3405         $('#' + newInputId + '7').prop('checked',false);
3406     }
3410  * Unbind all event handlers before tearing down a page
3411  */
3412 AJAX.registerTeardown('functions.js', function () {
3413     $(document).off('click', 'a.open_enum_editor');
3414     $(document).off('click', 'input.add_value');
3415     $(document).off('click', '#enum_editor td.drop');
3416     $(document).off('click', 'a.central_columns_dialog');
3420  * @var $enumEditorDialog An object that points to the jQuery
3421  *                          dialog of the ENUM/SET editor
3422  */
3423 var $enumEditorDialog = null;
3426  * Opens the ENUM/SET editor and controls its functions
3427  */
3428 AJAX.registerOnload('functions.js', function () {
3429     $(document).on('click', 'a.open_enum_editor', function () {
3430         // Get the name of the column that is being edited
3431         var colname = $(this).closest('tr').find('input').first().val();
3432         var title;
3433         var i;
3434         // And use it to make up a title for the page
3435         if (colname.length < 1) {
3436             title = Messages.enum_newColumnVals;
3437         } else {
3438             title = Messages.enum_columnVals.replace(
3439                 /%s/,
3440                 '"' + Functions.escapeHtml(decodeURIComponent(colname)) + '"'
3441             );
3442         }
3443         // Get the values as a string
3444         var inputstring = $(this)
3445             .closest('td')
3446             .find('input')
3447             .val();
3448         // Escape html entities
3449         inputstring = $('<div></div>')
3450             .text(inputstring)
3451             .html();
3452         // Parse the values, escaping quotes and
3453         // slashes on the fly, into an array
3454         var values = [];
3455         var inString = false;
3456         var curr;
3457         var next;
3458         var buffer = '';
3459         for (i = 0; i < inputstring.length; i++) {
3460             curr = inputstring.charAt(i);
3461             next = i === inputstring.length ? '' : inputstring.charAt(i + 1);
3462             if (! inString && curr === '\'') {
3463                 inString = true;
3464             } else if (inString && curr === '\\' && next === '\\') {
3465                 buffer += '&#92;';
3466                 i++;
3467             } else if (inString && next === '\'' && (curr === '\'' || curr === '\\')) {
3468                 buffer += '&#39;';
3469                 i++;
3470             } else if (inString && curr === '\'') {
3471                 inString = false;
3472                 values.push(buffer);
3473                 buffer = '';
3474             } else if (inString) {
3475                 buffer += curr;
3476             }
3477         }
3478         if (buffer.length > 0) {
3479             // The leftovers in the buffer are the last value (if any)
3480             values.push(buffer);
3481         }
3482         var fields = '';
3483         // If there are no values, maybe the user is about to make a
3484         // new list so we add a few for them to get started with.
3485         if (values.length === 0) {
3486             values.push('', '', '', '');
3487         }
3488         // Add the parsed values to the editor
3489         var dropIcon = Functions.getImage('b_drop');
3490         for (i = 0; i < values.length; i++) {
3491             fields += '<tr><td>' +
3492                    '<input type=\'text\' value=\'' + values[i] + '\'>' +
3493                    '</td><td class=\'drop\'>' +
3494                    dropIcon +
3495                    '</td></tr>';
3496         }
3497         /**
3498          * @var dialog HTML code for the ENUM/SET dialog
3499          */
3500         var dialog = '<div id=\'enum_editor\'>' +
3501                    '<fieldset>' +
3502                     '<legend>' + title + '</legend>' +
3503                     '<p>' + Functions.getImage('s_notice') +
3504                     Messages.enum_hint + '</p>' +
3505                     '<table class=\'values\'>' + fields + '</table>' +
3506                     '</fieldset><fieldset class=\'tblFooters\'>' +
3507                     '<table class=\'add\'><tr><td>' +
3508                     '<div class=\'slider\'></div>' +
3509                     '</td><td>' +
3510                     '<form><div><input type=\'submit\' class=\'add_value btn btn-primary\' value=\'' +
3511                     Functions.sprintf(Messages.enum_addValue, 1) +
3512                     '\'></div></form>' +
3513                     '</td></tr></table>' +
3514                     '<input type=\'hidden\' value=\'' + // So we know which column's data is being edited
3515                     $(this).closest('td').find('input').attr('id') +
3516                     '\'>' +
3517                     '</fieldset>' +
3518                     '</div>';
3519         /**
3520          * @var {object} buttonOptions Defines functions to be called when the buttons in
3521          * the buttonOptions jQuery dialog bar are pressed
3522          */
3523         var buttonOptions = {};
3524         buttonOptions[Messages.strGo] = function () {
3525             // When the submit button is clicked,
3526             // put the data back into the original form
3527             var valueArray = [];
3528             $(this).find('.values input').each(function (index, elm) {
3529                 var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, '\'\'');
3530                 valueArray.push('\'' + val + '\'');
3531             });
3532             // get the Length/Values text field where this value belongs
3533             var valuesId = $(this).find('input[type=\'hidden\']').val();
3534             $('input#' + valuesId).val(valueArray.join(','));
3535             $(this).dialog('close');
3536         };
3537         buttonOptions[Messages.strClose] = function () {
3538             $(this).dialog('close');
3539         };
3540         // Show the dialog
3541         var width = parseInt(
3542             (parseInt($('html').css('font-size'), 10) / 13) * 340,
3543             10
3544         );
3545         if (! width) {
3546             width = 340;
3547         }
3548         $enumEditorDialog = $(dialog).dialog({
3549             minWidth: width,
3550             maxHeight: 450,
3551             modal: true,
3552             title: Messages.enum_editor,
3553             buttons: buttonOptions,
3554             open: function () {
3555                 // Focus the "Go" button after opening the dialog
3556                 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button').first().trigger('focus');
3557             },
3558             close: function () {
3559                 $(this).remove();
3560             }
3561         });
3562         // slider for choosing how many fields to add
3563         $enumEditorDialog.find('.slider').slider({
3564             animate: true,
3565             range: 'min',
3566             value: 1,
3567             min: 1,
3568             max: 9,
3569             slide: function (event, ui) {
3570                 $(this).closest('table').find('input[type=submit]').val(
3571                     Functions.sprintf(Messages.enum_addValue, ui.value)
3572                 );
3573             }
3574         });
3575         // Focus the slider, otherwise it looks nearly transparent
3576         $('a.ui-slider-handle').addClass('ui-state-focus');
3577         return false;
3578     });
3580     $(document).on('click', 'a.central_columns_dialog', function () {
3581         var href = 'index.php?route=/database/central-columns';
3582         var db = CommonParams.get('db');
3583         var table = CommonParams.get('table');
3584         var maxRows = $(this).data('maxrows');
3585         var pick = $(this).data('pick');
3586         if (pick !== false) {
3587             pick = true;
3588         }
3589         var params = {
3590             'ajax_request' : true,
3591             'server' : CommonParams.get('server'),
3592             'db' : CommonParams.get('db'),
3593             'cur_table' : CommonParams.get('table'),
3594             'getColumnList':true
3595         };
3596         var colid = $(this).closest('td').find('input').attr('id');
3597         var fields = '';
3598         if (! (db + '_' + table in centralColumnList)) {
3599             centralColumnList.push(db + '_' + table);
3600             $.ajax({
3601                 type: 'POST',
3602                 url: href,
3603                 data: params,
3604                 success: function (data) {
3605                     centralColumnList[db + '_' + table] = data.message;
3606                 },
3607                 async:false
3608             });
3609         }
3610         var i = 0;
3611         var listSize = centralColumnList[db + '_' + table].length;
3612         var min = (listSize <= maxRows) ? listSize : maxRows;
3613         for (i = 0; i < min; i++) {
3614             fields += '<tr><td><div><span class="font_weight_bold">' +
3615                 Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_name) +
3616                 '</span><br><span class="color_gray">' + centralColumnList[db + '_' + table][i].col_type;
3618             if (centralColumnList[db + '_' + table][i].col_attribute !== '') {
3619                 fields += '(' + Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_attribute) + ') ';
3620             }
3621             if (centralColumnList[db + '_' + table][i].col_length !== '') {
3622                 fields += '(' + Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_length) + ') ';
3623             }
3624             fields += Functions.escapeHtml(centralColumnList[db + '_' + table][i].col_extra) + '</span>' +
3625                 '</div></td>';
3626             if (pick) {
3627                 fields += '<td><input class="btn btn-secondary pick w-100" type="submit" value="' +
3628                     Messages.pickColumn + '" onclick="Functions.autoPopulate(\'' + colid + '\',' + i + ')"></td>';
3629             }
3630             fields += '</tr>';
3631         }
3632         var resultPointer = i;
3633         var searchIn = '<input type="text" class="filter_rows" placeholder="' + Messages.searchList + '">';
3634         if (fields === '') {
3635             fields = Functions.sprintf(Messages.strEmptyCentralList, '\'' + Functions.escapeHtml(db) + '\'');
3636             searchIn = '';
3637         }
3638         var seeMore = '';
3639         if (listSize > maxRows) {
3640             seeMore = '<fieldset class="tblFooters text-center font_weight_bold">' +
3641                 '<a href=\'#\' id=\'seeMore\'>' + Messages.seeMore + '</a></fieldset>';
3642         }
3643         var centralColumnsDialog = '<div class=\'max_height_400\'>' +
3644             '<fieldset>' +
3645             searchIn +
3646             '<table id=\'col_list\' class=\'values w-100\'>' + fields + '</table>' +
3647             '</fieldset>' +
3648             seeMore +
3649             '</div>';
3651         var width = parseInt(
3652             (parseInt($('html').css('font-size'), 10) / 13) * 500,
3653             10
3654         );
3655         if (! width) {
3656             width = 500;
3657         }
3658         var buttonOptions = {};
3659         var $centralColumnsDialog = $(centralColumnsDialog).dialog({
3660             minWidth: width,
3661             maxHeight: 450,
3662             modal: true,
3663             title: Messages.pickColumnTitle,
3664             buttons: buttonOptions,
3665             open: function () {
3666                 $('#col_list').on('click', '.pick', function () {
3667                     $centralColumnsDialog.remove();
3668                 });
3669                 $('.filter_rows').on('keyup', function () {
3670                     $.uiTableFilter($('#col_list'), $(this).val());
3671                 });
3672                 $('#seeMore').on('click', function () {
3673                     fields = '';
3674                     min = (listSize <= maxRows + resultPointer) ? listSize : maxRows + resultPointer;
3675                     for (i = resultPointer; i < min; i++) {
3676                         fields += '<tr><td><div><span class="font_weight_bold">' +
3677                             centralColumnList[db + '_' + table][i].col_name +
3678                             '</span><br><span class="color_gray">' +
3679                             centralColumnList[db + '_' + table][i].col_type;
3681                         if (centralColumnList[db + '_' + table][i].col_attribute !== '') {
3682                             fields += '(' + centralColumnList[db + '_' + table][i].col_attribute + ') ';
3683                         }
3684                         if (centralColumnList[db + '_' + table][i].col_length !== '') {
3685                             fields += '(' + centralColumnList[db + '_' + table][i].col_length + ') ';
3686                         }
3687                         fields += centralColumnList[db + '_' + table][i].col_extra + '</span>' +
3688                             '</div></td>';
3689                         if (pick) {
3690                             fields += '<td><input class="btn btn-secondary pick w-100" type="submit" value="' +
3691                                 Messages.pickColumn + '" onclick="Functions.autoPopulate(\'' + colid + '\',' + i + ')"></td>';
3692                         }
3693                         fields += '</tr>';
3694                     }
3695                     $('#col_list').append(fields);
3696                     resultPointer = i;
3697                     if (resultPointer === listSize) {
3698                         $('#seeMore').hide();
3699                     }
3700                     return false;
3701                 });
3702                 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button').first().trigger('focus');
3703             },
3704             close: function () {
3705                 $('#col_list').off('click', '.pick');
3706                 $('.filter_rows').off('keyup');
3707                 $(this).remove();
3708             }
3709         });
3710         return false;
3711     });
3713     // $(document).on('click', 'a.show_central_list',function(e) {
3715     // });
3716     // When "add a new value" is clicked, append an empty text field
3717     $(document).on('click', 'input.add_value', function (e) {
3718         e.preventDefault();
3719         var numNewRows = $enumEditorDialog.find('div.slider').slider('value');
3720         while (numNewRows--) {
3721             $enumEditorDialog.find('.values')
3722                 .append(
3723                     '<tr class=\'hide\'><td>' +
3724                     '<input type=\'text\'>' +
3725                     '</td><td class=\'drop\'>' +
3726                     Functions.getImage('b_drop') +
3727                     '</td></tr>'
3728                 )
3729                 .find('tr').last()
3730                 .show('fast');
3731         }
3732     });
3734     // Removes the specified row from the enum editor
3735     $(document).on('click', '#enum_editor td.drop', function () {
3736         $(this).closest('tr').hide('fast', function () {
3737             $(this).remove();
3738         });
3739     });
3743  * Ensures indexes names are valid according to their type and, for a primary
3744  * key, lock index name to 'PRIMARY'
3745  * @param string   form_id  Variable which parses the form name as
3746  *                            the input
3747  * @return boolean  false    if there is no index form, true else
3748  */
3749 Functions.checkIndexName = function (formId) {
3750     if ($('#' + formId).length === 0) {
3751         return false;
3752     }
3754     // Gets the elements pointers
3755     var $theIdxName = $('#input_index_name');
3756     var $theIdxChoice = $('#select_index_choice');
3758     // Index is a primary key
3759     if ($theIdxChoice.find('option:selected').val() === 'PRIMARY') {
3760         $theIdxName.val('PRIMARY');
3761         $theIdxName.prop('disabled', true);
3762     } else {
3763         if ($theIdxName.val() === 'PRIMARY') {
3764             $theIdxName.val('');
3765         }
3766         $theIdxName.prop('disabled', false);
3767     }
3769     return true;
3772 AJAX.registerTeardown('functions.js', function () {
3773     $(document).off('click', '#index_frm input[type=submit]');
3775 AJAX.registerOnload('functions.js', function () {
3776     /**
3777      * Handler for adding more columns to an index in the editor
3778      */
3779     $(document).on('click', '#index_frm input[type=submit]', function (event) {
3780         event.preventDefault();
3781         var hadAddButtonHidden = $(this).closest('fieldset').find('.add_fields').hasClass('hide');
3782         if (hadAddButtonHidden === false) {
3783             var rowsToAdd = $(this)
3784                 .closest('fieldset')
3785                 .find('.slider')
3786                 .slider('value');
3788             var tempEmptyVal = function () {
3789                 $(this).val('');
3790             };
3792             var tempSetFocus = function () {
3793                 if ($(this).find('option:selected').val() === '') {
3794                     return true;
3795                 }
3796                 $(this).closest('tr').find('input').trigger('focus');
3797             };
3799             while (rowsToAdd--) {
3800                 var $indexColumns = $('#index_columns');
3801                 var $newrow = $indexColumns
3802                     .find('tbody > tr').first()
3803                     .clone()
3804                     .appendTo(
3805                         $indexColumns.find('tbody')
3806                     );
3807                 $newrow.find(':input').each(tempEmptyVal);
3808                 // focus index size input on column picked
3809                 $newrow.find('select').on('change', tempSetFocus);
3810             }
3811         }
3812     });
3815 Functions.indexEditorDialog = function (url, title, callbackSuccess, callbackFailure) {
3816     /* Remove the hidden dialogs if there are*/
3817     var $editIndexDialog = $('#edit_index_dialog');
3818     if ($editIndexDialog.length !== 0) {
3819         $editIndexDialog.remove();
3820     }
3821     var $div = $('<div id="edit_index_dialog"></div>');
3823     /**
3824      * @var button_options Object that stores the options
3825      *                     passed to jQueryUI dialog
3826      */
3827     var buttonOptions = {};
3828     buttonOptions[Messages.strGo] = function () {
3829         /**
3830          * @var the_form object referring to the export form
3831          */
3832         var $form = $('#index_frm');
3833         Functions.ajaxShowMessage(Messages.strProcessingRequest);
3834         Functions.prepareForAjaxRequest($form);
3835         // User wants to submit the form
3836         $.post($form.attr('action'), $form.serialize() + CommonParams.get('arg_separator') + 'do_save_data=1', function (data) {
3837             var $sqlqueryresults = $('.sqlqueryresults');
3838             if ($sqlqueryresults.length !== 0) {
3839                 $sqlqueryresults.remove();
3840             }
3841             if (typeof data !== 'undefined' && data.success === true) {
3842                 Functions.ajaxShowMessage(data.message);
3843                 Functions.highlightSql($('.result_query'));
3844                 $('.result_query .alert').remove();
3845                 /* Reload the field form*/
3846                 $('#table_index').remove();
3847                 $('<div id=\'temp_div\'><div>')
3848                     .append(data.index_table)
3849                     .find('#table_index')
3850                     .insertAfter('#index_header');
3851                 var $editIndexDialog = $('#edit_index_dialog');
3852                 if ($editIndexDialog.length > 0) {
3853                     $editIndexDialog.dialog('close');
3854                 }
3855                 $('div.no_indexes_defined').hide();
3856                 if (callbackSuccess) {
3857                     callbackSuccess();
3858                 }
3859                 Navigation.reload();
3860             } else {
3861                 var $tempDiv = $('<div id=\'temp_div\'><div>').append(data.error);
3862                 var $error;
3863                 if ($tempDiv.find('.error code').length !== 0) {
3864                     $error = $tempDiv.find('.error code').addClass('error');
3865                 } else {
3866                     $error = $tempDiv;
3867                 }
3868                 if (callbackFailure) {
3869                     callbackFailure();
3870                 }
3871                 Functions.ajaxShowMessage($error, false);
3872             }
3873         }); // end $.post()
3874     };
3875     buttonOptions[Messages.strPreviewSQL] = function () {
3876         // Function for Previewing SQL
3877         var $form = $('#index_frm');
3878         Functions.previewSql($form);
3879     };
3880     buttonOptions[Messages.strCancel] = function () {
3881         $(this).dialog('close');
3882     };
3883     var $msgbox = Functions.ajaxShowMessage();
3884     $.post('index.php?route=/table/indexes', url, function (data) {
3885         if (typeof data !== 'undefined' && data.success === false) {
3886             // in the case of an error, show the error message returned.
3887             Functions.ajaxShowMessage(data.error, false);
3888         } else {
3889             Functions.ajaxRemoveMessage($msgbox);
3890             // Show dialog if the request was successful
3891             $div
3892                 .append(data.message)
3893                 .dialog({
3894                     title: title,
3895                     width: 'auto',
3896                     open: Functions.verifyColumnsProperties,
3897                     modal: true,
3898                     buttons: buttonOptions,
3899                     close: function () {
3900                         $(this).remove();
3901                     }
3902                 });
3903             $div.find('.tblFooters').remove();
3904             Functions.showIndexEditDialog($div);
3905         }
3906     }); // end $.get()
3909 Functions.showIndexEditDialog = function ($outer) {
3910     Indexes.checkIndexType();
3911     Functions.checkIndexName('index_frm');
3912     var $indexColumns = $('#index_columns');
3913     $indexColumns.find('td').each(function () {
3914         $(this).css('width', $(this).width() + 'px');
3915     });
3916     $indexColumns.find('tbody').sortable({
3917         axis: 'y',
3918         containment: $indexColumns.find('tbody'),
3919         tolerance: 'pointer'
3920     });
3921     Functions.showHints($outer);
3922     Functions.initSlider();
3923     // Add a slider for selecting how many columns to add to the index
3924     $outer.find('.slider').slider({
3925         animate: true,
3926         value: 1,
3927         min: 1,
3928         max: 16,
3929         slide: function (event, ui) {
3930             $(this).closest('fieldset').find('input[type=submit]').val(
3931                 Functions.sprintf(Messages.strAddToIndex, ui.value)
3932             );
3933         }
3934     });
3935     $('div.add_fields').removeClass('hide');
3936     // focus index size input on column picked
3937     $outer.find('table#index_columns select').on('change', function () {
3938         if ($(this).find('option:selected').val() === '') {
3939             return true;
3940         }
3941         $(this).closest('tr').find('input').trigger('focus');
3942     });
3943     // Focus the slider, otherwise it looks nearly transparent
3944     $('a.ui-slider-handle').addClass('ui-state-focus');
3945     // set focus on index name input, if empty
3946     var input = $outer.find('input#input_index_name');
3947     if (! input.val()) {
3948         input.trigger('focus');
3949     }
3953  * Function to display tooltips that were
3954  * generated on the PHP side by PhpMyAdmin\Util::showHint()
3956  * @param object $div a div jquery object which specifies the
3957  *                    domain for searching for tooltips. If we
3958  *                    omit this parameter the function searches
3959  *                    in the whole body
3960  **/
3961 Functions.showHints = function ($div) {
3962     var $newDiv = $div;
3963     if ($newDiv === undefined || !($newDiv instanceof jQuery) || $newDiv.length === 0) {
3964         $newDiv = $('body');
3965     }
3966     $newDiv.find('.pma_hint').each(function () {
3967         Functions.tooltip(
3968             $(this).children('img'),
3969             'img',
3970             $(this).children('span').html()
3971         );
3972     });
3975 AJAX.registerOnload('functions.js', function () {
3976     Functions.showHints();
3979 Functions.mainMenuResizerCallback = function () {
3980     // 5 px margin for jumping menu in Chrome
3981     return $(document.body).width() - 5;
3984 // This must be fired only once after the initial page load
3985 $(function () {
3986     // Initialise the menu resize plugin
3987     $('#topmenu').menuResizer(Functions.mainMenuResizerCallback);
3988     // register resize event
3989     $(window).on('resize', function () {
3990         $('#topmenu').menuResizer('resize');
3991     });
3995  * Changes status of slider
3996  */
3997 Functions.setStatusLabel = function ($element) {
3998     var text;
3999     if ($element.css('display') === 'none') {
4000         text = '+ ';
4001     } else {
4002         text = '- ';
4003     }
4004     $element.closest('.slide-wrapper').prev().find('span').text(text);
4008  * var  toggleButton  This is a function that creates a toggle
4009  *                    sliding button given a jQuery reference
4010  *                    to the correct DOM element
4011  */
4012 Functions.toggleButton = function ($obj) {
4013     // In rtl mode the toggle switch is flipped horizontally
4014     // so we need to take that into account
4015     var right;
4016     if ($('span.text_direction', $obj).text() === 'ltr') {
4017         right = 'right';
4018     } else {
4019         right = 'left';
4020     }
4021     /**
4022      *  var  h  Height of the button, used to scale the
4023      *          background image and position the layers
4024      */
4025     var h = $obj.height();
4026     $('img', $obj).height(h);
4027     $('table', $obj).css('bottom', h - 1);
4028     /**
4029      *  var  on   Width of the "ON" part of the toggle switch
4030      *  var  off  Width of the "OFF" part of the toggle switch
4031      */
4032     var on  = $('td.toggleOn', $obj).width();
4033     var off = $('td.toggleOff', $obj).width();
4034     // Make the "ON" and "OFF" parts of the switch the same size
4035     // + 2 pixels to avoid overflowed
4036     $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
4037     $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
4038     /**
4039      *  var  w  Width of the central part of the switch
4040      */
4041     var w = parseInt(($('img', $obj).height() / 16) * 22, 10);
4042     // Resize the central part of the switch on the top
4043     // layer to match the background
4044     $(document.querySelectorAll('table td:nth-child(2) > div'), $obj).width(w);
4045     /**
4046      *  var  imgw    Width of the background image
4047      *  var  tblw    Width of the foreground layer
4048      *  var  offset  By how many pixels to move the background
4049      *               image, so that it matches the top layer
4050      */
4051     var imgw = $('img', $obj).width();
4052     var tblw = $('table', $obj).width();
4053     var offset = parseInt(((imgw - tblw) / 2), 10);
4054     // Move the background to match the layout of the top layer
4055     $obj.find('img').css(right, offset);
4056     /**
4057      *  var  offw    Outer width of the "ON" part of the toggle switch
4058      *  var  btnw    Outer width of the central part of the switch
4059      */
4060     var offw = $('td.toggleOff', $obj).outerWidth();
4061     var btnw = $(document.querySelectorAll('table td:nth-child(2)'), $obj).outerWidth();
4062     // Resize the main div so that exactly one side of
4063     // the switch plus the central part fit into it.
4064     $obj.width(offw + btnw + 2);
4065     /**
4066      *  var  move  How many pixels to move the
4067      *             switch by when toggling
4068      */
4069     var move = $('td.toggleOff', $obj).outerWidth();
4070     // If the switch is initialized to the
4071     // OFF state we need to move it now.
4072     if ($('div.toggle-container', $obj).hasClass('off')) {
4073         if (right === 'right') {
4074             $('div.toggle-container', $obj).animate({ 'left': '-=' + move + 'px' }, 0);
4075         } else {
4076             $('div.toggle-container', $obj).animate({ 'left': '+=' + move + 'px' }, 0);
4077         }
4078     }
4079     // Attach an 'onclick' event to the switch
4080     $('div.toggle-container', $obj).on('click', function () {
4081         if ($(this).hasClass('isActive')) {
4082             return false;
4083         } else {
4084             $(this).addClass('isActive');
4085         }
4086         var $msg = Functions.ajaxShowMessage();
4087         var $container = $(this);
4088         var callback = $('span.callback', this).text();
4089         var operator;
4090         var url;
4091         var removeClass;
4092         var addClass;
4093         // Perform the actual toggle
4094         if ($(this).hasClass('on')) {
4095             if (right === 'right') {
4096                 operator = '-=';
4097             } else {
4098                 operator = '+=';
4099             }
4100             url = $(this).find('td.toggleOff > span').text();
4101             removeClass = 'on';
4102             addClass = 'off';
4103         } else {
4104             if (right === 'right') {
4105                 operator = '+=';
4106             } else {
4107                 operator = '-=';
4108             }
4109             url = $(this).find('td.toggleOn > span').text();
4110             removeClass = 'off';
4111             addClass = 'on';
4112         }
4114         var parts = url.split('?');
4115         $.post(parts[0], parts[1] + '&ajax_request=true', function (data) {
4116             if (typeof data !== 'undefined' && data.success === true) {
4117                 Functions.ajaxRemoveMessage($msg);
4118                 $container
4119                     .removeClass(removeClass)
4120                     .addClass(addClass)
4121                     .animate({ 'left': operator + move + 'px' }, function () {
4122                         $container.removeClass('isActive');
4123                     });
4124                 // eslint-disable-next-line no-eval
4125                 eval(callback);
4126             } else {
4127                 Functions.ajaxShowMessage(data.error, false);
4128                 $container.removeClass('isActive');
4129             }
4130         });
4131     });
4135  * Unbind all event handlers before tearing down a page
4136  */
4137 AJAX.registerTeardown('functions.js', function () {
4138     $('div.toggle-container').off('click');
4141  * Initialise all toggle buttons
4142  */
4143 AJAX.registerOnload('functions.js', function () {
4144     $('div.toggleAjax').each(function () {
4145         var $button = $(this).show();
4146         $button.find('img').each(function () {
4147             if (this.complete) {
4148                 Functions.toggleButton($button);
4149             } else {
4150                 $(this).on('load', function () {
4151                     Functions.toggleButton($button);
4152                 });
4153             }
4154         });
4155     });
4159  * Unbind all event handlers before tearing down a page
4160  */
4161 AJAX.registerTeardown('functions.js', function () {
4162     $(document).off('change', 'select.pageselector');
4163     $('#update_recent_tables').off('ready');
4164     $('#sync_favorite_tables').off('ready');
4167 AJAX.registerOnload('functions.js', function () {
4168     /**
4169      * Autosubmit page selector
4170      */
4171     $(document).on('change', 'select.pageselector', function (event) {
4172         event.stopPropagation();
4173         // Check where to load the new content
4174         if ($(this).closest('#pma_navigation').length === 0) {
4175             // For the main page we don't need to do anything,
4176             $(this).closest('form').trigger('submit');
4177         } else {
4178             // but for the navigation we need to manually replace the content
4179             Navigation.treePagination($(this));
4180         }
4181     });
4183     /**
4184      * Load version information asynchronously.
4185      */
4186     if ($('li.jsversioncheck').length > 0) {
4187         $.ajax({
4188             dataType: 'json',
4189             url: 'index.php?route=/version-check',
4190             method: 'POST',
4191             data: {
4192                 'server': CommonParams.get('server')
4193             },
4194             success: Functions.currentVersion
4195         });
4196     }
4198     if ($('#is_git_revision').length > 0) {
4199         setTimeout(Functions.displayGitRevision, 10);
4200     }
4202     /**
4203      * Slider effect.
4204      */
4205     Functions.initSlider();
4207     var $updateRecentTables = $('#update_recent_tables');
4208     if ($updateRecentTables.length) {
4209         $.get(
4210             $updateRecentTables.attr('href'),
4211             { 'no_debug': true },
4212             function (data) {
4213                 if (typeof data !== 'undefined' && data.success === true) {
4214                     $('#pma_recent_list').html(data.list);
4215                 }
4216             }
4217         );
4218     }
4220     // Sync favorite tables from localStorage to pmadb.
4221     if ($('#sync_favorite_tables').length) {
4222         $.ajax({
4223             url: $('#sync_favorite_tables').attr('href'),
4224             cache: false,
4225             type: 'POST',
4226             data: {
4227                 'favoriteTables': (isStorageSupported('localStorage') && typeof window.localStorage.favoriteTables !== 'undefined')
4228                     ? window.localStorage.favoriteTables
4229                     : '',
4230                 'server': CommonParams.get('server'),
4231                 'no_debug': true
4232             },
4233             success: function (data) {
4234                 // Update localStorage.
4235                 if (isStorageSupported('localStorage')) {
4236                     window.localStorage.favoriteTables = data.favoriteTables;
4237                 }
4238                 $('#pma_favorite_list').html(data.list);
4239             }
4240         });
4241     }
4242 }); // end of $()
4245  * Initializes slider effect.
4246  */
4247 Functions.initSlider = function () {
4248     $('div.pma_auto_slider').each(function () {
4249         var $this = $(this);
4250         if ($this.data('slider_init_done')) {
4251             return;
4252         }
4253         var $wrapper = $('<div>', { 'class': 'slide-wrapper' });
4254         $wrapper.toggle($this.is(':visible'));
4255         $('<a>', { href: '#' + this.id, 'class': 'ajax' })
4256             .text($this.attr('title'))
4257             .prepend($('<span>'))
4258             .insertBefore($this)
4259             .on('click', function () {
4260                 var $wrapper = $this.closest('.slide-wrapper');
4261                 var visible = $this.is(':visible');
4262                 if (!visible) {
4263                     $wrapper.show();
4264                 }
4265                 $this[visible ? 'hide' : 'show']('blind', function () {
4266                     $wrapper.toggle(!visible);
4267                     $wrapper.parent().toggleClass('print_ignore', visible);
4268                     Functions.setStatusLabel($this);
4269                 });
4270                 return false;
4271             });
4272         $this.wrap($wrapper);
4273         $this.removeAttr('title');
4274         Functions.setStatusLabel($this);
4275         $this.data('slider_init_done', 1);
4276     });
4280  * Initializes slider effect.
4281  */
4282 AJAX.registerOnload('functions.js', function () {
4283     Functions.initSlider();
4287  * Restores sliders to the state they were in before initialisation.
4288  */
4289 AJAX.registerTeardown('functions.js', function () {
4290     $('div.pma_auto_slider').each(function () {
4291         var $this = $(this);
4292         $this.removeData();
4293         $this.parent().replaceWith($this);
4294         $this.parent().children('a').remove();
4295     });
4299  * Creates a message inside an object with a sliding effect
4301  * @param msg    A string containing the text to display
4302  * @param $obj   a jQuery object containing the reference
4303  *                 to the element where to put the message
4304  *                 This is optional, if no element is
4305  *                 provided, one will be created below the
4306  *                 navigation links at the top of the page
4308  * @return bool   True on success, false on failure
4309  */
4310 Functions.slidingMessage = function (msg, $object) {
4311     var $obj = $object;
4312     if (msg === undefined || msg.length === 0) {
4313         // Don't show an empty message
4314         return false;
4315     }
4316     if ($obj === undefined || !($obj instanceof jQuery) || $obj.length === 0) {
4317         // If the second argument was not supplied,
4318         // we might have to create a new DOM node.
4319         if ($('#PMA_slidingMessage').length === 0) {
4320             $('#page_content').prepend(
4321                 '<span id="PMA_slidingMessage" ' +
4322                 'class="pma_sliding_message"></span>'
4323             );
4324         }
4325         $obj = $('#PMA_slidingMessage');
4326     }
4327     if ($obj.has('div').length > 0) {
4328         // If there already is a message inside the
4329         // target object, we must get rid of it
4330         $obj
4331             .find('div')
4332             .first()
4333             .fadeOut(function () {
4334                 $obj
4335                     .children()
4336                     .remove();
4337                 $obj
4338                     .append('<div>' + msg + '</div>');
4339                 // highlight any sql before taking height;
4340                 Functions.highlightSql($obj);
4341                 $obj.find('div')
4342                     .first()
4343                     .hide();
4344                 $obj
4345                     .animate({
4346                         height: $obj.find('div').first().height()
4347                     })
4348                     .find('div')
4349                     .first()
4350                     .fadeIn();
4351             });
4352     } else {
4353         // Object does not already have a message
4354         // inside it, so we simply slide it down
4355         $obj.width('100%')
4356             .html('<div>' + msg + '</div>');
4357         // highlight any sql before taking height;
4358         Functions.highlightSql($obj);
4359         var h = $obj
4360             .find('div')
4361             .first()
4362             .hide()
4363             .height();
4364         $obj
4365             .find('div')
4366             .first()
4367             .css('height', 0)
4368             .show()
4369             .animate({
4370                 height: h
4371             }, function () {
4372             // Set the height of the parent
4373             // to the height of the child
4374                 $obj
4375                     .height(
4376                         $obj
4377                             .find('div')
4378                             .first()
4379                             .height()
4380                     );
4381             });
4382     }
4383     return true;
4387  * Attach CodeMirror2 editor to SQL edit area.
4388  */
4389 AJAX.registerOnload('functions.js', function () {
4390     var $elm = $('#sqlquery');
4391     if ($elm.siblings().filter('.CodeMirror').length > 0) {
4392         return;
4393     }
4394     if ($elm.length > 0) {
4395         if (typeof CodeMirror !== 'undefined') {
4396             codeMirrorEditor = Functions.getSqlEditor($elm);
4397             codeMirrorEditor.focus();
4398             codeMirrorEditor.on('blur', Functions.updateQueryParameters);
4399         } else {
4400             // without codemirror
4401             $elm.trigger('focus').on('blur', Functions.updateQueryParameters);
4402         }
4403     }
4404     Functions.highlightSql($('body'));
4406 AJAX.registerTeardown('functions.js', function () {
4407     if (codeMirrorEditor) {
4408         $('#sqlquery').text(codeMirrorEditor.getValue());
4409         codeMirrorEditor.toTextArea();
4410         codeMirrorEditor = false;
4411     }
4413 AJAX.registerOnload('functions.js', function () {
4414     // initializes all lock-page elements lock-id and
4415     // val-hash data property
4416     $('#page_content form.lock-page textarea, ' +
4417             '#page_content form.lock-page input[type="text"], ' +
4418             '#page_content form.lock-page input[type="number"], ' +
4419             '#page_content form.lock-page select').each(function (i) {
4420         $(this).data('lock-id', i);
4421         // val-hash is the hash of default value of the field
4422         // so that it can be compared with new value hash
4423         // to check whether field was modified or not.
4424         $(this).data('val-hash', AJAX.hash($(this).val()));
4425     });
4427     // initializes lock-page elements (input types checkbox and radio buttons)
4428     // lock-id and val-hash data property
4429     $('#page_content form.lock-page input[type="checkbox"], ' +
4430             '#page_content form.lock-page input[type="radio"]').each(function (i) {
4431         $(this).data('lock-id', i);
4432         $(this).data('val-hash', AJAX.hash($(this).is(':checked')));
4433     });
4437  * jQuery plugin to correctly filter input fields by value, needed
4438  * because some nasty values may break selector syntax
4439  */
4440 (function ($) {
4441     $.fn.filterByValue = function (value) {
4442         return this.filter(function () {
4443             return $(this).val() === value;
4444         });
4445     };
4446 }(jQuery));
4449  * Return value of a cell in a table.
4450  */
4451 Functions.getCellValue = function (td) {
4452     var $td = $(td);
4453     if ($td.is('.null')) {
4454         return '';
4455     } else if ((! $td.is('.to_be_saved')
4456         || $td.is('.set'))
4457         && $td.data('original_data')
4458     ) {
4459         return $td.data('original_data');
4460     } else {
4461         return $td.text();
4462     }
4465 $(window).on('popstate', function () {
4466     $('#printcss').attr('media','print');
4467     return true;
4471  * Unbind all event handlers before tearing down a page
4472  */
4473 AJAX.registerTeardown('functions.js', function () {
4474     $(document).off('click', 'a.themeselect');
4475     $(document).off('change', '.autosubmit');
4476     $('a.take_theme').off('click');
4479 AJAX.registerOnload('functions.js', function () {
4480     /**
4481      * Theme selector.
4482      */
4483     $(document).on('click', 'a.themeselect', function (e) {
4484         window.open(
4485             e.target,
4486             'themes',
4487             'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes'
4488         );
4489         return false;
4490     });
4492     /**
4493      * Automatic form submission on change.
4494      */
4495     $(document).on('change', '.autosubmit', function () {
4496         $(this).closest('form').trigger('submit');
4497     });
4499     /**
4500      * Theme changer.
4501      */
4502     $('a.take_theme').on('click', function () {
4503         var what = this.name;
4504         /* eslint-disable compat/compat */
4505         if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) {
4506             window.opener.document.forms.setTheme.elements.set_theme.value = what;
4507             window.opener.document.forms.setTheme.submit();
4508             window.close();
4509             return false;
4510         }
4511         /* eslint-enable compat/compat */
4512         return true;
4513     });
4517  * Produce print preview
4518  */
4519 Functions.printPreview = function () {
4520     $('#printcss').attr('media','all');
4521     Functions.createPrintAndBackButtons();
4525  * Create print and back buttons in preview page
4526  */
4527 Functions.createPrintAndBackButtons = function () {
4528     var backButton = $('<input>',{
4529         type: 'button',
4530         value: Messages.back,
4531         class: 'btn btn-secondary',
4532         id: 'back_button_print_view'
4533     });
4534     backButton.on('click', Functions.removePrintAndBackButton);
4535     backButton.appendTo('#page_content');
4536     var printButton = $('<input>',{
4537         type: 'button',
4538         value: Messages.print,
4539         class: 'btn btn-primary',
4540         id: 'print_button_print_view'
4541     });
4542     printButton.on('click', Functions.printPage);
4543     printButton.appendTo('#page_content');
4547  * Remove print and back buttons and revert to normal view
4548  */
4549 Functions.removePrintAndBackButton = function () {
4550     $('#printcss').attr('media','print');
4551     $('#back_button_print_view').remove();
4552     $('#print_button_print_view').remove();
4556  * Print page
4557  */
4558 Functions.printPage = function () {
4559     if (typeof(window.print) !== 'undefined') {
4560         window.print();
4561     }
4565  * Unbind all event handlers before tearing down a page
4566  */
4567 AJAX.registerTeardown('functions.js', function () {
4568     $('input#print').off('click');
4569     $(document).off('click', 'a.create_view.ajax');
4570     $(document).off('keydown', '#createViewDialog input, #createViewDialog select');
4571     $(document).off('change', '#fkc_checkbox');
4574 AJAX.registerOnload('functions.js', function () {
4575     $('input#print').on('click', Functions.printPage);
4576     $('.logout').on('click', function () {
4577         var form = $(
4578             '<form method="POST" action="' + $(this).attr('href') + '" class="disableAjax">' +
4579             '<input type="hidden" name="token" value="' + Functions.escapeHtml(CommonParams.get('token')) + '">' +
4580             '</form>'
4581         );
4582         $('body').append(form);
4583         form.submit();
4584         sessionStorage.clear();
4585         return false;
4586     });
4587     /**
4588      * Ajaxification for the "Create View" action
4589      */
4590     $(document).on('click', 'a.create_view.ajax', function (e) {
4591         e.preventDefault();
4592         Functions.createViewDialog($(this));
4593     });
4594     /**
4595      * Attach Ajax event handlers for input fields in the editor
4596      * and used to submit the Ajax request when the ENTER key is pressed.
4597      */
4598     if ($('#createViewDialog').length !== 0) {
4599         $(document).on('keydown', '#createViewDialog input, #createViewDialog select', function (e) {
4600             if (e.which === 13) { // 13 is the ENTER key
4601                 e.preventDefault();
4603                 // with preventing default, selection by <select> tag
4604                 // was also prevented in IE
4605                 $(this).trigger('blur');
4607                 $(this).closest('.ui-dialog').find('.ui-button').first().trigger('click');
4608             }
4609         }); // end $(document).on()
4610     }
4612     if ($('textarea[name="view[as]"]').length !== 0) {
4613         codeMirrorEditor = Functions.getSqlEditor($('textarea[name="view[as]"]'));
4614     }
4617 Functions.createViewDialog = function ($this) {
4618     var $msg = Functions.ajaxShowMessage();
4619     var sep = CommonParams.get('arg_separator');
4620     var params = Functions.getJsConfirmCommonParam(this, $this.getPostData());
4621     params += sep + 'ajax_dialog=1';
4622     $.post($this.attr('href'), params, function (data) {
4623         if (typeof data !== 'undefined' && data.success === true) {
4624             Functions.ajaxRemoveMessage($msg);
4625             var buttonOptions = {};
4626             buttonOptions[Messages.strGo] = function () {
4627                 if (typeof CodeMirror !== 'undefined') {
4628                     codeMirrorEditor.save();
4629                 }
4630                 $msg = Functions.ajaxShowMessage();
4631                 $.post('index.php?route=/view/create', $('#createViewDialog').find('form').serialize(), function (data) {
4632                     Functions.ajaxRemoveMessage($msg);
4633                     if (typeof data !== 'undefined' && data.success === true) {
4634                         $('#createViewDialog').dialog('close');
4635                         $('.result_query').html(data.message);
4636                         Navigation.reload();
4637                     } else {
4638                         Functions.ajaxShowMessage(data.error);
4639                     }
4640                 });
4641             };
4642             buttonOptions[Messages.strClose] = function () {
4643                 $(this).dialog('close');
4644             };
4645             var $dialog = $('<div></div>').attr('id', 'createViewDialog').append(data.message).dialog({
4646                 width: 600,
4647                 minWidth: 400,
4648                 modal: true,
4649                 buttons: buttonOptions,
4650                 title: Messages.strCreateView,
4651                 close: function () {
4652                     $(this).remove();
4653                 }
4654             });
4655             // Attach syntax highlighted editor
4656             codeMirrorEditor = Functions.getSqlEditor($dialog.find('textarea'));
4657             $('input:visible[type=text]', $dialog).first().trigger('focus');
4658         } else {
4659             Functions.ajaxShowMessage(data.error);
4660         }
4661     });
4665  * Makes the breadcrumbs and the menu bar float at the top of the viewport
4666  */
4667 $(function () {
4668     if ($('#floating_menubar').length && $('#PMA_disable_floating_menubar').length === 0) {
4669         var left = $('html').attr('dir') === 'ltr' ? 'left' : 'right';
4670         $('#floating_menubar')
4671             .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width())
4672             .css(left, 0)
4673             .css({
4674                 'position': 'fixed',
4675                 'top': 0,
4676                 'width': '100%',
4677                 'z-index': 99
4678             })
4679             .append($('#server-breadcrumb'))
4680             .append($('#topmenucontainer'));
4681         // Allow the DOM to render, then adjust the padding on the body
4682         setTimeout(function () {
4683             $('body').css(
4684                 'padding-top',
4685                 $('#floating_menubar').outerHeight(true)
4686             );
4687             $('#topmenu').menuResizer('resize');
4688         }, 4);
4689     }
4693  * Scrolls the page to the top if clicking the server-breadcrumb bar
4694  */
4695 $(function () {
4696     $(document).on('click', '#server-breadcrumb, #goto_pagetop', function (event) {
4697         event.preventDefault();
4698         $('html, body').animate({ scrollTop: 0 }, 'fast');
4699     });
4702 var checkboxesSel = 'input.checkall:checkbox:enabled';
4703 Functions.checkboxesSel = checkboxesSel;
4706  * Watches checkboxes in a form to set the checkall box accordingly
4707  */
4708 Functions.checkboxesChanged = function () {
4709     var $form = $(this.form);
4710     // total number of checkboxes in current form
4711     var totalBoxes = $form.find(checkboxesSel).length;
4712     // number of checkboxes checked in current form
4713     var checkedBoxes = $form.find(checkboxesSel + ':checked').length;
4714     var $checkall = $form.find('input.checkall_box');
4715     if (totalBoxes === checkedBoxes) {
4716         $checkall.prop({ checked: true, indeterminate: false });
4717     } else if (checkedBoxes > 0) {
4718         $checkall.prop({ checked: true, indeterminate: true });
4719     } else {
4720         $checkall.prop({ checked: false, indeterminate: false });
4721     }
4724 $(document).on('change', checkboxesSel, Functions.checkboxesChanged);
4726 $(document).on('change', 'input.checkall_box', function () {
4727     var isChecked = $(this).is(':checked');
4728     $(this.form).find(checkboxesSel).not('.row-hidden').prop('checked', isChecked)
4729         .parents('tr').toggleClass('marked', isChecked);
4732 $(document).on('click', '.checkall-filter', function () {
4733     var $this = $(this);
4734     var selector = $this.data('checkall-selector');
4735     $('input.checkall_box').prop('checked', false);
4736     $this.parents('form').find(checkboxesSel).filter(selector).prop('checked', true).trigger('change')
4737         .parents('tr').toggleClass('marked', true);
4738     return false;
4742  * Watches checkboxes in a sub form to set the sub checkall box accordingly
4743  */
4744 Functions.subCheckboxesChanged = function () {
4745     var $form = $(this).parent().parent();
4746     // total number of checkboxes in current sub form
4747     var totalBoxes = $form.find(checkboxesSel).length;
4748     // number of checkboxes checked in current sub form
4749     var checkedBoxes = $form.find(checkboxesSel + ':checked').length;
4750     var $checkall = $form.find('input.sub_checkall_box');
4751     if (totalBoxes === checkedBoxes) {
4752         $checkall.prop({ checked: true, indeterminate: false });
4753     } else if (checkedBoxes > 0) {
4754         $checkall.prop({ checked: true, indeterminate: true });
4755     } else {
4756         $checkall.prop({ checked: false, indeterminate: false });
4757     }
4760 $(document).on('change', checkboxesSel + ', input.checkall_box:checkbox:enabled', Functions.subCheckboxesChanged);
4762 $(document).on('change', 'input.sub_checkall_box', function () {
4763     var isChecked = $(this).is(':checked');
4764     var $form = $(this).parent().parent();
4765     $form.find(checkboxesSel).prop('checked', isChecked)
4766         .parents('tr').toggleClass('marked', isChecked);
4770  * Rows filtering
4772  * - rows to filter are identified by data-filter-row attribute
4773  *   which contains uppercase string to filter
4774  * - it is simple substring case insensitive search
4775  * - optionally number of matching rows is written to element with
4776  *   id filter-rows-count
4777  */
4778 $(document).on('keyup', '#filterText', function () {
4779     var filterInput = $(this).val().toUpperCase().replace(/ /g, '_');
4780     var count = 0;
4781     $('[data-filter-row]').each(function () {
4782         var $row = $(this);
4783         /* Can not use data() here as it does magic conversion to int for numeric values */
4784         if ($row.attr('data-filter-row').indexOf(filterInput) > -1) {
4785             count += 1;
4786             $row.show();
4787             $row.find('input.checkall').removeClass('row-hidden');
4788         } else {
4789             $row.hide();
4790             $row.find('input.checkall').addClass('row-hidden').prop('checked', false);
4791             $row.removeClass('marked');
4792         }
4793     });
4794     setTimeout(function () {
4795         $(checkboxesSel).trigger('change');
4796     }, 300);
4797     $('#filter-rows-count').html(count);
4799 AJAX.registerOnload('functions.js', function () {
4800     /* Trigger filtering of the list based on incoming database name */
4801     var $filter = $('#filterText');
4802     if ($filter.val()) {
4803         $filter.trigger('keyup').trigger('select');
4804     }
4808  * Formats a byte number to human-readable form
4810  * @param bytes the bytes to format
4811  * @param optional subdecimals the number of digits after the point
4812  * @param optional pointchar the char to use as decimal point
4813  */
4814 Functions.formatBytes = function (bytesToFormat, subDecimals, pointChar) {
4815     var bytes = bytesToFormat;
4816     var decimals = subDecimals;
4817     var point = pointChar;
4818     if (!decimals) {
4819         decimals = 0;
4820     }
4821     if (!point) {
4822         point = '.';
4823     }
4824     var units = ['B', 'KiB', 'MiB', 'GiB'];
4825     for (var i = 0; bytes > 1024 && i < units.length; i++) {
4826         bytes /= 1024;
4827     }
4828     var factor = Math.pow(10, decimals);
4829     bytes = Math.round(bytes * factor) / factor;
4830     bytes = bytes.toString().split('.').join(point);
4831     return bytes + ' ' + units[i];
4834 AJAX.registerOnload('functions.js', function () {
4835     /**
4836      * Reveal the login form to users with JS enabled
4837      * and focus the appropriate input field
4838      */
4839     var $loginform = $('#loginform');
4840     if ($loginform.length) {
4841         $loginform.find('.js-show').show();
4842         if ($('#input_username').val()) {
4843             $('#input_password').trigger('focus');
4844         } else {
4845             $('#input_username').trigger('focus');
4846         }
4847     }
4848     var $httpsWarning = $('#js-https-mismatch');
4849     if ($httpsWarning.length) {
4850         if ((window.location.protocol === 'https:') !== CommonParams.get('is_https')) {
4851             $httpsWarning.show();
4852         }
4853     }
4857  * Formats timestamp for display
4858  */
4859 Functions.formatDateTime = function (date, seconds) {
4860     var result = $.datepicker.formatDate('yy-mm-dd', date);
4861     var timefmt = 'HH:mm';
4862     if (seconds) {
4863         timefmt = 'HH:mm:ss';
4864     }
4865     return result + ' ' + $.datepicker.formatTime(
4866         timefmt, {
4867             hour: date.getHours(),
4868             minute: date.getMinutes(),
4869             second: date.getSeconds()
4870         }
4871     );
4875  * Check than forms have less fields than max allowed by PHP.
4876  */
4877 Functions.checkNumberOfFields = function () {
4878     if (typeof maxInputVars === 'undefined') {
4879         return false;
4880     }
4881     if (false === maxInputVars) {
4882         return false;
4883     }
4884     $('form').each(function () {
4885         var nbInputs = $(this).find(':input').length;
4886         if (nbInputs > maxInputVars) {
4887             var warning = Functions.sprintf(Messages.strTooManyInputs, maxInputVars);
4888             Functions.ajaxShowMessage(warning);
4889             return false;
4890         }
4891         return true;
4892     });
4894     return true;
4898  * Ignore the displayed php errors.
4899  * Simply removes the displayed errors.
4901  * @param  clearPrevErrors whether to clear errors stored
4902  *             in $_SESSION['prev_errors'] at server
4904  */
4905 Functions.ignorePhpErrors = function (clearPrevErrors) {
4906     var clearPrevious = clearPrevErrors;
4907     if (typeof(clearPrevious) === 'undefined' ||
4908         clearPrevious === null
4909     ) {
4910         clearPrevious = false;
4911     }
4912     // send AJAX request to /error-report with send_error_report=0, exception_type=php & token.
4913     // It clears the prev_errors stored in session.
4914     if (clearPrevious) {
4915         var $pmaReportErrorsForm = $('#pma_report_errors_form');
4916         $pmaReportErrorsForm.find('input[name="send_error_report"]').val(0); // change send_error_report to '0'
4917         $pmaReportErrorsForm.trigger('submit');
4918     }
4920     // remove displayed errors
4921     var $pmaErrors = $('#pma_errors');
4922     $pmaErrors.fadeOut('slow');
4923     $pmaErrors.remove();
4927  * Toggle the Datetimepicker UI if the date value entered
4928  * by the user in the 'text box' is not going to be accepted
4929  * by the Datetimepicker plugin (but is accepted by MySQL)
4930  */
4931 Functions.toggleDatepickerIfInvalid = function ($td, $inputField) {
4932     // Regex allowed by the Datetimepicker UI
4933     var dtexpDate = new RegExp(['^([0-9]{4})',
4934         '-(((01|03|05|07|08|10|12)-((0[1-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)',
4935         '-((0[1-9])|([1-2][0-9])|30)))$'].join(''));
4936     var dtexpTime = new RegExp(['^(([0-1][0-9])|(2[0-3]))',
4937         ':((0[0-9])|([1-5][0-9]))',
4938         ':((0[0-9])|([1-5][0-9]))(.[0-9]{1,6}){0,1}$'].join(''));
4940     // If key-ed in Time or Date values are unsupported by the UI, close it
4941     if ($td.attr('data-type') === 'date' && ! dtexpDate.test($inputField.val())) {
4942         $inputField.datepicker('hide');
4943     } else if ($td.attr('data-type') === 'time' && ! dtexpTime.test($inputField.val())) {
4944         $inputField.datepicker('hide');
4945     } else {
4946         $inputField.datepicker('show');
4947     }
4951  * Function to submit the login form after validation is done.
4952  * NOTE: do NOT use a module or it will break the callback, issue #15435
4953  */
4954 // eslint-disable-next-line no-unused-vars, camelcase
4955 var Functions_recaptchaCallback = function () {
4956     $('#login_form').trigger('submit');
4960  * Unbind all event handlers before tearing down a page
4961  */
4962 AJAX.registerTeardown('functions.js', function () {
4963     $(document).off('keydown', 'form input, form textarea, form select');
4966 AJAX.registerOnload('functions.js', function () {
4967     /**
4968      * Handle 'Ctrl/Alt + Enter' form submits
4969      */
4970     $('form input, form textarea, form select').on('keydown', function (e) {
4971         if ((e.ctrlKey && e.which === 13) || (e.altKey && e.which === 13)) {
4972             var $form = $(this).closest('form');
4974             // There could be multiple submit buttons on the same form,
4975             // we assume all of them behave identical and just click one.
4976             if (! $form.find('input[type="submit"]').first() ||
4977                 ! $form.find('input[type="submit"]').first().trigger('click')
4978             ) {
4979                 $form.trigger('submit');
4980             }
4981         }
4982     });
4986  * Unbind all event handlers before tearing down a page
4987  */
4988 AJAX.registerTeardown('functions.js', function () {
4989     $(document).off('change', 'input[type=radio][name="pw_hash"]');
4990     $(document).off('mouseover', '.sortlink');
4991     $(document).off('mouseout', '.sortlink');
4994 AJAX.registerOnload('functions.js', function () {
4995     /*
4996      * Display warning regarding SSL when sha256_password
4997      * method is selected
4998      * Used in /user-password (Change Password link on index.php)
4999      */
5000     $(document).on('change', 'select#select_authentication_plugin_cp', function () {
5001         if (this.value === 'sha256_password') {
5002             $('#ssl_reqd_warning_cp').show();
5003         } else {
5004             $('#ssl_reqd_warning_cp').hide();
5005         }
5006     });
5008     Cookies.defaults.path = CommonParams.get('rootPath');
5010     // Bind event handlers for toggling sort icons
5011     $(document).on('mouseover', '.sortlink', function () {
5012         $(this).find('.soimg').toggle();
5013     });
5014     $(document).on('mouseout', '.sortlink', function () {
5015         $(this).find('.soimg').toggle();
5016     });
5020  * Returns an HTML IMG tag for a particular image from a theme,
5021  * which may be an actual file or an icon from a sprite
5023  * @param string image      The name of the file to get
5024  * @param string alternate  Used to set 'alt' and 'title' attributes of the image
5025  * @param object attributes An associative array of other attributes
5027  * @return Object The requested image, this object has two methods:
5028  *                  .toString()        - Returns the IMG tag for the requested image
5029  *                  .attr(name)        - Returns a particular attribute of the IMG
5030  *                                       tag given it's name
5031  *                  .attr(name, value) - Sets a particular attribute of the IMG
5032  *                                       tag to the given value
5033  */
5034 Functions.getImage = function (image, alternate, attributes) {
5035     var alt = alternate;
5036     var attr = attributes;
5037     // custom image object, it will eventually be returned by this functions
5038     var retval = {
5039         data: {
5040             // this is private
5041             alt: '',
5042             title: '',
5043             src: 'themes/dot.gif',
5044         },
5045         attr: function (name, value) {
5046             if (value === undefined) {
5047                 if (this.data[name] === undefined) {
5048                     return '';
5049                 } else {
5050                     return this.data[name];
5051                 }
5052             } else {
5053                 this.data[name] = value;
5054             }
5055         },
5056         toString: function () {
5057             var retval = '<' + 'img';
5058             for (var i in this.data) {
5059                 retval += ' ' + i + '="' + this.data[i] + '"';
5060             }
5061             retval += ' /' + '>';
5062             return retval;
5063         }
5064     };
5065     // initialise missing parameters
5066     if (attr === undefined) {
5067         attr = {};
5068     }
5069     if (alt === undefined) {
5070         alt = '';
5071     }
5072     // set alt
5073     if (attr.alt !== undefined) {
5074         retval.attr('alt', Functions.escapeHtml(attr.alt));
5075     } else {
5076         retval.attr('alt', Functions.escapeHtml(alt));
5077     }
5078     // set title
5079     if (attr.title !== undefined) {
5080         retval.attr('title', Functions.escapeHtml(attr.title));
5081     } else {
5082         retval.attr('title', Functions.escapeHtml(alt));
5083     }
5084     // set css classes
5085     retval.attr('class', 'icon ic_' + image);
5086     // set all other attributes
5087     for (var i in attr) {
5088         if (i === 'src') {
5089             // do not allow to override the 'src' attribute
5090             continue;
5091         }
5093         retval.attr(i, attr[i]);
5094     }
5096     return retval;
5100  * Sets a configuration value.
5102  * A configuration value may be set in both browser's local storage and
5103  * remotely in server's configuration table.
5105  * NOTE: Depending on server's configuration, the configuration table may be or
5106  * not persistent.
5108  * @param {string}     key         Configuration key.
5109  * @param {object}     value       Configuration value.
5110  */
5111 Functions.configSet = function (key, value) {
5112     var serialized = JSON.stringify(value);
5113     localStorage.setItem(key, serialized);
5114     $.ajax({
5115         url: 'index.php?route=/ajax/config-set',
5116         type: 'POST',
5117         dataType: 'json',
5118         data: {
5119             'ajax_request': true,
5120             key: key,
5121             server: CommonParams.get('server'),
5122             value: serialized,
5123         },
5124         success: function (data) {
5125             // Updating value in local storage.
5126             if (! data.success) {
5127                 if (data.error) {
5128                     Functions.ajaxShowMessage(data.error);
5129                 } else {
5130                     Functions.ajaxShowMessage(data.message);
5131                 }
5132             }
5133             // Eventually, call callback.
5134         }
5135     });
5139  * Gets a configuration value. A configuration value will be searched in
5140  * browser's local storage first and if not found, a call to the server will be
5141  * made.
5143  * If value should not be cached and the up-to-date configuration value from
5144  * right from the server is required, the third parameter should be `false`.
5146  * @param {string}     key         Configuration key.
5147  * @param {boolean}    cached      Configuration type.
5149  * @return {object}                Configuration value.
5150  */
5151 Functions.configGet = function (key, cached) {
5152     var isCached = (typeof cached !== 'undefined') ? cached : true;
5153     var value = localStorage.getItem(key);
5154     if (isCached && value !== undefined && value !== null) {
5155         return JSON.parse(value);
5156     }
5158     // Result not found in local storage or ignored.
5159     // Hitting the server.
5160     $.ajax({
5161         // TODO: This is ugly, but usually when a configuration is needed,
5162         // processing cannot continue until that value is found.
5163         // Another solution is to provide a callback as a parameter.
5164         async: false,
5165         url: 'index.php?route=/ajax/config-get',
5166         type: 'POST',
5167         dataType: 'json',
5168         data: {
5169             'ajax_request': true,
5170             server: CommonParams.get('server'),
5171             key: key
5172         },
5173         success: function (data) {
5174             // Updating value in local storage.
5175             if (data.success) {
5176                 localStorage.setItem(key, JSON.stringify(data.value));
5177             } else {
5178                 Functions.ajaxShowMessage(data.message);
5179             }
5180             // Eventually, call callback.
5181         }
5182     });
5183     return JSON.parse(localStorage.getItem(key));
5187  * Return POST data as stored by Generator::linkOrButton
5188  */
5189 Functions.getPostData = function () {
5190     var dataPost = this.attr('data-post');
5191     // Strip possible leading ?
5192     if (dataPost !== undefined && dataPost.substring(0,1) === '?') {
5193         dataPost = dataPost.substr(1);
5194     }
5195     return dataPost;
5197 jQuery.fn.getPostData = Functions.getPostData;