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