Translated using Weblate (Tatar)
[phpmyadmin.git] / js / functions.js
blobbea148e732c1db53b2eab595a56e7587d55f8e4c
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * general function, usually for data manipulation pages
4  *
5  */
7 /**
8  * @var sql_box_locked lock for the sqlbox textarea in the querybox
9  */
10 var sql_box_locked = false;
12 /**
13  * @var array holds elements which content should only selected once
14  */
15 var only_once_elements = [];
17 /**
18  * @var   int   ajax_message_count   Number of AJAX messages shown since page load
19  */
20 var ajax_message_count = 0;
22 /**
23  * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab
24  */
25 var codemirror_editor = false;
27 /**
28  * @var codemirror_editor object containing CodeMirror editor of the inline query editor
29  */
30 var codemirror_inline_editor = false;
32 /**
33  * @var sql_autocomplete_in_progress bool shows if Table/Column name autocomplete AJAX is in progress
34  */
35 var sql_autocomplete_in_progress = false;
37 /**
38  * @var sql_autocomplete object containing list of columns in each table
39  */
40 var sql_autocomplete = false;
42 /**
43  * @var sql_autocomplete_default_table string containing default table to autocomplete columns
44  */
45 var sql_autocomplete_default_table = '';
47 /**
48  * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests
49  */
50 var chart_activeTimeouts = {};
52 /**
53  * @var central_column_list array to hold the columns in central list per db.
54  */
55 var central_column_list = [];
57 /**
58  * @var primary_indexes array to hold 'Primary' index columns.
59  */
60 var primary_indexes = [];
62 /**
63  * @var unique_indexes array to hold 'Unique' index columns.
64  */
65 var unique_indexes = [];
67 /**
68  * @var indexes array to hold 'Index' columns.
69  */
70 var indexes = [];
72 /**
73  * @var fulltext_indexes array to hold 'Fulltext' columns.
74  */
75 var fulltext_indexes = [];
77 /**
78  * @var spatial_indexes array to hold 'Spatial' columns.
79  */
80 var spatial_indexes = [];
82 /**
83  * Make sure that ajax requests will not be cached
84  * by appending a random variable to their parameters
85  */
86 $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
87     var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000);
88     if (typeof options.data == "string") {
89         options.data += "&_nocache=" + nocache;
90     } else if (typeof options.data == "object") {
91         options.data = $.extend(originalOptions.data, {'_nocache' : nocache});
92     }
93 });
95 /**
96  * Creates an SQL editor which supports auto completing etc.
97  *
98  * @param $textarea jQuery object wrapping the textarea to be made the editor
99  * @param options   optional options for CodeMirror
100  * @param resize    optional resizing ('vertical', 'horizontal', 'both')
101  */
102 function PMA_getSQLEditor($textarea, options, resize) {
103     if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') {
105         // merge options for CodeMirror
106         var defaults = {
107             lineNumbers: true,
108             matchBrackets: true,
109             extraKeys: {"Ctrl-Space": "autocomplete"},
110             hintOptions: {"completeSingle": false, "completeOnSingleClick": true},
111             indentUnit: 4,
112             mode: "text/x-mysql",
113             lineWrapping: true
114         };
116         if (CodeMirror.sqlLint) {
117             $.extend(defaults, {
118                 gutters: ["CodeMirror-lint-markers"],
119                 lint: {
120                     "getAnnotations": CodeMirror.sqlLint,
121                     "async": true,
122                 }
123             });
124         }
126         $.extend(true, defaults, options);
128         // create CodeMirror editor
129         var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults);
130         // allow resizing
131         if (! resize) {
132             resize = 'vertical';
133         }
134         var handles = '';
135         if (resize == 'vertical') {
136             handles = 'n, s';
137         }
138         if (resize == 'both') {
139             handles = 'all';
140         }
141         if (resize == 'horizontal') {
142             handles = 'e, w';
143         }
144         $(codemirrorEditor.getWrapperElement())
145             .css('resize', resize)
146             .resizable({
147                 handles: handles,
148                 resize: function() {
149                     codemirrorEditor.setSize($(this).width(), $(this).height());
150                 }
151             });
152         // enable autocomplete
153         codemirrorEditor.on("inputRead", codemirrorAutocompleteOnInputRead);
155         return codemirrorEditor;
156     }
157     return null;
161  * Clear text selection
162  */
163 function PMA_clearSelection() {
164     if (document.selection && document.selection.empty) {
165         document.selection.empty();
166     } else if (window.getSelection) {
167         var sel = window.getSelection();
168         if (sel.empty) {
169             sel.empty();
170         }
171         if (sel.removeAllRanges) {
172             sel.removeAllRanges();
173         }
174     }
178  * Create a jQuery UI tooltip
180  * @param $elements     jQuery object representing the elements
181  * @param item          the item
182  *                      (see http://api.jqueryui.com/tooltip/#option-items)
183  * @param myContent     content of the tooltip
184  * @param additionalOptions to override the default options
186  */
187 function PMA_tooltip($elements, item, myContent, additionalOptions)
189     if ($('#no_hint').length > 0) {
190         return;
191     }
193     var defaultOptions = {
194         content: myContent,
195         items:  item,
196         tooltipClass: "tooltip",
197         track: true,
198         show: false,
199         hide: false
200     };
202     $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
206  * HTML escaping
207  */
209 function escapeHtml(unsafe) {
210     if (typeof(unsafe) != 'undefined') {
211         return unsafe
212             .toString()
213             .replace(/&/g, "&")
214             .replace(/</g, "&lt;")
215             .replace(/>/g, "&gt;")
216             .replace(/"/g, "&quot;")
217             .replace(/'/g, "&#039;");
218     } else {
219         return false;
220     }
223 function PMA_sprintf() {
224     return sprintf.apply(this, arguments);
228  * Hides/shows the default value input field, depending on the default type
229  * Ticks the NULL checkbox if NULL is chosen as default value.
230  */
231 function PMA_hideShowDefaultValue($default_type)
233     if ($default_type.val() == 'USER_DEFINED') {
234         $default_type.siblings('.default_value').show().focus();
235     } else {
236         $default_type.siblings('.default_value').hide();
237         if ($default_type.val() == 'NULL') {
238             var $null_checkbox = $default_type.closest('tr').find('.allow_null');
239             $null_checkbox.prop('checked', true);
240         }
241     }
245  * Hides/shows the input field for column expression based on whether
246  * VIRTUAL/PERSISTENT is selected
248  * @param $virtuality virtuality dropdown
249  */
250 function PMA_hideShowExpression($virtuality)
252     if ($virtuality.val() == '') {
253         $virtuality.siblings('.expression').hide();
254     } else {
255         $virtuality.siblings('.expression').show();
256     }
260  * Show notices for ENUM columns; add/hide the default value
262  */
263 function PMA_verifyColumnsProperties()
265     $("select.column_type").each(function () {
266         PMA_showNoticeForEnum($(this));
267     });
268     $("select.default_type").each(function () {
269         PMA_hideShowDefaultValue($(this));
270     });
271     $('select.virtuality').each(function () {
272         PMA_hideShowExpression($(this));
273     });
277  * Add a hidden field to the form to indicate that this will be an
278  * Ajax request (only if this hidden field does not exist)
280  * @param $form object   the form
281  */
282 function PMA_prepareForAjaxRequest($form)
284     if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
285         $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
286     }
290  * Generate a new password and copy it to the password input areas
292  * @param passwd_form object   the form that holds the password fields
294  * @return boolean  always true
295  */
296 function suggestPassword(passwd_form)
298     // restrict the password to just letters and numbers to avoid problems:
299     // "editors and viewers regard the password as multiple words and
300     // things like double click no longer work"
301     var pwchars = "abcdefhjmnpqrstuvwxyz23456789ABCDEFGHJKLMNPQRSTUVWYXZ";
302     var passwordlength = 16;    // do we want that to be dynamic?  no, keep it simple :)
303     var passwd = passwd_form.generated_pw;
304     passwd.value = '';
306     for (var i = 0; i < passwordlength; i++) {
307         passwd.value += pwchars.charAt(Math.floor(Math.random() * pwchars.length));
308     }
309     passwd_form.text_pma_pw.value = passwd.value;
310     passwd_form.text_pma_pw2.value = passwd.value;
311     return true;
315  * Version string to integer conversion.
316  */
317 function parseVersionString(str)
319     if (typeof(str) != 'string') { return false; }
320     var add = 0;
321     // Parse possible alpha/beta/rc/
322     var state = str.split('-');
323     if (state.length >= 2) {
324         if (state[1].substr(0, 2) == 'rc') {
325             add = - 20 - parseInt(state[1].substr(2), 10);
326         } else if (state[1].substr(0, 4) == 'beta') {
327             add =  - 40 - parseInt(state[1].substr(4), 10);
328         } else if (state[1].substr(0, 5) == 'alpha') {
329             add =  - 60 - parseInt(state[1].substr(5), 10);
330         } else if (state[1].substr(0, 3) == 'dev') {
331             /* We don't handle dev, it's git snapshot */
332             add = 0;
333         }
334     }
335     // Parse version
336     var x = str.split('.');
337     // Use 0 for non existing parts
338     var maj = parseInt(x[0], 10) || 0;
339     var min = parseInt(x[1], 10) || 0;
340     var pat = parseInt(x[2], 10) || 0;
341     var hotfix = parseInt(x[3], 10) || 0;
342     return  maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
346  * Indicates current available version on main page.
347  */
348 function PMA_current_version(data)
350     if (data && data.version && data.date) {
351         var current = parseVersionString($('span.version').text());
352         var latest = parseVersionString(data.version);
353         var version_information_message = '<span class="latest">' +
354             PMA_messages.strLatestAvailable +
355             ' ' + escapeHtml(data.version) +
356             '</span>';
357         if (latest > current) {
358             var message = PMA_sprintf(
359                 PMA_messages.strNewerVersion,
360                 escapeHtml(data.version),
361                 escapeHtml(data.date)
362             );
363             var htmlClass = 'notice';
364             if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
365                 /* Security update */
366                 htmlClass = 'error';
367             }
368             $('#newer_version_notice').remove();
369             $('#maincontainer').after('<div id="newer_version_notice" class="' + htmlClass + '">' + message + '</div>');
370         }
371         if (latest === current) {
372             version_information_message = ' (' + PMA_messages.strUpToDate + ')';
373         }
374         var $liPmaVersion = $('#li_pma_version');
375         $liPmaVersion.find('span.latest').remove();
376         $liPmaVersion.append(version_information_message);
377     }
381  * Loads Git revision data from ajax for index.php
382  */
383 function PMA_display_git_revision()
385     $('#is_git_revision').remove();
386     $('#li_pma_version_git').remove();
387     $.get(
388         "index.php",
389         {
390             "server": PMA_commonParams.get('server'),
391             "token": PMA_commonParams.get('token'),
392             "git_revision": true,
393             "ajax_request": true,
394             "no_debug": true
395         },
396         function (data) {
397             if (typeof data !== 'undefined' && data.success === true) {
398                 $(data.message).insertAfter('#li_pma_version');
399             }
400         }
401     );
405  * for libraries/display_change_password.lib.php
406  *     libraries/user_password.php
408  */
410 function displayPasswordGenerateButton()
412     $('#tr_element_before_generate_password').parent().append('<tr class="vmiddle"><td>' + PMA_messages.strGeneratePassword + '</td><td><input type="button" class="button" id="button_generate_password" value="' + PMA_messages.strGenerate + '" onclick="suggestPassword(this.form)" /><input type="text" name="generated_pw" id="generated_pw" /></td></tr>');
413     $('#div_element_before_generate_password').parent().append('<div class="item"><label for="button_generate_password">' + PMA_messages.strGeneratePassword + ':</label><span class="options"><input type="button" class="button" id="button_generate_password" value="' + PMA_messages.strGenerate + '" onclick="suggestPassword(this.form)" /></span><input type="text" name="generated_pw" id="generated_pw" /></div>');
417  * Adds a date/time picker to an element
419  * @param object  $this_element   a jQuery object pointing to the element
420  */
421 function PMA_addDatepicker($this_element, type, options)
423     var showTimepicker = true;
424     if (type=="date") {
425         showTimepicker = false;
426     }
428     var defaultOptions = {
429         showOn: 'button',
430         buttonImage: themeCalendarImage, // defined in js/messages.php
431         buttonImageOnly: true,
432         stepMinutes: 1,
433         stepHours: 1,
434         showSecond: true,
435         showMillisec: true,
436         showMicrosec: true,
437         showTimepicker: showTimepicker,
438         showButtonPanel: false,
439         dateFormat: 'yy-mm-dd', // yy means year with four digits
440         timeFormat: 'HH:mm:ss.lc',
441         constrainInput: false,
442         altFieldTimeOnly: false,
443         showAnim: '',
444         beforeShow: function (input, inst) {
445             // Remember that we came from the datepicker; this is used
446             // in tbl_change.js by verificationsAfterFieldChange()
447             $this_element.data('comes_from', 'datepicker');
448             if ($(input).closest('.cEdit').length > 0) {
449                 setTimeout(function () {
450                     inst.dpDiv.css({
451                         top: 0,
452                         left: 0,
453                         position: 'relative'
454                     });
455                 }, 0);
456             }
457             // Fix wrong timepicker z-index, doesn't work without timeout
458             setTimeout(function () {
459                 $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
460             }, 0);
461         },
462         onSelect: function() {
463             $this_element.data('datepicker').inline = true;
464         },
465         onClose: function (dateText, dp_inst) {
466             // The value is no more from the date picker
467             $this_element.data('comes_from', '');
468         }
469     };
470     if (type == "datetime" || type == "timestamp") {
471         $this_element.datetimepicker($.extend(defaultOptions, options));
472     }
473     else if (type == "date") {
474         $this_element.datetimepicker($.extend(defaultOptions, options));
475     }
476     else if (type == "time") {
477         $this_element.timepicker($.extend(defaultOptions, options));
478     }
482  * selects the content of a given object, f.e. a textarea
484  * @param element     object  element of which the content will be selected
485  * @param lock        var     variable which holds the lock for this element
486  *                              or true, if no lock exists
487  * @param only_once   boolean if true this is only done once
488  *                              f.e. only on first focus
489  */
490 function selectContent(element, lock, only_once)
492     if (only_once && only_once_elements[element.name]) {
493         return;
494     }
496     only_once_elements[element.name] = true;
498     if (lock) {
499         return;
500     }
502     element.select();
506  * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
507  * This function is called while clicking links
509  * @param theLink     object the link
510  * @param theSqlQuery object the sql query to submit
512  * @return boolean  whether to run the query or not
513  */
514 function confirmLink(theLink, theSqlQuery)
516     // Confirmation is not required in the configuration file
517     // or browser is Opera (crappy js implementation)
518     if (PMA_messages.strDoYouReally === '' || typeof(window.opera) != 'undefined') {
519         return true;
520     }
522     var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, theSqlQuery));
523     if (is_confirmed) {
524         if ($(theLink).hasClass('formLinkSubmit')) {
525             var name = 'is_js_confirmed';
526             if ($(theLink).attr('href').indexOf('usesubform') != -1) {
527                 name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]';
528             }
530             $(theLink).parents('form').append('<input type="hidden" name="' + name + '" value="1" />');
531         } else if (typeof(theLink.href) != 'undefined') {
532             theLink.href += '&is_js_confirmed=1';
533         } else if (typeof(theLink.form) != 'undefined') {
534             theLink.form.action += '?is_js_confirmed=1';
535         }
536     }
538     return is_confirmed;
539 } // end of the 'confirmLink()' function
542  * Displays an error message if a "DROP DATABASE" statement is submitted
543  * while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before
544  * submitting it if required.
545  * This function is called by the 'checkSqlQuery()' js function.
547  * @param theForm1 object   the form
548  * @param sqlQuery1 object  the sql query textarea
550  * @return boolean  whether to run the query or not
552  * @see     checkSqlQuery()
553  */
554 function confirmQuery(theForm1, sqlQuery1)
556     // Confirmation is not required in the configuration file
557     if (PMA_messages.strDoYouReally === '') {
558         return true;
559     }
561     // "DROP DATABASE" statement isn't allowed
562     if (PMA_messages.strNoDropDatabases !== '') {
563         var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i');
564         if (drop_re.test(sqlQuery1.value)) {
565             alert(PMA_messages.strNoDropDatabases);
566             theForm1.reset();
567             sqlQuery1.focus();
568             return false;
569         } // end if
570     } // end if
572     // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
573     //
574     // TODO: find a way (if possible) to use the parser-analyser
575     // for this kind of verification
576     // For now, I just added a ^ to check for the statement at
577     // beginning of expression
579     var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i');
580     var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
581     var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
582     var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
584     if (do_confirm_re_0.test(sqlQuery1.value) ||
585         do_confirm_re_1.test(sqlQuery1.value) ||
586         do_confirm_re_2.test(sqlQuery1.value) ||
587         do_confirm_re_3.test(sqlQuery1.value)) {
588         var message;
589         if (sqlQuery1.value.length > 100) {
590             message = sqlQuery1.value.substr(0, 100) + '\n    ...';
591         } else {
592             message = sqlQuery1.value;
593         }
594         var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, message));
595         // statement is confirmed -> update the
596         // "is_js_confirmed" form field so the confirm test won't be
597         // run on the server side and allows to submit the form
598         if (is_confirmed) {
599             theForm1.elements.is_js_confirmed.value = 1;
600             return true;
601         }
602         // statement is rejected -> do not submit the form
603         else {
604             window.focus();
605             sqlQuery1.focus();
606             return false;
607         } // end if (handle confirm box result)
608     } // end if (display confirm box)
610     return true;
611 } // end of the 'confirmQuery()' function
614  * Displays an error message if the user submitted the sql query form with no
615  * sql query, else checks for "DROP/DELETE/ALTER" statements
617  * @param theForm object the form
619  * @return boolean  always false
621  * @see     confirmQuery()
622  */
623 function checkSqlQuery(theForm)
625     // get the textarea element containing the query
626     var sqlQuery;
627     if (codemirror_editor) {
628         codemirror_editor.save();
629         sqlQuery = codemirror_editor.getValue();
630     } else {
631         sqlQuery = theForm.elements.sql_query.value;
632     }
633     var isEmpty  = 1;
634     var space_re = new RegExp('\\s+');
635     if (typeof(theForm.elements.sql_file) != 'undefined' &&
636             theForm.elements.sql_file.value.replace(space_re, '') !== '') {
637         return true;
638     }
639     if (isEmpty && typeof(theForm.elements.id_bookmark) != 'undefined' &&
640             (theForm.elements.id_bookmark.value !== null || theForm.elements.id_bookmark.value !== '') &&
641             theForm.elements.id_bookmark.selectedIndex !== 0) {
642         return true;
643     }
644     // Checks for "DROP/DELETE/ALTER" statements
645     if (sqlQuery.replace(space_re, '') !== '') {
646         return confirmQuery(theForm, sqlQuery);
647     }
648     theForm.reset();
649     isEmpty = 1;
651     if (isEmpty) {
652         alert(PMA_messages.strFormEmpty);
653         codemirror_editor.focus();
654         return false;
655     }
657     return true;
658 } // end of the 'checkSqlQuery()' function
661  * Check if a form's element is empty.
662  * An element containing only spaces is also considered empty
664  * @param object   the form
665  * @param string   the name of the form field to put the focus on
667  * @return boolean  whether the form field is empty or not
668  */
669 function emptyCheckTheField(theForm, theFieldName)
671     var theField = theForm.elements[theFieldName];
672     var space_re = new RegExp('\\s+');
673     return theField.value.replace(space_re, '') === '';
674 } // end of the 'emptyCheckTheField()' function
677  * Ensures a value submitted in a form is numeric and is in a range
679  * @param object   the form
680  * @param string   the name of the form field to check
681  * @param integer  the minimum authorized value
682  * @param integer  the maximum authorized value
684  * @return boolean  whether a valid number has been submitted or not
685  */
686 function checkFormElementInRange(theForm, theFieldName, message, min, max)
688     var theField         = theForm.elements[theFieldName];
689     var val              = parseInt(theField.value, 10);
691     if (typeof(min) == 'undefined') {
692         min = 0;
693     }
694     if (typeof(max) == 'undefined') {
695         max = Number.MAX_VALUE;
696     }
698     // It's not a number
699     if (isNaN(val)) {
700         theField.select();
701         alert(PMA_messages.strEnterValidNumber);
702         theField.focus();
703         return false;
704     }
705     // It's a number but it is not between min and max
706     else if (val < min || val > max) {
707         theField.select();
708         alert(PMA_sprintf(message, val));
709         theField.focus();
710         return false;
711     }
712     // It's a valid number
713     else {
714         theField.value = val;
715     }
716     return true;
718 } // end of the 'checkFormElementInRange()' function
721 function checkTableEditForm(theForm, fieldsCnt)
723     // TODO: avoid sending a message if user just wants to add a line
724     // on the form but has not completed at least one field name
726     var atLeastOneField = 0;
727     var i, elm, elm2, elm3, val, id;
729     for (i = 0; i < fieldsCnt; i++) {
730         id = "#field_" + i + "_2";
731         elm = $(id);
732         val = elm.val();
733         if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') {
734             elm2 = $("#field_" + i + "_3");
735             val = parseInt(elm2.val(), 10);
736             elm3 = $("#field_" + i + "_1");
737             if (isNaN(val) && elm3.val() !== "") {
738                 elm2.select();
739                 alert(PMA_messages.strEnterValidLength);
740                 elm2.focus();
741                 return false;
742             }
743         }
745         if (atLeastOneField === 0) {
746             id = "field_" + i + "_1";
747             if (!emptyCheckTheField(theForm, id)) {
748                 atLeastOneField = 1;
749             }
750         }
751     }
752     if (atLeastOneField === 0) {
753         var theField = theForm.elements.field_0_1;
754         alert(PMA_messages.strFormEmpty);
755         theField.focus();
756         return false;
757     }
759     // at least this section is under jQuery
760     var $input = $("input.textfield[name='table']");
761     if ($input.val() === "") {
762         alert(PMA_messages.strFormEmpty);
763         $input.focus();
764         return false;
765     }
767     return true;
768 } // enf of the 'checkTableEditForm()' function
771  * True if last click is to check a row.
772  */
773 var last_click_checked = false;
776  * Zero-based index of last clicked row.
777  * Used to handle the shift + click event in the code above.
778  */
779 var last_clicked_row = -1;
782  * Zero-based index of last shift clicked row.
783  */
784 var last_shift_clicked_row = -1;
786 var _idleSecondsCounter = 0;
787 var IncInterval;
788 var updateTimeout;
789 AJAX.registerTeardown('functions.js', function () {
790     clearTimeout(updateTimeout);
791     clearInterval(IncInterval);
792     $(document).off('mousemove');
795 AJAX.registerOnload('functions.js', function () {
796     document.onclick = function() {
797         _idleSecondsCounter = 0;
798     };
799     $(document).on('mousemove',function() {
800         _idleSecondsCounter = 0;
801     });
802     document.onkeypress = function() {
803         _idleSecondsCounter = 0;
804     };
806     function SetIdleTime() {
807         _idleSecondsCounter++;
808     }
809     function UpdateIdleTime() {
810         var href = 'index.php';
811         var params = {
812                 'ajax_request' : true,
813                 'token' : PMA_commonParams.get('token'),
814                 'server' : PMA_commonParams.get('server'),
815                 'db' : PMA_commonParams.get('db'),
816                 'access_time':_idleSecondsCounter
817             };
818         $.ajax({
819                 type: 'POST',
820                 url: href,
821                 data: params,
822                 success: function (data) {
823                     if (data.success) {
824                         if (PMA_commonParams.get('LoginCookieValidity')-_idleSecondsCounter > 5) {
825                             var interval = (PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter - 5) * 1000;
826                             if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function
827                                 interval = Math.pow(2, 31) - 1;
828                             }
829                             updateTimeout = window.setTimeout(UpdateIdleTime, interval);
830                         } else {
831                             updateTimeout = window.setTimeout(UpdateIdleTime, 2000);
832                         }
833                     } else { //timeout occurred
834                         window.location.reload(true);
835                         clearInterval(IncInterval);
836                     }
837                 }
838             });
839     }
840     if (PMA_commonParams.get('logged_in') && PMA_commonParams.get('auth_type') == 'cookie') {
841         IncInterval = window.setInterval(SetIdleTime, 1000);
842         var interval = (PMA_commonParams.get('LoginCookieValidity') - 5) * 1000;
843         if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function
844             interval = Math.pow(2, 31) - 1;
845         }
846         updateTimeout = window.setTimeout(UpdateIdleTime, interval);
847     }
850  * Unbind all event handlers before tearing down a page
851  */
852 AJAX.registerTeardown('functions.js', function () {
853     $(document).off('click', 'input:checkbox.checkall');
855 AJAX.registerOnload('functions.js', function () {
856     /**
857      * Row marking in horizontal mode (use "on" so that it works also for
858      * next pages reached via AJAX); a tr may have the class noclick to remove
859      * this behavior.
860      */
862     $(document).on('click', 'input:checkbox.checkall', function (e) {
863         $this = $(this);
864         var $tr = $this.closest('tr');
865         var $table = $this.closest('table');
867         if (!e.shiftKey || last_clicked_row == -1) {
868             // usual click
870             var $checkbox = $tr.find(':checkbox.checkall');
871             var checked = $this.prop('checked');
872             $checkbox.prop('checked', checked).trigger('change');
873             if (checked) {
874                 $tr.addClass('marked');
875             } else {
876                 $tr.removeClass('marked');
877             }
878             last_click_checked = checked;
880             // remember the last clicked row
881             last_clicked_row = last_click_checked ? $table.find('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1;
882             last_shift_clicked_row = -1;
883         } else {
884             // handle the shift click
885             PMA_clearSelection();
886             var start, end;
888             // clear last shift click result
889             if (last_shift_clicked_row >= 0) {
890                 if (last_shift_clicked_row >= last_clicked_row) {
891                     start = last_clicked_row;
892                     end = last_shift_clicked_row;
893                 } else {
894                     start = last_shift_clicked_row;
895                     end = last_clicked_row;
896                 }
897                 $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
898                     .slice(start, end + 1)
899                     .removeClass('marked')
900                     .find(':checkbox')
901                     .prop('checked', false)
902                     .trigger('change');
903             }
905             // handle new shift click
906             var curr_row = $table.find('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr);
907             if (curr_row >= last_clicked_row) {
908                 start = last_clicked_row;
909                 end = curr_row;
910             } else {
911                 start = curr_row;
912                 end = last_clicked_row;
913             }
914             $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
915                 .slice(start, end + 1)
916                 .addClass('marked')
917                 .find(':checkbox')
918                 .prop('checked', true)
919                 .trigger('change');
921             // remember the last shift clicked row
922             last_shift_clicked_row = curr_row;
923         }
924     });
926     addDateTimePicker();
928     /**
929      * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
930      */
931     if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
932         $('input[type=text]').attr('autocapitalize', 'off').attr('autocorrect', 'off');
933     }
937  * Row highlighting in horizontal mode (use "on"
938  * so that it works also for pages reached via AJAX)
939  */
940 /*AJAX.registerOnload('functions.js', function () {
941     $(document).on('hover', 'tr.odd, tr.even',function (event) {
942         var $tr = $(this);
943         $tr.toggleClass('hover',event.type=='mouseover');
944         $tr.children().toggleClass('hover',event.type=='mouseover');
945     });
946 })*/
949  * This array is used to remember mark status of rows in browse mode
950  */
951 var marked_row = [];
954  * marks all rows and selects its first checkbox inside the given element
955  * the given element is usually a table or a div containing the table or tables
957  * @param container    DOM element
958  */
959 function markAllRows(container_id)
962     $("#" + container_id).find("input:checkbox:enabled").prop('checked', true)
963     .trigger("change")
964     .parents("tr").addClass("marked");
965     return true;
969  * marks all rows and selects its first checkbox inside the given element
970  * the given element is usually a table or a div containing the table or tables
972  * @param container    DOM element
973  */
974 function unMarkAllRows(container_id)
977     $("#" + container_id).find("input:checkbox:enabled").prop('checked', false)
978     .trigger("change")
979     .parents("tr").removeClass("marked");
980     return true;
984  * Checks/unchecks all checkbox in given container (f.e. a form, fieldset or div)
986  * @param string   container_id  the container id
987  * @param boolean  state         new value for checkbox (true or false)
988  * @return boolean  always true
989  */
990 function setCheckboxes(container_id, state)
993     $("#" + container_id).find("input:checkbox").prop('checked', state);
994     return true;
995 } // end of the 'setCheckboxes()' function
998   * Checks/unchecks all options of a <select> element
999   *
1000   * @param string   the form name
1001   * @param string   the element name
1002   * @param boolean  whether to check or to uncheck options
1003   *
1004   * @return boolean  always true
1005   */
1006 function setSelectOptions(the_form, the_select, do_check)
1008     $("form[name='" + the_form + "'] select[name='" + the_select + "']").find("option").prop('selected', do_check);
1009     return true;
1010 } // end of the 'setSelectOptions()' function
1013  * Sets current value for query box.
1014  */
1015 function setQuery(query)
1017     if (codemirror_editor) {
1018         codemirror_editor.setValue(query);
1019         codemirror_editor.focus();
1020     } else {
1021         document.sqlform.sql_query.value = query;
1022         document.sqlform.sql_query.focus();
1023     }
1027  * Handles 'Simulate query' button on SQL query box.
1029  * @return void
1030  */
1031 function PMA_handleSimulateQueryButton()
1033     var update_re = new RegExp('^\\s*UPDATE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+SET\\s', 'i');
1034     var delete_re = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
1035     var query = '';
1037     if (codemirror_editor) {
1038         query = codemirror_editor.getValue();
1039     } else {
1040         query = $('#sqlquery').val();
1041     }
1043     var $simulateDml = $('#simulate_dml');
1044     if (update_re.test(query) || delete_re.test(query)) {
1045         if (! $simulateDml.length) {
1046             $('#button_submit_query')
1047             .before('<input type="button" id="simulate_dml"' +
1048                 'tabindex="199" value="' +
1049                 PMA_messages.strSimulateDML +
1050                 '" />');
1051         }
1052     } else {
1053         if ($simulateDml.length) {
1054             $simulateDml.remove();
1055         }
1056     }
1060   * Create quick sql statements.
1061   *
1062   */
1063 function insertQuery(queryType)
1065     if (queryType == "clear") {
1066         setQuery('');
1067         return;
1068     } else if (queryType == "format") {
1069         if (codemirror_editor) {
1070             $('#querymessage').html(PMA_messages.strFormatting +
1071                 '&nbsp;<img class="ajaxIcon" src="' +
1072                 pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1073             var href = 'db_sql_format.php';
1074             var params = {
1075                 'ajax_request': true,
1076                 'token': PMA_commonParams.get('token'),
1077                 'sql': codemirror_editor.getValue()
1078             };
1079             $.ajax({
1080                 type: 'POST',
1081                 url: href,
1082                 data: params,
1083                 success: function (data) {
1084                     if (data.success) {
1085                         codemirror_editor.setValue(data.sql);
1086                     }
1087                     $('#querymessage').html('');
1088                 }
1089             });
1090         }
1091         return;
1092     } else if (queryType == "saved") {
1093         if ($.cookie('auto_saved_sql')) {
1094             setQuery($.cookie('auto_saved_sql'));
1095         } else {
1096             PMA_ajaxShowMessage(PMA_messages.strNoAutoSavedQuery);
1097         }
1098         return;
1099     }
1101     var query = "";
1102     var myListBox = document.sqlform.dummy;
1103     var table = document.sqlform.table.value;
1105     if (myListBox.options.length > 0) {
1106         sql_box_locked = true;
1107         var columnsList = "";
1108         var valDis = "";
1109         var editDis = "";
1110         var NbSelect = 0;
1111         for (var i = 0; i < myListBox.options.length; i++) {
1112             NbSelect++;
1113             if (NbSelect > 1) {
1114                 columnsList += ", ";
1115                 valDis += ",";
1116                 editDis += ",";
1117             }
1118             columnsList += myListBox.options[i].value;
1119             valDis += "[value-" + NbSelect + "]";
1120             editDis += myListBox.options[i].value + "=[value-" + NbSelect + "]";
1121         }
1122         if (queryType == "selectall") {
1123             query = "SELECT * FROM `" + table + "` WHERE 1";
1124         } else if (queryType == "select") {
1125             query = "SELECT " + columnsList + " FROM `" + table + "` WHERE 1";
1126         } else if (queryType == "insert") {
1127             query = "INSERT INTO `" + table + "`(" + columnsList + ") VALUES (" + valDis + ")";
1128         } else if (queryType == "update") {
1129             query = "UPDATE `" + table + "` SET " + editDis + " WHERE 1";
1130         } else if (queryType == "delete") {
1131             query = "DELETE FROM `" + table + "` WHERE 1";
1132         }
1133         setQuery(query);
1134         sql_box_locked = false;
1135     }
1140   * Inserts multiple fields.
1141   *
1142   */
1143 function insertValueQuery()
1145     var myQuery = document.sqlform.sql_query;
1146     var myListBox = document.sqlform.dummy;
1148     if (myListBox.options.length > 0) {
1149         sql_box_locked = true;
1150         var columnsList = "";
1151         var NbSelect = 0;
1152         for (var i = 0; i < myListBox.options.length; i++) {
1153             if (myListBox.options[i].selected) {
1154                 NbSelect++;
1155                 if (NbSelect > 1) {
1156                     columnsList += ", ";
1157                 }
1158                 columnsList += myListBox.options[i].value;
1159             }
1160         }
1162         /* CodeMirror support */
1163         if (codemirror_editor) {
1164             codemirror_editor.replaceSelection(columnsList);
1165         //IE support
1166         } else if (document.selection) {
1167             myQuery.focus();
1168             var sel = document.selection.createRange();
1169             sel.text = columnsList;
1170             document.sqlform.insert.focus();
1171         }
1172         //MOZILLA/NETSCAPE support
1173         else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart == "0") {
1174             var startPos = document.sqlform.sql_query.selectionStart;
1175             var endPos = document.sqlform.sql_query.selectionEnd;
1176             var SqlString = document.sqlform.sql_query.value;
1178             myQuery.value = SqlString.substring(0, startPos) + columnsList + SqlString.substring(endPos, SqlString.length);
1179         } else {
1180             myQuery.value += columnsList;
1181         }
1182         sql_box_locked = false;
1183     }
1187  * Updates the input fields for the parameters based on the query
1188  */
1189 function updateQueryParameters() {
1191     if ($('#parameterized').is(':checked')) {
1192         var query = codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val();
1194         var allParameters = query.match(/:[a-zA-Z0-9_]+/g);
1195          var parameters = [];
1196          // get unique parameters
1197          if (allParameters) {
1198              $.each(allParameters, function(i, parameter){
1199                  if ($.inArray(parameter, parameters) === -1) {
1200                      parameters.push(parameter);
1201                  }
1202              });
1203          }
1205          var $temp = $('<div />');
1206          $temp.append($('#parametersDiv').children());
1207          $('#parametersDiv').empty();
1209          $.each(parameters, function (i, parameter) {
1210              var paramName = parameter.substring(1);
1211              var $param = $temp.find('#paramSpan_' + paramName );
1212              if (! $param.length) {
1213                  $param = $('<span class="parameter" id="paramSpan_' + paramName + '" />');
1214                  $('<label for="param_' + paramName + '" />').text(parameter).appendTo($param);
1215                  $('<input type="text" name="parameters[' + parameter + ']" id="param_' + paramName + '" />').appendTo($param);
1216              }
1217              $('#parametersDiv').append($param);
1218          });
1219     } else {
1220         $('#parametersDiv').empty();
1221     }
1225  * Add a date/time picker to each element that needs it
1226  * (only when jquery-ui-timepicker-addon.js is loaded)
1227  */
1228 function addDateTimePicker() {
1229     if ($.timepicker !== undefined) {
1230         $('input.timefield, input.datefield, input.datetimefield').each(function () {
1232             var decimals = $(this).parent().attr('data-decimals');
1233             var type = $(this).parent().attr('data-type');
1235             var showMillisec = false;
1236             var showMicrosec = false;
1237             var timeFormat = 'HH:mm:ss';
1238             // check for decimal places of seconds
1239             if (decimals > 0 && type.indexOf('time') != -1){
1240                 if (decimals > 3) {
1241                     showMillisec = true;
1242                     showMicrosec = true;
1243                     timeFormat = 'HH:mm:ss.lc';
1244                 } else {
1245                     showMillisec = true;
1246                     timeFormat = 'HH:mm:ss.l';
1247                 }
1248             }
1249             PMA_addDatepicker($(this), type, {
1250                 showMillisec: showMillisec,
1251                 showMicrosec: showMicrosec,
1252                 timeFormat: timeFormat
1253             });
1254         });
1255     }
1259   * Refresh/resize the WYSIWYG scratchboard
1260   */
1261 function refreshLayout()
1263     var $elm = $('#pdflayout');
1264     var orientation = $('#orientation_opt').val();
1265     var paper = 'A4';
1266     var $paperOpt = $('#paper_opt');
1267     if ($paperOpt.length == 1) {
1268         paper = $paperOpt.val();
1269     }
1270     var posa = 'y';
1271     var posb = 'x';
1272     if (orientation == 'P') {
1273         posa = 'x';
1274         posb = 'y';
1275     }
1276     $elm.css('width', pdfPaperSize(paper, posa) + 'px');
1277     $elm.css('height', pdfPaperSize(paper, posb) + 'px');
1281  * Initializes positions of elements.
1282  */
1283 function TableDragInit() {
1284     $('.pdflayout_table').each(function () {
1285         var $this = $(this);
1286         var number = $this.data('number');
1287         var x = $('#c_table_' + number + '_x').val();
1288         var y = $('#c_table_' + number + '_y').val();
1289         $this.css('left', x + 'px');
1290         $this.css('top', y + 'px');
1291         /* Make elements draggable */
1292         $this.draggable({
1293             containment: "parent",
1294             drag: function (evt, ui) {
1295                 var number = $this.data('number');
1296                 $('#c_table_' + number + '_x').val(parseInt(ui.position.left, 10));
1297                 $('#c_table_' + number + '_y').val(parseInt(ui.position.top, 10));
1298             }
1299         });
1300     });
1304  * Resets drag and drop positions.
1305  */
1306 function resetDrag() {
1307     $('.pdflayout_table').each(function () {
1308         var $this = $(this);
1309         var x = $this.data('x');
1310         var y = $this.data('y');
1311         $this.css('left', x + 'px');
1312         $this.css('top', y + 'px');
1313     });
1317  * User schema handlers.
1318  */
1319 $(function () {
1320     /* Move in scratchboard on manual change */
1321     $(document).on('change', '.position-change', function () {
1322         var $this = $(this);
1323         var $elm = $('#table_' + $this.data('number'));
1324         $elm.css($this.data('axis'), $this.val() + 'px');
1325     });
1326     /* Refresh on paper size/orientation change */
1327     $(document).on('change', '.paper-change', function () {
1328         var $elm = $('#pdflayout');
1329         if ($elm.css('visibility') == 'visible') {
1330             refreshLayout();
1331             TableDragInit();
1332         }
1333     });
1334     /* Show/hide the WYSIWYG scratchboard */
1335     $(document).on('click', '#toggle-dragdrop', function () {
1336         var $elm = $('#pdflayout');
1337         if ($elm.css('visibility') == 'hidden') {
1338             refreshLayout();
1339             TableDragInit();
1340             $elm.css('visibility', 'visible');
1341             $elm.css('display', 'block');
1342             $('#showwysiwyg').val('1');
1343         } else {
1344             $elm.css('visibility', 'hidden');
1345             $elm.css('display', 'none');
1346             $('#showwysiwyg').val('0');
1347         }
1348     });
1349     /* Reset scratchboard */
1350     $(document).on('click', '#reset-dragdrop', function () {
1351         resetDrag();
1352     });
1356  * Returns paper sizes for a given format
1357  */
1358 function pdfPaperSize(format, axis)
1360     switch (format.toUpperCase()) {
1361     case '4A0':
1362         if (axis == 'x') {
1363             return 4767.87;
1364         } else {
1365             return 6740.79;
1366         }
1367         break;
1368     case '2A0':
1369         if (axis == 'x') {
1370             return 3370.39;
1371         } else {
1372             return 4767.87;
1373         }
1374         break;
1375     case 'A0':
1376         if (axis == 'x') {
1377             return 2383.94;
1378         } else {
1379             return 3370.39;
1380         }
1381         break;
1382     case 'A1':
1383         if (axis == 'x') {
1384             return 1683.78;
1385         } else {
1386             return 2383.94;
1387         }
1388         break;
1389     case 'A2':
1390         if (axis == 'x') {
1391             return 1190.55;
1392         } else {
1393             return 1683.78;
1394         }
1395         break;
1396     case 'A3':
1397         if (axis == 'x') {
1398             return 841.89;
1399         } else {
1400             return 1190.55;
1401         }
1402         break;
1403     case 'A4':
1404         if (axis == 'x') {
1405             return 595.28;
1406         } else {
1407             return 841.89;
1408         }
1409         break;
1410     case 'A5':
1411         if (axis == 'x') {
1412             return 419.53;
1413         } else {
1414             return 595.28;
1415         }
1416         break;
1417     case 'A6':
1418         if (axis == 'x') {
1419             return 297.64;
1420         } else {
1421             return 419.53;
1422         }
1423         break;
1424     case 'A7':
1425         if (axis == 'x') {
1426             return 209.76;
1427         } else {
1428             return 297.64;
1429         }
1430         break;
1431     case 'A8':
1432         if (axis == 'x') {
1433             return 147.40;
1434         } else {
1435             return 209.76;
1436         }
1437         break;
1438     case 'A9':
1439         if (axis == 'x') {
1440             return 104.88;
1441         } else {
1442             return 147.40;
1443         }
1444         break;
1445     case 'A10':
1446         if (axis == 'x') {
1447             return 73.70;
1448         } else {
1449             return 104.88;
1450         }
1451         break;
1452     case 'B0':
1453         if (axis == 'x') {
1454             return 2834.65;
1455         } else {
1456             return 4008.19;
1457         }
1458         break;
1459     case 'B1':
1460         if (axis == 'x') {
1461             return 2004.09;
1462         } else {
1463             return 2834.65;
1464         }
1465         break;
1466     case 'B2':
1467         if (axis == 'x') {
1468             return 1417.32;
1469         } else {
1470             return 2004.09;
1471         }
1472         break;
1473     case 'B3':
1474         if (axis == 'x') {
1475             return 1000.63;
1476         } else {
1477             return 1417.32;
1478         }
1479         break;
1480     case 'B4':
1481         if (axis == 'x') {
1482             return 708.66;
1483         } else {
1484             return 1000.63;
1485         }
1486         break;
1487     case 'B5':
1488         if (axis == 'x') {
1489             return 498.90;
1490         } else {
1491             return 708.66;
1492         }
1493         break;
1494     case 'B6':
1495         if (axis == 'x') {
1496             return 354.33;
1497         } else {
1498             return 498.90;
1499         }
1500         break;
1501     case 'B7':
1502         if (axis == 'x') {
1503             return 249.45;
1504         } else {
1505             return 354.33;
1506         }
1507         break;
1508     case 'B8':
1509         if (axis == 'x') {
1510             return 175.75;
1511         } else {
1512             return 249.45;
1513         }
1514         break;
1515     case 'B9':
1516         if (axis == 'x') {
1517             return 124.72;
1518         } else {
1519             return 175.75;
1520         }
1521         break;
1522     case 'B10':
1523         if (axis == 'x') {
1524             return 87.87;
1525         } else {
1526             return 124.72;
1527         }
1528         break;
1529     case 'C0':
1530         if (axis == 'x') {
1531             return 2599.37;
1532         } else {
1533             return 3676.54;
1534         }
1535         break;
1536     case 'C1':
1537         if (axis == 'x') {
1538             return 1836.85;
1539         } else {
1540             return 2599.37;
1541         }
1542         break;
1543     case 'C2':
1544         if (axis == 'x') {
1545             return 1298.27;
1546         } else {
1547             return 1836.85;
1548         }
1549         break;
1550     case 'C3':
1551         if (axis == 'x') {
1552             return 918.43;
1553         } else {
1554             return 1298.27;
1555         }
1556         break;
1557     case 'C4':
1558         if (axis == 'x') {
1559             return 649.13;
1560         } else {
1561             return 918.43;
1562         }
1563         break;
1564     case 'C5':
1565         if (axis == 'x') {
1566             return 459.21;
1567         } else {
1568             return 649.13;
1569         }
1570         break;
1571     case 'C6':
1572         if (axis == 'x') {
1573             return 323.15;
1574         } else {
1575             return 459.21;
1576         }
1577         break;
1578     case 'C7':
1579         if (axis == 'x') {
1580             return 229.61;
1581         } else {
1582             return 323.15;
1583         }
1584         break;
1585     case 'C8':
1586         if (axis == 'x') {
1587             return 161.57;
1588         } else {
1589             return 229.61;
1590         }
1591         break;
1592     case 'C9':
1593         if (axis == 'x') {
1594             return 113.39;
1595         } else {
1596             return 161.57;
1597         }
1598         break;
1599     case 'C10':
1600         if (axis == 'x') {
1601             return 79.37;
1602         } else {
1603             return 113.39;
1604         }
1605         break;
1606     case 'RA0':
1607         if (axis == 'x') {
1608             return 2437.80;
1609         } else {
1610             return 3458.27;
1611         }
1612         break;
1613     case 'RA1':
1614         if (axis == 'x') {
1615             return 1729.13;
1616         } else {
1617             return 2437.80;
1618         }
1619         break;
1620     case 'RA2':
1621         if (axis == 'x') {
1622             return 1218.90;
1623         } else {
1624             return 1729.13;
1625         }
1626         break;
1627     case 'RA3':
1628         if (axis == 'x') {
1629             return 864.57;
1630         } else {
1631             return 1218.90;
1632         }
1633         break;
1634     case 'RA4':
1635         if (axis == 'x') {
1636             return 609.45;
1637         } else {
1638             return 864.57;
1639         }
1640         break;
1641     case 'SRA0':
1642         if (axis == 'x') {
1643             return 2551.18;
1644         } else {
1645             return 3628.35;
1646         }
1647         break;
1648     case 'SRA1':
1649         if (axis == 'x') {
1650             return 1814.17;
1651         } else {
1652             return 2551.18;
1653         }
1654         break;
1655     case 'SRA2':
1656         if (axis == 'x') {
1657             return 1275.59;
1658         } else {
1659             return 1814.17;
1660         }
1661         break;
1662     case 'SRA3':
1663         if (axis == 'x') {
1664             return 907.09;
1665         } else {
1666             return 1275.59;
1667         }
1668         break;
1669     case 'SRA4':
1670         if (axis == 'x') {
1671             return 637.80;
1672         } else {
1673             return 907.09;
1674         }
1675         break;
1676     case 'LETTER':
1677         if (axis == 'x') {
1678             return 612.00;
1679         } else {
1680             return 792.00;
1681         }
1682         break;
1683     case 'LEGAL':
1684         if (axis == 'x') {
1685             return 612.00;
1686         } else {
1687             return 1008.00;
1688         }
1689         break;
1690     case 'EXECUTIVE':
1691         if (axis == 'x') {
1692             return 521.86;
1693         } else {
1694             return 756.00;
1695         }
1696         break;
1697     case 'FOLIO':
1698         if (axis == 'x') {
1699             return 612.00;
1700         } else {
1701             return 936.00;
1702         }
1703         break;
1704     } // end switch
1706     return 0;
1710  * Get checkbox for foreign key checks
1712  * @return string
1713  */
1714 function getForeignKeyCheckboxLoader() {
1715     var html = '';
1716     html    += '<div>';
1717     html    += '<div class="load-default-fk-check-value">';
1718     html    += PMA_getImage('ajax_clock_small.gif');
1719     html    += '</div>';
1720     html    += '</div>';
1721     return html;
1724 function loadForeignKeyCheckbox() {
1725     // Load default foreign key check value
1726     var params = {
1727         'ajax_request': true,
1728         'token': PMA_commonParams.get('token'),
1729         'server': PMA_commonParams.get('server'),
1730         'get_default_fk_check_value': true
1731     };
1732     $.get('sql.php', params, function (data) {
1733         var html = '<input type="hidden" name="fk_checks" value="0" />' +
1734             '<input type="checkbox" name="fk_checks" id="fk_checks"' +
1735             (data.default_fk_check_value ? ' checked="checked"' : '') + ' />' +
1736             '<label for="fk_checks">' + PMA_messages.strForeignKeyCheck + '</label>';
1737         $('.load-default-fk-check-value').replaceWith(html);
1738     });
1741 function getJSConfirmCommonParam(elem) {
1742     return {
1743         'is_js_confirmed' : 1,
1744         'ajax_request' : true,
1745         'fk_checks': $(elem).find('#fk_checks').is(':checked') ? 1 : 0
1746     };
1750  * Unbind all event handlers before tearing down a page
1751  */
1752 AJAX.registerTeardown('functions.js', function () {
1753     $(document).off('click', "a.inline_edit_sql");
1754     $(document).off('click', "input#sql_query_edit_save");
1755     $(document).off('click', "input#sql_query_edit_discard");
1756     $('input.sqlbutton').unbind('click');
1757     if (codemirror_editor) {
1758         codemirror_editor.off('blur');
1759     } else {
1760         $(document).off('blur', '#sqlquery');
1761     }
1762     $(document).off('change', '#parameterized');
1763     $('#sqlquery').unbind('keydown');
1764     $('#sql_query_edit').unbind('keydown');
1766     if (codemirror_inline_editor) {
1767         // Copy the sql query to the text area to preserve it.
1768         $('#sql_query_edit').text(codemirror_inline_editor.getValue());
1769         $(codemirror_inline_editor.getWrapperElement()).unbind('keydown');
1770         codemirror_inline_editor.toTextArea();
1771         codemirror_inline_editor = false;
1772     }
1773     if (codemirror_editor) {
1774         $(codemirror_editor.getWrapperElement()).unbind('keydown');
1775     }
1779  * Jquery Coding for inline editing SQL_QUERY
1780  */
1781 AJAX.registerOnload('functions.js', function () {
1782     // If we are coming back to the page by clicking forward button
1783     // of the browser, bind the code mirror to inline query editor.
1784     bindCodeMirrorToInlineEditor();
1785     $(document).on('click', "a.inline_edit_sql", function () {
1786         if ($('#sql_query_edit').length) {
1787             // An inline query editor is already open,
1788             // we don't want another copy of it
1789             return false;
1790         }
1792         var $form = $(this).prev('form');
1793         var sql_query  = $form.find("input[name='sql_query']").val().trim();
1794         var $inner_sql = $(this).parent().prev().find('code.sql');
1795         var old_text   = $inner_sql.html();
1797         var new_content = "<textarea name=\"sql_query_edit\" id=\"sql_query_edit\">" + sql_query + "</textarea>\n";
1798         new_content    += getForeignKeyCheckboxLoader();
1799         new_content    += "<input type=\"submit\" id=\"sql_query_edit_save\" class=\"button btnSave\" value=\"" + PMA_messages.strGo + "\"/>\n";
1800         new_content    += "<input type=\"button\" id=\"sql_query_edit_discard\" class=\"button btnDiscard\" value=\"" + PMA_messages.strCancel + "\"/>\n";
1801         var $editor_area = $('div#inline_editor');
1802         if ($editor_area.length === 0) {
1803             $editor_area = $('<div id="inline_editor_outer"></div>');
1804             $editor_area.insertBefore($inner_sql);
1805         }
1806         $editor_area.html(new_content);
1807         loadForeignKeyCheckbox();
1808         $inner_sql.hide();
1810         bindCodeMirrorToInlineEditor();
1811         return false;
1812     });
1814     $(document).on('click', "input#sql_query_edit_save", function () {
1815         $(".success").hide();
1816         //hide already existing success message
1817         var sql_query;
1818         if (codemirror_inline_editor) {
1819             codemirror_inline_editor.save();
1820             sql_query = codemirror_inline_editor.getValue();
1821         } else {
1822             sql_query = $(this).parent().find('#sql_query_edit').val();
1823         }
1824         var fk_check = $(this).parent().find('#fk_checks').is(':checked');
1826         var $form = $("a.inline_edit_sql").prev('form');
1827         var $fake_form = $('<form>', {action: 'import.php', method: 'post'})
1828                 .append($form.find("input[name=server], input[name=db], input[name=table], input[name=token]").clone())
1829                 .append($('<input/>', {type: 'hidden', name: 'show_query', value: 1}))
1830                 .append($('<input/>', {type: 'hidden', name: 'is_js_confirmed', value: 0}))
1831                 .append($('<input/>', {type: 'hidden', name: 'sql_query', value: sql_query}))
1832                 .append($('<input/>', {type: 'hidden', name: 'fk_checks', value: fk_check ? 1 : 0}));
1833         if (! checkSqlQuery($fake_form[0])) {
1834             return false;
1835         }
1836         $fake_form.appendTo($('body')).submit();
1837     });
1839     $(document).on('click', "input#sql_query_edit_discard", function () {
1840         var $divEditor = $('div#inline_editor_outer');
1841         $divEditor.siblings('code.sql').show();
1842         $divEditor.remove();
1843     });
1845     $('input.sqlbutton').click(function (evt) {
1846         insertQuery(evt.target.id);
1847         PMA_handleSimulateQueryButton();
1848         return false;
1849     });
1851     $(document).on('change', '#parameterized', updateQueryParameters);
1853     var $inputUsername = $('#input_username');
1854     if ($inputUsername) {
1855         if ($inputUsername.val() === '') {
1856             $inputUsername.focus();
1857         } else {
1858             $('#input_password').focus();
1859         }
1860     }
1864  * "inputRead" event handler for CodeMirror SQL query editors for autocompletion
1865  */
1866 function codemirrorAutocompleteOnInputRead(instance) {
1867     if (!sql_autocomplete_in_progress
1868         && (!instance.options.hintOptions.tables || !sql_autocomplete)) {
1870         if (!sql_autocomplete) {
1871             // Reset after teardown
1872             instance.options.hintOptions.tables = false;
1873             instance.options.hintOptions.defaultTable = '';
1875             sql_autocomplete_in_progress = true;
1877             var href = 'db_sql_autocomplete.php';
1878             var params = {
1879                 'ajax_request': true,
1880                 'token': PMA_commonParams.get('token'),
1881                 'server': PMA_commonParams.get('server'),
1882                 'db': PMA_commonParams.get('db'),
1883                 'no_debug': true
1884             };
1886             var columnHintRender = function(elem, self, data) {
1887                 $('<div class="autocomplete-column-name">')
1888                     .text(data.columnName)
1889                     .appendTo(elem);
1890                 $('<div class="autocomplete-column-hint">')
1891                     .text(data.columnHint)
1892                     .appendTo(elem);
1893             };
1895             $.ajax({
1896                 type: 'POST',
1897                 url: href,
1898                 data: params,
1899                 success: function (data) {
1900                     if (data.success) {
1901                         var tables = $.parseJSON(data.tables);
1902                         sql_autocomplete_default_table = PMA_commonParams.get('table');
1903                         sql_autocomplete = [];
1904                         for (var table in tables) {
1905                             if (tables.hasOwnProperty(table)) {
1906                                 var columns = tables[table];
1907                                 table = {
1908                                     text: table,
1909                                     columns: []
1910                                 };
1911                                 for (var column in columns) {
1912                                     if (columns.hasOwnProperty(column)) {
1913                                         var displayText = columns[column].Type;
1914                                         if (columns[column].Key == 'PRI') {
1915                                             displayText += ' | Primary';
1916                                         } else if (columns[column].Key == 'UNI') {
1917                                             displayText += ' | Unique';
1918                                         }
1919                                         table.columns.push({
1920                                             text: column,
1921                                             displayText: column + " | " +  displayText,
1922                                             columnName: column,
1923                                             columnHint: displayText,
1924                                             render: columnHintRender
1925                                         });
1926                                     }
1927                                 }
1928                             }
1929                             sql_autocomplete.push(table);
1930                         }
1931                         instance.options.hintOptions.tables = sql_autocomplete;
1932                         instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
1933                     }
1934                 },
1935                 complete: function () {
1936                     sql_autocomplete_in_progress = false;
1937                 }
1938             });
1939         }
1940         else {
1941             instance.options.hintOptions.tables = sql_autocomplete;
1942             instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
1943         }
1944     }
1945     if (instance.state.completionActive) {
1946         return;
1947     }
1948     var cur = instance.getCursor();
1949     var token = instance.getTokenAt(cur);
1950     var string = '';
1951     if (token.string.match(/^[.`\w@]\w*$/)) {
1952         string = token.string;
1953     }
1954     if (string.length > 0) {
1955         CodeMirror.commands.autocomplete(instance);
1956     }
1960  * Remove autocomplete information before tearing down a page
1961  */
1962 AJAX.registerTeardown('functions.js', function () {
1963     sql_autocomplete = false;
1964     sql_autocomplete_default_table = '';
1968  * Binds the CodeMirror to the text area used to inline edit a query.
1969  */
1970 function bindCodeMirrorToInlineEditor() {
1971     var $inline_editor = $('#sql_query_edit');
1972     if ($inline_editor.length > 0) {
1973         if (typeof CodeMirror !== 'undefined') {
1974             var height = $inline_editor.css('height');
1975             codemirror_inline_editor = PMA_getSQLEditor($inline_editor);
1976             codemirror_inline_editor.getWrapperElement().style.height = height;
1977             codemirror_inline_editor.refresh();
1978             codemirror_inline_editor.focus();
1979             $(codemirror_inline_editor.getWrapperElement())
1980                 .bind('keydown', catchKeypressesFromSqlTextboxes);
1981         } else {
1982             $inline_editor
1983                 .focus()
1984                 .bind('keydown', catchKeypressesFromSqlTextboxes);
1985         }
1986     }
1989 function catchKeypressesFromSqlTextboxes(event) {
1990     // ctrl-enter is 10 in chrome and ie, but 13 in ff
1991     if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) {
1992         if ($('#sql_query_edit').length > 0) {
1993             $("#sql_query_edit_save").trigger('click');
1994         } else if ($('#sqlquery').length > 0) {
1995             $("#button_submit_query").trigger('click');
1996         }
1997     }
2001  * Adds doc link to single highlighted SQL element
2002  */
2003 function PMA_doc_add($elm, params)
2005     if (typeof mysql_doc_template == 'undefined') {
2006         return;
2007     }
2009     var url = PMA_sprintf(
2010         decodeURIComponent(mysql_doc_template),
2011         params[0]
2012     );
2013     if (params.length > 1) {
2014         url += '#' + params[1];
2015     }
2016     var content = $elm.text();
2017     $elm.text('');
2018     $elm.append('<a target="mysql_doc" class="cm-sql-doc" href="' + url + '">' + content + '</a>');
2022  * Generates doc links for keywords inside highlighted SQL
2023  */
2024 function PMA_doc_keyword(idx, elm)
2026     var $elm = $(elm);
2027     /* Skip already processed ones */
2028     if ($elm.find('a').length > 0) {
2029         return;
2030     }
2031     var keyword = $elm.text().toUpperCase();
2032     var $next = $elm.next('.cm-keyword');
2033     if ($next) {
2034         var next_keyword = $next.text().toUpperCase();
2035         var full = keyword + ' ' + next_keyword;
2037         var $next2 = $next.next('.cm-keyword');
2038         if ($next2) {
2039             var next2_keyword = $next2.text().toUpperCase();
2040             var full2 = full + ' ' + next2_keyword;
2041             if (full2 in mysql_doc_keyword) {
2042                 PMA_doc_add($elm, mysql_doc_keyword[full2]);
2043                 PMA_doc_add($next, mysql_doc_keyword[full2]);
2044                 PMA_doc_add($next2, mysql_doc_keyword[full2]);
2045                 return;
2046             }
2047         }
2048         if (full in mysql_doc_keyword) {
2049             PMA_doc_add($elm, mysql_doc_keyword[full]);
2050             PMA_doc_add($next, mysql_doc_keyword[full]);
2051             return;
2052         }
2053     }
2054     if (keyword in mysql_doc_keyword) {
2055         PMA_doc_add($elm, mysql_doc_keyword[keyword]);
2056     }
2060  * Generates doc links for builtins inside highlighted SQL
2061  */
2062 function PMA_doc_builtin(idx, elm)
2064     var $elm = $(elm);
2065     var builtin = $elm.text().toUpperCase();
2066     if (builtin in mysql_doc_builtin) {
2067         PMA_doc_add($elm, mysql_doc_builtin[builtin]);
2068     }
2072  * Higlights SQL using CodeMirror.
2073  */
2074 function PMA_highlightSQL($base)
2076     var $elm = $base.find('code.sql');
2077     $elm.each(function () {
2078         var $sql = $(this);
2079         var $pre = $sql.find('pre');
2080         /* We only care about visible elements to avoid double processing */
2081         if ($pre.is(":visible")) {
2082             var $highlight = $('<div class="sql-highlight cm-s-default"></div>');
2083             $sql.append($highlight);
2084             if (typeof CodeMirror != 'undefined') {
2085                 CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]);
2086                 $pre.hide();
2087                 $highlight.find('.cm-keyword').each(PMA_doc_keyword);
2088                 $highlight.find('.cm-builtin').each(PMA_doc_builtin);
2089             }
2090         }
2091     });
2095  * Updates an element containing code.
2097  * @param jQuery Object $base base element which contains the raw and the
2098  *                            highlighted code.
2100  * @param string htmlValue    code in HTML format, displayed if code cannot be
2101  *                            highlighted
2103  * @param string rawValue     raw code, used as a parameter for highlighter
2105  * @return bool               whether content was updated or not
2106  */
2107 function PMA_updateCode($base, htmlValue, rawValue)
2109     var $code = $base.find('code');
2110     if ($code.length == 0) {
2111         return false;
2112     }
2114     // Determines the type of the content and appropriate CodeMirror mode.
2115     var type = '', mode = '';
2116     if  ($code.hasClass('json')) {
2117         type = 'json';
2118         mode = 'application/json';
2119     } else if ($code.hasClass('sql')) {
2120         type = 'sql';
2121         mode = 'text/x-mysql';
2122     } else if ($code.hasClass('xml')) {
2123         type = 'xml';
2124         mode = 'application/xml';
2125     } else {
2126         return false;
2127     }
2129     // Element used to display unhighlighted code.
2130     var $notHighlighted = $('<pre>' + htmlValue + '</pre>');
2132     // Tries to highlight code using CodeMirror.
2133     if (typeof CodeMirror != 'undefined') {
2134         var $highlighted = $('<div class="' + type + '-highlight cm-s-default"></div>');
2135         CodeMirror.runMode(rawValue, mode, $highlighted[0]);
2136         $notHighlighted.hide();
2137         $code.html('').append($notHighlighted, $highlighted[0]);
2138     } else {
2139         $code.html('').append($notHighlighted);
2140     }
2142     return true;
2146  * Show a message on the top of the page for an Ajax request
2148  * Sample usage:
2150  * 1) var $msg = PMA_ajaxShowMessage();
2151  * This will show a message that reads "Loading...". Such a message will not
2152  * disappear automatically and cannot be dismissed by the user. To remove this
2153  * message either the PMA_ajaxRemoveMessage($msg) function must be called or
2154  * another message must be show with PMA_ajaxShowMessage() function.
2156  * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
2157  * This is a special case. The behaviour is same as above,
2158  * just with a different message
2160  * 3) var $msg = PMA_ajaxShowMessage('The operation was successful');
2161  * This will show a message that will disappear automatically and it can also
2162  * be dismissed by the user.
2164  * 4) var $msg = PMA_ajaxShowMessage('Some error', false);
2165  * This will show a message that will not disappear automatically, but it
2166  * can be dismissed by the user after he has finished reading it.
2168  * @param string  message     string containing the message to be shown.
2169  *                              optional, defaults to 'Loading...'
2170  * @param mixed   timeout     number of milliseconds for the message to be visible
2171  *                              optional, defaults to 5000. If set to 'false', the
2172  *                              notification will never disappear
2173  * @return jQuery object       jQuery Element that holds the message div
2174  *                              this object can be passed to PMA_ajaxRemoveMessage()
2175  *                              to remove the notification
2176  */
2177 function PMA_ajaxShowMessage(message, timeout)
2179     /**
2180      * @var self_closing Whether the notification will automatically disappear
2181      */
2182     var self_closing = true;
2183     /**
2184      * @var dismissable Whether the user will be able to remove
2185      *                  the notification by clicking on it
2186      */
2187     var dismissable = true;
2188     // Handle the case when a empty data.message is passed.
2189     // We don't want the empty message
2190     if (message === '') {
2191         return true;
2192     } else if (! message) {
2193         // If the message is undefined, show the default
2194         message = PMA_messages.strLoading;
2195         dismissable = false;
2196         self_closing = false;
2197     } else if (message == PMA_messages.strProcessingRequest) {
2198         // This is another case where the message should not disappear
2199         dismissable = false;
2200         self_closing = false;
2201     }
2202     // Figure out whether (or after how long) to remove the notification
2203     if (timeout === undefined) {
2204         timeout = 5000;
2205     } else if (timeout === false) {
2206         self_closing = false;
2207     }
2208     // Create a parent element for the AJAX messages, if necessary
2209     if ($('#loading_parent').length === 0) {
2210         $('<div id="loading_parent"></div>')
2211         .prependTo("#page_content");
2212     }
2213     // Update message count to create distinct message elements every time
2214     ajax_message_count++;
2215     // Remove all old messages, if any
2216     $("span.ajax_notification[id^=ajax_message_num]").remove();
2217     /**
2218      * @var    $retval    a jQuery object containing the reference
2219      *                    to the created AJAX message
2220      */
2221     var $retval = $(
2222             '<span class="ajax_notification" id="ajax_message_num_' +
2223             ajax_message_count +
2224             '"></span>'
2225     )
2226     .hide()
2227     .appendTo("#loading_parent")
2228     .html(message)
2229     .show();
2230     // If the notification is self-closing we should create a callback to remove it
2231     if (self_closing) {
2232         $retval
2233         .delay(timeout)
2234         .fadeOut('medium', function () {
2235             if ($(this).is(':data(tooltip)')) {
2236                 $(this).tooltip('destroy');
2237             }
2238             // Remove the notification
2239             $(this).remove();
2240         });
2241     }
2242     // If the notification is dismissable we need to add the relevant class to it
2243     // and add a tooltip so that the users know that it can be removed
2244     if (dismissable) {
2245         $retval.addClass('dismissable').css('cursor', 'pointer');
2246         /**
2247          * Add a tooltip to the notification to let the user know that (s)he
2248          * can dismiss the ajax notification by clicking on it.
2249          */
2250         PMA_tooltip(
2251             $retval,
2252             'span',
2253             PMA_messages.strDismiss
2254         );
2255     }
2256     PMA_highlightSQL($retval);
2258     return $retval;
2262  * Removes the message shown for an Ajax operation when it's completed
2264  * @param jQuery object   jQuery Element that holds the notification
2266  * @return nothing
2267  */
2268 function PMA_ajaxRemoveMessage($this_msgbox)
2270     if ($this_msgbox !== undefined && $this_msgbox instanceof jQuery) {
2271         $this_msgbox
2272         .stop(true, true)
2273         .fadeOut('medium');
2274         if ($this_msgbox.is(':data(tooltip)')) {
2275             $this_msgbox.tooltip('destroy');
2276         } else {
2277             $this_msgbox.remove();
2278         }
2279     }
2283  * Requests SQL for previewing before executing.
2285  * @param jQuery Object $form Form containing query data
2287  * @return void
2288  */
2289 function PMA_previewSQL($form)
2291     var form_url = $form.attr('action');
2292     var form_data = $form.serialize() +
2293         '&do_save_data=1' +
2294         '&preview_sql=1' +
2295         '&ajax_request=1';
2296     var $msgbox = PMA_ajaxShowMessage();
2297     $.ajax({
2298         type: 'POST',
2299         url: form_url,
2300         data: form_data,
2301         success: function (response) {
2302             PMA_ajaxRemoveMessage($msgbox);
2303             if (response.success) {
2304                 var $dialog_content = $('<div/>')
2305                     .append(response.sql_data);
2306                 var button_options = {};
2307                 button_options[PMA_messages.strClose] = function () {
2308                     $(this).dialog('close');
2309                 };
2310                 var $response_dialog = $dialog_content.dialog({
2311                     minWidth: 550,
2312                     maxHeight: 400,
2313                     modal: true,
2314                     buttons: button_options,
2315                     title: PMA_messages.strPreviewSQL,
2316                     close: function () {
2317                         $(this).remove();
2318                     },
2319                     open: function () {
2320                         // Pretty SQL printing.
2321                         PMA_highlightSQL($(this));
2322                     }
2323                 });
2324             } else {
2325                 PMA_ajaxShowMessage(response.message);
2326             }
2327         },
2328         error: function () {
2329             PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest);
2330         }
2331     });
2335  * check for reserved keyword column name
2337  * @param jQuery Object $form Form
2339  * @returns true|false
2340  */
2342 function PMA_checkReservedWordColumns($form) {
2343     var is_confirmed = true;
2344     $.ajax({
2345         type: 'POST',
2346         url: "tbl_structure.php",
2347         data: $form.serialize() + '&reserved_word_check=1',
2348         success: function (data) {
2349             if (typeof data.success != 'undefined' && data.success === true) {
2350                 is_confirmed = confirm(data.message);
2351             }
2352         },
2353         async:false
2354     });
2355     return is_confirmed;
2358 // This event only need to be fired once after the initial page load
2359 $(function () {
2360     /**
2361      * Allows the user to dismiss a notification
2362      * created with PMA_ajaxShowMessage()
2363      */
2364     $(document).on('click', 'span.ajax_notification.dismissable', function () {
2365         PMA_ajaxRemoveMessage($(this));
2366     });
2367     /**
2368      * The below two functions hide the "Dismiss notification" tooltip when a user
2369      * is hovering a link or button that is inside an ajax message
2370      */
2371     $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
2372         if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
2373             $(this).parents('span.ajax_notification').tooltip('disable');
2374         }
2375     });
2376     $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
2377         if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
2378             $(this).parents('span.ajax_notification').tooltip('enable');
2379         }
2380     });
2384  * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
2385  */
2386 function PMA_showNoticeForEnum(selectElement)
2388     var enum_notice_id = selectElement.attr("id").split("_")[1];
2389     enum_notice_id += "_" + (parseInt(selectElement.attr("id").split("_")[2], 10) + 1);
2390     var selectedType = selectElement.val();
2391     if (selectedType == "ENUM" || selectedType == "SET") {
2392         $("p#enum_notice_" + enum_notice_id).show();
2393     } else {
2394         $("p#enum_notice_" + enum_notice_id).hide();
2395     }
2399  * Creates a Profiling Chart with jqplot. Used in sql.js
2400  * and in server_status_monitor.js
2401  */
2402 function PMA_createProfilingChartJqplot(target, data)
2404     return $.jqplot(target, [data],
2405         {
2406             seriesDefaults: {
2407                 renderer: $.jqplot.PieRenderer,
2408                 rendererOptions: {
2409                     showDataLabels:  true
2410                 }
2411             },
2412             highlighter: {
2413                 show: true,
2414                 tooltipLocation: 'se',
2415                 sizeAdjust: 0,
2416                 tooltipAxes: 'pieref',
2417                 useAxesFormatters: false,
2418                 formatString: '%s, %.9Ps'
2419             },
2420             legend: {
2421                 show: true,
2422                 location: 'e',
2423                 rendererOptions: {numberColumns: 2}
2424             },
2425             // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
2426             seriesColors: [
2427                 '#fce94f',
2428                 '#fcaf3e',
2429                 '#e9b96e',
2430                 '#8ae234',
2431                 '#729fcf',
2432                 '#ad7fa8',
2433                 '#ef2929',
2434                 '#eeeeec',
2435                 '#888a85',
2436                 '#c4a000',
2437                 '#ce5c00',
2438                 '#8f5902',
2439                 '#4e9a06',
2440                 '#204a87',
2441                 '#5c3566',
2442                 '#a40000',
2443                 '#babdb6',
2444                 '#2e3436'
2445             ]
2446         }
2447     );
2451  * Formats a profiling duration nicely (in us and ms time).
2452  * Used in server_status_monitor.js
2454  * @param  integer    Number to be formatted, should be in the range of microsecond to second
2455  * @param  integer    Accuracy, how many numbers right to the comma should be
2456  * @return string     The formatted number
2457  */
2458 function PMA_prettyProfilingNum(num, acc)
2460     if (!acc) {
2461         acc = 2;
2462     }
2463     acc = Math.pow(10, acc);
2464     if (num * 1000 < 0.1) {
2465         num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
2466     } else if (num < 0.1) {
2467         num = Math.round(acc * (num * 1000)) / acc + 'm';
2468     } else {
2469         num = Math.round(acc * num) / acc;
2470     }
2472     return num + 's';
2477  * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
2479  * @param string      Query to be formatted
2480  * @return string      The formatted query
2481  */
2482 function PMA_SQLPrettyPrint(string)
2484     if (typeof CodeMirror == 'undefined') {
2485         return string;
2486     }
2488     var mode = CodeMirror.getMode({}, "text/x-mysql");
2489     var stream = new CodeMirror.StringStream(string);
2490     var state = mode.startState();
2491     var token, tokens = [];
2492     var output = '';
2493     var tabs = function (cnt) {
2494         var ret = '';
2495         for (var i = 0; i < 4 * cnt; i++) {
2496             ret += " ";
2497         }
2498         return ret;
2499     };
2501     // "root-level" statements
2502     var statements = {
2503         'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'],
2504         'update': ['update', 'set', 'where'],
2505         'insert into': ['insert into', 'values']
2506     };
2507     // don't put spaces before these tokens
2508     var spaceExceptionsBefore = {';': true, ',': true, '.': true, '(': true};
2509     // don't put spaces after these tokens
2510     var spaceExceptionsAfter = {'.': true};
2512     // Populate tokens array
2513     var str = '';
2514     while (! stream.eol()) {
2515         stream.start = stream.pos;
2516         token = mode.token(stream, state);
2517         if (token !== null) {
2518             tokens.push([token, stream.current().toLowerCase()]);
2519         }
2520     }
2522     var currentStatement = tokens[0][1];
2524     if (! statements[currentStatement]) {
2525         return string;
2526     }
2527     // Holds all currently opened code blocks (statement, function or generic)
2528     var blockStack = [];
2529     // Holds the type of block from last iteration (the current is in blockStack[0])
2530     var previousBlock;
2531     // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
2532     var newBlock, endBlock;
2533     // How much to indent in the current line
2534     var indentLevel = 0;
2535     // Holds the "root-level" statements
2536     var statementPart, lastStatementPart = statements[currentStatement][0];
2538     blockStack.unshift('statement');
2540     // Iterate through every token and format accordingly
2541     for (var i = 0; i < tokens.length; i++) {
2542         previousBlock = blockStack[0];
2544         // New block => push to stack
2545         if (tokens[i][1] == '(') {
2546             if (i < tokens.length - 1 && tokens[i + 1][0] == 'statement-verb') {
2547                 blockStack.unshift(newBlock = 'statement');
2548             } else if (i > 0 && tokens[i - 1][0] == 'builtin') {
2549                 blockStack.unshift(newBlock = 'function');
2550             } else {
2551                 blockStack.unshift(newBlock = 'generic');
2552             }
2553         } else {
2554             newBlock = null;
2555         }
2557         // Block end => pop from stack
2558         if (tokens[i][1] == ')') {
2559             endBlock = blockStack[0];
2560             blockStack.shift();
2561         } else {
2562             endBlock = null;
2563         }
2565         // A subquery is starting
2566         if (i > 0 && newBlock == 'statement') {
2567             indentLevel++;
2568             output += "\n" + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + "\n" + tabs(indentLevel + 1);
2569             currentStatement = tokens[i + 1][1];
2570             i++;
2571             continue;
2572         }
2574         // A subquery is ending
2575         if (endBlock == 'statement' && indentLevel > 0) {
2576             output += "\n" + tabs(indentLevel);
2577             indentLevel--;
2578         }
2580         // One less indentation for statement parts (from, where, order by, etc.) and a newline
2581         statementPart = statements[currentStatement].indexOf(tokens[i][1]);
2582         if (statementPart != -1) {
2583             if (i > 0) {
2584                 output += "\n";
2585             }
2586             output += tabs(indentLevel) + tokens[i][1].toUpperCase();
2587             output += "\n" + tabs(indentLevel + 1);
2588             lastStatementPart = tokens[i][1];
2589         }
2590         // Normal indentation and spaces for everything else
2591         else {
2592             if (! spaceExceptionsBefore[tokens[i][1]] &&
2593                ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) &&
2594                output.charAt(output.length - 1) != ' ') {
2595                 output += " ";
2596             }
2597             if (tokens[i][0] == 'keyword') {
2598                 output += tokens[i][1].toUpperCase();
2599             } else {
2600                 output += tokens[i][1];
2601             }
2602         }
2604         // split columns in select and 'update set' clauses, but only inside statements blocks
2605         if ((lastStatementPart == 'select' || lastStatementPart == 'where'  || lastStatementPart == 'set') &&
2606             tokens[i][1] == ',' && blockStack[0] == 'statement') {
2608             output += "\n" + tabs(indentLevel + 1);
2609         }
2611         // split conditions in where clauses, but only inside statements blocks
2612         if (lastStatementPart == 'where' &&
2613             (tokens[i][1] == 'and' || tokens[i][1] == 'or' || tokens[i][1] == 'xor')) {
2615             if (blockStack[0] == 'statement') {
2616                 output += "\n" + tabs(indentLevel + 1);
2617             }
2618             // Todo: Also split and or blocks in newlines & indentation++
2619             //if (blockStack[0] == 'generic')
2620              //   output += ...
2621         }
2622     }
2623     return output;
2627  * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
2628  *  return a jQuery object yet and hence cannot be chained
2630  * @param string      question
2631  * @param string      url           URL to be passed to the callbackFn to make
2632  *                                  an Ajax call to
2633  * @param function    callbackFn    callback to execute after user clicks on OK
2634  * @param function    openCallback  optional callback to run when dialog is shown
2635  */
2637 jQuery.fn.PMA_confirm = function (question, url, callbackFn, openCallback) {
2638     var confirmState = PMA_commonParams.get('confirm');
2639     if (! confirmState) {
2640         // user does not want to confirm
2641         if ($.isFunction(callbackFn)) {
2642             callbackFn.call(this, url);
2643             return true;
2644         }
2645     }
2646     if (PMA_messages.strDoYouReally === '') {
2647         return true;
2648     }
2650     /**
2651      * @var    button_options  Object that stores the options passed to jQueryUI
2652      *                          dialog
2653      */
2654     var button_options = [
2655         {
2656             text: PMA_messages.strOK,
2657             'class': 'submitOK',
2658             click: function () {
2659                 $(this).dialog("close");
2660                 if ($.isFunction(callbackFn)) {
2661                     callbackFn.call(this, url);
2662                 }
2663             }
2664         },
2665         {
2666             text: PMA_messages.strCancel,
2667             'class': 'submitCancel',
2668             click: function () {
2669                 $(this).dialog("close");
2670             }
2671         }
2672     ];
2674     $('<div/>', {'id': 'confirm_dialog', 'title': PMA_messages.strConfirm})
2675     .prepend(question)
2676     .dialog({
2677         buttons: button_options,
2678         close: function () {
2679             $(this).remove();
2680         },
2681         open: openCallback,
2682         modal: true
2683     });
2687  * jQuery function to sort a table's body after a new row has been appended to it.
2688  * Also fixes the even/odd classes of the table rows at the end.
2690  * @param string      text_selector   string to select the sortKey's text
2692  * @return jQuery Object for chaining purposes
2693  */
2694 jQuery.fn.PMA_sort_table = function (text_selector) {
2695     return this.each(function () {
2697         /**
2698          * @var table_body  Object referring to the table's <tbody> element
2699          */
2700         var table_body = $(this);
2701         /**
2702          * @var rows    Object referring to the collection of rows in {@link table_body}
2703          */
2704         var rows = $(this).find('tr').get();
2706         //get the text of the field that we will sort by
2707         $.each(rows, function (index, row) {
2708             row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase());
2709         });
2711         //get the sorted order
2712         rows.sort(function (a, b) {
2713             if (a.sortKey < b.sortKey) {
2714                 return -1;
2715             }
2716             if (a.sortKey > b.sortKey) {
2717                 return 1;
2718             }
2719             return 0;
2720         });
2722         //pull out each row from the table and then append it according to it's order
2723         $.each(rows, function (index, row) {
2724             $(table_body).append(row);
2725             row.sortKey = null;
2726         });
2728         //Re-check the classes of each row
2729         $(this).find('tr:odd')
2730         .removeClass('even').addClass('odd')
2731         .end()
2732         .find('tr:even')
2733         .removeClass('odd').addClass('even');
2734     });
2738  * Unbind all event handlers before tearing down a page
2739  */
2740 AJAX.registerTeardown('functions.js', function () {
2741     $(document).off('submit', "#create_table_form_minimal.ajax");
2742     $(document).off('submit', "form.create_table_form.ajax");
2743     $(document).off('click', "form.create_table_form.ajax input[name=submit_num_fields]");
2744     $(document).off('keyup', "form.create_table_form.ajax input");
2745     $(document).off('change', "input[name=partition_count],input[name=subpartition_count],select[name=partition_by]");
2749  * jQuery coding for 'Create Table'.  Used on db_operations.php,
2750  * db_structure.php and db_tracking.php (i.e., wherever
2751  * libraries/display_create_table.lib.php is used)
2753  * Attach Ajax Event handlers for Create Table
2754  */
2755 AJAX.registerOnload('functions.js', function () {
2756     /**
2757      * Attach event handler for submission of create table form (save)
2758      */
2759     $(document).on('submit', "form.create_table_form.ajax", function (event) {
2760         event.preventDefault();
2762         /**
2763          * @var    the_form    object referring to the create table form
2764          */
2765         var $form = $(this);
2767         /*
2768          * First validate the form; if there is a problem, avoid submitting it
2769          *
2770          * checkTableEditForm() needs a pure element and not a jQuery object,
2771          * this is why we pass $form[0] as a parameter (the jQuery object
2772          * is actually an array of DOM elements)
2773          */
2775         if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
2776             PMA_prepareForAjaxRequest($form);
2777             if (PMA_checkReservedWordColumns($form)) {
2778                 PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
2779                 //User wants to submit the form
2780                 $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
2781                     if (typeof data !== 'undefined' && data.success === true) {
2782                         $('#properties_message')
2783                          .removeClass('error')
2784                          .html('');
2785                         PMA_ajaxShowMessage(data.message);
2786                         // Only if the create table dialog (distinct panel) exists
2787                         var $createTableDialog = $("#create_table_dialog");
2788                         if ($createTableDialog.length > 0) {
2789                             $createTableDialog.dialog("close").remove();
2790                         }
2791                         $('#tableslistcontainer').before(data.formatted_sql);
2793                         /**
2794                          * @var tables_table    Object referring to the <tbody> element that holds the list of tables
2795                          */
2796                         var tables_table = $("#tablesForm").find("tbody").not("#tbl_summary_row");
2797                         // this is the first table created in this db
2798                         if (tables_table.length === 0) {
2799                             PMA_commonActions.refreshMain(
2800                                 PMA_commonParams.get('opendb_url')
2801                             );
2802                         } else {
2803                             /**
2804                              * @var curr_last_row   Object referring to the last <tr> element in {@link tables_table}
2805                              */
2806                             var curr_last_row = $(tables_table).find('tr:last');
2807                             /**
2808                              * @var curr_last_row_index_string   String containing the index of {@link curr_last_row}
2809                              */
2810                             var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0];
2811                             /**
2812                              * @var curr_last_row_index Index of {@link curr_last_row}
2813                              */
2814                             var curr_last_row_index = parseFloat(curr_last_row_index_string);
2815                             /**
2816                              * @var new_last_row_index   Index of the new row to be appended to {@link tables_table}
2817                              */
2818                             var new_last_row_index = curr_last_row_index + 1;
2819                             /**
2820                              * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table}
2821                              */
2822                             var new_last_row_id = 'checkbox_tbl_' + new_last_row_index;
2824                             data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id);
2825                             //append to table
2826                             $(data.new_table_string)
2827                              .appendTo(tables_table);
2829                             //Sort the table
2830                             $(tables_table).PMA_sort_table('th');
2832                             // Adjust summary row
2833                             PMA_adjustTotals();
2834                         }
2836                         //Refresh navigation as a new table has been added
2837                         PMA_reloadNavigation();
2838                         // Redirect to table structure page on creation of new table
2839                         var params_12 = 'ajax_request=true&ajax_page_request=true';
2840                         if (! (history && history.pushState)) {
2841                             params_12 += PMA_MicroHistory.menus.getRequestParam();
2842                         }
2843                         tblStruct_url = 'tbl_structure.php?server=' + data._params.server +
2844                             '&db='+ data._params.db + '&token=' + data._params.token +
2845                             '&goto=db_structure.php&table=' + data._params.table + '';
2846                         $.get(tblStruct_url, params_12, AJAX.responseHandler);
2847                     } else {
2848                         PMA_ajaxShowMessage(
2849                             '<div class="error">' + data.error + '</div>',
2850                             false
2851                         );
2852                     }
2853                 }); // end $.post()
2854             }
2855         } // end if (checkTableEditForm() )
2856     }); // end create table form (save)
2858     /**
2859      * Submits the intermediate changes in the table creation form
2860      * to refresh the UI accordingly
2861      */
2862     function submitChangesInCreateTableForm (actionParam) {
2864         /**
2865          * @var    the_form    object referring to the create table form
2866          */
2867         var $form = $('form.create_table_form.ajax');
2869         var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
2870         PMA_prepareForAjaxRequest($form);
2872         //User wants to add more fields to the table
2873         $.post($form.attr('action'), $form.serialize() + "&" + actionParam, function (data) {
2874             if (typeof data !== 'undefined' && data.success) {
2875                 var $pageContent = $("#page_content");
2876                 $pageContent.html(data.message);
2877                 PMA_highlightSQL($pageContent);
2878                 PMA_verifyColumnsProperties();
2879                 PMA_hideShowConnection($('.create_table_form select[name=tbl_storage_engine]'));
2880                 PMA_ajaxRemoveMessage($msgbox);
2881             } else {
2882                 PMA_ajaxShowMessage(data.error);
2883             }
2884         }); //end $.post()
2885     }
2887     /**
2888      * Attach event handler for create table form (add fields)
2889      */
2890     $(document).on('click', "form.create_table_form.ajax input[name=submit_num_fields]", function (event) {
2891         event.preventDefault();
2893         if (!checkFormElementInRange(this.form, 'added_fields', PMA_messages.strLeastColumnError, 1)) {
2894             return;
2895         }
2897         submitChangesInCreateTableForm('submit_num_fields=1');
2898     }); // end create table form (add fields)
2900     $(document).on('keydown', "form.create_table_form.ajax input[name=added_fields]", function (event) {
2901         if (event.keyCode == 13) {
2902             event.preventDefault();
2903             event.stopImmediatePropagation();
2904             $(this)
2905                 .closest('form')
2906                 .find('input[name=submit_num_fields]')
2907                 .click();
2908         }
2909     });
2911     /**
2912      * Attach event handler to manage changes in number of partitions and subpartitions
2913      */
2914     $(document).on('change', "input[name=partition_count],input[name=subpartition_count],select[name=partition_by]", function (event) {
2915         $this = $(this);
2916         $form = $this.parents('form');
2917         if ($form.is(".create_table_form.ajax")) {
2918             submitChangesInCreateTableForm('submit_partition_change=1');
2919         } else {
2920             $form.submit();
2921         }
2922     });
2924     $("input[value=AUTO_INCREMENT]").change(function(){
2925         if (this.checked) {
2926             var col = /\d/.exec($(this).attr('name'));
2927             col = col[0];
2928             var $selectFieldKey = $('select[name="field_key[' + col + ']"]');
2929             if ($selectFieldKey.val() === 'none_'+col) {
2930                 $selectFieldKey.val('primary_'+col).change();
2931             }
2932         }
2933     });
2934     $('body')
2935     .off('click', 'input.preview_sql')
2936     .on('click', 'input.preview_sql', function () {
2937         var $form = $(this).closest('form');
2938         PMA_previewSQL($form);
2939     });
2944  * Validates the password field in a form
2946  * @see    PMA_messages.strPasswordEmpty
2947  * @see    PMA_messages.strPasswordNotSame
2948  * @param  object $the_form The form to be validated
2949  * @return bool
2950  */
2951 function PMA_checkPassword($the_form)
2953     // Did the user select 'no password'?
2954     if ($the_form.find('#nopass_1').is(':checked')) {
2955         return true;
2956     } else {
2957         var $pred = $the_form.find('#select_pred_password');
2958         if ($pred.length && ($pred.val() == 'none' || $pred.val() == 'keep')) {
2959             return true;
2960         }
2961     }
2963     var $password = $the_form.find('input[name=pma_pw]');
2964     var $password_repeat = $the_form.find('input[name=pma_pw2]');
2965     var alert_msg = false;
2967     if ($password.val() === '') {
2968         alert_msg = PMA_messages.strPasswordEmpty;
2969     } else if ($password.val() != $password_repeat.val()) {
2970         alert_msg = PMA_messages.strPasswordNotSame;
2971     }
2973     if (alert_msg) {
2974         alert(alert_msg);
2975         $password.val('');
2976         $password_repeat.val('');
2977         $password.focus();
2978         return false;
2979     }
2980     return true;
2984  * Unbind all event handlers before tearing down a page
2985  */
2986 AJAX.registerTeardown('functions.js', function () {
2987     $(document).off('click', '#change_password_anchor.ajax');
2990  * Attach Ajax event handlers for 'Change Password' on index.php
2991  */
2992 AJAX.registerOnload('functions.js', function () {
2994     /**
2995      * Attach Ajax event handler on the change password anchor
2996      */
2997     $(document).on('click', '#change_password_anchor.ajax', function (event) {
2998         event.preventDefault();
3000         var $msgbox = PMA_ajaxShowMessage();
3002         /**
3003          * @var button_options  Object containing options to be passed to jQueryUI's dialog
3004          */
3005         var button_options = {};
3006         button_options[PMA_messages.strGo] = function () {
3008             event.preventDefault();
3010             /**
3011              * @var $the_form    Object referring to the change password form
3012              */
3013             var $the_form = $("#change_password_form");
3015             if (! PMA_checkPassword($the_form)) {
3016                 return false;
3017             }
3019             /**
3020              * @var this_value  String containing the value of the submit button.
3021              * Need to append this for the change password form on Server Privileges
3022              * page to work
3023              */
3024             var this_value = $(this).val();
3026             var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
3027             $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
3029             $.post($the_form.attr('action'), $the_form.serialize() + '&change_pw=' + this_value, function (data) {
3030                 if (typeof data === 'undefined' || data.success !== true) {
3031                     PMA_ajaxShowMessage(data.error, false);
3032                     return;
3033                 }
3035                 var $pageContent = $("#page_content");
3036                 $pageContent.prepend(data.message);
3037                 PMA_highlightSQL($pageContent);
3038                 $("#change_password_dialog").hide().remove();
3039                 $("#edit_user_dialog").dialog("close").remove();
3040                 PMA_ajaxRemoveMessage($msgbox);
3041             }); // end $.post()
3042         };
3044         button_options[PMA_messages.strCancel] = function () {
3045             $(this).dialog('close');
3046         };
3047         $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
3048             if (typeof data === 'undefined' || !data.success) {
3049                 PMA_ajaxShowMessage(data.error, false);
3050                 return;
3051             }
3053             $('<div id="change_password_dialog"></div>')
3054                 .dialog({
3055                     title: PMA_messages.strChangePassword,
3056                     width: 600,
3057                     close: function (ev, ui) {
3058                         $(this).remove();
3059                     },
3060                     buttons: button_options,
3061                     modal: true
3062                 })
3063                 .append(data.message);
3064             // for this dialog, we remove the fieldset wrapping due to double headings
3065             $("fieldset#fieldset_change_password")
3066                 .find("legend").remove().end()
3067                 .find("table.noclick").unwrap().addClass("some-margin")
3068                 .find("input#text_pma_pw").focus();
3069             displayPasswordGenerateButton();
3070             $('#fieldset_change_password_footer').hide();
3071             PMA_ajaxRemoveMessage($msgbox);
3072             $('#change_password_form').bind('submit', function (e) {
3073                 e.preventDefault();
3074                 $(this)
3075                     .closest('.ui-dialog')
3076                     .find('.ui-dialog-buttonpane .ui-button')
3077                     .first()
3078                     .click();
3079             });
3080         }); // end $.get()
3081     }); // end handler for change password anchor
3082 }); // end $() for Change Password
3085  * Unbind all event handlers before tearing down a page
3086  */
3087 AJAX.registerTeardown('functions.js', function () {
3088     $(document).off('change', "select.column_type");
3089     $(document).off('change', "select.default_type");
3090     $(document).off('change', "select.virtuality");
3091     $(document).off('change', 'input.allow_null');
3092     $(document).off('change', '.create_table_form select[name=tbl_storage_engine]');
3095  * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
3096  * the page loads and when the selected data type changes
3097  */
3098 AJAX.registerOnload('functions.js', function () {
3099     // is called here for normal page loads and also when opening
3100     // the Create table dialog
3101     PMA_verifyColumnsProperties();
3102     //
3103     // needs on() to work also in the Create Table dialog
3104     $(document).on('change', "select.column_type", function () {
3105         PMA_showNoticeForEnum($(this));
3106     });
3107     $(document).on('change', "select.default_type", function () {
3108         PMA_hideShowDefaultValue($(this));
3109     });
3110     $(document).on('change', "select.virtuality", function () {
3111         PMA_hideShowExpression($(this));
3112     });
3113     $(document).on('change', 'input.allow_null', function () {
3114         PMA_validateDefaultValue($(this));
3115     });
3116     $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () {
3117         PMA_hideShowConnection($(this));
3118     });
3122  * If the chosen storage engine is FEDERATED show connection field. Hide otherwise
3124  * @param $engine_selector storage engine selector
3125  */
3126 function PMA_hideShowConnection($engine_selector)
3128     var $connection = $('.create_table_form input[name=connection]');
3129     var index = $connection.parent('td').index() + 1;
3130     var $labelTh = $connection.parents('tr').prev('tr').children('th:nth-child(' + index + ')');
3131     if ($engine_selector.val() != 'FEDERATED') {
3132         $connection
3133             .prop('disabled', true)
3134             .parent('td').hide();
3135         $labelTh.hide();
3136     } else {
3137         $connection
3138             .prop('disabled', false)
3139             .parent('td').show();
3140         $labelTh.show();
3141     }
3145  * If the column does not allow NULL values, makes sure that default is not NULL
3146  */
3147 function PMA_validateDefaultValue($null_checkbox)
3149     if (! $null_checkbox.prop('checked')) {
3150         var $default = $null_checkbox.closest('tr').find('.default_type');
3151         if ($default.val() == 'NULL') {
3152             $default.val('NONE');
3153         }
3154     }
3158  * function to populate the input fields on picking a column from central list
3160  * @param string  input_id input id of the name field for the column to be populated
3161  * @param integer offset of the selected column in central list of columns
3162  */
3163 function autoPopulate(input_id, offset)
3165     var db = PMA_commonParams.get('db');
3166     var table = PMA_commonParams.get('table');
3167     input_id = input_id.substring(0, input_id.length - 1);
3168     $('#' + input_id + '1').val(central_column_list[db + '_' + table][offset].col_name);
3169     var col_type = central_column_list[db + '_' + table][offset].col_type.toUpperCase();
3170     $('#' + input_id + '2').val(col_type);
3171     var $input3 = $('#' + input_id + '3');
3172     $input3.val(central_column_list[db + '_' + table][offset].col_length);
3173     if(col_type === 'ENUM' || col_type === 'SET') {
3174         $input3.next().show();
3175     } else {
3176         $input3.next().hide();
3177     }
3178     var col_default = central_column_list[db + '_' + table][offset].col_default.toUpperCase();
3179     var $input4 = $('#' + input_id + '4');
3180     if (col_default !== '' && col_default !== 'NULL' && col_default !== 'CURRENT_TIMESTAMP') {
3181         $input4.val("USER_DEFINED");
3182         $input4.next().next().show();
3183         $input4.next().next().val(central_column_list[db + '_' + table][offset].col_default);
3184     } else {
3185         $input4.val(central_column_list[db + '_' + table][offset].col_default);
3186         $input4.next().next().hide();
3187     }
3188     $('#' + input_id + '5').val(central_column_list[db + '_' + table][offset].col_collation);
3189     var $input6 = $('#' + input_id + '6');
3190     $input6.val(central_column_list[db + '_' + table][offset].col_attribute);
3191     if(central_column_list[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') {
3192         $input6.val(central_column_list[db + '_' + table][offset].col_extra);
3193     }
3194     if(central_column_list[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') {
3195         $('#' + input_id + '9').prop("checked",true).change();
3196     } else {
3197         $('#' + input_id + '9').prop("checked",false);
3198     }
3199     if(central_column_list[db + '_' + table][offset].col_isNull !== '0') {
3200         $('#' + input_id + '7').prop("checked",true);
3201     } else {
3202         $('#' + input_id + '7').prop("checked",false);
3203     }
3207  * Unbind all event handlers before tearing down a page
3208  */
3209 AJAX.registerTeardown('functions.js', function () {
3210     $(document).off('click', "a.open_enum_editor");
3211     $(document).off('click', "input.add_value");
3212     $(document).off('click', "#enum_editor td.drop");
3213     $(document).off('click', 'a.central_columns_dialog');
3216  * @var $enum_editor_dialog An object that points to the jQuery
3217  *                          dialog of the ENUM/SET editor
3218  */
3219 var $enum_editor_dialog = null;
3221  * Opens the ENUM/SET editor and controls its functions
3222  */
3223 AJAX.registerOnload('functions.js', function () {
3224     $(document).on('click', "a.open_enum_editor", function () {
3225         // Get the name of the column that is being edited
3226         var colname = $(this).closest('tr').find('input:first').val();
3227         var title;
3228         var i;
3229         // And use it to make up a title for the page
3230         if (colname.length < 1) {
3231             title = PMA_messages.enum_newColumnVals;
3232         } else {
3233             title = PMA_messages.enum_columnVals.replace(
3234                 /%s/,
3235                 '"' + escapeHtml(decodeURIComponent(colname)) + '"'
3236             );
3237         }
3238         // Get the values as a string
3239         var inputstring = $(this)
3240             .closest('td')
3241             .find("input")
3242             .val();
3243         // Escape html entities
3244         inputstring = $('<div/>')
3245             .text(inputstring)
3246             .html();
3247         // Parse the values, escaping quotes and
3248         // slashes on the fly, into an array
3249         var values = [];
3250         var in_string = false;
3251         var curr, next, buffer = '';
3252         for (i = 0; i < inputstring.length; i++) {
3253             curr = inputstring.charAt(i);
3254             next = i == inputstring.length ? '' : inputstring.charAt(i + 1);
3255             if (! in_string && curr == "'") {
3256                 in_string = true;
3257             } else if (in_string && curr == "\\" && next == "\\") {
3258                 buffer += "&#92;";
3259                 i++;
3260             } else if (in_string && next == "'" && (curr == "'" || curr == "\\")) {
3261                 buffer += "&#39;";
3262                 i++;
3263             } else if (in_string && curr == "'") {
3264                 in_string = false;
3265                 values.push(buffer);
3266                 buffer = '';
3267             } else if (in_string) {
3268                 buffer += curr;
3269             }
3270         }
3271         if (buffer.length > 0) {
3272             // The leftovers in the buffer are the last value (if any)
3273             values.push(buffer);
3274         }
3275         var fields = '';
3276         // If there are no values, maybe the user is about to make a
3277         // new list so we add a few for him/her to get started with.
3278         if (values.length === 0) {
3279             values.push('', '', '', '');
3280         }
3281         // Add the parsed values to the editor
3282         var drop_icon = PMA_getImage('b_drop.png');
3283         for (i = 0; i < values.length; i++) {
3284             fields += "<tr><td>" +
3285                    "<input type='text' value='" + values[i] + "'/>" +
3286                    "</td><td class='drop'>" +
3287                    drop_icon +
3288                    "</td></tr>";
3289         }
3290         /**
3291          * @var dialog HTML code for the ENUM/SET dialog
3292          */
3293         var dialog = "<div id='enum_editor'>" +
3294                    "<fieldset>" +
3295                     "<legend>" + title + "</legend>" +
3296                     "<p>" + PMA_getImage('s_notice.png') +
3297                     PMA_messages.enum_hint + "</p>" +
3298                     "<table class='values'>" + fields + "</table>" +
3299                     "</fieldset><fieldset class='tblFooters'>" +
3300                     "<table class='add'><tr><td>" +
3301                     "<div class='slider'></div>" +
3302                     "</td><td>" +
3303                     "<form><div><input type='submit' class='add_value' value='" +
3304                     PMA_sprintf(PMA_messages.enum_addValue, 1) +
3305                     "'/></div></form>" +
3306                     "</td></tr></table>" +
3307                     "<input type='hidden' value='" + // So we know which column's data is being edited
3308                     $(this).closest('td').find("input").attr("id") +
3309                     "' />" +
3310                     "</fieldset>" +
3311                     "</div>";
3312         /**
3313          * @var  Defines functions to be called when the buttons in
3314          * the buttonOptions jQuery dialog bar are pressed
3315          */
3316         var buttonOptions = {};
3317         buttonOptions[PMA_messages.strGo] = function () {
3318             // When the submit button is clicked,
3319             // put the data back into the original form
3320             var value_array = [];
3321             $(this).find(".values input").each(function (index, elm) {
3322                 var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, "''");
3323                 value_array.push("'" + val + "'");
3324             });
3325             // get the Length/Values text field where this value belongs
3326             var values_id = $(this).find("input[type='hidden']").val();
3327             $("input#" + values_id).val(value_array.join(","));
3328             $(this).dialog("close");
3329         };
3330         buttonOptions[PMA_messages.strClose] = function () {
3331             $(this).dialog("close");
3332         };
3333         // Show the dialog
3334         var width = parseInt(
3335             (parseInt($('html').css('font-size'), 10) / 13) * 340,
3336             10
3337         );
3338         if (! width) {
3339             width = 340;
3340         }
3341         $enum_editor_dialog = $(dialog).dialog({
3342             minWidth: width,
3343             maxHeight: 450,
3344             modal: true,
3345             title: PMA_messages.enum_editor,
3346             buttons: buttonOptions,
3347             open: function () {
3348                 // Focus the "Go" button after opening the dialog
3349                 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
3350             },
3351             close: function () {
3352                 $(this).remove();
3353             }
3354         });
3355         // slider for choosing how many fields to add
3356         $enum_editor_dialog.find(".slider").slider({
3357             animate: true,
3358             range: "min",
3359             value: 1,
3360             min: 1,
3361             max: 9,
3362             slide: function (event, ui) {
3363                 $(this).closest('table').find('input[type=submit]').val(
3364                     PMA_sprintf(PMA_messages.enum_addValue, ui.value)
3365                 );
3366             }
3367         });
3368         // Focus the slider, otherwise it looks nearly transparent
3369         $('a.ui-slider-handle').addClass('ui-state-focus');
3370         return false;
3371     });
3373     $(document).on('click', 'a.central_columns_dialog', function (e) {
3374         var href = "db_central_columns.php";
3375         var db = PMA_commonParams.get('db');
3376         var table = PMA_commonParams.get('table');
3377         var maxRows = $(this).data('maxrows');
3378         var pick = $(this).data('pick');
3379         if (pick !== false) {
3380             pick = true;
3381         }
3382         var params = {
3383             'ajax_request' : true,
3384             'token' : PMA_commonParams.get('token'),
3385             'server' : PMA_commonParams.get('server'),
3386             'db' : PMA_commonParams.get('db'),
3387             'cur_table' : PMA_commonParams.get('table'),
3388             'getColumnList':true
3389         };
3390         var colid = $(this).closest('td').find("input").attr("id");
3391         var fields = '';
3392         if (! (db + '_' + table in central_column_list)) {
3393             central_column_list.push(db + '_' + table);
3394             $.ajax({
3395                 type: 'POST',
3396                 url: href,
3397                 data: params,
3398                 success: function (data) {
3399                     central_column_list[db + '_' + table] = $.parseJSON(data.message);
3400                 },
3401                 async:false
3402             });
3403         }
3404         var i = 0;
3405         var list_size = central_column_list[db + '_' + table].length;
3406         var min = (list_size <= maxRows) ? list_size : maxRows;
3407         for (i = 0; i < min; i++) {
3409             fields += '<tr><td><div><span style="font-weight:bold">' +
3410                 escapeHtml(central_column_list[db + '_' + table][i].col_name) +
3411                 '</span><br><span style="color:gray">' + central_column_list[db + '_' + table][i].col_type;
3413             if (central_column_list[db + '_' + table][i].col_attribute !== '') {
3414                 fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_attribute) + ') ';
3415             }
3416             if (central_column_list[db + '_' + table][i].col_length !== '') {
3417                 fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_length) +') ';
3418             }
3419             fields += escapeHtml(central_column_list[db + '_' + table][i].col_extra) + '</span>' +
3420                 '</div></td>';
3421             if (pick) {
3422                 fields += '<td><input class="pick" style="width:100%" type="submit" value="' +
3423                     PMA_messages.pickColumn + '" onclick="autoPopulate(\'' + colid + '\',' + i + ')"/></td>';
3424             }
3425             fields += '</tr>';
3426         }
3427         var result_pointer = i;
3428         var search_in = '<input type="text" class="filter_rows" placeholder="' + PMA_messages.searchList + '">';
3429         if (fields === '') {
3430             fields = PMA_sprintf(PMA_messages.strEmptyCentralList, "'" + db + "'");
3431             search_in = '';
3432         }
3433         var seeMore = '';
3434         if (list_size > maxRows) {
3435             seeMore = "<fieldset class='tblFooters' style='text-align:center;font-weight:bold'>" +
3436                 "<a href='#' id='seeMore'>" + PMA_messages.seeMore + "</a></fieldset>";
3437         }
3438         var central_columns_dialog = "<div style='max-height:400px'>" +
3439             "<fieldset>" +
3440             search_in +
3441             "<table id='col_list' style='width:100%' class='values'>" + fields + "</table>" +
3442             "</fieldset>" +
3443             seeMore +
3444             "</div>";
3446         var width = parseInt(
3447             (parseInt($('html').css('font-size'), 10) / 13) * 500,
3448             10
3449         );
3450         if (! width) {
3451             width = 500;
3452         }
3453         var buttonOptions = {};
3454         var $central_columns_dialog = $(central_columns_dialog).dialog({
3455             minWidth: width,
3456             maxHeight: 450,
3457             modal: true,
3458             title: PMA_messages.pickColumnTitle,
3459             buttons: buttonOptions,
3460             open: function () {
3461                 $('#col_list').on("click", ".pick", function (){
3462                     $central_columns_dialog.remove();
3463                 });
3464                 $(".filter_rows").on("keyup", function () {
3465                     $.uiTableFilter($("#col_list"), $(this).val());
3466                 });
3467                 $("#seeMore").click(function() {
3468                     fields = '';
3469                     min = (list_size <= maxRows + result_pointer) ? list_size : maxRows + result_pointer;
3470                     for (i = result_pointer; i < min; i++) {
3472                         fields += '<tr><td><div><span style="font-weight:bold">' +
3473                             central_column_list[db + '_' + table][i].col_name +
3474                             '</span><br><span style="color:gray">' +
3475                             central_column_list[db + '_' + table][i].col_type;
3477                         if (central_column_list[db + '_' + table][i].col_attribute !== '') {
3478                             fields += '(' + central_column_list[db + '_' + table][i].col_attribute + ') ';
3479                         }
3480                         if (central_column_list[db + '_' + table][i].col_length !== '') {
3481                             fields += '(' + central_column_list[db + '_' + table][i].col_length + ') ';
3482                         }
3483                         fields += central_column_list[db + '_' + table][i].col_extra + '</span>' +
3484                             '</div></td>';
3485                         if (pick) {
3486                             fields += '<td><input class="pick" style="width:100%" type="submit" value="' +
3487                                 PMA_messages.pickColumn + '" onclick="autoPopulate(\'' + colid + '\',' + i + ')"/></td>';
3488                         }
3489                         fields += '</tr>';
3490                     }
3491                     $("#col_list").append(fields);
3492                     result_pointer = i;
3493                     if (result_pointer === list_size) {
3494                         $('.tblFooters').hide();
3495                     }
3496                     return false;
3497                 });
3498                 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
3499             },
3500             close: function () {
3501                 $('#col_list').off("click", ".pick");
3502                 $(".filter_rows").off("keyup");
3503                 $(this).remove();
3504             }
3505         });
3506         return false;
3507     });
3509    // $(document).on('click', 'a.show_central_list',function(e) {
3511    // });
3512     // When "add a new value" is clicked, append an empty text field
3513     $(document).on('click', "input.add_value", function (e) {
3514         e.preventDefault();
3515         var num_new_rows = $enum_editor_dialog.find("div.slider").slider('value');
3516         while (num_new_rows--) {
3517             $enum_editor_dialog.find('.values')
3518                 .append(
3519                     "<tr style='display: none;'><td>" +
3520                     "<input type='text' />" +
3521                     "</td><td class='drop'>" +
3522                     PMA_getImage('b_drop.png') +
3523                     "</td></tr>"
3524                 )
3525                 .find('tr:last')
3526                 .show('fast');
3527         }
3528     });
3530     // Removes the specified row from the enum editor
3531     $(document).on('click', "#enum_editor td.drop", function () {
3532         $(this).closest('tr').hide('fast', function () {
3533             $(this).remove();
3534         });
3535     });
3539  * Ensures indexes names are valid according to their type and, for a primary
3540  * key, lock index name to 'PRIMARY'
3541  * @param string   form_id  Variable which parses the form name as
3542  *                            the input
3543  * @return boolean  false    if there is no index form, true else
3544  */
3545 function checkIndexName(form_id)
3547     if ($("#" + form_id).length === 0) {
3548         return false;
3549     }
3551     // Gets the elements pointers
3552     var $the_idx_name = $("#input_index_name");
3553     var $the_idx_choice = $("#select_index_choice");
3555     // Index is a primary key
3556     if ($the_idx_choice.find("option:selected").val() == 'PRIMARY') {
3557         $the_idx_name.val('PRIMARY');
3558         $the_idx_name.prop("disabled", true);
3559     }
3561     // Other cases
3562     else {
3563         if ($the_idx_name.val() == 'PRIMARY') {
3564             $the_idx_name.val("");
3565         }
3566         $the_idx_name.prop("disabled", false);
3567     }
3569     return true;
3570 } // end of the 'checkIndexName()' function
3572 AJAX.registerTeardown('functions.js', function () {
3573     $(document).off('click', '#index_frm input[type=submit]');
3575 AJAX.registerOnload('functions.js', function () {
3576     /**
3577      * Handler for adding more columns to an index in the editor
3578      */
3579     $(document).on('click', '#index_frm input[type=submit]', function (event) {
3580         event.preventDefault();
3581         var rows_to_add = $(this)
3582             .closest('fieldset')
3583             .find('.slider')
3584             .slider('value');
3586         var tempEmptyVal = function () {
3587             $(this).val('');
3588         };
3590         var tempSetFocus = function () {
3591             if ($(this).find("option:selected").val() === '') {
3592                 return true;
3593             }
3594             $(this).closest("tr").find("input").focus();
3595         };
3597         while (rows_to_add--) {
3598             var $indexColumns = $('#index_columns');
3599             var $newrow = $indexColumns
3600                 .find('tbody > tr:first')
3601                 .clone()
3602                 .appendTo(
3603                     $indexColumns.find('tbody')
3604                 );
3605             $newrow.find(':input').each(tempEmptyVal);
3606             // focus index size input on column picked
3607             $newrow.find('select').change(tempSetFocus);
3608         }
3609     });
3612 function indexEditorDialog(url, title, callback_success, callback_failure)
3614     /*Remove the hidden dialogs if there are*/
3615     var $editIndexDialog = $('#edit_index_dialog');
3616     if ($editIndexDialog.length !== 0) {
3617         $editIndexDialog.remove();
3618     }
3619     var $div = $('<div id="edit_index_dialog"></div>');
3621     /**
3622      * @var button_options Object that stores the options
3623      *                     passed to jQueryUI dialog
3624      */
3625     var button_options = {};
3626     button_options[PMA_messages.strGo] = function () {
3627         /**
3628          * @var    the_form    object referring to the export form
3629          */
3630         var $form = $("#index_frm");
3631         var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
3632         PMA_prepareForAjaxRequest($form);
3633         //User wants to submit the form
3634         $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
3635             var $sqlqueryresults = $(".sqlqueryresults");
3636             if ($sqlqueryresults.length !== 0) {
3637                 $sqlqueryresults.remove();
3638             }
3639             if (typeof data !== 'undefined' && data.success === true) {
3640                 PMA_ajaxShowMessage(data.message);
3641                 var $resultQuery = $('.result_query');
3642                 if ($resultQuery.length) {
3643                     $resultQuery.remove();
3644                 }
3645                 if (data.sql_query) {
3646                     $('<div class="result_query"></div>')
3647                         .html(data.sql_query)
3648                         .prependTo('#page_content');
3649                     PMA_highlightSQL($('#page_content'));
3650                 }
3651                 $(".result_query .notice").remove();
3652                 $resultQuery.prepend(data.message);
3653                 /*Reload the field form*/
3654                 $("#table_index").remove();
3655                 $("<div id='temp_div'><div>")
3656                     .append(data.index_table)
3657                     .find("#table_index")
3658                     .insertAfter("#index_header");
3659                 var $editIndexDialog = $("#edit_index_dialog");
3660                 if ($editIndexDialog.length > 0) {
3661                     $editIndexDialog.dialog("close");
3662                 }
3663                 $('div.no_indexes_defined').hide();
3664                 if (callback_success) {
3665                     callback_success();
3666                 }
3667                 PMA_reloadNavigation();
3668             } else {
3669                 var $temp_div = $("<div id='temp_div'><div>").append(data.error);
3670                 var $error;
3671                 if ($temp_div.find(".error code").length !== 0) {
3672                     $error = $temp_div.find(".error code").addClass("error");
3673                 } else {
3674                     $error = $temp_div;
3675                 }
3676                 if (callback_failure) {
3677                     callback_failure();
3678                 }
3679                 PMA_ajaxShowMessage($error, false);
3680             }
3681         }); // end $.post()
3682     };
3683     button_options[PMA_messages.strPreviewSQL] = function () {
3684         // Function for Previewing SQL
3685         var $form = $('#index_frm');
3686         PMA_previewSQL($form);
3687     };
3688     button_options[PMA_messages.strCancel] = function () {
3689         $(this).dialog('close');
3690     };
3691     var $msgbox = PMA_ajaxShowMessage();
3692     $.get("tbl_indexes.php", url, function (data) {
3693         if (typeof data !== 'undefined' && data.success === false) {
3694             //in the case of an error, show the error message returned.
3695             PMA_ajaxShowMessage(data.error, false);
3696         } else {
3697             PMA_ajaxRemoveMessage($msgbox);
3698             // Show dialog if the request was successful
3699             $div
3700             .append(data.message)
3701             .dialog({
3702                 title: title,
3703                 width: 450,
3704                 height: 350,
3705                 open: PMA_verifyColumnsProperties,
3706                 modal: true,
3707                 buttons: button_options,
3708                 close: function () {
3709                     $(this).remove();
3710                 }
3711             });
3712             $div.find('.tblFooters').remove();
3713             showIndexEditDialog($div);
3714         }
3715     }); // end $.get()
3718 function showIndexEditDialog($outer)
3720     checkIndexType();
3721     checkIndexName("index_frm");
3722     var $indexColumns = $('#index_columns');
3723     $indexColumns.find('td').each(function () {
3724         $(this).css("width", $(this).width() + 'px');
3725     });
3726     $indexColumns.find('tbody').sortable({
3727         axis: 'y',
3728         containment: $indexColumns.find("tbody"),
3729         tolerance: 'pointer'
3730     });
3731     PMA_showHints($outer);
3732     PMA_init_slider();
3733     // Add a slider for selecting how many columns to add to the index
3734     $outer.find('.slider').slider({
3735         animate: true,
3736         value: 1,
3737         min: 1,
3738         max: 16,
3739         slide: function (event, ui) {
3740             $(this).closest('fieldset').find('input[type=submit]').val(
3741                 PMA_sprintf(PMA_messages.strAddToIndex, ui.value)
3742             );
3743         }
3744     });
3745     $('div.add_fields').removeClass('hide');
3746     // focus index size input on column picked
3747     $outer.find('table#index_columns select').change(function () {
3748         if ($(this).find("option:selected").val() === '') {
3749             return true;
3750         }
3751         $(this).closest("tr").find("input").focus();
3752     });
3753     // Focus the slider, otherwise it looks nearly transparent
3754     $('a.ui-slider-handle').addClass('ui-state-focus');
3755     // set focus on index name input, if empty
3756     var input = $outer.find('input#input_index_name');
3757     input.val() || input.focus();
3761  * Function to display tooltips that were
3762  * generated on the PHP side by PMA\libraries\Util::showHint()
3764  * @param object $div a div jquery object which specifies the
3765  *                    domain for searching for tooltips. If we
3766  *                    omit this parameter the function searches
3767  *                    in the whole body
3768  **/
3769 function PMA_showHints($div)
3771     if ($div === undefined || ! $div instanceof jQuery || $div.length === 0) {
3772         $div = $("body");
3773     }
3774     $div.find('.pma_hint').each(function () {
3775         PMA_tooltip(
3776             $(this).children('img'),
3777             'img',
3778             $(this).children('span').html()
3779         );
3780     });
3783 AJAX.registerOnload('functions.js', function () {
3784     PMA_showHints();
3787 function PMA_mainMenuResizerCallback() {
3788     // 5 px margin for jumping menu in Chrome
3789     return $(document.body).width() - 5;
3791 // This must be fired only once after the initial page load
3792 $(function () {
3793     // Initialise the menu resize plugin
3794     $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
3795     // register resize event
3796     $(window).resize(function () {
3797         $('#topmenu').menuResizer('resize');
3798     });
3802  * Get the row number from the classlist (for example, row_1)
3803  */
3804 function PMA_getRowNumber(classlist)
3806     return parseInt(classlist.split(/\s+row_/)[1], 10);
3810  * Changes status of slider
3811  */
3812 function PMA_set_status_label($element)
3814     var text;
3815     if ($element.css('display') == 'none') {
3816         text = '+ ';
3817     } else {
3818         text = '- ';
3819     }
3820     $element.closest('.slide-wrapper').prev().find('span').text(text);
3824  * var  toggleButton  This is a function that creates a toggle
3825  *                    sliding button given a jQuery reference
3826  *                    to the correct DOM element
3827  */
3828 var toggleButton = function ($obj) {
3829     // In rtl mode the toggle switch is flipped horizontally
3830     // so we need to take that into account
3831     var right;
3832     if ($('span.text_direction', $obj).text() == 'ltr') {
3833         right = 'right';
3834     } else {
3835         right = 'left';
3836     }
3837     /**
3838      *  var  h  Height of the button, used to scale the
3839      *          background image and position the layers
3840      */
3841     var h = $obj.height();
3842     $('img', $obj).height(h);
3843     $('table', $obj).css('bottom', h - 1);
3844     /**
3845      *  var  on   Width of the "ON" part of the toggle switch
3846      *  var  off  Width of the "OFF" part of the toggle switch
3847      */
3848     var on  = $('td.toggleOn', $obj).width();
3849     var off = $('td.toggleOff', $obj).width();
3850     // Make the "ON" and "OFF" parts of the switch the same size
3851     // + 2 pixels to avoid overflowed
3852     $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
3853     $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
3854     /**
3855      *  var  w  Width of the central part of the switch
3856      */
3857     var w = parseInt(($('img', $obj).height() / 16) * 22, 10);
3858     // Resize the central part of the switch on the top
3859     // layer to match the background
3860     $('table td:nth-child(2) > div', $obj).width(w);
3861     /**
3862      *  var  imgw    Width of the background image
3863      *  var  tblw    Width of the foreground layer
3864      *  var  offset  By how many pixels to move the background
3865      *               image, so that it matches the top layer
3866      */
3867     var imgw = $('img', $obj).width();
3868     var tblw = $('table', $obj).width();
3869     var offset = parseInt(((imgw - tblw) / 2), 10);
3870     // Move the background to match the layout of the top layer
3871     $obj.find('img').css(right, offset);
3872     /**
3873      *  var  offw    Outer width of the "ON" part of the toggle switch
3874      *  var  btnw    Outer width of the central part of the switch
3875      */
3876     var offw = $('td.toggleOff', $obj).outerWidth();
3877     var btnw = $('table td:nth-child(2)', $obj).outerWidth();
3878     // Resize the main div so that exactly one side of
3879     // the switch plus the central part fit into it.
3880     $obj.width(offw + btnw + 2);
3881     /**
3882      *  var  move  How many pixels to move the
3883      *             switch by when toggling
3884      */
3885     var move = $('td.toggleOff', $obj).outerWidth();
3886     // If the switch is initialized to the
3887     // OFF state we need to move it now.
3888     if ($('div.container', $obj).hasClass('off')) {
3889         if (right == 'right') {
3890             $('div.container', $obj).animate({'left': '-=' + move + 'px'}, 0);
3891         } else {
3892             $('div.container', $obj).animate({'left': '+=' + move + 'px'}, 0);
3893         }
3894     }
3895     // Attach an 'onclick' event to the switch
3896     $('div.container', $obj).click(function () {
3897         if ($(this).hasClass('isActive')) {
3898             return false;
3899         } else {
3900             $(this).addClass('isActive');
3901         }
3902         var $msg = PMA_ajaxShowMessage();
3903         var $container = $(this);
3904         var callback = $('span.callback', this).text();
3905         var operator, url, removeClass, addClass;
3906         // Perform the actual toggle
3907         if ($(this).hasClass('on')) {
3908             if (right == 'right') {
3909                 operator = '-=';
3910             } else {
3911                 operator = '+=';
3912             }
3913             url = $(this).find('td.toggleOff > span').text();
3914             removeClass = 'on';
3915             addClass = 'off';
3916         } else {
3917             if (right == 'right') {
3918                 operator = '+=';
3919             } else {
3920                 operator = '-=';
3921             }
3922             url = $(this).find('td.toggleOn > span').text();
3923             removeClass = 'off';
3924             addClass = 'on';
3925         }
3926         $.post(url, {'ajax_request': true}, function (data) {
3927             if (typeof data !== 'undefined' && data.success === true) {
3928                 PMA_ajaxRemoveMessage($msg);
3929                 $container
3930                 .removeClass(removeClass)
3931                 .addClass(addClass)
3932                 .animate({'left': operator + move + 'px'}, function () {
3933                     $container.removeClass('isActive');
3934                 });
3935                 eval(callback);
3936             } else {
3937                 PMA_ajaxShowMessage(data.error, false);
3938                 $container.removeClass('isActive');
3939             }
3940         });
3941     });
3945  * Unbind all event handlers before tearing down a page
3946  */
3947 AJAX.registerTeardown('functions.js', function () {
3948     $('div.container').unbind('click');
3951  * Initialise all toggle buttons
3952  */
3953 AJAX.registerOnload('functions.js', function () {
3954     $('div.toggleAjax').each(function () {
3955         var $button = $(this).show();
3956         $button.find('img').each(function () {
3957             if (this.complete) {
3958                 toggleButton($button);
3959             } else {
3960                 $(this).load(function () {
3961                     toggleButton($button);
3962                 });
3963             }
3964         });
3965     });
3969  * Unbind all event handlers before tearing down a page
3970  */
3971 AJAX.registerTeardown('functions.js', function () {
3972     $(document).off('change', 'select.pageselector');
3973     $(document).off('click', 'a.formLinkSubmit');
3974     $('#update_recent_tables').unbind('ready');
3975     $('#sync_favorite_tables').unbind('ready');
3978 AJAX.registerOnload('functions.js', function () {
3980     /**
3981      * Autosubmit page selector
3982      */
3983     $(document).on('change', 'select.pageselector', function (event) {
3984         event.stopPropagation();
3985         // Check where to load the new content
3986         if ($(this).closest("#pma_navigation").length === 0) {
3987             // For the main page we don't need to do anything,
3988             $(this).closest("form").submit();
3989         } else {
3990             // but for the navigation we need to manually replace the content
3991             PMA_navigationTreePagination($(this));
3992         }
3993     });
3995     /**
3996      * Load version information asynchronously.
3997      */
3998     if ($('li.jsversioncheck').length > 0) {
3999         $.getJSON('version_check.php', {'server' : PMA_commonParams.get('server')}, PMA_current_version);
4000     }
4002     if ($('#is_git_revision').length > 0) {
4003         setTimeout(PMA_display_git_revision, 10);
4004     }
4006     /**
4007      * Slider effect.
4008      */
4009     PMA_init_slider();
4011     /**
4012      * Enables the text generated by PMA\libraries\Util::linkOrButton() to be clickable
4013      */
4014     $(document).on('click', 'a.formLinkSubmit', function (e) {
4015         if (! $(this).hasClass('requireConfirm')) {
4016             submitFormLink($(this));
4017             return false;
4018         }
4019     });
4021     var $updateRecentTables = $('#update_recent_tables');
4022     if ($updateRecentTables.length) {
4023         $.get(
4024             $updateRecentTables.attr('href'),
4025             {no_debug: true},
4026             function (data) {
4027                 if (typeof data !== 'undefined' && data.success === true) {
4028                     $('#pma_recent_list').html(data.list);
4029                 }
4030             }
4031         );
4032     }
4034     // Sync favorite tables from localStorage to pmadb.
4035     if ($('#sync_favorite_tables').length) {
4036         $.ajax({
4037             url: $('#sync_favorite_tables').attr("href"),
4038             cache: false,
4039             type: 'POST',
4040             data: {
4041                 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
4042                     ? window.localStorage.favorite_tables
4043                     : '',
4044                 no_debug: true
4045             },
4046             success: function (data) {
4047                 // Update localStorage.
4048                 if (isStorageSupported('localStorage')) {
4049                     window.localStorage.favorite_tables = data.favorite_tables;
4050                 }
4051                 $('#pma_favorite_list').html(data.list);
4052             }
4053         });
4054     }
4055 }); // end of $()
4058  * Submits the form placed in place of a link due to the excessive url length
4060  * @param $link anchor
4061  * @returns {Boolean}
4062  */
4063 function submitFormLink($link)
4065     if ($link.attr('href').indexOf('=') != -1) {
4066         var data = $link.attr('href').substr($link.attr('href').indexOf('#') + 1).split('=', 2);
4067         $link.parents('form').append('<input type="hidden" name="' + data[0] + '" value="' + data[1] + '"/>');
4068     }
4069     $link.parents('form').submit();
4073  * Initializes slider effect.
4074  */
4075 function PMA_init_slider()
4077     $('div.pma_auto_slider').each(function () {
4078         var $this = $(this);
4079         if ($this.data('slider_init_done')) {
4080             return;
4081         }
4082         var $wrapper = $('<div>', {'class': 'slide-wrapper'});
4083         $wrapper.toggle($this.is(':visible'));
4084         $('<a>', {href: '#' + this.id, "class": 'ajax'})
4085             .text($this.attr('title'))
4086             .prepend($('<span>'))
4087             .insertBefore($this)
4088             .click(function () {
4089                 var $wrapper = $this.closest('.slide-wrapper');
4090                 var visible = $this.is(':visible');
4091                 if (!visible) {
4092                     $wrapper.show();
4093                 }
4094                 $this[visible ? 'hide' : 'show']('blind', function () {
4095                     $wrapper.toggle(!visible);
4096                     $wrapper.parent().toggleClass("print_ignore", visible);
4097                     PMA_set_status_label($this);
4098                 });
4099                 return false;
4100             });
4101         $this.wrap($wrapper);
4102         $this.removeAttr('title');
4103         PMA_set_status_label($this);
4104         $this.data('slider_init_done', 1);
4105     });
4109  * Initializes slider effect.
4110  */
4111 AJAX.registerOnload('functions.js', function () {
4112     PMA_init_slider();
4116  * Restores sliders to the state they were in before initialisation.
4117  */
4118 AJAX.registerTeardown('functions.js', function () {
4119     $('div.pma_auto_slider').each(function () {
4120         var $this = $(this);
4121         $this.removeData();
4122         $this.parent().replaceWith($this);
4123         $this.parent().children('a').remove();
4124     });
4128  * Creates a message inside an object with a sliding effect
4130  * @param msg    A string containing the text to display
4131  * @param $obj   a jQuery object containing the reference
4132  *                 to the element where to put the message
4133  *                 This is optional, if no element is
4134  *                 provided, one will be created below the
4135  *                 navigation links at the top of the page
4137  * @return bool   True on success, false on failure
4138  */
4139 function PMA_slidingMessage(msg, $obj)
4141     if (msg === undefined || msg.length === 0) {
4142         // Don't show an empty message
4143         return false;
4144     }
4145     if ($obj === undefined || ! $obj instanceof jQuery || $obj.length === 0) {
4146         // If the second argument was not supplied,
4147         // we might have to create a new DOM node.
4148         if ($('#PMA_slidingMessage').length === 0) {
4149             $('#page_content').prepend(
4150                 '<span id="PMA_slidingMessage" ' +
4151                 'style="display: inline-block;"></span>'
4152             );
4153         }
4154         $obj = $('#PMA_slidingMessage');
4155     }
4156     if ($obj.has('div').length > 0) {
4157         // If there already is a message inside the
4158         // target object, we must get rid of it
4159         $obj
4160         .find('div')
4161         .first()
4162         .fadeOut(function () {
4163             $obj
4164             .children()
4165             .remove();
4166             $obj
4167             .append('<div>' + msg + '</div>');
4168             // highlight any sql before taking height;
4169             PMA_highlightSQL($obj);
4170             $obj.find('div')
4171                 .first()
4172                 .hide();
4173             $obj
4174             .animate({
4175                 height: $obj.find('div').first().height()
4176             })
4177             .find('div')
4178             .first()
4179             .fadeIn();
4180         });
4181     } else {
4182         // Object does not already have a message
4183         // inside it, so we simply slide it down
4184         $obj.width('100%')
4185             .html('<div>' + msg + '</div>');
4186         // highlight any sql before taking height;
4187         PMA_highlightSQL($obj);
4188         var h = $obj
4189             .find('div')
4190             .first()
4191             .hide()
4192             .height();
4193         $obj
4194         .find('div')
4195         .first()
4196         .css('height', 0)
4197         .show()
4198         .animate({
4199                 height: h
4200             }, function () {
4201             // Set the height of the parent
4202             // to the height of the child
4203                 $obj
4204                 .height(
4205                     $obj
4206                     .find('div')
4207                     .first()
4208                     .height()
4209                 );
4210             });
4211     }
4212     return true;
4213 } // end PMA_slidingMessage()
4216  * Attach CodeMirror2 editor to SQL edit area.
4217  */
4218 AJAX.registerOnload('functions.js', function () {
4219     var $elm = $('#sqlquery');
4220     if ($elm.length > 0) {
4221         if (typeof CodeMirror != 'undefined') {
4222             codemirror_editor = PMA_getSQLEditor($elm);
4223             codemirror_editor.focus();
4224             $(codemirror_editor.getWrapperElement())
4225                 .bind('keydown', catchKeypressesFromSqlTextboxes);
4226             codemirror_editor.on("blur", updateQueryParameters);
4227         } else {
4228             // without codemirror
4229             $elm.focus()
4230                 .bind('keydown', catchKeypressesFromSqlTextboxes)
4231                 .bind('blur', updateQueryParameters);
4232         }
4233     }
4234     PMA_highlightSQL($('body'));
4236 AJAX.registerTeardown('functions.js', function () {
4237     if (codemirror_editor) {
4238         $('#sqlquery').text(codemirror_editor.getValue());
4239         codemirror_editor.toTextArea();
4240         codemirror_editor = false;
4241     }
4243 AJAX.registerOnload('functions.js', function () {
4244     // initializes all lock-page elements lock-id and
4245     // val-hash data property
4246     $('#page_content form.lock-page textarea, ' +
4247             '#page_content form.lock-page input[type="text"], '+
4248             '#page_content form.lock-page input[type="number"], '+
4249             '#page_content form.lock-page select').each(function (i) {
4250         $(this).data('lock-id', i);
4251         // val-hash is the hash of default value of the field
4252         // so that it can be compared with new value hash
4253         // to check whether field was modified or not.
4254         $(this).data('val-hash', AJAX.hash($(this).val()));
4255     });
4257     // initializes lock-page elements (input types checkbox and radio buttons)
4258     // lock-id and val-hash data property
4259     $('#page_content form.lock-page input[type="checkbox"], ' +
4260             '#page_content form.lock-page input[type="radio"]').each(function (i) {
4261         $(this).data('lock-id', i);
4262         $(this).data('val-hash', AJAX.hash($(this).is(":checked")));
4263     });
4266  * jQuery plugin to cancel selection in HTML code.
4267  */
4268 (function ($) {
4269     $.fn.noSelect = function (p) { //no select plugin by Paulo P.Marinas
4270         var prevent = (p === null) ? true : p;
4271         var is_msie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv\:11\./);
4272         var is_firefox = navigator.userAgent.indexOf('Firefox') > -1;
4273         var is_safari = navigator.userAgent.indexOf("Safari") > -1;
4274         var is_opera = navigator.userAgent.indexOf("Presto") > -1;
4275         if (prevent) {
4276             return this.each(function () {
4277                 if (is_msie || is_safari) {
4278                     $(this).bind('selectstart', function () {
4279                         return false;
4280                     });
4281                 } else if (is_firefox) {
4282                     $(this).css('MozUserSelect', 'none');
4283                     $('body').trigger('focus');
4284                 } else if (is_opera) {
4285                     $(this).bind('mousedown', function () {
4286                         return false;
4287                     });
4288                 } else {
4289                     $(this).attr('unselectable', 'on');
4290                 }
4291             });
4292         } else {
4293             return this.each(function () {
4294                 if (is_msie || is_safari) {
4295                     $(this).unbind('selectstart');
4296                 } else if (is_firefox) {
4297                     $(this).css('MozUserSelect', 'inherit');
4298                 } else if (is_opera) {
4299                     $(this).unbind('mousedown');
4300                 } else {
4301                     $(this).removeAttr('unselectable');
4302                 }
4303             });
4304         }
4305     }; //end noSelect
4306 })(jQuery);
4309  * jQuery plugin to correctly filter input fields by value, needed
4310  * because some nasty values may break selector syntax
4311  */
4312 (function ($) {
4313     $.fn.filterByValue = function (value) {
4314         return this.filter(function () {
4315             return $(this).val() === value;
4316         });
4317     };
4318 })(jQuery);
4321  * Return value of a cell in a table.
4322  */
4323 function PMA_getCellValue(td) {
4324     var $td = $(td);
4325     if ($td.is('.null')) {
4326         return '';
4327     } else if (! $td.is('.to_be_saved') && $td.data('original_data')) {
4328         return $td.data('original_data');
4329     } else {
4330         return $td.text();
4331     }
4335  * Unbind all event handlers before tearing down a page
4336  */
4337 AJAX.registerTeardown('functions.js', function () {
4338     $(document).off('click', 'a.themeselect');
4339     $(document).off('change', '.autosubmit');
4340     $('a.take_theme').unbind('click');
4343 AJAX.registerOnload('functions.js', function () {
4344     /**
4345      * Theme selector.
4346      */
4347     $(document).on('click', 'a.themeselect', function (e) {
4348         window.open(
4349             e.target,
4350             'themes',
4351             'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes'
4352             );
4353         return false;
4354     });
4356     /**
4357      * Automatic form submission on change.
4358      */
4359     $(document).on('change', '.autosubmit', function (e) {
4360         $(this).closest('form').submit();
4361     });
4363     /**
4364      * Theme changer.
4365      */
4366     $('a.take_theme').click(function (e) {
4367         var what = this.name;
4368         if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) {
4369             window.opener.document.forms.setTheme.elements.set_theme.value = what;
4370             window.opener.document.forms.setTheme.submit();
4371             window.close();
4372             return false;
4373         }
4374         return true;
4375     });
4379  * Print button
4380  */
4381 function printPage()
4383     // Do print the page
4384     if (typeof(window.print) != 'undefined') {
4385         window.print();
4386     }
4390  * Unbind all event handlers before tearing down a page
4391  */
4392 AJAX.registerTeardown('functions.js', function () {
4393     $('input#print').unbind('click');
4394     $(document).off('click', 'a.create_view.ajax');
4395     $(document).off('keydown', '#createViewDialog input, #createViewDialog select');
4396     $(document).off('change', '#fkc_checkbox');
4399 AJAX.registerOnload('functions.js', function () {
4400     $('input#print').click(printPage);
4401     /**
4402      * Ajaxification for the "Create View" action
4403      */
4404     $(document).on('click', 'a.create_view.ajax', function (e) {
4405         e.preventDefault();
4406         PMA_createViewDialog($(this));
4407     });
4408     /**
4409      * Attach Ajax event handlers for input fields in the editor
4410      * and used to submit the Ajax request when the ENTER key is pressed.
4411      */
4412     if ($('#createViewDialog').length !== 0) {
4413         $(document).on('keydown', '#createViewDialog input, #createViewDialog select', function (e) {
4414             if (e.which === 13) { // 13 is the ENTER key
4415                 e.preventDefault();
4417                 // with preventing default, selection by <select> tag
4418                 // was also prevented in IE
4419                 $(this).blur();
4421                 $(this).closest('.ui-dialog').find('.ui-button:first').click();
4422             }
4423         }); // end $(document).on()
4424     }
4426     syntaxHighlighter = PMA_getSQLEditor($('textarea[name="view[as]"]'));
4430 function PMA_createViewDialog($this)
4432     var $msg = PMA_ajaxShowMessage();
4433     var syntaxHighlighter = null;
4434     $.get($this.attr('href') + '&ajax_request=1&ajax_dialog=1', function (data) {
4435         if (typeof data !== 'undefined' && data.success === true) {
4436             PMA_ajaxRemoveMessage($msg);
4437             var buttonOptions = {};
4438             buttonOptions[PMA_messages.strGo] = function () {
4439                 if (typeof CodeMirror !== 'undefined') {
4440                     syntaxHighlighter.save();
4441                 }
4442                 $msg = PMA_ajaxShowMessage();
4443                 $.post('view_create.php', $('#createViewDialog').find('form').serialize(), function (data) {
4444                     PMA_ajaxRemoveMessage($msg);
4445                     if (typeof data !== 'undefined' && data.success === true) {
4446                         $('#createViewDialog').dialog("close");
4447                         $('.result_query').html(data.message);
4448                         PMA_reloadNavigation();
4449                     } else {
4450                         PMA_ajaxShowMessage(data.error, false);
4451                     }
4452                 });
4453             };
4454             buttonOptions[PMA_messages.strClose] = function () {
4455                 $(this).dialog("close");
4456             };
4457             var $dialog = $('<div/>').attr('id', 'createViewDialog').append(data.message).dialog({
4458                 width: 600,
4459                 minWidth: 400,
4460                 modal: true,
4461                 buttons: buttonOptions,
4462                 title: PMA_messages.strCreateView,
4463                 close: function () {
4464                     $(this).remove();
4465                 }
4466             });
4467             // Attach syntax highlighted editor
4468             syntaxHighlighter = PMA_getSQLEditor($dialog.find('textarea'));
4469             $('input:visible[type=text]', $dialog).first().focus();
4470         } else {
4471             PMA_ajaxShowMessage(data.error);
4472         }
4473     });
4477  * Makes the breadcrumbs and the menu bar float at the top of the viewport
4478  */
4479 $(function () {
4480     if ($("#floating_menubar").length && $('#PMA_disable_floating_menubar').length === 0) {
4481         var left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
4482         $("#floating_menubar")
4483             .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width())
4484             .css(left, 0)
4485             .css({
4486                 'position': 'fixed',
4487                 'top': 0,
4488                 'width': '100%',
4489                 'z-index': 99
4490             })
4491             .append($('#serverinfo'))
4492             .append($('#topmenucontainer'));
4493         // Allow the DOM to render, then adjust the padding on the body
4494         setTimeout(function () {
4495             $('body').css(
4496                 'padding-top',
4497                 $('#floating_menubar').outerHeight(true)
4498             );
4499             $('#topmenu').menuResizer('resize');
4500         }, 4);
4501     }
4505  * Scrolls the page to the top if clicking the serverinfo bar
4506  */
4507 $(function () {
4508     $(document).delegate("#serverinfo, #goto_pagetop", "click", function (event) {
4509         event.preventDefault();
4510         $('html, body').animate({scrollTop: 0}, 'fast');
4511     });
4514 var checkboxes_sel = "input.checkall:checkbox:enabled";
4516  * Watches checkboxes in a form to set the checkall box accordingly
4517  */
4518 var checkboxes_changed = function () {
4519     var $form = $(this.form);
4520     // total number of checkboxes in current form
4521     var total_boxes = $form.find(checkboxes_sel).length;
4522     // number of checkboxes checked in current form
4523     var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
4524     var $checkall = $form.find("input.checkall_box");
4525     if (total_boxes == checked_boxes) {
4526         $checkall.prop({checked: true, indeterminate: false});
4527     }
4528     else if (checked_boxes > 0) {
4529         $checkall.prop({checked: true, indeterminate: true});
4530     }
4531     else {
4532         $checkall.prop({checked: false, indeterminate: false});
4533     }
4535 $(document).on("change", checkboxes_sel, checkboxes_changed);
4537 $(document).on("change", "input.checkall_box", function () {
4538     var is_checked = $(this).is(":checked");
4539     $(this.form).find(checkboxes_sel).prop("checked", is_checked)
4540     .parents("tr").toggleClass("marked", is_checked);
4544  * Watches checkboxes in a sub form to set the sub checkall box accordingly
4545  */
4546 var sub_checkboxes_changed = function () {
4547     var $form = $(this).parent().parent();
4548     // total number of checkboxes in current sub form
4549     var total_boxes = $form.find(checkboxes_sel).length;
4550     // number of checkboxes checked in current sub form
4551     var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
4552     var $checkall = $form.find("input.sub_checkall_box");
4553     if (total_boxes == checked_boxes) {
4554         $checkall.prop({checked: true, indeterminate: false});
4555     }
4556     else if (checked_boxes > 0) {
4557         $checkall.prop({checked: true, indeterminate: true});
4558     }
4559     else {
4560         $checkall.prop({checked: false, indeterminate: false});
4561     }
4563 $(document).on("change", checkboxes_sel + ", input.checkall_box:checkbox:enabled", sub_checkboxes_changed);
4565 $(document).on("change", "input.sub_checkall_box", function () {
4566     var is_checked = $(this).is(":checked");
4567     var $form = $(this).parent().parent();
4568     $form.find(checkboxes_sel).prop("checked", is_checked)
4569     .parents("tr").toggleClass("marked", is_checked);
4573  * Toggles row colors of a set of 'tr' elements starting from a given element
4575  * @param $start Starting element
4576  */
4577 function toggleRowColors($start)
4579     for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) {
4580         if ($curr_row.hasClass('odd')) {
4581             $curr_row.removeClass('odd').addClass('even');
4582         } else if ($curr_row.hasClass('even')) {
4583             $curr_row.removeClass('even').addClass('odd');
4584         }
4585     }
4589  * Formats a byte number to human-readable form
4591  * @param bytes the bytes to format
4592  * @param optional subdecimals the number of digits after the point
4593  * @param optional pointchar the char to use as decimal point
4594  */
4595 function formatBytes(bytes, subdecimals, pointchar) {
4596     if (!subdecimals) {
4597         subdecimals = 0;
4598     }
4599     if (!pointchar) {
4600         pointchar = '.';
4601     }
4602     var units = ['B', 'KiB', 'MiB', 'GiB'];
4603     for (var i = 0; bytes > 1024 && i < units.length; i++) {
4604         bytes /= 1024;
4605     }
4606     var factor = Math.pow(10, subdecimals);
4607     bytes = Math.round(bytes * factor) / factor;
4608     bytes = bytes.toString().split('.').join(pointchar);
4609     return bytes + ' ' + units[i];
4612 AJAX.registerOnload('functions.js', function () {
4613     /**
4614      * Opens pma more themes link in themes browser, in new window instead of popup
4615      * This way, we don't break HTML validity
4616      */
4617     $("a._blank").prop("target", "_blank");
4618     /**
4619      * Reveal the login form to users with JS enabled
4620      * and focus the appropriate input field
4621      */
4622     var $loginform = $('#loginform');
4623     if ($loginform.length) {
4624         $loginform.find('.js-show').show();
4625         if ($('#input_username').val()) {
4626             $('#input_password').focus();
4627         } else {
4628             $('#input_username').focus();
4629         }
4630     }
4634  * Dynamically adjust the width of the boxes
4635  * on the table and db operations pages
4636  */
4637 (function () {
4638     function DynamicBoxes() {
4639         var $boxContainer = $('#boxContainer');
4640         if ($boxContainer.length) {
4641             var minWidth = $boxContainer.data('box-width');
4642             var viewport = $(window).width() - $('#pma_navigation').width();
4643             var slots = Math.floor(viewport / minWidth);
4644             $boxContainer.children()
4645             .each(function () {
4646                 if (viewport < minWidth) {
4647                     $(this).width(minWidth);
4648                 } else {
4649                     $(this).css('width', ((1 /  slots) * 100) + "%");
4650                 }
4651             })
4652             .removeClass('clearfloat')
4653             .filter(':nth-child(' + slots + 'n+1)')
4654             .addClass('clearfloat');
4655         }
4656     }
4657     AJAX.registerOnload('functions.js', function () {
4658         DynamicBoxes();
4659     });
4660     $(function () {
4661         $(window).resize(DynamicBoxes);
4662     });
4663 })();
4666  * Formats timestamp for display
4667  */
4668 function PMA_formatDateTime(date, seconds) {
4669     var result = $.datepicker.formatDate('yy-mm-dd', date);
4670     var timefmt = 'HH:mm';
4671     if (seconds) {
4672         timefmt = 'HH:mm:ss';
4673     }
4674     return result + ' ' + $.datepicker.formatTime(
4675         timefmt, {
4676             hour: date.getHours(),
4677             minute: date.getMinutes(),
4678             second: date.getSeconds()
4679         }
4680     );
4684  * Check than forms have less fields than max allowed by PHP.
4685  */
4686 function checkNumberOfFields() {
4687     if (typeof maxInputVars === 'undefined') {
4688         return false;
4689     }
4690     if (false === maxInputVars) {
4691         return false;
4692     }
4693     $('form').each(function() {
4694         var nbInputs = $(this).find(':input').length;
4695         if (nbInputs > maxInputVars) {
4696             var warning = PMA_sprintf(PMA_messages.strTooManyInputs, maxInputVars);
4697             PMA_ajaxShowMessage(warning);
4698             return false;
4699         }
4700         return true;
4701     });
4703     return true;
4707  * Ignore the displayed php errors.
4708  * Simply removes the displayed errors.
4710  * @param  clearPrevErrors whether to clear errors stored
4711  *             in $_SESSION['prev_errors'] at server
4713  */
4714 function PMA_ignorePhpErrors(clearPrevErrors){
4715     if (typeof(clearPrevErrors) === "undefined" ||
4716         clearPrevErrors === null
4717     ) {
4718         str = false;
4719     }
4720     // send AJAX request to error_report.php with send_error_report=0, exception_type=php & token.
4721     // It clears the prev_errors stored in session.
4722     if(clearPrevErrors){
4723         var $pmaReportErrorsForm = $('#pma_report_errors_form');
4724         $pmaReportErrorsForm.find('input[name="send_error_report"]').val(0); // change send_error_report to '0'
4725         $pmaReportErrorsForm.submit();
4726     }
4728     // remove displayed errors
4729     var $pmaErrors = $('#pma_errors');
4730     $pmaErrors.fadeOut( "slow");
4731     $pmaErrors.remove();
4735  * checks whether browser supports web storage
4737  * @param type the type of storage i.e. localStorage or sessionStorage
4739  * @returns bool
4740  */
4741 function isStorageSupported(type)
4743     try {
4744         window[type].setItem('PMATest', 'test');
4745         // Check whether key-value pair was set successfully
4746         if (window[type].getItem('PMATest') === 'test') {
4747             // Supported, remove test variable from storage
4748             window[type].removeItem('PMATest');
4749             return true;
4750         }
4751     } catch(error) {
4752         // Not supported
4753         PMA_ajaxShowMessage(PMA_messages.strNoLocalStorage, false);
4754     }
4755     return false;
4759  * Unbind all event handlers before tearing down a page
4760  */
4761 AJAX.registerTeardown('functions.js', function(){
4762     $(document).off('keydown', 'form input, form textarea, form select');
4765 AJAX.registerOnload('functions.js', function () {
4766     /**
4767      * Handle 'Ctrl/Alt + Enter' form submits
4768      */
4769     $('form input, form textarea, form select').on('keydown', function(e){
4770         if((e.ctrlKey && e.which == 13) || (e.altKey && e.which == 13)) {
4771             $form = $(this).closest('form');
4772             if (! $form.find('input[type="submit"]') ||
4773                 ! $form.find('input[type="submit"]').click()
4774             ) {
4775                 $form.submit();
4776             }
4777         }
4778     });
4782  * Unbind all event handlers before tearing down a page
4783  */
4784 AJAX.registerTeardown('functions.js', function(){
4785     $(document).off('change', 'input[type=radio][name="pw_hash"]');
4788 AJAX.registerOnload('functions.js', function(){
4789     /*
4790      * Display warning regarding SSL when sha256_password
4791      * method is selected
4792      * Used in user_password.php (Change Password link on index.php)
4793      */
4794     $(document).on("change", 'select#select_authentication_plugin_cp', function() {
4795         if (this.value === 'sha256_password') {
4796             $('#ssl_reqd_warning_cp').show();
4797         } else {
4798             $('#ssl_reqd_warning_cp').hide();
4799         }
4800     });