PageSourceSearch

https://whatbills.com/phpmyadmin/js/functions.js?v=5.0.2

js whatbills.com collected 2026-10-04 15:59:38 UTC 177,939 bytes, 5,154 lines download raw bytes

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

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