PageSourceSearch

https://cido.de/js/get_scripts.js.php?scripts%5B%5D=jquery/jquery.…=navigation.js&v=4.6.6deb5ubuntu0.5

js cido.de collected 2026-09-27 13:45:25 UTC 377,729 bytes, 10,706 lines download raw bytes

1/**
2 * Debounce and throttle function's decorator plugin 1.0.5
3 *
4 * Copyright (c) 2009 Filatov Dmitry ([email protected])
5 * Dual licensed under the MIT and GPL licenses:
6 * http://www.opensource.org/licenses/mit-license.php
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 */
10
11(function($) {
12
13$.extend({
14
15	debounce : function(fn, timeout, invokeAsap, ctx) {
16
17		if(arguments.length == 3 && typeof invokeAsap != 'boolean') {
18			ctx = invokeAsap;
19			invokeAsap = false;
20		}
21
22		var timer;
23
24		return function() {
25
26			var args = arguments;
27            ctx = ctx || this;
28
29			invokeAsap && !timer && fn.apply(ctx, args);
30
31			clearTimeout(timer);
32
33			timer = setTimeout(function() {
34				!invokeAsap && fn.apply(ctx, args);
35				timer = null;
36			}, timeout);
37
38		};
39
40	},
41
42	throttle : function(fn, timeout, ctx) {
43
44		var timer, args, needInvoke;
45
46		return function() {
47
48			args = arguments;
49			needInvoke = true;
50			ctx = ctx || this;
51
52			if(!timer) {
53				(function() {
54					if(needInvoke) {
55						fn.apply(ctx, args);
56						needInvoke = false;
57						timer = setTimeout(arguments.callee, timeout);
58					}
59					else {
60						timer = null;
61					}
62				})();
63			}
64
65		};
66
67	}
68
69});
70
71})(jQuery);;
72
73/* vim: set expandtab sw=4 ts=4 sts=4: */
74/**
75 * Handles the resizing of a menu according to the available screen width
76 *
77 * Uses themes/original/css/resizable-menu.css.php
78 *
79 * To initialise:
80 * $('#myMenu').menuResizer(function () {
81 *     // This function will be called to find out how much
82 *     // available horizontal space there is for the menu
83 *     return $('body').width() - 5; // Some extra margin for good measure
84 * });
85 *
86 * To trigger a resize operation:
87 * $('#myMenu').menuResizer('resize'); // Bind this to $(window).resize()
88 *
89 * To restore the menu to a state like before it was initialized:
90 * $('#myMenu').menuResizer('destroy');
91 *
92 * @package PhpMyAdmin
93 */
94(function ($) {
95    function MenuResizer($container, widthCalculator) {
96        var self = this;
97        self.$container = $container;
98        self.widthCalculator = widthCalculator;
99        // create submenu container
100        var link = $('<a />', {href: '#', 'class': 'tab nowrap'})
101            .text(PMA_messages.strMore)
102            .bind('click', false); // same as event.preventDefault()
103        var img = $container.find('li img');
104        if (img.length) {
105            $(PMA_getImage('b_more.png').toString()).prependTo(link);
106        }
107        var $submenu = $('<li />', {'class': 'submenu'})
108            .append(link)
109            .append($('<ul />'))
110            .mouseenter(function() {
111                if ($(this).find('ul .tabactive').length === 0) {
112                    $(this)
113                    .addClass('submenuhover')
114                    .find('> a')
115                    .addClass('tabactive');
116                }
117            })
118            .mouseleave(function() {
119                if ($(this).find('ul .tabactive').length === 0) {
120                    $(this)
121                    .removeClass('submenuhover')
122                    .find('> a')
123                    .removeClass('tabactive');
124                }
125            });
126        $container.children('.clearfloat').remove();
127        $container.append($submenu).append("<div class='clearfloat'></div>");
128        setTimeout(function () {
129            self.resize();
130        }, 4);
131    }
132    MenuResizer.prototype.resize = function () {
133        var wmax = this.widthCalculator.call(this.$container);
134        var $submenu = this.$container.find('.submenu:last');
135        var submenu_w = $submenu.outerWidth(true);
136        var $submenu_ul = $submenu.find('ul');
137        var $li = this.$container.find('> li');
138        var $li2 = $submenu_ul.find('li');
139        var more_shown = $li2.length > 0;
140        // Calculate the total width used by all the shown tabs
141        var total_len = more_shown ? submenu_w : 0;
142        var l = $li.length - 1;
143        var i;
144        for (i = 0; i < l; i++) {
145            total_len += $($li[i]).outerWidth(true);
146        }
147        // Now hide menu elements that don't fit into the menubar
148        var hidden = false; // Whether we have hidden any tabs
149        while (total_len >= wmax && --l >= 0) { // Process the tabs backwards
150            hidden = true;
151            var el = $($li[l]);
152            var el_width = el.outerWidth(true);
153            el.data('width', el_width);
154            if (! more_shown) {
155                total_len -= el_width;
156                el.prependTo($submenu_ul);
157                total_len += submenu_w;
158                more_shown = true;
159            } else {
160                total_len -= el_width;
161                el.prependTo($submenu_ul);
162            }
163        }
164        // If we didn't hide any tabs, then there might be some space to show some
165        if (! hidden) {
166            // Show menu elements that do fit into the menubar
167            for (i = 0, l = $li2.length; i < l; i++) {
168                total_len += $($li2[i]).data('width');
169                // item fits or (it is the last item
170                // and it would fit if More got removed)
171                if (total_len < wmax ||
172                    (i == $li2.length - 1 && total_len - submenu_w < wmax)
173                ) {
174                    $($li2[i]).insertBefore($submenu);
175                } else {
176                    break;
177                }
178            }
179        }
180        // Show/hide the "More" tab as needed
181        if ($submenu_ul.find('li').length > 0) {
182            $submenu.addClass('shown');
183        } else {
184            $submenu.removeClass('shown');
185        }
186        if (this.$container.find('> li').length == 1) {
187            // If there is only the "More" tab left, then we need
188            // to align the submenu to the left edge of the tab
189            $submenu_ul.removeClass().addClass('only');
190        } else {
191            // Otherwise we align the submenu to the right edge of the tab
192            $submenu_ul.removeClass().addClass('notonly');
193        }
194        if ($submenu.find('.tabactive').length) {
195            $submenu
196            .addClass('active')
197            .find('> a')
198            .removeClass('tab')
199            .addClass('tabactive');
200        } else {
201            $submenu
202            .removeClass('active')
203            .find('> a')
204            .addClass('tab')
205            .removeClass('tabactive');
206        }
207    };
208    MenuResizer.prototype.destroy = function () {
209        var $submenu = this.$container.find('li.submenu').removeData();
210        $submenu.find('li').appendTo(this.$container);
211        $submenu.remove();
212    };
213
214    /** Public API */
215    var methods = {
216        init: function(widthCalculator) {
217            return this.each(function () {
218                var $this = $(this);
219                if (! $this.data('menuResizer')) {
220                    $this.data(
221                        'menuResizer',
222                        new MenuResizer($this, widthCalculator)
223                    );
224                }
225            });
226        },
227        resize: function () {
228            return this.each(function () {
229                var self = $(this).data('menuResizer');
230                if (self) {
231                    self.resize();
232                }
233            });
234        },
235        destroy: function () {
236            return this.each(function () {
237                var self = $(this).data('menuResizer');
238                if (self) {
239                    self.destroy();
240                }
241            });
242        }
243    };
244
245    /** Extend jQuery */
246    $.fn.menuResizer = function(method) {
247        if (methods[method]) {
248            return methods[method].call(this);
249        } else if (typeof method === 'function') {
250            return methods.init.apply(this, [method]);
251        } else {
252            $.error('Method ' +  method + ' does not exist on jQuery.menuResizer');
253        }
254    };
255})(jQuery);
256;
257
258/* vim: set expandtab sw=4 ts=4 sts=4: */
259/**
260 * Conditionally included if framing is not allowed
261 */
262if (self == top) {
263    var style_element = document.getElementById("cfs-style");
264    // check if style_element has already been removed
265    // to avoid frequently reported js error
266    if (typeof(style_element) != 'undefined' && style_element != null) {
267        style_element.parentNode.removeChild(style_element);
268    }
269} else {
270    top.location = self.location;
271}
272;
273
274/* vim: set expandtab sw=4 ts=4 sts=4: */
275/**
276 * JavaScript functionality for Routines, Triggers and Events.
277 *
278 * @package PhpMyadmin
279 */
280/**
281 * @var RTE Contains all the JavaScript functionality
282 *          for Routines, Triggers and Events
283 */
284var RTE = {
285    /**
286     * Construct for the object that provides the
287     * functionality for Routines, Triggers and Events
288     */
289    object: function (type) {
290        $.extend(this, RTE.COMMON);
291        this.editorType = type;
292
293        switch (type) {
294        case 'routine':
295            $.extend(this, RTE.ROUTINE);
296            break;
297        case 'trigger':
298            // nothing extra yet for triggers
299            break;
300        case 'event':
301            $.extend(this, RTE.EVENT);
302            break;
303        default:
304            break;
305        }
306    },
307    /**
308     * @var string param_template Template for a row in the routine editor
309     */
310    param_template: ''
311};
312
313/**
314 * @var RTE.COMMON a JavaScript namespace containing the functionality
315 *                 for Routines, Triggers and Events
316 *
317 *                 This namespace is extended by the functionality required
318 *                 to handle a specific item (a routine, trigger or event)
319 *                 in the relevant javascript files in this folder
320 */
321RTE.COMMON = {
322    /**
323     * @var $ajaxDialog Query object containing the reference to the
324     *                  dialog that contains the editor
325     */
326    $ajaxDialog: null,
327    /**
328     * @var syntaxHiglighter Reference to the codemirror editor
329     */
330    syntaxHiglighter: null,
331    /**
332     * @var buttonOptions Object containing options for
333     *                    the jQueryUI dialog buttons
334     */
335    buttonOptions: {},
336    /**
337     * @var editorType Type of the editor
338     */
339    editorType: null,
340    /**
341     * Validate editor form fields.
342     */
343    validate: function () {
344        /**
345         * @var $elm a jQuery object containing the reference
346         *           to an element that is being validated
347         */
348        var $elm = null;
349        // Common validation. At the very least the name
350        // and the definition must be provided for an item
351        $elm = $('table.rte_table').last().find('input[name=item_name]');
352        if ($elm.val() === '') {
353            $elm.focus();
354            alert(PMA_messages.strFormEmpty);
355            return false;
356        }
357        $elm = $('table.rte_table').find('textarea[name=item_definition]');
358        if ($elm.val() === '') {
359            if (this.syntaxHiglighter !== null) {
360                this.syntaxHiglighter.focus();
361            }
362            else {
363                $('textarea[name=item_definition]').last().focus();
364            }
365            alert(PMA_messages.strFormEmpty);
366            return false;
367        }
368        // The validation has so far passed, so now
369        // we can validate item-specific fields.
370        return this.validateCustom();
371    }, // end validate()
372    /**
373     * Validate custom editor form fields.
374     * This function can be overridden by
375     * other files in this folder
376     */
377    validateCustom: function () {
378        return true;
379    }, // end validateCustom()
380    /**
381     * Execute some code after the ajax
382     * dialog for the editor is shown.
383     * This function can be overridden by
384     * other files in this folder
385     */
386    postDialogShow: function () {
387        // Nothing by default
388    }, // end postDialogShow()
389
390    exportDialog: function ($this) {
391        var $msg = PMA_ajaxShowMessage();
392        if ($this.hasClass('mult_submit')) {
393            var combined = {
394                success: true,
395                title: PMA_messages.strExport,
396                message: '',
397                error: ''
398            };
399            // export anchors of all selected rows
400            var export_anchors = $('input.checkall:checked').parents('tr').find('.export_anchor');
401            var count = export_anchors.length;
402            var returnCount = 0;
403
404            // No routine is exportable (due to privilege issues)
405            if (count === 0) {
406                PMA_ajaxShowMessage(PMA_messages.NoExportable);
407            }
408
409            export_anchors.each(function () {
410                $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
411                    returnCount++;
412                    if (data.success === true) {
413                        combined.message += "\n" + data.message + "\n";
414                        if (returnCount == count) {
415                            showExport(combined);
416                        }
417                    } else {
418                        // complain even if one export is failing
419                        combined.success = false;
420                        combined.error += "\n" + data.error + "\n";
421                        if (returnCount == count) {
422                            showExport(combined);
423                        }
424                    }
425                });
426            });
427        } else {
428            $.get($this.attr('href'), {'ajax_request': true}, showExport);
429        }
430        PMA_ajaxRemoveMessage($msg);
431
432        function showExport(data) {
433            if (data.success === true) {
434                PMA_ajaxRemoveMessage($msg);
435                /**
436                 * @var button_options Object containing options
437                 *                     for jQueryUI dialog buttons
438                 */
439                var button_options = {};
440                button_options[PMA_messages.strClose] = function () {
441                    $(this).dialog("close").remove();
442                };
443                /**
444                 * Display the dialog to the user
445                 */
446                data.message = '<textarea cols="40" rows="15" style="width: 100%;">' + data.message + '</textarea>';
447                var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
448                    width: 500,
449                    buttons: button_options,
450                    title: data.title
451                });
452                // Attach syntax highlighted editor to export dialog
453                /**
454                 * @var $elm jQuery object containing the reference
455                 *           to the Export textarea.
456                 */
457                var $elm = $ajaxDialog.find('textarea');
458                PMA_getSQLEditor($elm);
459            } else {
460                PMA_ajaxShowMessage(data.error, false);
461            }
462        } // end showExport()
463    },  // end exportDialog()
464    editorDialog: function (is_new, $this) {
465        var that = this;
466        /**
467         * @var $edit_row jQuery object containing the reference to
468         *                the row of the the item being edited
469         *                from the list of items
470         */
471        var $edit_row = null;
472        if ($this.hasClass('edit_anchor')) {
473            // Remeber the row of the item being edited for later,
474            // so that if the edit is successful, we can replace the
475            // row with info about the modified item.
476            $edit_row = $this.parents('tr');
477        }
478        /**
479         * @var $msg jQuery object containing the reference to
480         *           the AJAX message shown to the user
481         */
482        var $msg = PMA_ajaxShowMessage();
483        $.get($this.attr('href'), {'ajax_request': true}, function (data) {
484            if (data.success === true) {
485                // We have successfully fetched the editor form
486                PMA_ajaxRemoveMessage($msg);
487                // Now define the function that is called when
488                // the user presses the "Go" button
489                that.buttonOptions[PMA_messages.strGo] = function () {
490                    // Move the data from the codemirror editor back to the
491                    // textarea, where it can be used in the form submission.
492                    if (typeof CodeMirror != 'undefined') {
493                        that.syntaxHiglighter.save();
494                    }
495                    // Validate editor and submit request, if passed.
496                    if (that.validate()) {
497                        /**
498                         * @var data Form data to be sent in the AJAX request
499                         */
500                        var data = $('form.rte_form').last().serialize();
501                        $msg = PMA_ajaxShowMessage(
502                            PMA_messages.strProcessingRequest
503                        );
504                        var url = $('form.rte_form').last().attr('action');
505                        $.post(url, data, function (data) {
506                            if (data.success === true) {
507                                // Item created successfully
508                                PMA_ajaxRemoveMessage($msg);
509                                PMA_slidingMessage(data.message);
510                                that.$ajaxDialog.dialog('close');
511                                // If we are in 'edit' mode, we must
512                                // remove the reference to the old row.
513                                if (mode === 'edit' && $edit_row !== null ) {
514                                    $edit_row.remove();
515                                }
516                                // Sometimes, like when moving a trigger from
517                                // a table to another one, the new row should
518                                // not be inserted into the list. In this case
519                                // "data.insert" will be set to false.
520                                if (data.insert) {
521                                    // Insert the new row at the correct
522                                    // location in the list of items
523                                    /**
524                                     * @var text Contains the name of an item from
525                                     *           the list that is used in comparisons
526                                     *           to find the correct location where
527                                     *           to insert a new row.
528                                     */
529                                    var text = '';
530                                    /**
531                                     * @var inserted Whether a new item has been
532                                     *               inserted in the list or not
533                                     */
534                                    var inserted = false;
535                                    $('table.data').find('tr').each(function () {
536                                        text = $(this)
537                                                .children('td')
538                                                .eq(0)
539                                                .find('strong')
540                                                .text()
541                                                .toUpperCase();
542                                        text = $.trim(text);
543                                        if (text !== '' && text > data.name) {
544                                            $(this).before(data.new_row);
545                                            inserted = true;
546                                            return false;
547                                        }
548                                    });
549                                    if (! inserted) {
550                                        // If we didn't manage to insert the row yet,
551                                        // it must belong at the end of the list,
552                                        // so we insert it there.
553                                        $('table.data').append(data.new_row);
554                                    }
555                                    // Fade-in the new row
556                                    $('tr.ajaxInsert')
557                                        .show('slow')
558                                        .removeClass('ajaxInsert');
559                                } else if ($('table.data').find('tr').has('td').length === 0) {
560                                    // If we are not supposed to insert the new row,
561                                    // we will now check if the table is empty and
562                                    // needs to be hidden. This will be the case if
563                                    // we were editing the only item in the list,
564                                    // which we removed and will not be inserting
565                                    // something else in its place.
566                                    $('table.data').hide("slow", function () {
567                                        $('#nothing2display').show("slow");
568                                    });
569                                }
570                                // Now we have inserted the row at the correct
571                                // position, but surely at least some row classes
572                                // are wrong now. So we will itirate throught
573                                // all rows and assign correct classes to them
574                                /**
575                                 * @var ct Count of processed rows
576                                 */
577                                var ct = 0;
578                                /**
579                                 * @var rowclass Class to be attached to the row
580                                 *               that is being processed
581                                 */
582                                var rowclass = '';
583                                $('table.data').find('tr').has('td').each(function () {
584                                    rowclass = (ct % 2 === 0) ? 'odd' : 'even';
585                                    $(this).removeClass().addClass(rowclass);
586                                    ct++;
587                                });
588                                // If this is the first item being added, remove
589                                // the "No items" message and show the list.
590                                if ($('table.data').find('tr').has('td').length > 0 &&
591                                    $('#nothing2display').is(':visible')
592                                    ) {
593                                    $('#nothing2display').hide("slow", function () {
594                                        $('table.data').show("slow");
595                                    });
596                                }
597                                PMA_reloadNavigation();
598                            } else {
599                                PMA_ajaxShowMessage(data.error, false);
600                            }
601                        }); // end $.post()
602                    } // end "if (that.validate())"
603                }; // end of function that handles the submission of the Editor
604                that.buttonOptions[PMA_messages.strClose] = function () {
605                    $(this).dialog("close");
606                };
607                /**
608                 * Display the dialog to the user
609                 */
610                that.$ajaxDialog = $('<div id="rteDialog">' + data.message + '</div>').dialog({
611                    width: 700,
612                    minWidth: 500,
613                    maxHeight: $(window).height(),
614                    buttons: that.buttonOptions,
615                    title: data.title,
616                    modal: true,
617                    open: function () {
618                        if ($('#rteDialog').parents('.ui-dialog').height() > $(window).height()) {
619                            $('#rteDialog').dialog("option", "height", $(window).height());
620                        }
621                        $(this).find('input[name=item_name]').focus();
622                        $(this).find('input.datefield').each(function () {
623                            PMA_addDatepicker($(this).css('width', '95%'), 'date');
624                        });
625                        $(this).find('input.datetimefield').each(function () {
626                            PMA_addDatepicker($(this).css('width', '95%'), 'datetime');
627                        });
628                        $.datepicker.initialized = false;
629                    },
630                    close: function () {
631                        $(this).remove();
632                    }
633                });
634                /**
635                 * @var mode Used to remeber whether the editor is in
636                 *           "Edit" or "Add" mode
637                 */
638                var mode = 'add';
639                if ($('input[name=editor_process_edit]').length > 0) {
640                    mode = 'edit';
641                }
642                // Attach syntax highlighted editor to the definition
643                /**
644                 * @var elm jQuery object containing the reference to
645                 *                 the Definition textarea.
646                 */
647                var $elm = $('textarea[name=item_definition]').last();
648                var linterOptions = {};
649                linterOptions[that.editorType + '_editor'] = true;
650                that.syntaxHiglighter = PMA_getSQLEditor($elm, {}, null, linterOptions);
651
652                // Execute item-specific code
653                that.postDialogShow(data);
654            } else {
655                PMA_ajaxShowMessage(data.error, false);
656            }
657        }); // end $.get()
658    },
659
660    dropDialog: function ($this) {
661        /**
662         * @var $curr_row Object containing reference to the current row
663         */
664        var $curr_row = $this.parents('tr');
665        /**
666         * @var question String containing the question to be asked for c
666onfirmation
667         */
668        var question = $('<div/>').text(
669            $curr_row.children('td').children('.drop_sql').html()
670        );
671        // We ask for confirmation first here, before submitting the ajax request
672        $this.PMA_confirm(question, $this.attr('href'), function (url) {
673            /**
674             * @var msg jQuery object containing the reference to
675             *          the AJAX message shown to the user
676             */
677            var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
678            $.post(url, {'is_js_confirmed': 1, 'ajax_request': true}, function (data) {
679                if (data.success === true) {
680                    /**
681                     * @var $table Object containing reference
682                     *             to the main list of elements
683                     */
684                    var $table = $curr_row.parent();
685                    // Check how many rows will be left after we remove
686                    // the one that the user has requested us to remove
687                    if ($table.find('tr').length === 3) {
688                        // If there are two rows left, it means that they are
689                        // the header of the table and the rows that we are
690                        // about to remove, so after the removal there will be
691                        // nothing to show in the table, so we hide it.
692                        $table.hide("slow", function () {
693                            $(this).find('tr.even, tr.odd').remove();
694                            $('.withSelected').remove();
695                            $('#nothing2display').show("slow");
696                        });
697                    } else {
698                        $curr_row.hide("slow", function () {
699                            $(this).remove();
700                            // Now we have removed the row from the list, but maybe
701                            // some row classes are wrong now. So we will itirate
702                            // throught all rows and assign correct classes to them.
703                            /**
704                             * @var ct Count of processed rows
705                             */
706                            var ct = 0;
707                            /**
708                             * @var rowclass Class to be attached to the row
709                             *               that is being processed
710                             */
711                            var rowclass = '';
712                            $table.find('tr').has('td').each(function () {
713                                rowclass = (ct % 2 === 1) ? 'odd' : 'even';
714                                $(this).removeClass().addClass(rowclass);
715                                ct++;
716                            });
717                        });
718                    }
719                    // Get rid of the "Loading" message
720                    PMA_ajaxRemoveMessage($msg);
721                    // Show the query that we just executed
722                    PMA_slidingMessage(data.sql_query);
723                    PMA_reloadNavigation();
724                } else {
725                    PMA_ajaxShowMessage(data.error, false);
726                }
727            }); // end $.post()
728        }); // end $.PMA_confirm()
729    },
730
731    dropMultipleDialog: function ($this) {
732        // We ask for confirmation here
733        $this.PMA_confirm(PMA_messages.strDropRTEitems, '', function (url) {
734            /**
735             * @var msg jQuery object containing the reference to
736             *          the AJAX message shown to the user
737             */
738            var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
739
740            // drop anchors of all selected rows
741            var drop_anchors = $('input.checkall:checked').parents('tr').find('.drop_anchor');
742            var success = true;
743            var count = drop_anchors.length;
744            var returnCount = 0;
745
746            drop_anchors.each(function () {
747                var $anchor = $(this);
748                /**
749                 * @var $curr_row Object containing reference to the current row
750                 */
751                var $curr_row = $anchor.parents('tr');
752                $.post($anchor.attr('href'), {'is_js_confirmed': 1, 'ajax_request': true}, function (data) {
753                    returnCount++;
754                    if (data.success === true) {
755                        /**
756                         * @var $table Object containing reference
757                         *             to the main list of elements
758                         */
759                        var $table = $curr_row.parent();
760                        // Check how many rows will be left after we remove
761                        // the one that the user has requested us to remove
762                        if ($table.find('tr').length === 3) {
763                            // If there are two rows left, it means that they are
764                            // the header of the table and the rows that we are
765                            // about to remove, so after the removal there will be
766                            // nothing to show in the table, so we hide it.
767                            $table.hide("slow", function () {
768                                $(this).find('tr.even, tr.odd').remove();
769                                $('.withSelected').remove();
770                                $('#nothing2display').show("slow");
771                            });
772                        } else {
773                            $curr_row.hide("fast", function () {
774                                $(this).remove();
775                                // Now we have removed the row from the list, but maybe
776                                // some row classes are wrong now. So we will itirate
777                                // throught all rows and assign correct classes to them.
778                                /**
779                                 * @var ct Count of processed rows
780                                 */
781                                var ct = 0;
782                                /**
783                                 * @var rowclass Class to be attached to the row
784                                 *               that is being processed
785                                 */
786                                var rowclass = '';
787                                $table.find('tr').has('td').each(function () {
788                                    rowclass = (ct % 2 === 1) ? 'odd' : 'even';
789                                    $(this).removeClass().addClass(rowclass);
790                                    ct++;
791                                });
792                            });
793                        }
794                        if (returnCount == count) {
795                            if (success) {
796                                // Get rid of the "Loading" message
797                                PMA_ajaxRemoveMessage($msg);
798                                $('#rteListForm_checkall').prop({checked: fal
798se, indeterminate: false});
799                            }
800                            PMA_reloadNavigation();
801                        }
802                    } else {
803                        PMA_ajaxShowMessage(data.error, false);
804                        success = false;
805                        if (returnCount == count) {
806                            PMA_reloadNavigation();
807                        }
808                    }
809                }); // end $.post()
810            }); // end drop_anchors.each()
811        }); // end $.PMA_confirm()
812    }
813}; // end RTE namespace
814
815/**
816 * @var RTE.EVENT JavaScript functionality for events
817 */
818RTE.EVENT = {
819    validateCustom: function () {
820        /**
821         * @var elm a jQuery object containing the reference
822         *          to an element that is being validated
823         */
824        var $elm = null;
825        if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'RECURRING') {
826            // The interval field must not be empty for recurring events
827            $elm = this.$ajaxDialog.find('input[name=item_interval_value]');
828            if ($elm.val() === '') {
829                $elm.focus();
830                alert(PMA_messages.strFormEmpty);
831                return false;
832            }
833        } else {
834            // The execute_at field must not be empty for "once off" events
835            $elm = this.$ajaxDialog.find('input[name=item_execute_at]');
836            if ($elm.val() === '') {
837                $elm.focus();
838                alert(PMA_messages.strFormEmpty);
839                return false;
840            }
841        }
842        return true;
843    }
844};
845
846/**
847 * @var RTE.ROUTINE JavaScript functionality for routines
848 */
849RTE.ROUTINE = {
850    /**
851     * Overriding the postDialogShow() function defined in common.js
852     *
853     * @param data JSON-encoded data from the ajax request
854     */
855    postDialogShow: function (data) {
856        // Cache the template for a parameter table row
857        RTE.param_template = data.param_template;
858        var that = this;
859        // Make adjustments in the dialog to make it AJAX compatible
860        $('td.routine_param_remove').show();
861        $('input[name=routine_removeparameter]').remove();
862        $('input[name=routine_addparameter]').css('width', '100%');
863        // Enable/disable the 'options' dropdowns for parameters as necessary
864        $('table.routine_params_table').last().find('th[colspan=2]').attr('colspan', '1');
865        $('table.routine_params_table').last().find('tr').has('td').each(function () {
866            that.setOptionsForParameter(
867                $(this).find('select[name^=item_param_type]'),
868                $(this).find('input[name^=item_param_length]'),
869                $(this).find('select[name^=item_param_opts_text]'),
870                $(this).find('select[name^=item_param_opts_num]')
871            );
872        });
873        // Enable/disable the 'options' dropdowns for
874        // function return value as necessary
875        this.setOptionsForParameter(
876            $('table.rte_table').last().find('select[name=item_returntype]'),
877            $('table.rte_table').last().find('input[name=item_returnlength]'),
878            $('table.rte_table').last().find('select[name=item_returnopts_text]'),
879            $('table.rte_table').last().find('select[name=item_returnopts_num]')
880        );
881        // Allow changing parameter order
882        $('.routine_params_table tbody').sortable({
883            containment: '.routine_params_table tbody',
884            handle: '.dragHandle',
885            stop: function(event, ui) {
886                that.reindexParameters();
887            },
888        });
889    },
890    /**
891     * Reindexes the parameters after dropping a parameter or reordering parameters
892     */
893    reindexParameters: function () {
894        /**
895         * @var index Counter used for reindexing the input
896         *            fields in the routine parameters table
897         */
898        var index = 0;
899        $('table.routine_params_table tbody').find('tr').each(function () {
900            $(this).find(':input').each(function () {
901                /**
902                 * @var inputname The value of the name attribute of
903                 *                the input field being reindexed
904                 */
905                var inputname = $(this).attr('name');
906                if (inputname.substr(0, 14) === 'item_param_dir') {
907                    $(this).attr('name', inputname.substr(0, 14) + '[' + index + ']');
908                } else if (inputname.substr(0, 15) === 'item_param_name') {
909                    $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']');
910                } else if (inputname.substr(0, 15) === 'item_param_type') {
911                    $(this).attr('name', inputname.substr(0, 15) + '[' + index + ']');
912                } else if (inputname.substr(0, 17) === 'item_param_length') {
913                    $(this).attr('name', inputname.substr(0, 17) + '[' + index + ']');
914                    $(this).attr('id', 'item_param_length_' + index);
915                } else if (inputname.substr(0, 20) === 'item_param_opts_text') {
916                    $(this).attr('name', inputname.substr(0, 20) + '[' + index + ']');
917                } else if (inputname.substr(0, 19) === 'item_param_opts_num') {
918                    $(this).attr('name', inputname.substr(0, 19) + '[' + index + ']');
919                }
920            });
921            index++;
922        });
923    },
924    /**
925     * Overriding the validateCustom() function defined in common.js
926     */
927    validateCustom: function () {
928        /**
929         * @var isSuccess Stores the outcome of the validation
930         */
931        var isSuccess = true;
932        /**
933         * @var inputname The value of the "name" attribute for
934         *                the field that is being processed
935         */
936        var inputname = '';
937        this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () {
938            // Every parameter of a routine must have
939            // a non-empty direction, name and type
940            if (isSuccess) {
941                $(this).find(':input').each(function () {
942                    inputname = $(this).attr('name');
943                    if (inputname.substr(0, 14) === 'item_param_dir' ||
944                        inputname.substr(0, 15) === 'item_param_name' ||
945                        inputname.substr(0, 15) === 'item_param_type') {
946                        if ($(this).val() === '') {
947                            $(this).focus();
948                            isSuccess = false;
949                            return false;
950                        }
951                    }
952                });
953            } else {
954                return false;
955            }
956        });
957        if (! isSuccess) {
958            alert(PMA_messages.strFormEmpty);
959            return false;
960        }
961        this.$ajaxDialog.find('table.routine_params_table').last().find('tr').each(function () {
962            // SET, ENUM, VARCHAR and VARBINARY fields must have length/values
963            var $inputtyp = $(this).find('select[name^=item_param_type]');
964            var $inputlen = $(this).find('input[name^=item_param_length]');
965            if ($inputtyp.length && $inputlen.length) {
966                if (($inputtyp.val() === 'ENUM' || $inputtyp.val() === 'SET' || $inputtyp.val().substr(0, 3) === 'VAR') &&
967                    $inputlen.val() === ''
968                   ) {
969                    $inputlen.focus();
970                    isSuccess = false;
971                    return false;
972                }
973            }
974        });
975        if (! isSuccess) {
976            alert(PMA_messages.strFormEmpty);
977            return false;
978        }
979        if (this.$ajaxDialog.find('select[name=item_type]').find(':selected').val() === 'FUNCTION') {
980            // The length/values of return variable for functions must
981            // be set, if the type is SET, ENUM, VARCHAR or VARBINARY.
982            var $returntyp = this.$ajaxDialog.find('select[name=item_returntype]');
983            var $returnlen = this.$ajaxDialog.find('input[name=item_returnlength]');
984            if (($returntyp.val() === 'ENUM' || $returntyp.val() === 'SET' || $returntyp.val().substr(0, 3) === 'VAR') &&
985                $returnlen.val() === ''
986                ) {
987                $returnlen.focus();
988                alert(PMA_messages.strFormEmpty);
989                return false;
990            }
991        }
992        if ($('select[name=item_type]').find(':selected').val() === 'FUNCTION') {
993            // A function must contain a RETURN statement in its definition
994            if (this.$ajaxDialog.find('table.rte_table').find('textarea[name=item_definition]').val().toUpperCase().indexOf('RETURN') < 0) {
995                this.syntaxHiglighter.focus();
996                alert(PMA_messages.MissingReturn);
997                return false;
998            }
999        }
1000        return true;
1001    },
1002    /**
1003     * Enable/disable the "options" dropdown and "length" input for
1004     * parameters and the return variable in the routine editor
1005     * as necessary.
1006     *
1007     * @param type a jQuery object containing the reference
1008     *             to the "Type" dropdown box
1009     * @param len  a jQuery object containing the reference
1010     *             to the "Length" input box
1011     * @param text a jQuery object containing the reference
1012     *             to the dropdown box with options for
1013     *             parameters of text type
1014     * @param num  a jQuery object containing the reference
1015     *             to the dropdown box with options for
1016     *             parameters of numeric type
1017     */
1018    setOptionsForParameter: function ($type, $len, $text, $num) {
1019        /**
1020         * @var no_opts a jQuery object containing the reference
1021         *              to an element to be displayed when no
1022         *              options are available
1023         */
1024        var $no_opts = $text.parent().parent().find('.no_opts');
1025        /**
1026         * @var no_len a jQuery object containing the reference
1027         *             to an element to be displayed when no
1028         *             "length/values" field is available
1029         */
1030        var $no_len  = $len.parent().parent().find('.no_len');
1031
1032        // Process for parameter options
1033        switch ($type.val()) {
1034        case 'TINYINT':
1035        case 'SMALLINT':
1036        case 'MEDIUMINT':
1037        case 'INT':
1038        case 'BIGINT':
1039        case 'DECIMAL':
1040        case 'FLOAT':
1041        case 'DOUBLE':
1042        case 'REAL':
1043            $text.parent().hide();
1044            $num.parent().show();
1045            $no_opts.hide();
1046            break;
1047        case 'TINYTEXT':
1048        case 'TEXT':
1049        case 'MEDIUMTEXT':
1050        case 'LONGTEXT':
1051        case 'CHAR':
1052        case 'VARCHAR':
1053        case 'SET':
1054        case 'ENUM':
1055            $text.parent().show();
1056            $num.parent().hide();
1057            $no_opts.hide();
1058            break;
1059        default:
1060            $text.parent().hide();
1061            $num.parent().hide();
1062            $no_opts.show();
1063            break;
1064        }
1065        // Process for parameter length
1066        switch ($type.val()) {
1067        case 'DATE':
1068        case 'TINYBLOB':
1069        case 'TINYTEXT':
1070        case 'BLOB':
1071        case 'TEXT':
1072        case 'MEDIUMBLOB':
1073        case 'MEDIUMTEXT':
1074        case 'LONGBLOB':
1075        case 'LONGTEXT':
1076            $text.closest('tr').find('a:first').hide();
1077            $len.parent().hide();
1078            $no_len.show();
1079            break;
1080        default:
1081            if ($type.val() == 'ENUM' || $type.val() == 'SET') {
1082                $text.closest('tr').find('a:first').show();
1083            } else {
1084                $text.closest('tr').find('a:first').hide();
1085            }
1086            $len.parent().show();
1087            $no_len.hide();
1088            break;
1089        }
1090    },
1091    executeDialog: function ($this) {
1092        var that = this;
1093        /**
1094         * @var msg jQuery object containing the reference to
1095         *          the AJAX message shown to the user
1096         */
1097        var $msg = PMA_ajaxShowMessage();
1098        $.post($this.attr('href'), {'ajax_request': true}, function (data) {
1099            if (data.success === true) {
1100                PMA_ajaxRemoveMessage($msg);
1101                // If 'data.dialog' is true we show a dialog with a form
1102                // to get the input parameters for routine, otherwise
1103                // we just show the results of the query
1104                if (data.dialog) {
1105                    // Define the function that is called when
1106                    // the user presses the "Go" button
1107                    that.buttonOptions[PMA_messages.strGo] = function () {
1108                        /**
1109                         * @var data Form data to be sent in the AJAX request
1110                         */
1111                        var data = $('form.rte_form').last().serialize();
1112                        $msg = PMA_ajaxShowMessage(
1113                            PMA_messages.strProcessingRequest
1114                        );
1115                        $.post('db_routines.php', data, function (data) {
1116                            if (data.success === true) {
1117                                // Routine executed successfully
1118                                PMA_ajaxRemoveMessage($msg);
1119                                PMA_slidingMessage(data.message);
1120                                $ajaxDialog.dialog('close');
1121                            } else {
1122                                PMA_ajaxShowMessage(data.error, false);
1123                            }
1124                        });
1125                    };
1126                    that.buttonOptions[PMA_messages.strClose] = function () {
1127                        $(this).dialog("close");
1128                    };
1129                    /**
1130                     * Display the dialog to the user
1131                     */
1132                    var $ajaxDialog = $('<div>' + data.message + '</div>').dialog({
1133                        width: 650,
1134                        buttons: that.buttonOptions,
1135                        title: data.title,
1136                        modal: true,
1137                        close: function () {
1138                            $(this).remove();
1139                        }
1140                    });
1141                    $ajaxDialog.find('input[name^=params]').first().focus();
1142                    /**
1143                     * Attach the datepickers to the relevant form fields
1144                     */
1145                    $ajaxDialog.find('input.datefield, input.datetimefield').each(function () {
1146                        PMA_addDatepicker($(this).css('width', '95%'));
1147                    });
1148                    /*
1149                    * Define the function if the user presses enter
1150                    */
1151                    $('form.rte_form').on('keyup', function (event) {
1152                        event.preventDefault();
1153                        if (event.keyCode === 13) {
1154                            /**
1155                            * @var data Form data to be sent in the AJAX request
1156                            */
1157                            var data = $(this).serialize();
1158                            $msg = PMA_ajaxShowMessage(
1159                                PMA_messages.strProcessingRequest
1160                            );
1161                            var url = $(this).attr('action');
1162                            $.post(url, data, function (data) {
1163                                if (data.success === true) {
1164                                    // Routine executed successfully
1165                                    PMA_ajaxRemoveMessage($msg);
1166                                    PMA_slidingMessage(data.message);
1167                                    $('form.rte_form').off('keyup');
1168                                    $ajaxDialog.remove();
1169                                } else {
1170                                    PMA_ajaxShowMessage(data.error, false);
1171                                }
1172                            });
1173                        }
1174                    });
1175                } else {
1176                    // Routine executed successfully
1177                    PMA_slidingMessage(data.message);
1178                }
1179            } else {
1180                PMA_ajaxShowMessage(data.error, false);
1181            }
1182        }); // end $.post()
1183    }
1184};
1185
1186/**
1187 * Attach Ajax event handlers for the Routines, Triggers and Events editor
1188 */
1189$(function () {
1190    /**
1191     * Attach Ajax event handlers for the Add/Edit functionality.
1192     */
1193    $(document).on('click', 'a.ajax.add_anchor, a.ajax.edit_anchor', function (event) {
1194        event.preventDefault();
1195        var type = $(this).attr('href').substr(0, $(this).attr('href').indexOf('?'));
1196        if (type.indexOf('routine') != -1) {
1197            type = 'routine';
1198        } else if (type.indexOf('trigger') != -1) {
1199            type = 'trigger';
1200        } else if (type.indexOf('event') != -1) {
1201            type = 'event';
1202        } else {
1203            type = '';
1204        }
1205        var dialog = new RTE.object(type);
1206        dialog.editorDialog($(this).hasClass('add_anchor'), $(this));
1207    }); // end $(document).on()
1208
1209    /**
1210     * Attach Ajax event handlers for the Execute routine functionality
1211     */
1212    $(document).on('click', 'a.ajax.exec_anchor', function (event) {
1213        event.preventDefault();
1214        var dialog = new RTE.object('routine');
1215        dialog.executeDialog($(this));
1216    }); // end $(document).on()
1217
1218    /**
1219     * Attach Ajax event handlers for Export of Routines, Triggers and Events
1220     */
1221    $(document).on('click', 'a.ajax.export_anchor', function (event) {
1222        event.preventDefault();
1223        var dialog = new RTE.object();
1224        dialog.exportDialog($(this));
1225    }); // end $(document).on()
1226
1227    $(document).on('click', '#rteListForm.ajax .mult_submit[value="export"]', function (event) {
1228        event.preventDefault();
1229        var dialog = new RTE.object();
1230        dialog.exportDialog($(this));
1231    }); // end $(document).on()
1232
1233    /**
1234     * Attach Ajax event handlers for Drop functionality
1235     * of Routines, Triggers and Events.
1236     */
1237    $(document).on('click', 'a.ajax.drop_anchor', function (event) {
1238        event.preventDefault();
1239        var dialog = new RTE.object();
1240        dialog.dropDialog($(this));
1241    }); // end $(document).on()
1242
1243    $(document).on('click', '#rteListForm.ajax .mult_submit[value="drop"]', function (event) {
1244        event.preventDefault();
1245        var dialog = new RTE.object();
1246        dialog.dropMultipleDialog($(this));
1247    }); // end $(document).on()
1248
1249    /**
1250     * Attach Ajax event handlers for the "Change event/routine type"
1251     * functionality in the events editor, so that the correct
1252     * rows are shown in the editor when changing the event type
1253     */
1254    $(document).on('change', 'select[name=item_type]', function () {
1255        $(this)
1256        .closest('table')
1257        .find('tr.recurring_event_row, tr.onetime_event_row, tr.routine_return_row, .routine_direction_cell')
1258        .toggle();
1259    }); // end $(document).on()
1260
1261    /**
1262     * Attach Ajax event handlers for the "Change parameter type"
1263     * functionality in the routines editor, so that the correct
1264     * option/length fields, if any, are shown when changing
1265     * a parameter type
1266     */
1267    $(document).on('change', 'select[name^=item_param_type]', function () {
1268        /**
1269         * @var row jQuery object containing the reference to
1270         *          a row in the routine parameters table
1271         */
1272        var $row = $(this).parents('tr').first();
1273        var rte = new RTE.object('routine');
1274        rte.setOptionsForParameter(
1275            $row.find('select[name^=item_param_type]'),
1276            $row.find('input[name^=item_param_length]'),
1277            $row.find('select[name^=item_param_opts_text]'),
1278            $row.find('select[name^=item_param_opts_num]')
1279        );
1280    }); // end $(document).on()
1281
1282    /**
1283     * Attach Ajax event handlers for the "Change the type of return
1284     * variable of function" functionality, so that the correct fields,
1285     * if any, are shown when changing the function return type type
1286     */
1287    $(document).on('change', 'select[name=item_returntype]', function () {
1288        var rte = new RTE.object('routine');
1289        var $table = $(this).closest('table.rte_table');
1290        rte.setOptionsForParameter(
1291            $table.find('select[name=item_returntype]'),
1292            $table.find('input[name=item_returnlength]'),
1293            $table.find('select[name=item_returnopts_text]'),
1294            $table.find('select[name=item_returnopts_num]')
1295        );
1296    }); // end $(document).on()
1297
1298    /**
1299     * Attach Ajax event handlers for the "Add parameter to routine" functionality
1300     */
1301    $(document).on('click', 'input[name=routine_addparameter]', function (event) {
1302        event.preventDefault();
1303        /**
1304         * @var routine_params_table jQuery object containing the reference
1305         *                           to the routine parameters table
1306         */
1307        var $routine_params_table = $(this).closest('div.ui-dialog').find('.routine_params_table');
1308        /**
1309         * @var new_param_row A string containing the HTML code for the
1310         *                    new row for the routine parameters table
1311         */
1312        var new_param_row = RTE.param_template.replace(/%s/g, $routine_params_table.find('tr').length - 1);
1313        // Append the new row to the parameters table
1314        $routine_params_table.append(new_param_row);
1315        // Make sure that the row is correctly shown according to the type of routine
1316        if ($(this).closest('div.ui-dialog').find('table.rte_table select[name=item_type]').val() === 'FUNCTION') {
1317            $('tr.routine_return_row').show();
1318            $('td.routine_direction_cell').hide();
1319        }
1320        /**
1321         * @var newrow jQuery object containing the reference to the newly
1322         *             inserted row in the routine parameters table
1323         */
1324        var $newrow = $(this).closest('div.ui-dialog').find('table.routine_params_table').find('tr').has('td').last();
1325        // Enable/disable the 'options' dropdowns for parameters as necessary
1326        var rte = new RTE.object('routine');
1327        rte.setOptionsForParameter(
1328            $newrow.find('select[name^=item_param_type]'),
1329            $newrow.find('input[name^=item_param_length]'),
1330            $newrow.find('select[name^=item_param_opts_text]'),
1331            $newrow.find('select[name^=item_param_opts_num]')
1332        );
1333    }); // end $(document).on()
1334
1335    /**
1336     * Attach Ajax event handlers for the
1337     * "Remove parameter from routine" functionality
1338     */
1339    $(document).on('click', 'a.routine_param_remove_anchor', function (event) {
1340        event.preventDefault();
1341        $(this).parent().parent().remove();
1342        // After removing a parameter, the indices of the name attributes in
1343        // the input fields lose the correct order and need to be reordered.
1344        RTE.ROUTINE.reindexParameters();
1345    }); // end $(document).on()
1346}); // end of $()
1347;
1348
1349/**
1350 * https://github.com/csnover/TraceKit
1351 * @license MIT
1352 * @namespace TraceKit
1353 */
1354(function(window, undefined) {
1355if (!window) {
1356    return;
1357}
1358
1359var TraceKit = {};
1360var _oldTraceKit = window.TraceKit;
1361
1362// global reference to slice
1363var _slice = [].slice;
1364var UNKNOWN_FUNCTION = '?';
1365
1366/**
1367 * A better form of hasOwnProperty<br/>
1368 * Example: `_has(MainHostObject, property) === true/false`
1369 *
1370 * @param {Object} object to check property
1371 * @param {string} key to check
1372 * @return {Boolean} true if the object has the key and it is not inherited
1373 */
1374function _has(object, key) {
1375    return Object.prototype.hasOwnProperty.call(object, key);
1376}
1377
1378/**
1379 * Returns true if the parameter is undefined<br/>
1380 * Example: `_isUndefined(val) === true/false`
1381 *
1382 * @param {*} what Value to check
1383 * @return {Boolean} true if undefined and false otherwise
1384 */
1385function _isUndefined(what) {
1386    return typeof what === 'undefined';
1387}
1388
1389/**
1390 * Export TraceKit out to another variable<br/>
1391 * Example: `var TK = TraceKit.noConflict()`
1392 * @return {Object} The TraceKit object
1393 * @memberof TraceKit
1394 */
1395TraceKit.noConflict = function noConflict() {
1396    window.TraceKit = _oldTraceKit;
1397    return TraceKit;
1398};
1399
1400/**
1401 * Wrap any function in a TraceKit reporter<br/>
1402 * Example: `func = TraceKit.wrap(func);`
1403 *
1404 * @param {Function} func Function to be wrapped
1405 * @return {Function} The wrapped func
1406 * @memberof TraceKit
1407 */
1408TraceKit.wrap = function traceKitWrapper(func) {
1409    function wrapped() {
1410        try {
1411            return func.apply(this, arguments);
1412        } catch (e) {
1413            TraceKit.report(e);
1414            throw e;
1415        }
1416    }
1417    return wrapped;
1418};
1419
1420/**
1421 * Cross-browser processing of unhandled exceptions
1422 *
1423 * Syntax:
1424 * ```js
1425 *   TraceKit.report.subscribe(function(stackInfo) { ... })
1426 *   TraceKit.report.unsubscribe(function(stackInfo) { ... })
1427 *   TraceKit.report(exception)
1428 *   try { ...code... } catch(ex) { TraceKit.report(ex); }
1429 * ```
1430 *
1431 * Supports:
1432 *   - Firefox: full stack trace with line numbers, plus column number
1433 *     on top frame; column number is not guaranteed
1434 *   - Opera: full stack trace with line and column numbers
1435 *   - Chrome: full stack trace with line and column numbers
1436 *   - Safari: line and column number for the top frame only; some frames
1437 *     may be missing, and column number is not guaranteed
1438 *   - IE: line and column number for the top frame only; some frames
1439 *     may be missing, and column number is not guaranteed
1440 *
1441 * In theory, TraceKit should work on all of the following versions:
1442 *   - IE5.5+ (only 8.0 tested)
1443 *   - Firefox 0.9+ (only 3.5+ tested)
1444 *   - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
1445 *     Exceptions Have Stacktrace to be enabled in opera:config)
1446 *   - Safari 3+ (only 4+ tested)
1447 *   - Chrome 1+ (only 5+ tested)
1448 *   - Konqueror 3.5+ (untested)
1449 *
1450 * Requires TraceKit.computeStackTrace.
1451 *
1452 * Tries to catch all unhandled exceptions and report them to the
1453 * subscribed handlers. Please note that TraceKit.report will rethrow the
1454 * exception. This is REQUIRED in order to get a useful stack trace in IE.
1455 * If the exception does not reach the top of the browser, you will only
1456 * get a stack trace from the point where TraceKit.report was called.
1457 *
1458 * Handlers receive a TraceKit.StackTrace object as described in the
1459 * TraceKit.computeStackTrace docs.
1460 *
1461 * @memberof TraceKit
1462 * @namespace
1463 */
1464TraceKit.report = (function reportModuleWrapper() {
1465    var handlers = [],
1466        lastArgs = null,
1467        lastException = null,
1468        lastExceptionStack = null;
1469
1470    /**
1471     * Add a crash handler.
1472     * @param {Function} handler
1473     * @memberof TraceKit.report
1474     */
1475    function subscribe(handler) {
1476        installGlobalHandler();
1477        handlers.push(handler);
1478    }
1479
1480    /**
1481     * Remove a crash handler.
1482     * @param {Function} handler
1483     * @memberof TraceKit.report
1484     */
1485    function unsubscribe(handler) {
1486        for (var i = handlers.length - 1; i >= 0; --i) {
1487            if (handlers[i] === handler) {
1488                handlers.splice(i, 1);
1489            }
1490        }
1491    }
1492
1493    /**
1494     * Dispatch stack information to all handlers.
1495     * @param {TraceKit.StackTrace} stack
1496     * @param {boolean} isWindowError Is this a top-level window error?
1497     * @memberof TraceKit.report
1498     * @throws An exception if an error occurs while calling an handler.
1499     */
1500    function notifyHandlers(stack, isWindowError) {
1501        var exception = null;
1502        if (isWindowError && !TraceKit.collectWindowErrors) {
1503          return;
1504        }
1505        for (var i in handlers) {
1506            if (_has(handlers, i)) {
1507                try {
1508                    handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
1509                } catch (inner) {
1510                    exception = inner;
1511                }
1512            }
1513        }
1514
1515        if (exception) {
1516            throw exception;
1517        }
1518    }
1519
1520    var _oldOnerrorHandler, _onErrorHandlerInstalled;
1521
1522    /**
1523     * Ensures all global unhandled exceptions are recorded.
1524     * Supported by Gecko and IE.
1525     * @param {string} message Error message.
1526     * @param {string} url URL of script that generated the exception.
1527     * @param {(number|string)} lineNo The line number at which the error occurred.
1528     * @param {(number|string)=} columnNo The column number at which the error occurred.
1529     * @param {Error=} errorObj The actual Error object.
1530     * @memberof TraceKit.report
1531     */
1532    function traceKitWindowOnError(message, url, lineNo, columnNo, errorObj) {
1533        var stack = null;
1534
1535        if (lastExceptionStack) {
1536            TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
1537    	    processLastException();
1538	    } else if (errorObj) {
1539            stack = TraceKit.computeStackTrace(errorObj);
1540            notifyHandlers(stack, true);
1541        } else {
1542            var location = {
1543              'url': url,
1544              'line': lineNo,
1545              'column': columnNo
1546            };
1547            location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
1548            location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
1549            stack = {
1550              'mode': 'onerror',
1551              'message': message,
1552              'stack': [location]
1553            };
1554
1555            notifyHandlers(stack, true);
1556        }
1557
1558        if (_oldOnerrorHandler) {
1559            return _oldOnerrorHandler.apply(this, arguments);
1560        }
1561
1562        return false;
1563    }
1564
1565    /**
1566     * Install a global onerror handler
1567     * @memberof TraceKit.report
1568     */
1569    function installGlobalHandler () {
1570        if (_onErrorHandlerInstalled === true) {
1571            return;
1572        }
1573        _oldOnerrorHandler = window.onerror;
1574        window.onerror = traceKitWindowOnError;
1575        _onErrorHandlerInstalled = true;
1576    }
1577
1578    /**
1579     * Process the most recent exception
1580     * @memberof TraceKit.report
1581     */
1582    function processLastException() {
1583        var _lastExceptionStack = lastExceptionStack,
1584            _lastArgs = lastArgs;
1585        lastArgs = null;
1586        lastExceptionStack = null;
1587        lastException = null;
1588        notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
1589    }
1590
1591    /**
1592     * Reports an unhandled Error to TraceKit.
1593     * @param {Error} ex
1594     * @memberof TraceKit.report
1595     * @throws An exception if an incomplete stack trace is detected (old IE browsers).
1596     */
1597    function report(ex) {
1598        if (lastExceptionStack) {
1599            if (lastException === ex) {
1600                return; // already caught by an inner catch block, ignore
1601            } else {
1602              processLastException();
1603            }
1604        }
1605
1606        var stack = TraceKit.computeStackTrace(ex);
1607        lastExceptionStack = stack;
1608        lastException = ex;
1609        lastArgs = _slice.call(arguments, 1);
1610
1611        // If the stack trace is incomplete, wait for 2 seconds for
1612        // slow slow IE to see if onerror occurs or not before reporting
1613        // this exception; otherwise, we will end up with an incomplete
1614        // stack trace
1615        window.setTimeout(function () {
1616            if (lastException === ex) {
1617                processLastException();
1618            }
1619        }, (stack.incomplete ? 2000 : 0));
1620
1621        throw ex; // re-throw to propagate to the top level (and cause window.onerror)
1622    }
1623
1624    report.subscribe = subscribe;
1625    report.unsubscribe = unsubscribe;
1626    return report;
1627}());
1628
1629/**
1630 * An object representing a single stack frame.
1631 * @typedef {Object} StackFrame
1632 * @property {string} url The JavaScript or HTML file URL.
1633 * @property {string} func The function name, or empty for anonymous functions (if guessing did not work).
1634 * @property {string[]?} args The arguments passed to the function, if known.
1635 * @property {number=} line The line number, if known.
1636 * @property {number=} column The column number, if known.
1637 * @property {string[]} context An array of source code lines; the middle element corresponds to the correct line#.
1638 * @memberof TraceKit
1639 */
1640
1641/**
1642 * An object representing a JavaScript stack trace.
1643 * @typedef {Object} StackTrace
1644 * @property {string} name The name of the thrown exception.
1645 * @property {string} message The exception error message.
1646 * @property {TraceKit.StackFrame[]} stack An array of stack frames.
1647 * @property {string} mode 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace.
1648 * @memberof TraceKit
1649 */
1650
1651/**
1652 * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
1653 *
1654 * Syntax:
1655 *   ```js
1656 *   s = TraceKit.computeStackTrace.ofCaller([depth])
1657 *   s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
1658 *   ```
1659 *
1660 * Supports:
1661 *   - Firefox:  full stack trace with line numbers and unreliable column
1662 *               number on top frame
1663 *   - Opera 10: full stack trace with line and column numbers
1664 *   - Opera 9-: full stack trace with line numbers
1665 *   - Chrome:   full stack trace with line and column numbers
1666 *   - Safari:   line and column number for the topmost stacktrace element
1667 *               only
1668 *   - IE:       no line numbers whatsoever
1669 *
1670 * Tries to guess names of anonymous functions by looking for assignments
1671 * in the source code. In IE and Safari, we have to guess source file names
1672 * by searching for function bodies inside all page scripts. This will not
1673 * work for scripts that are loaded cross-domain.
1674 * Here be dragons: some function names may be guessed incorrectly, and
1675 * duplicate functions may be mismatched.
1676 *
1677 * TraceKit.computeStackTrace should only be used for tracing purposes.
1678 * Logging of unhandled exceptions should be done with TraceKit.report,
1679 * which builds on top of TraceKit.computeStackTrace and provides better
1680 * IE support by utilizing the window.onerror event to retrieve information
1681 * about the top of the stack.
1682 *
1683 * Note: In IE and Safari, no stack trace is recorded on the Error object,
1684 * so computeStackTrace instead walks its *own* chain of callers.
1685 * This means that:
1686 *  * in Safari, some methods may be missing from the stack trace;
1687 *  * in IE, the topmost function in the stack trace will always be the
1688 *    caller of computeStackTrace.
1689 *
1690 * This is okay for tracing (because you are likely to be calling
1691 * computeStackTrace from the function you want to be the topmost element
1692 * of the stack trace anyway), but not okay for logging unhandled
1693 * exceptions (because your catch block will likely be far away from the
1694 * inner function that actually caused the exception).
1695 *
1696 * Tracing example:
1697 *  ```js
1698 *     function trace(message) {
1699 *         var stackInfo = TraceKit.computeStackTrace.ofCaller();
1700 *         var data = message + "\n";
1701 *         for(var i in stackInfo.stack) {
1702 *             var item = stackInfo.stack[i];
1703 *             data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
1704 *         }
1705 *         if (window.console)
1706 *             console.info(data);
1707 *         else
1708 *             alert(data);
1709 *     }
1710 * ```
1711 * @memberof TraceKit
1712 * @namespace
1713 */
1714TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
1715    var debug = false,
1716        sourceCache = {};
1717
1718    /**
1719     * Attempts to retrieve source code via XMLHttpRequest, which is used
1720     * to look up anonymous function names.
1721     * @param {string} url URL of source code.
1722     * @return {string} Source contents.
1723     * @memberof TraceKit.computeStackTrace
1724     */
1725    function loadSource(url) {
1726        if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
1727            return '';
1728        }
1729        try {
1730            var getXHR = function() {
1731                try {
1732                    return new window.XMLHttpRequest();
1733                } catch (e) {
1734                    // explicitly bubble up the exception if not found
1735                    return new window.ActiveXObject('Microsoft.XMLHTTP');
1736                }
1737            };
1738
1739            var request = getXHR();
1740            request.open('GET', url, false);
1741            request.send('');
1742            return request.responseText;
1743        } catch (e) {
1744            return '';
1745        }
1746    }
1747
1748    /**
1749     * Retrieves source code from the source code cache.
1750     * @param {string} url URL of source code.
1751     * @return {Array.<string>} Source contents.
1752     * @memberof TraceKit.computeStackTrace
1753     */
1754    function getSource(url) {
1755        if (typeof url !== 'string') {
1756            return [];
1757        }
1758
1759        if (!_has(sourceCache, url)) {
1760            // URL needs to be able to fetched within the acceptable domain.  Otherwise,
1761            // cross-domain errors will be triggered.
1762            /*
1763                Regex matches:
1764                0 - Full Url
1765                1 - Protocol
1766                2 - Domain
1767                3 - Port (Useful for internal applications)
1768                4 - Path
1769            */
1770            var source = '';
1771            var domain = '';
1772            try { domain = window.document.domain; } catch (e) { }
1773            var match = /(.*)\:\/\/([^:\/]+)([:\d]*)\/{0,1}([\s\S]*)/.exec(url);
1774            if (match && match[2] === domain) {
1775                source = loadSource(url);
1776            }
1777            sourceCache[url] = source ? source.split('\n') : [];
1778        }
1779
1780        return sourceCache[url];
1781    }
1782
1783    /**
1784     * Tries to use an externally loaded copy of source code to determine
1785     * the name of a function by looking at the name of the variable it was
1786     * assigned to, if any.
1787     * @param {string} url URL of source code.
1788     * @param {(string|number)} lineNo Line number in source code.
1789     * @return {string} The function name, if discoverable.
1790     * @memberof TraceKit.computeStackTrace
1791     */
1792    function guessFunctionName(url, lineNo) {
1793        var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
1794            reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
1795            line = '',
1796            maxLines = 10,
1797            source = getSource(url),
1798            m;
1799
1800        if (!source.length) {
1801            return UNKNOWN_FUNCTION;
1802        }
1803
1804        // Walk backwards from the first line in the function until we find the line which
1805        // matches the pattern above, which is the function definition
1806        for (var i = 0; i < maxLines; ++i) {
1807            line = source[lineNo - i] + line;
1808
1809            if (!_isUndefined(line)) {
1810                if ((m = reGuessFunction.exec(line))) {
1811                    return m[1];
1812                } else if ((m = reFunctionArgNames.exec(line))) {
1813                    return m[1];
1814                }
1815            }
1816        }
1817
1818        return UNKNOWN_FUNCTION;
1819    }
1820
1821    /**
1822     * Retrieves the surrounding lines from where an exception occurred.
1823     * @param {string} url URL of source code.
1824     * @param {(string|number)} line Line number in source code to centre
1825     * around for context.
1826     * @return {?Array.<string>} Lines of source code.
1827     * @memberof TraceKit.computeStackTrace
1828     */
1829    function gatherContext(url, line) {
1830        var source = getSource(url);
1831
1832        if (!source.length) {
1833            return null;
1834        }
1835
1836        var context = [],
1837            // linesBefore & linesAfter are inclusive with the offending line.
1838            // if linesOfContext is even, there will be one extra line
1839            //   *before* the offending line.
1840            linesBefore = Math.floor(TraceKit.linesOfContext / 2),
1841            // Add one extra line if linesOfContext is odd
1842            linesAfter = linesBefore + (TraceKit.linesOfContext 
vendor: 6,747 bytes, lines 1842-2039
1842% 2),
1843            start = Math.max(0, line - linesBefore - 1),
1844            end = Math.min(source.length, line + linesAfter - 1);
1845
1846        line -= 1; // convert to 0-based index
1847
1848        for (var i = start; i < end; ++i) {
1849            if (!_isUndefined(source[i])) {
1850                context.push(source[i]);
1851            }
1852        }
1853
1854        return context.length > 0 ? context : null;
1855    }
1856
1857    /**
1858     * Escapes special characters, except for whitespace, in a string to be
1859     * used inside a regular expression as a string literal.
1860     * @param {string} text The string.
1861     * @return {string} The escaped string literal.
1862     * @memberof TraceKit.computeStackTrace
1863     */
1864    function escapeRegExp(text) {
1865        return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
1866    }
1867
1868    /**
1869     * Escapes special characters in a string to be used inside a regular
1870     * expression as a string literal. Also ensures that HTML entities will
1871     * be matched the same as their literal friends.
1872     * @param {string} body The string.
1873     * @return {string} The escaped string.
1874     * @memberof TraceKit.computeStackTrace
1875     */
1876    function escapeCodeAsRegExpForMatchingInsideHTML(body) {
1877        return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
1878    }
1879
1880    /**
1881     * Determines where a code fragment occurs in the source code.
1882     * @param {RegExp} re The function definition.
1883     * @param {Array.<string>} urls A list of URLs to search.
1884     * @return {?Object.<string, (string|number)>} An object containing
1885     * the url, line, and column number of the defined function.
1886     * @memberof TraceKit.computeStackTrace
1887     */
1888    function findSourceInUrls(re, urls) {
1889        var source, m;
1890        for (var i = 0, j = urls.length; i < j; ++i) {
1891            // console.log('searching', urls[i]);
1892            if ((source = getSource(urls[i])).length) {
1893                source = source.join('\n');
1894                if ((m = re.exec(source))) {
1895                    // console.log('Found function in ' + urls[i]);
1896
1897                    return {
1898                        'url': urls[i],
1899                        'line': source.substring(0, m.index).split('\n').length,
1900                        'column': m.index - source.lastIndexOf('\n', m.index) - 1
1901                    };
1902                }
1903            }
1904        }
1905
1906        // console.log('no match');
1907
1908        return null;
1909    }
1910
1911    /**
1912     * Determines at which column a code fragment occurs on a line of the
1913     * source code.
1914     * @param {string} fragment The code fragment.
1915     * @param {string} url The URL to search.
1916     * @param {(string|number)} line The line number to examine.
1917     * @return {?number} The column number.
1918     * @memberof TraceKit.computeStackTrace
1919     */
1920    function findSourceInLine(fragment, url, line) {
1921        var source = getSource(url),
1922            re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
1923            m;
1924
1925        line -= 1;
1926
1927        if (source && source.length > line && (m = re.exec(source[line]))) {
1928            return m.index;
1929        }
1930
1931        return null;
1932    }
1933
1934    /**
1935     * Determines where a function was defined within the source code.
1936     * @param {(Function|string)} func A function reference or serialized
1937     * function definition.
1938     * @return {?Object.<string, (string|number)>} An object containing
1939     * the url, line, and column number of the defined function.
1940     * @memberof TraceKit.computeStackTrace
1941     */
1942    function findSourceByFunctionBody(func) {
1943        if (_isUndefined(window && window.document)) {
1944            return;
1945        }
1946
1947        var urls = [window.location.href],
1948            scripts = window.document.getElementsByTagName('script'),
1949            body,
1950            code = '' + func,
1951            codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
1952            eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
1953            re,
1954            parts,
1955            result;
1956
1957        for (var i = 0; i < scripts.length; ++i) {
1958            var script = scripts[i];
1959            if (script.src) {
1960                urls.push(script.src);
1961            }
1962        }
1963
1964        if (!(parts = codeRE.exec(code))) {
1965            re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
1966        }
1967
1968        // not sure if this is really necessary, but I don’t have a test
1969        // corpus large enough to confirm that and it was in the original.
1970        else {
1971            var name = parts[1] ? '\\s+' + parts[1] : '',
1972                args = parts[2].split(',').join('\\s*,\\s*');
1973
1974            body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
1975            re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
1976        }
1977
1978        // look for a normal function definition
1979        if ((result = findSourceInUrls(re, urls))) {
1980            return result;
1981        }
1982
1983        // look for an old-school event handler function
1984        if ((parts = eventRE.exec(code))) {
1985            var event = parts[1];
1986            body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
1987
1988            // look for a function defined in HTML as an onXXX handler
1989            re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
1990
1991            if ((result = findSourceInUrls(re, urls[0]))) {
1992                return result;
1993            }
1994
1995            // look for ???
1996            re = new RegExp(body);
1997
1998            if ((result = findSourceInUrls(re, urls))) {
1999                return result;
2000            }
2001        }
2002
2003        return null;
2004    }
2005
2006    // Contents of Exception in various browsers.
2007    //
2008    // SAFARI:
2009    // ex.message = Can't find variable: qq
2010    // ex.line = 59
2011    // ex.sourceId = 580238192
2012    // ex.sourceURL = http://...
2013    // ex.expressionBeginOffset = 96
2014    // ex.expressionCaretOffset = 98
2015    // ex.expressionEndOffset = 98
2016    // ex.name = ReferenceError
2017    //
2018    // FIREFOX:
2019    // ex.message = qq is not defined
2020    // ex.fileName = http://...
2021    // ex.lineNumber = 59
2022    // ex.columnNumber = 69
2023    // ex.stack = ...stack trace... (see the example below)
2024    // ex.name = ReferenceError
2025    //
2026    // CHROME:
2027    // ex.message = qq is not defined
2028    // ex.name = ReferenceError
2029    // ex.type = not_defined
2030    // ex.arguments = ['aa']
2031    // ex.stack = ...stack trace...
2032    //
2033    // INTERNET EXPLORER:
2034    // ex.message = ...
2035    // ex.name = ReferenceError
2036    //
2037    // OPERA:
2038    // ex.message = ...message... (see the example below)
2039    // ex.name = ReferenceError
2040    // ex.opera#sourceloc = 11  (pretty much useless, duplicates the info in ex.message)
2041    // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
2042
2043    /**
2044     * Computes stack trace information from the stack property.
2045     * Chrome and Gecko use this property.
2046     * @param {Error} ex
2047     * @return {?TraceKit.StackTrace} Stack trace information.
2048     * @memberof TraceKit.computeStackTrace
2049     */
2050    function computeStackTraceFromStackProp(ex) {
2051        if (!ex.stack) {
2052            return null;
2053        }
2054
2055        var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|webpack|eval).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
2056            gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i,
2057            winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
2058            lines = ex.stack.split('\n'),
2059            stack = [],
2060            parts,
2061            element,
2062            reference = /^(.*) is undefined$/.exec(ex.message);
2063
2064        for (var i = 0, j = lines.length; i < j; ++i) {
2065            if ((parts = chrome.exec(lines[i]))) {
2066                var isNative = parts[2] && parts[2].indexOf('native') !== -1;
2067                element = {
2068                    'url': !isNative ? parts[2] : null,
2069                    'func': parts[1] || UNKNOWN_FUNCTION,
2070                    'args': isNative ? [parts[2]] : [],
2071                    'line': parts[3] ? +parts[3] : null,
2072                    'column': parts[4] ? +parts[4] : null
2073                };
2074            } else if ( parts = winjs.exec(lines[i]) ) {
2075                element = {
2076                    'url': parts[2],
2077                    'func': parts[1] || UNKNOWN_FUNCTION,
2078                    'args': [],
2079                    'line': +parts[3],
2080                    'column': parts[4] ? +parts[4] : null
2081                };
2082            } else if ((parts = gecko.exec(lines[i]))) {
2083                element = {
2084                    'url': parts[3],
2085                    'func': parts[1] || UNKNOWN_FUNCTION,
2086                    'args': parts[2] ? parts[2].split(',') : [],
2087                    'line': parts[4] ? +parts[4] : null,
2088                    'column': parts[5] ? +parts[5] : null
2089                };
2090            } else {
2091                continue;
2092            }
2093
2094            if (!element.func && element.line) {
2095                element.func = guessFunctionName(element.url, element.line);
2096            }
2097
2098            if (element.line) {
2099                element.context = gatherContext(element.url, element.line);
2100            }
2101
2102            stack.push(element);
2103        }
2104
2105        if (!stack.length) {
2106            return null;
2107        }
2108
2109        if (stack[0] && stack[0].line && !stack[0].column && reference) {
2110            stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
2111        } else if (!stack[0].column && !_isUndefined(ex.columnNumber)) {
2112            // FireFox uses this awesome columnNumber property for its top frame
2113            // Also note, Firefox's column number is 0-based and everything else expects 1-based,
2114            // so adding 1
2115            stack[0].column = ex.columnNumber + 1;
2116        }
2117
2118        return {
2119            'mode': 'stack',
2120            'name': ex.name,
2121            'message': ex.message,
2122            'stack': stack
2123        };
2124    }
2125
2126    /**
2127     * Computes stack trace information from the stacktrace property.
2128     * Opera 10+ uses this property.
2129     * @param {Error} ex
2130     * @return {?TraceKit.StackTrace} Stack trace information.
2131     * @memberof TraceKit.computeStackTrace
2132     */
2133    function computeStackTraceFromStacktraceProp(ex) {
2134        // Access and store the stacktrace property before doing ANYTHING
2135        // else to it because Opera is not very good at providing it
2136        // reliably in other circumstances.
2137        var stacktrace = ex.stacktrace;
2138        if (!stacktrace) {
2139            return;
2140        }
2141
2142        var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,
2143            opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i,
2144            lines = stacktrace.split('\n'),
2145            stack = [],
2146            parts;
2147
2148        for (var line = 0; line < lines.length; line += 2) {
2149            var element = null;
2150            if ((parts = opera10Regex.exec(lines[line]))) {
2151                element = {
2152                    'url': parts[2],
2153                    'line': +parts[1],
2154                    'column': null,
2155                    'func': parts[3],
2156                    'args':[]
2157                };
2158            } else if ((parts = opera11Regex.exec(lines[line]))) {
2159                element = {
2160                    'url': parts[6],
2161                    'line': +parts[1],
2162                    'column': +parts[2],
2163                    'func': parts[3] || parts[4],
2164                    'args': parts[5] ? parts[5].split(',') : []
2165                };
2166            }
2167
2168            if (element) {
2169                if (!element.func && element.line) {
2170                    element.func = guessFunctionName(element.url, element.line);
2171                }
2172                if (element.line) {
2173                    try {
2174                        element.context = gatherContext(element.url, element.line);
2175                    } catch (exc) {}
2176                }
2177
2178                if (!element.context) {
2179                    element.context = [lines[line + 1]];
2180                }
2181
2182                stack.push(element);
2183            }
2184        }
2185
2186        if (!stack.length) {
2187            return null;
2188        }
2189
2190        return {
2191            'mode': 'stacktrace',
2192            'name': ex.name,
2193            'message': ex.message,
2194            'stack': stack
2195        };
2196    }
2197
2198    /**
2199     * NOT TESTED.
2200     * Computes stack trace information from an error message that includes
2201     * the stack trace.
2202     * Opera 9 and earlier use this method if the option to show stack
2203     * traces is turned on in opera:config.
2204     * @param {Error} ex
2205     * @return {?TraceKit.StackTrace} Stack information.
2206     * @memberof TraceKit.computeStackTrace
2207     */
2208    function computeStackTraceFromOperaMultiLineMessage(ex) {
2209        // TODO: Clean this function up
2210        // Opera includes a stack trace into the exception message. An example is:
2211        //
2212        // Statement on line 3: Undefined variable: undefinedFunc
2213        // Backtrace:
2214        //   Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
2215        //         undefinedFunc(a);
2216        //   Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
2217        //           zzz(x, y, z);
2218        //   Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
2219        //           yyy(a, a, a);
2220        //   Line 1 of function script
2221        //     try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
2222        //   ...
2223
2224        var lines = ex.message.split('\n');
2225        if (lines.length < 4) {
2226            return null;
2227        }
2228
2229        var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
2230            lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
2231            lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
2232            stack = [],
2233            scripts = (window && window.document && window.document.getElementsByTagName('script')),
2234            inlineScriptBlocks = [],
2235            parts;
2236
2237        for (var s in scripts) {
2238            if (_has(scripts, s) && !scripts[s].src) {
2239                inlineScriptBlocks.push(scripts[s]);
2240            }
2241        }
2242
2243        for (var line = 2; line < lines.length; line += 2) {
2244            var item = null;
2245            if ((parts = lineRE1.exec(lines[line]))) {
2246                item = {
2247                    'url': parts[2],
2248                    'func': parts[3],
2249                    'args': [],
2250                    'line': +parts[1],
2251                    'column': null
2252                };
2253            } else if ((parts = lineRE2.exec(lines[line]))) {
2254                item = {
2255                    'url': parts[3],
2256                    'func': parts[4],
2257                    'args': [],
2258                    'line': +parts[1],
2259                    'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.
2260                };
2261                var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
2262                var script = inlineScriptBlocks[parts[2] - 1];
2263                if (script) {
2264                    var source = getSource(item.url);
2265                    if (source) {
2266                        source = source.join('\n');
2267                        var pos = source.indexOf(script.innerText);
2268                        if (pos >= 0) {
2269                            item.line = relativeLine + source.substring(0, pos).split('\n').length;
2270                        }
2271                    }
2272                }
2273            } else if ((parts = lineRE3.exec(lines[line]))) {
2274                var url = window.location.href.replace(/#.*$/, '');
2275                var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[line + 1]));
2276                var src = findSourceInUrls(re, [url]);
2277                item = {
2278                    'url': url,
2279                    'func': '',
2280                    'args': [],
2281                    'line': src ? src.line : parts[1],
2282                    'column': null
2283                };
2284            }
2285
2286            if (item) {
2287                if (!item.func) {
2288                    item.func = guessFunctionName(item.url, item.line);
2289                }
2290                var context = gatherContext(item.url, item.line);
2291                var midline = (context ? context[Math.floor(context.length / 2)] : null);
2292                if (context && midline.replace(/^\s*/, '') === lines[line + 1].replace(/^\s*/, '')) {
2293                    item.context = context;
2294                } else {
2295                    // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
2296                    item.context = [lines[line + 1]];
2297                }
2298                stack.push(item);
2299            }
2300        }
2301        if (!stack.length) {
2302            return null; // could not parse multiline exception message as Opera stack trace
2303        }
2304
2305        return {
2306            'mode': 'multiline',
2307            'name': ex.name,
2308            'message': lines[0],
2309            'stack': stack
2310        };
2311    }
2312
2313    /**
2314     * Adds information about the first frame to incomplete stack traces.
2315     * Safari and IE require this to get complete data on the first frame.
2316     * @param {TraceKit.StackTrace} stackInfo Stack trace information from
2317     * one of the compute* methods.
2318     * @param {string} url The URL of the script that caused an error.
2319     * @param {(number|string)} lineNo The line number of the script that
2320     * caused an error.
2321     * @param {string=} message The error generated by the browser, which
2322     * hopefully contains the name of the object that caused the error.
2323     * @return {boolean} Whether or not the stack information was
2324     * augmented.
2325     * @memberof TraceKit.computeStackTrace
2326     */
2327    function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
2328        var initial = {
2329            'url': url,
2330            'line': lineNo
2331        };
2332
2333        if (initial.url && initial.line) {
2334            stackInfo.incomplete = false;
2335
2336            if (!initial.func) {
2337                initial.func = guessFunctionName(initial.url, initial.line);
2338            }
2339
2340            if (!initial.context) {
2341                initial.context = gatherContext(initial.url, initial.line);
2342            }
2343
2344            var reference = / '([^']+)' /.exec(message);
2345            if (reference) {
2346                initial.column = findSourceInLine(reference[1], initial.url, initial.line);
2347            }
2348
2349            if (stackInfo.stack.length > 0) {
2350                if (stackInfo.stack[0].url === initial.url) {
2351                    if (stackInfo.stack[0].line === initial.line) {
2352                        return false; // already in stack trace
2353                    } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
2354                        stackInfo.stack[0].line = initial.line;
2355                        stackInfo.stack[0].context = initial.context;
2356                        return false;
2357                    }
2358                }
2359            }
2360
2361            stackInfo.stack.unshift(initial);
2362            stackInfo.partial = true;
2363            return true;
2364        } else {
2365            stackInfo.incomplete = true;
2366        }
2367
2368        return false;
2369    }
2370
2371    /**
2372     * Computes stack trace information by walking the arguments.caller
2373     * chain at the time the exception occurred. This will cause earlier
2374     * frames to be missed but is the only way to get any stack trace in
2375     * Safari and IE. The top frame is restored by
2376     * {@link augmentStackTraceWithInitialElement}.
2377     * @param {Error} ex
2378     * @return {TraceKit.StackTrace=} Stack trace information.
2379     * @memberof TraceKit.computeStackTrace
2380     */
2381    function computeStackTraceByWalkingCallerChain(ex, depth) {
2382        var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
2383            stack = [],
2384            funcs = {},
2385            recursion = false,
2386            parts,
2387            item,
2388            source;
2389
2390        for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
2391            if (curr === computeStackTrace || curr === TraceKit.report) {
2392                // console.log('skipping internal function');
2393                continue;
2394            }
2395
2396            item = {
2397                'url': null,
2398                'func': UNKNOWN_FUNCTION,
2399                'args': [],
2400                'line': null,
2401                'column': null
2402            };
2403
2404            if (curr.name) {
2405                item.func = curr.name;
2406            } else if ((parts = functionName.exec(curr.toString()))) {
2407                item.func = parts[1];
2408            }
2409
2410            if (typeof item.func === 'undefined') {
2411              try {
2412                item.func = parts.input.substring(0, parts.input.indexOf('{'));
2413              } catch (e) { }
2414            }
2415
2416            if ((source = findSourceByFunctionBody(curr))) {
2417                item.url = source.url;
2418                item.line = source.line;
2419
2420                if (item.func === UNKNOWN_FUNCTION) {
2421                    item.func = guessFunctionName(item.url, item.line);
2422                }
2423
2424                var reference = / '([^']+)' /.exec(ex.message || ex.description);
2425                if (reference) {
2426                    item.column = findSourceInLine(reference[1], source.url, source.line);
2427                }
2428            }
2429
2430            if (funcs['' + curr]) {
2431                recursion = true;
2432            }else{
2433                funcs['' + curr] = true;
2434            }
2435
2436            stack.push(item);
2437        }
2438
2439        if (depth) {
2440            // console.log('depth is ' + depth);
2441            // console.log('stack is ' + stack.length);
2442            stack.splice(0, depth);
2443        }
2444
2445        var result = {
2446            'mode': 'callers',
2447            'name': ex.name,
2448            'message': ex.message,
2449            'stack': stack
2450        };
2451        augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
2452        return result;
2453    }
2454
2455    /**
2456     * Computes a stack trace for an exception.
2457     * @param {Error} ex
2458     * @param {(string|number)=} depth
2459     * @memberof TraceKit.computeStackTrace
2460     */
2461    function computeStackTrace(ex, depth) {
2462        var stack = null;
2463        depth = (depth == null ? 0 : +depth);
2464
2465        try {
2466            // This must be tried first because Opera 10 *destroys*
2467            // its stacktrace property if you try to access the stack
2468            // property first!!
2469            stack = computeStackTraceFromStacktraceProp(ex);
2470            if (stack) {
2471                return stack;
2472            }
2473        } catch (e) {
2474            if (debug) {
2475                throw e;
2476            }
2477        }
2478
2479        try {
2480            stack = computeStackTraceFromStackProp(ex);
2481            if (stack) {
2482                return stack;
2483            }
2484        } catch (e) {
2485            if (debug) {
2486                throw e;
2487            }
2488        }
2489
2490        try {
2491            stack = computeStackTraceFromOperaMultiLineMessage(ex);
2492            if (stack) {
2493                return stack;
2494            }
2495        } catch (e) {
2496            if (debug) {
2497                throw e;
2498            }
2499        }
2500
2501        try {
2502            stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
2503            if (stack) {
2504                return stack;
2505            }
2506        } catch (e) {
2507            if (debug) {
2508                throw e;
2509            }
2510        }
2511
2512        return {
2513            'mode': 'failed'
2514        };
2515    }
2516
2517    /**
2518     * Logs a stacktrace starting from the previous call and working down.
2519     * @param {(number|string)=} depth How many frames deep to trace.
2520     * @return {TraceKit.StackTrace} Stack trace information.
2521     * @memberof TraceKit.computeStackTrace
2522     */
2523    function computeStackTraceOfCaller(depth) {
2524        depth = (depth == null ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame
2525        try {
2526            throw new Error();
2527        } catch (ex) {
2528            return computeStackTrace(ex, depth + 1);
2529        }
2530    }
2531
2532    computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
2533    computeStackTrace.guessFunctionName = guessFunctionName;
2534    computeStackTrace.gatherContext = gatherContext;
2535    computeStackTrace.ofCaller = computeStackTraceOfCaller;
2536    computeStackTrace.getSource = getSource;
2537
2538    return computeStackTrace;
2539}());
2540
2541/**
2542 * Extends support for global error handling for asynchronous browser
2543 * functions. Adopted from Closure Library's errorhandler.js
2544 * @memberof TraceKit
2545 */
2546TraceKit.extendToAsynchronousCallbacks = function () {
2547    var _helper = function _helper(fnName) {
2548        var originalFn = window[fnName];
2549        window[fnName] = function traceKitAsyncExtension() {
2550            // Make a copy of the arguments
2551            var args = _slice.call(arguments);
2552            var originalCallback = args[0];
2553            if (typeof (originalCallback) === 'function') {
2554                args[0] = TraceKit.wrap(originalCallback);
2555            }
2556            // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
2557            // also only supports 2 argument and doesn't care what "this" is, so we
2558            // can just call the original function directly.
2559            if (originalFn.apply) {
2560                return originalFn.apply(this, args);
2561            } else {
2562                return originalFn(args[0], args[1]);
2563            }
2564        };
2565    };
2566
2567    _helper('setTimeout');
2568    _helper('setInterval');
2569};
2570
2571//Default options:
2572if (!TraceKit.remoteFetching) {
2573    TraceKit.remoteFetching = true;
2574}
2575if (!TraceKit.collectWindowErrors) {
2576    TraceKit.collectWindowErrors = true;
2577}
2578if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) {
2579    // 5 lines before, the offending line, 5 lines after
2580    TraceKit.linesOfContext = 11;
2581}
2582
2583// UMD export
2584if (typeof module !== 'undefined' && module.exports && window.module !== module) {
2585    module.exports = TraceKit;
2586} else if (typeof define === 'function' && define.amd) {
2587    define('TraceKit', [], TraceKit);
2588} else {
2589    window.TraceKit = TraceKit;
2590}
2591
2592}(typeof window !== 'undefined' ? window : global));
2593;
2594
2595/* vim: set expandtab sw=4 ts=4 sts=4: */
2596/**
2597 * general function, usually for data manipulation pages
2598 *
2599 */
2600
2601var ErrorReport = {
2602    /**
2603     * @var object stores the last exception info
2604     */
2605    _last_exception: null,
2606    /**
2607     * handles thrown error exceptions based on user preferences
2608     *
2609     * @return void
2610     */
2611    error_handler: function (exception) {
2612        if (exception.name === null || typeof(exception.name) == "undefined") {
2613            exception.name = ErrorReport._extractExceptionName(exception);
2614        }
2615        ErrorReport._last_exception = exception;
2616        $.get("error_report.php", {
2617            ajax_request: true,
2618            server: PMA_commonParams.get('server'),
2619            token: PMA_commonParams.get('token'),
2620            get_settings: true,
2621            exception_type: 'js'
2622        }, function (data) {
2623            if (data.success !== true) {
2624                PMA_ajaxShowMessage(data.error, false);
2625                return;
2626            }
2627            if (data.report_setting == "ask") {
2628                ErrorReport._showErrorNotification();
2629            } else if (data.report_setting == "always") {
2630                report_data = ErrorReport._get_report_data(exception);
2631                post_data = $.extend(report_data, {
2632                    send_error_report: true,
2633                    automatic: true
2634                });
2635                $.post("error_report.php", post_data, function (data) {
2636                    if (data.success === false) {
2637                        //in the case of an error, show the error message returned.
2638                        PMA_ajaxShowMessage(data.error, false);
2639                    } else {
2640                        PMA_ajaxShowMessage(data.message, false);
2641                    }
2642                });
2643            }
2644        });
2645    },
2646    /**
2647     * Shows the modal dialog previewing the report
2648     *
2649     * @param exception object error report info
2650     *
2651     * @return void
2652     */
2653    _showReportDialog: function (exception) {
2654        var report_data = ErrorReport._get_report_data(exception);
2655
2656        /*Remove the hidden dialogs if there are*/
2657        if ($('#error_report_dialog').length !== 0) {
2658            $('#error_report_dialog').remove();
2659        }
2660        var $div = $('<div id="error_report_dialog"></div>');
2661
2662        var button_options = {};
2663
2664        button_options[PMA_messages.strSendErrorReport] = function () {
2665            var $dialog = $(this);
2666            var post_data = $.extend(report_data, {
2667                send_error_report: true,
2668                description: $("#report_description").val(),
2669                always_send: $("#always_send_checkbox")[0].checked
2670            });
2671            $.post("error_report.php", post_data, function (data) {
2672                $dialog.dialog('close');
2673                if (data.success === false) {
2674                    //in the case of an error, show the error message returned.
2675                    PMA_ajaxShowMessage(data.error, false);
2676                } else {
2677                    PMA_ajaxShowMessage(data.message, 3000);
2678                }
2679            });
2680        };
2681
2682        button_options[PMA_messages.strCancel] = function () {
2683            $(this).dialog('close');
2684        };
2685
2686        $.post("error_report.php", report_data, function (data) {
2687            if (data.success === false) {
2688                //in the case of an error, show the error message returned.
2689                PMA_ajaxShowMessage(data.error, false);
2690            } else {
2691                // Show dialog if the request was successful
2692                $div
2693                .append(data.message)
2694                .dialog({
2695                    title: PMA_messages.strSubmitErrorReport,
2696                    width: 650,
2697                    modal: true,
2698                    buttons: button_options,
2699                    close: function () {
2700                        $(this).remove();
2701                    }
2702                });
2703            }
2704        }); // end $.get()
2705    },
2706    /**
2707     * Shows the small notification that asks for user permission
2708     *
2709     * @return void
2710     */
2711    _showErrorNotification: function () {
2712        ErrorReport._removeErrorNotification();
2713
2714        var $div = $(
2715            '<div style="position:fixed;bottom:0;left:0;right:0;margin:0;' +
2716            'z-index:1000" class="error" id="error_notification"></div>'
2717        ).append(
2718            PMA_getImage("s_error.png") + PMA_messages.strErrorOccurred
2719        );
2720
2721        var $buttons = $('<div class="floatright"></div>');
2722
2723        var button_html  = '<button id="show_error_report">';
2724        button_html += PMA_messages.strShowReportDetails;
2725        button_html += '</button>';
2726
2727        button_html += '<a id="change_error_settings">';
2728        button_html += PMA_getImage('s_cog.png', PMA_messages.strChangeReportSettings);
2729        button_html += '</a>';
2730
2731        button_html += '<a href="#" id="ignore_error">';
2732        button_html += PMA_getImage('b_close.png', PMA_messages.strIgnore);
2733        button_html += '</a>';
2734
2735        $buttons.html(button_html);
2736
2737        $div.append($buttons);
2738        $div.appendTo(document.body);
2739        $("#change_error_settings").on("click", ErrorReport._redirect_to_settings);
2740        $("#show_error_report").on("click", ErrorReport._createReportDialog);
2741        $("#ignore_error").on("click", ErrorReport._removeErrorNotification);
2742    },
2743    /**
2744     * Removes the notification if it was displayed before
2745     *
2746     * @return void
2747     */
2748    _removeErrorNotification: function (e) {
2749        if (e) {
2750            // don't remove the hash fragment by navigating to #
2751            e.preventDefault();
2752        }
2753        $("#error_notification").fadeOut(function () {
2754            $(this).remove();
2755        });
2756    },
2757    /**
2758     * Extracts Exception name from message if it exists
2759     *
2760     * @return String
2761     */
2762    _extractExceptionName: function (exception) {
2763        if (exception.message === null || typeof(exception.message) == "undefined") {
2764            return "";
2765        }
2766
2767        var reg = /([a-zA-Z]+):/;
2768        var regex_result = reg.exec(exception.message);
2769        if (regex_result && regex_result.length == 2) {
2770            return regex_result[1];
2771        }
2772
2773        return "";
2774    },
2775    /**
2776     * Shows the modal dialog previewing the report
2777     *
2778     * @return void
2779     */
2780    _createReportDialog: function () {
2781        ErrorReport._removeErrorNotification();
2782        ErrorReport._showReportDialog(ErrorReport._last_exception);
2783    },
2784    /**
2785     * Redirects to the settings page containing error report
2786     * preferences
2787     *
2788     * @return void
2789     */
2790    _redirect_to_settings: function () {
2791        window.location.href = "prefs_forms.php?token=" + PMA_commonParams.get('token');
2792    },
2793    /**
2794     * Returns the report data to send to the server
2795     *
2796     * @param exception object exception info
2797     *
2798     * @return object
2799     */
2800    _get_report_data: function (exception) {
2801        var report_data = {
2802            "ajax_request": true,
2803            "token": PMA_commonParams.get('token'),
2804            "exception": exception,
2805            "current_url": window.location.href,
2806            "exception_type": 'js'
2807        };
2808        if (AJAX.scriptHandler._scripts.length > 0) {
2809            report_data.scripts = AJAX.scriptHandler._scripts.map(
2810                function (script) {
2811                    return script.name;
2812                }
2813            );
2814        }
2815        return report_data;
2816    },
2817    /**
2818     * Wraps all global functions that start with PMA_
2819     *
2820     * @return void
2821     */
2822    wrap_global_functions: function () {
2823        for (var key in window) {
2824            if (key.indexOf("PMA_") === 0) {
2825                var global = window[key];
2826                if (typeof(global) === "function") {
2827                    window[key] = ErrorReport.wrap_function(global);
2828                }
2829            }
2830        }
2831    },
2832    /**
2833     * Wraps given function in error reporting code and returns wrapped function
2834     *
2835     * @param func function to be wrapped
2836     *
2837     * @return function
2838     */
2839    wrap_function: function (func) {
2840        if (!func.wrapped) {
2841            var new_func = function () {
2842                try {
2843                    return func.apply(this, arguments);
2844                } catch (x) {
2845                    TraceKit.report(x);
2846                }
2847            };
2848            new_func.wrapped = true;
2849            //Set guid of wrapped function same as original function, so it can be removed
2850            //See bug#4146 (problem with jquery draggable and sortable)
2851            new_func.guid = func.guid = func.guid || new_func.guid || jQuery.guid++;
2852            return new_func;
2853        } else {
2854            return func;
2855        }
2856    },
2857    /**
2858     * Automatically wraps the callback in AJAX.registerOnload
2859     *
2860     * @return void
2861     */
2862    _wrap_ajax_onload_callback: function () {
2863        var oldOnload = AJAX.registerOnload;
2864        AJAX.registerOnload = function (file, func) {
2865            func = ErrorReport.wrap_function(func);
2866            oldOnload.call(this, file, func);
2867        };
2868    },
2869    /**
2870     * Automatically wraps the callback in $.fn.on
2871     *
2872     * @return void
2873     */
2874    _wrap_$_on_callback: function () {
2875        var oldOn = $.fn.on;
2876        $.fn.on = function () {
2877            for (var i = 1; i <= 3; i++) {
2878                if (typeof(arguments[i]) === "function") {
2879                    arguments[i] = ErrorReport.wrap_function(arguments[i]);
2880                    break;
2881                }
2882            }
2883            return oldOn.apply(this, arguments);
2884        };
2885    },
2886    /**
2887     * Wraps all global functions that start with PMA_
2888     * also automatically wraps the callback in AJAX.registerOnload
2889     *
2890     * @return void
2891     */
2892    set_up_error_reporting: function () {
2893        ErrorReport.wrap_global_functions();
2894        ErrorReport._wrap_ajax_onload_callback();
2895        ErrorReport._wrap_$_on_callback();
2896    }
2897
2898};
2899
2900TraceKit.report.subscribe(ErrorReport.error_handler);
2901ErrorReport.set_up_error_reporting();
2902$(function () {
2903    ErrorReport.wrap_global_functions();
2904});
2905;
2906
2907/* vim: set expandtab sw=4 ts=4 sts=4: */
2908/**
2909 * Functions used in configuration forms and on user preferences pages
2910 */
2911
2912/**
2913 * checks whether browser supports web storage
2914 *
2915 * @param type the type of storage i.e. localStorage or sessionStorage
2916 *
2917 * @returns bool
2918 */
2919function isStorageSupported(type, warn)
2920{
2921    try {
2922        window[type].setItem('PMATest', 'test');
2923        // Check whether key-value pair was set successfully
2924        if (window[type].getItem('PMATest') === 'test') {
2925            // Supported, remove test variable from storage
2926            window[type].removeItem('PMATest');
2927            return true;
2928        }
2929    } catch(error) {
2930        // Not supported
2931        if (warn) {
2932            PMA_ajaxShowMessage(PMA_messages.strNoLocalStorage, false);
2933        }
2934    }
2935    return false;
2936}
2937
2938/**
2939 * Unbind all event handlers before tearing down a page
2940 */
2941AJAX.registerTeardown('config.js', function () {
2942    $('.optbox input[id], .optbox select[id], .optbox textarea[id]').unbind('change').unbind('keyup');
2943    $('.optbox input[type=button][name=submit_reset]').unbind('click');
2944    $('div.tabs_contents').undelegate();
2945    $('#import_local_storage, #export_local_storage').unbind('click');
2946    $('form.prefs-form').unbind('change').unbind('submit');
2947    $(document).off('click', 'div.click-hide-message');
2948    $('#prefs_autoload').find('a').unbind('click');
2949});
2950
2951AJAX.registerOnload('config.js', function () {
2952    var $topmenu_upt = $('#topmenu2.user_prefs_tabs');
2953    $topmenu_upt.find('li.active a').attr('rel', 'samepage');
2954    $topmenu_upt.find('li:not(.active) a').attr('rel', 'newpage');
2955});
2956
2957// default values for fields
2958var defaultValues = {};
2959
2960/**
2961 * Returns field type
2962 *
2963 * @param {Element} field
2964 */
2965function getFieldType(field)
2966{
2967    var $field = $(field);
2968    var tagName = $field.prop('tagName');
2969    if (tagName == 'INPUT') {
2970        return $field.attr('type');
2971    } else if (tagName == 'SELECT') {
2972        return 'select';
2973    } else if (tagName == 'TEXTAREA') {
2974        return 'text';
2975    }
2976    return '';
2977}
2978
2979/**
2980 * Enables or disables the "restore default value" button
2981 *
2982 * @param {Element} field
2983 * @param {boolean} display
2984 */
2985function setRestoreDefaultBtn(field, display)
2986{
2987    var $el = $(field).closest('td').find('.restore-default img');
2988    $el[display ? 'show' : 'hide']();
2989}
2990
2991/**
2992 * Marks field depending on its value (system default or custom)
2993 *
2994 * @param {Element} field
2995 */
2996function markField(field)
2997{
2998    var $field = $(field);
2999    var type = getFieldType($field);
3000    var isDefault = checkFieldDefault($field, type);
3001
3002    // checkboxes uses parent <span> for marking
3003    var $fieldMarker = (type == 'checkbox') ? $field.parent() : $field;
3004    setRestoreDefaultBtn($field, !isDefault);
3005    $fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom');
3006}
3007
3008/**
3009 * Sets field value
3010 *
3011 * value must be of type:
3012 * o undefined (omitted) - restore default value (form default, not PMA default)
3013 * o String - if field_type is 'text'
3014 * o boolean - if field_type is 'checkbox'
3015 * o Array of values - if field_type is 'select'
3016 *
3017 * @param {Element} field
3018 * @param {String}  field_type  see {@link #getFieldType}
3019 * @param {String|Boolean}  [value]
3020 */
3021function setFieldValue(field, field_type, value)
3022{
3023    var $field = $(field);
3024    switch (field_type) {
3025    case 'text':
3026    case 'number':
3027        $field.val(value !== undefined ? value : $field.attr('defaultValue'));
3028        break;
3029    case 'checkbox':
3030        $field.prop('checked', (value !== undefined ? value : $field.attr('defaultChecked')));
3031        break;
3032    case 'select':
3033        var options = $field.prop('options');
3034        var i, imax = options.length;
3035        if (value === undefined) {
3036            for (i = 0; i < imax; i++) {
3037                options[i].selected = options[i].defaultSelected;
3038            }
3039        } else {
3040            for (i = 0; i < imax; i++) {
3041                options[i].selected = (value.indexOf(options[i].value) != -1);
3042            }
3043        }
3044        break;
3045    }
3046    markField($field);
3047}
3048
3049/**
3050 * Gets field value
3051 *
3052 * Will return one of:
3053 * o String - if type is 'text'
3054 * o boolean - if type is 'checkbox'
3055 * o Array of values - if type is 'select'
3056 *
3057 * @param {Element} field
3058 * @param {String}  field_type returned by {@link #getFieldType}
3059 * @type Boolean|String|String[]
3060 */
3061function getFieldValue(field, field_type)
3062{
3063    var $field = $(field);
3064    switch (field_type) {
3065    case 'text':
3066    case 'number':
3067        return $field.prop('value');
3068    case 'checkbox':
3069        return $field.prop('checked');
3070    case 'select':
3071        var options = $field.prop('options');
3072        var i, imax = options.length, items = [];
3073        for (i = 0; i < imax; i++) {
3074            if (options[i].selected) {
3075                items.push(options[i].value);
3076            }
3077        }
3078        return items;
3079    }
3080    return null;
3081}
3082
3083/**
3084 * Returns values for all fields in fieldsets
3085 */
3086function getAllValues()
3087{
3088    var $elements = $('fieldset input, fieldset select, fieldset textarea');
3089    var values = {};
3090    var type, value;
3091    for (var i = 0; i < $elements.length; i++) {
3092        type = getFieldType($elements[i]);
3093        value = getFieldValue($elements[i], type);
3094        if (typeof value != 'undefined') {
3095            // we only have single selects, fatten array
3096            if (type == 'select') {
3097                value = value[0];
3098            }
3099            values[$elements[i].name] = value;
3100        }
3101    }
3102    return values;
3103}
3104
3105/**
3106 * Checks whether field has its default value
3107 *
3108 * @param {Element} field
3109 * @param {String}  type
3110 * @return boolean
3111 */
3112function checkFieldDefault(field, type)
3113{
3114    var $field = $(field);
3115    var field_id = $field.attr('id');
3116    if (typeof defaultValues[field_id] == 'undefined') {
3117        return true;
3118    }
3119    var isDefault = true;
3120    var currentValue = getFieldValue($field, type);
3121    if (type != 'select') {
3122        isDefault = currentValue == defaultValues[field_id];
3123    } else {
3124        // compare arrays, will work for our representation of select values
3125        if (currentValue.length != defaultValues[field_id].length) {
3126            isDefault = false;
3127        }
3128        else {
3129            for (var i = 0; i < currentValue.length; i++) {
3130                if (currentValue[i] != defaultValues[field_id][i]) {
3131                    isDefault = false;
3132                    break;
3133                }
3134            }
3135        }
3136    }
3137    return isDefault;
3138}
3139
3140/**
3141 * Returns element's id prefix
3142 * @param {Element} element
3143 */
3144function getIdPrefix(element)
3145{
3146    return $(element).attr('id').replace(/[^-]+$/, '');
3147}
3148
3149// ------------------------------------------------------------------
3150// Form validation and field operations
3151//
3152
3153// form validator assignments
3154var validate = {};
3155
3156// form validator list
3157var validators = {
3158    // regexp: numeric value
3159    _regexp_numeric: /^[0-9]+$/,
3160    // regexp: extract parts from PCRE expression
3161    _regexp_pcre_extract: /(.)(.*)\1(.*)?/,
3162    /**
3163     * Validates positive number
3164     *
3165     * @param {boolean} isKeyUp
3166     */
3167    PMA_validatePositiveNumber: function (isKeyUp) {
3168        if (isKeyUp && this.value === '') {
3169            return true;
3170        }
3171        var result = this.value != '0' && validators._regexp_numeric.test(this.value);
3172        return result ? true : PMA_messages.error_nan_p;
3173    },
3174    /**
3175     * Validates non-negative number
3176     *
3177     * @param {boolean} isKeyUp
3178     */
3179    PMA_validateNonNegativeNumber: function (isKeyUp) {
3180        if (isKeyUp && this.value === '') {
3181            return true;
3182        }
3183        var result = validators._regexp_numeric.test(this.value);
3184        return result ? true : PMA_messages.error_nan_nneg;
3185    },
3186    /**
3187     * Validates port number
3188     *
3189     * @param {boolean} isKeyUp
3190     */
3191    PMA_validatePortNumber: function (isKeyUp) {
3192        if (this.value === '') {
3193            return true;
3194        }
3195        var result = validators._regexp_numeric.test(this.value) && this.value != '0';
3196        return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port;
3197    },
3198    /**
3199     * Validates value according to given regular expression
3200     *
3201     * @param {boolean} isKeyUp
3202     * @param {string}  regexp
3203     */
3204    PMA_validateByRegex: function (isKeyUp, regexp) {
3205        if (isKeyUp && this.value === '') {
3206            return true;
3207        }
3208        // convert PCRE regexp
3209        var parts = regexp.match(validators._regexp_pcre_extract);
3210        var valid = this.value.match(new RegExp(parts[2], parts[3])) !== null;
3211        return valid ? true : PMA_messages.error_invalid_value;
3212    },
3213    /**
3214     * Validates upper bound for numeric inputs
3215     *
3216     * @param {boolean} isKeyUp
3217     * @param {int} max_value
3218     */
3219    PMA_validateUpperBound: function (isKeyUp, max_value) {
3220        var val = parseInt(this.value, 10);
3221        if (isNaN(val)) {
3222            return true;
3223        }
3224        return val <= max_value ? true : PMA_sprintf(PMA_messages.error_value_lte, max_value);
3225    },
3226    // field validators
3227    _field: {
3228    },
3229    // fieldset validators
3230    _fieldset: {
3231    }
3232};
3233
3234/**
3235 * Registers validator for given field
3236 *
3237 * @param {String}  id       field id
3238 * @param {String}  type     validator (key in validators object)
3239 * @param {boolean} onKeyUp  whether fire on key up
3240 * @param {Array}   params   validation function parameters
3241 */
3242function validateField(id, type, onKeyUp, params)
3243{
3244    if (typeof validators[type] == 'undefined') {
3245        return;
3246    }
3247    if (typeof validate[id] == 'undefined') {
3248        validate[id] = [];
3249    }
3250    validate[id].push([type, params, onKeyUp]);
3251}
3252
3253/**
3254 * Returns validation functions associated with form field
3255 *
3256 * @param {String}  field_id     form field id
3257 * @param {boolean} onKeyUpOnly  see validateField
3258 * @type Array
3259 * @return array of [function, parameters to be passed to function]
3260 */
3261function getFieldValidators(field_id, onKeyUpOnly)
3262{
3263    // look for field bound validator
3264    var name = field_id && field_id.match(/[^-]+$/)[0];
3265    if (typeof validators._field[name] != 'undefined') {
3266        return [[validators._field[name], null]];
3267    }
3268
3269    // look for registered validators
3270    var functions = [];
3271    if (typeof validate[field_id] != 'undefined') {
3272        // validate[field_id]: array of [type, params, onKeyUp]
3273        for (var i = 0, imax = validate[field_id].length; i < imax; i++) {
3274            if (onKeyUpOnly && !validate[field_id][i][2]) {
3275                continue;
3276            }
3277            functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
3278        }
3279    }
3280
3281    return functions;
3282}
3283
3284/**
3285 * Displays errors for given form fields
3286 *
3287 * WARNING: created DOM elements must be identical with the ones made by
3288 * display_input() in FormDisplay.tpl.php!
3289 *
3290 * @param {Object} error_list list of errors in the form {field id: error array}
3291 */
3292function displayErrors(error_list)
3293{
3294    var tempIsEmpty = function (item) {
3295        return item !== '';
3296    };
3297
3298    for (var field_id in error_list) {
3299        var errors = error_list[field_id];
3300        var $field = $('#' + field_id);
3301        var isFieldset = $field.attr('tagName') == 'FIELDSET';
3302        var $errorCnt;
3303        if (isFieldset) {
3304            $errorCnt = $field.find('dl.errors');
3305        } else {
3306            $errorCnt = $field.siblings('.inline_errors');
3307        }
3308
3309        // remove empty errors (used to clear error list)
3310        errors = $.grep(errors, tempIsEmpty);
3311
3312        // CSS error class
3313        if (!isFieldset) {
3314            // checkboxes uses parent <span> for marking
3315            var $fieldMarker = ($field.attr('type') == 'checkbox') ? $field.parent() : $field;
3316            $fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error');
3317        }
3318
3319        if (errors.length) {
3320            // if error container doesn't exist, create it
3321            if ($errorCnt.length === 0) {
3322                if (isFieldset) {
3323                    $errorCnt = $('<dl class="errors" />');
3324                    $field.find('table').before($errorCnt);
3325                } else {
3326                    $errorCnt = $('<dl class="inline_errors" />');
3327                    $field.closest('td').append($errorCnt);
3328                }
3329            }
3330
3331            var html = '';
3332            for (var i = 0, imax = errors.length; i < imax; i++) {
3333                html += '<dd>' + errors[i] + '</dd>';
3334            }
3335            $errorCnt.html(html);
3336        } else if ($errorCnt !== null) {
3337            // remove useless error container
3338            $errorCnt.remove();
3339        }
3340    }
3341}
3342
3343/**
3344 * Validates fieldset and puts errors in 'errors' object
3345 *
3346 * @param {Element} fieldset
3347 * @param {boolean} isKeyUp
3348 * @param {Object}  errors
3349 */
3350function validate_fieldset(fieldset, isKeyUp, errors)
3351{
3352    var $fieldset = $(fieldset);
3353    if ($fieldset.length && typeof validators._fieldset[$fieldset.attr('id')] != 'undefined') {
3354        var fieldset_errors = validators._fieldset[$fieldset.attr('id')].apply($fieldset[0], [isKeyUp]);
3355        for (var field_id in fieldset_errors) {
3356            if (typeof errors[field_id] == 'undefined') {
3357                errors[field_id] = [];
3358            }
3359            if (typeof fieldset_errors[field_id] == 'string') {
3360                fieldset_errors[field_id] = [fieldset_errors[field_id]];
3361            }
3362            $.merge(errors[field_id], fieldset_errors[field_id]);
3363        }
3364    }
3365}
3366
3367/**
3368 * Validates form field and puts errors in 'errors' object
3369 *
3370 * @param {Element} field
3371 * @param {boolean} isKeyUp
3372 * @param {Object}  errors
3373 */
3374function validate_field(field, isKeyUp, errors)
3375{
3376    var args, result;
3377    var $field = $(field);
3378    var field_id = $field.attr('id');
3379    errors[field_id] = [];
3380    var functions = getFieldValidators(field_id, isKeyUp);
3381    for (var i = 0; i < functions.length; i++) {
3382        if (typeof functions[i][1] !== 'undefined' && functions[i][1] !== null) {
3383            args = functions[i][1].slice(0);
3384        } else {
3385            args = [];
3386        }
3387        args.unshift(isKeyUp);
3388        result = functions[i][0].apply($field[0], args);
3389        if (result !== true) {
3390            if (typeof result == 'string') {
3391                result = [result];
3392            }
3393            $.merge(errors[field_id], result);
3394        }
3395    }
3396}
3397
3398/**
3399 * Validates form field and parent fieldset
3400 *
3401 * @param {Element} field
3402 * @param {boolean} isKeyUp
3403 */
3404function validate_field_and_fieldset(field, isKeyUp)
3405{
3406    var $field = $(field);
3407    var errors = {};
3408    validate_field($field, isKeyUp, errors);
3409    validate_fieldset($field.closest('fieldset.optbox'), isKeyUp, errors);
3410    displayErrors(errors);
3411}
3412
3413function loadInlineConfig() {
3414    if (!Array.isArray(configInlineParams)) {
3415        return;
3416    }
3417    for (var i = 0; i < configInlineParams.length; ++i) {
3418        if (typeof configInlineParams[i] === 'function') {
3419            configInlineParams[i]();
3420        }
3421    }
3422}
3423
3424function setupValidation() {
3425    validate = {};
3426    configScriptLoaded = true;
3427    if (configScriptLoaded && typeof configInlineParams !== "undefined") {
3428        loadInlineConfig();
3429    }
3430    // register validators and mark custom values
3431    var $elements = $('.optbox input[id], .optbox select[id], .optbox textarea[id]');
3432    $elements.each(function () {
3433        markField(this);
3434        var $el = $(this);
3435        $el.bind('change', function () {
3436            validate_field_and_fieldset(this, false);
3437            markField(this);
3438        });
3439        var tagName = $el.attr('tagName');
3440        // text fields can be validated after each change
3441        if (tagName == 'INPUT' && $el.attr('type') == 'text') {
3442            $el.keyup(function () {
3443                validate_field_and_fieldset($el, true);
3444                markField($el);
3445            });
3446        }
3447        // disable textarea spellcheck
3448        if (tagName == 'TEXTAREA') {
3449            $el.attr('spellcheck', false);
3450        }
3451    });
3452
3453    // check whether we've refreshed a page and browser remembered modified
3454    // form values
3455    var $check_page_refresh = $('#check_page_refresh');
3456    if ($check_page_refresh.length === 0 || $check_page_refresh.val() == '1') {
3457        // run all field validators
3458        var errors = {};
3459        for (var i = 0; i < $elements.length; i++) {
3460            validate_field($elements[i], false, errors);
3461        }
3462        // run all fieldset validators
3463        $('fieldset.optbox').each(function () {
3464            validate_fieldset(this, false, errors);
3465        });
3466
3467        displayErrors(errors);
3468    } else if ($check_page_refresh) {
3469        $check_page_refresh.val('1');
3470    }
3471}
3472
3473AJAX.registerOnload('config.js', function () {
3474    setupValidation();
3475});
3476
3477//
3478// END: Form validation and field operations
3479// ------------------------------------------------------------------
3480
3481// ------------------------------------------------------------------
3482// Tabbed forms
3483//
3484
3485/**
3486 * Sets active tab
3487 *
3488 * @param {String} tab_id
3489 */
3490function setTab(tab_id)
3491{
3492    $('ul.tabs').each(function() {
3493        var $this = $(this);
3494        if (!$this.find('li a[href=#' + tab_id + ']').length) {
3495            return;
3496        }
3497        $this.find('li').removeClass('active').find('a[href=#' + tab_id + ']').parent().addClass('active');
3498        $this.parent().find('div.tabs_contents fieldset').hide().filter('#' + tab_id).show();
3499        var hashValue = 'tab_' + tab_id;
3500        location.hash = hashValue;
3501        $this.parent().find('input[name=tab_hash]').val(hashValue);
3502    });
3503}
3504
3505function setupConfigTabs() {
3506    var forms = $('form.config-form');
3507    forms.each(function() {
3508        var $this = $(this);
3509        var $tabs = $this.find('ul.tabs');
3510        if (!$tabs.length) {
3511            return;
3512        }
3513        // add tabs events and activate one tab (the first one or indicated by location hash)
3514        $tabs.find('li').removeClass('active');
3515        $tabs.find('a')
3516            .click(function (e) {
3517                e.preventDefault();
3518                setTab($(this).attr('href').substr(1));
3519            })
3520            .filter(':first')
3521            .parent()
3522            .addClass('active');
3523        $this.find('div.tabs_contents fieldset').hide().filter(':first').show();
3524    });
3525}
3526
3527AJAX.registerOnload('config.js', function () {
3528    setupConfigTabs();
3529
3530    // tab links handling, check each 200ms
3531    // (works with history in FF, further browser support here would be an overkill)
3532    var prev_hash;
3533    var tab_check_fnc = function () {
3534        if (location.hash != prev_hash) {
3535            prev_hash = location.hash;
3536            if (prev_hash.match(/^#tab_[a-zA-Z0-9_]+$/)) {
3537                // session ID is sometimes appended here
3538                var hash = prev_hash.substr(5).split('&')[0];
3539                if ($('#' + hash).length) {
3540                    setTab(hash);
3541                }
3542            }
3543        }
3544    };
3545    tab_check_fnc();
3546    setInterval(tab_check_fnc, 200);
3547});
3548
3549//
3550// END: Tabbed forms
3551// ------------------------------------------------------------------
3552
3553// ------------------------------------------------------------------
3554// Form reset buttons
3555//
3556
3557AJAX.registerOnload('config.js', function () {
3558    $('.optbox input[type=button][name=submit_reset]').click(function () {
3559        var fields = $(this).closest('fieldset').find('input, select, textarea');
3560        for (var i = 0, imax = fields.length; i < imax; i++) {
3561            setFieldValue(fields[i], getFieldType(fields[i]));
3562        }
3563    });
3564});
3565
3566//
3567// END: Form reset buttons
3568// ------------------------------------------------------------------
3569
3570// ------------------------------------------------------------------
3571// "Restore default" and "set value" buttons
3572//
3573
3574/**
3575 * Restores field's default value
3576 *
3577 * @param {String} field_id
3578 */
3579function restoreField(field_id)
3580{
3581    var $field = $('#' + field_id);
3582    if ($field.length === 0 || defaultValues[field_id] === undefined) {
3583        return;
3584    }
3585    setFieldValue($field, getFieldType($field), defaultValues[field_id]);
3586}
3587
3588function setupRestoreField() {
3589    $('div.tabs_contents')
3590        .delegate('.restore-default, .set-value', 'mouseenter', function () {
3591            $(this).css('opacity', 1);
3592        })
3593        .delegate('.restore-default, .set-value', 'mouseleave', function () {
3594            $(this).css('opacity', 0.25);
3595        })
3596        .delegate('.restore-default, .set-value', 'click', function (e) {
3597            e.preventDefault();
3598            var href = $(this).attr('href');
3599            var field_sel;
3600            if ($(this).hasClass('restore-default')) {
3601                field_sel = href;
3602                restoreField(field_sel.substr(1));
3603            } else {
3604                field_sel = href.match(/^[^=]+/)[0];
3605                var value = href.match(/\=(.+)$/)[1];
3606                setFieldValue($(field_sel), 'text', value);
3607            }
3608            $(field_sel).trigger('change');
3609        })
3610        .find('.restore-default, .set-value')
3611        // inline-block for IE so opacity inheritance works
3612        .css({display: 'inline-block', opacity: 0.25});
3613}
3614
3615AJAX.registerOnload('config.js', function () {
3616    setupRestoreField();
3617});
3618
3619//
3620// END: "Restore default" and "set value" buttons
3621// ------------------------------------------------------------------
3622
3623// ------------------------------------------------------------------
3624// User preferences import/export
3625//
3626
3627AJAX.registerOnload('config.js', function () {
3628    offerPrefsAutoimport();
3629    var $radios = $('#import_local_storage, #export_local_storage');
3630    if (!$radios.length) {
3631        return;
3632    }
3633
3634    // enable JavaScript dependent fields
3635    $radios
3636        .prop('disabled', false)
3637        .add('#export_text_file, #import_text_file')
3638        .click(function () {
3639            var enable_id = $(this).attr('id');
3640            var disable_id;
3641            if (enable_id.match(/local_storage$/)) {
3642                disable_id = enable_id.replace(/local_storage$/, 'text_file');
3643            } else {
3644                disable_id = enable_id.replace(/text_file$/, 'local_storage');
3645            }
3646            $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true);
3647            $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false);
3648        });
3649
3650    // detect localStorage state
3651    var ls_supported = isStorageSupported('localStorage', true);
3652    var ls_exists = ls_supported ? (window.localStorage.config || false) : false;
3653    $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide();
3654    $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide();
3655    if (ls_exists) {
3656        updatePrefsDate();
3657    }
3658    $('form.prefs-form').change(function () {
3659        var $form = $(this);
3660        var disabled = false;
3661        if (!ls_supported) {
3662            disabled = $form.find('input[type=radio][value$=local_storage]').prop('checked');
3663        } else if (!ls_exists && $form.attr('name') == 'prefs_import' &&
3664            $('#import_local_storage')[0].checked
3665            ) {
3666            disabled = true;
3667        }
3668        $form.find('input[type=submit]').prop('disabled', disabled);
3669    }).submit(function (e) {
3670        var $form = $(this);
3671        if ($form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
3672            e.preventDefault();
3673            // use AJAX to read JSON settings and save them
3674            savePrefsToLocalStorage($form);
3675        } else if ($form.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked) {
3676            // set 'json' input and submit form
3677            $form.find('input[name=json]').val(window.localStorage.config);
3678        }
3679    });
3680
3681    $(document).on('click', 'div.click-hide-message', function () {
3682        $(this)
3683        .hide()
3684        .parent('.group')
3685        .css('height', '')
3686        .next('form')
3687        .show();
3688    });
3689});
3690
3691/**
3692 * Saves user preferences to localStorage
3693 *
3694 * @param {Element} form
3695 */
3696function savePrefsToLocalStorage(form)
3697{
3698    $form = $(form);
3699    var submit = $form.find('input[type=submit]');
3700    submit.prop('disabled', true);
3701    $.ajax({
3702        url: 'prefs_manage.php',
3703        cache: false,
3704        type: 'POST',
3705        data: {
3706            ajax_request: true,
3707            server: $form.find('input[name=server]').val(),
3708            token: $form.find('input[name=token]').val(),
3709            submit_get_json: true
3710        },
3711        success: function (data) {
3712            if (typeof data !== 'undefined' && data.success === true) {
3713                window.localStorage.config = data.prefs;
3714                window.localStorage.config_mtime = data.mtime;
3715                window.localStorage.config_mtime_local = (new Date()).toUTCString();
3716                updatePrefsDate();
3717                $('div.localStorage-empty').hide();
3718                $('div.localStorage-exists').show();
3719                var group = $form.parent('.group');
3720                group.css('height', group.height() + 'px');
3721                $form.hide('fast');
3722                $form.prev('.click-hide-message').show('fast');
3723            } else {
3724                PMA_ajaxShowMessage(data.error);
3725            }
3726        },
3727        complete: function () {
3728            submit.prop('disabled', false);
3729        }
3730    });
3731}
3732
3733/**
3734 * Updates preferences timestamp in Import form
3735 */
3736function updatePrefsDate()
3737{
3738    var d = new Date(window.localStorage.config_mtime_local);
3739    var msg = PMA_messages.strSavedOn.replace(
3740        '@DATE@',
3741        PMA_formatDateTime(d)
3742    );
3743    $('#opts_import_local_storage').find('div.localStorage-exists').html(msg);
3744}
3745
3746/**
3747 * Prepares message which informs that localStorage preferences are available and can be imported
3748 */
3749function offerPrefsAutoimport()
3750{
3751    var has_config = (isStorageSupported('localStorage')) && (window.localStorage.config || false);
3752    var $cnt = $('#prefs_autoload');
3753    if (!$cnt.length || !has_config) {
3754        return;
3755    }
3756    $cnt.find('a').click(function (e) {
3757        e.preventDefault();
3758        var $a = $(this);
3759        if ($a.attr('href') == '#no') {
3760            $cnt.remove();
3761            $.post('index.php', {
3762                token: $cnt.find('input[name=token]').val(),
3763                prefs_autoload: 'hide'
3764            }, null, 'html');
3765            return;
3766        }
3767        $cnt.find('input[name=json]').val(window.localStorage.config);
3768        $cnt.find('form').submit();
3769    });
3770    $cnt.show();
3771}
3772
3773//
3774// END: User preferences import/export
3775// ------------------------------------------------------------------
3776;
3777
3778/**
3779 * Definition of links to MySQL documentation.
3780 */
3781
3782var mysql_doc_keyword = {
3783    /* Multi word */
3784    'CHARACTER SET': Array('charset'),
3785    'SHOW AUTHORS': Array('show-authors'),
3786    'SHOW BINARY LOGS': Array('show-binary-logs'),
3787    'SHOW BINLOG EVENTS': Array('show-binlog-events'),
3788    'SHOW CHARACTER SET': Array('show-character-set'),
3789    'SHOW COLLATION': Array('show-collation'),
3790    'SHOW COLUMNS': Array('show-columns'),
3791    'SHOW CONTRIBUTORS': Array('show-contributors'),
3792    'SHOW CREATE DATABASE': Array('show-create-database'),
3793    'SHOW CREATE EVENT': Array('show-create-event'),
3794    'SHOW CREATE FUNCTION': Array('show-create-function'),
3795    'SHOW CREATE PROCEDURE': Array('show-create-procedure'),
3796    'SHOW CREATE TABLE': Array('show-create-table'),
3797    'SHOW CREATE TRIGGER': Array('show-create-trigger'),
3798    'SHOW CREATE VIEW': Array('show-create-view'),
3799    'SHOW DATABASES': Array('show-databases'),
3800    'SHOW ENGINE': Array('show-engine'),
3801    'SHOW ENGINES': Array('show-engines'),
3802    'SHOW ERRORS': Array('show-errors'),
3803    'SHOW EVENTS': Array('show-events'),
3804    'SHOW FUNCTION CODE': Array('show-function-code'),
3805    'SHOW FUNCTION STATUS': Array('show-function-status'),
3806    'SHOW GRANTS': Array('show-grants'),
3807    'SHOW INDEX': Array('show-index'),
3808    'SHOW MASTER STATUS': Array('show-master-status'),
3809    'SHOW OPEN TABLES': Array('show-open-tables'),
3810    'SHOW PLUGINS': Array('show-plugins'),
3811    'SHOW PRIVILEGES': Array('show-privileges'),
3812    'SHOW PROCEDURE CODE': Array('show-procedure-code'),
3813    'SHOW PROCEDURE STATUS': Array('show-procedure-status'),
3814    'SHOW PROCESSLIST': Array('show-processlist'),
3815    'SHOW PROFILE': Array('show-profile'),
3816    'SHOW PROFILES': Array('show-profiles'),
3817    'SHOW RELAYLOG EVENTS': Array('show-relaylog-events'),
3818    'SHOW SLAVE HOSTS': Array('show-slave-hosts'),
3819    'SHOW SLAVE STATUS': Array('show-slave-status'),
3820    'SHOW STATUS': Array('show-status'),
3821    'SHOW TABLE STATUS': Array('show-table-status'),
3822    'SHOW TABLES': Array('show-tables'),
3823    'SHOW TRIGGERS': Array('show-triggers'),
3824    'SHOW VARIABLES': Array('show-variables'),
3825    'SHOW WARNINGS': Array('show-warnings'),
3826    'LOAD DATA INFILE': Array('load-data'),
3827    'LOAD XML': Array('load-xml'),
3828    'LOCK TABLES': Array('lock-tables'),
3829    'UNLOCK TABLES': Array('lock-tables'),
3830    'ALTER DATABASE': Array('alter-database'),
3831    'ALTER EVENT': Array('alter-event'),
3832    'ALTER LOGFILE GROUP': Array('alter-logfile-group'),
3833    'ALTER FUNCTION': Array('alter-function'),
3834    'ALTER PROCEDURE': Array('alter-procedure'),
3835    'ALTER SERVER': Array('alter-server'),
3836    'ALTER TABLE': Array('alter-table'),
3837    'ALTER TABLESPACE': Array('alter-tablespace'),
3838    'ALTER VIEW': Array('alter-view'),
3839    'CREATE DATABASE': Array('create-database'),
3840    'CREATE EVENT': Array('create-event'),
3841    'CREATE FUNCTION': Array('create-function'),
3842    'CREATE INDEX': Array('create-index'),
3843    'CREATE LOGFILE GROUP': Array('create-logfile-group'),
3844    'CREATE PROCEDURE': Array('create-procedure'),
3845    'CREATE SERVER': Array('create-server'),
3846    'CREATE TABLE': Array('create-table'),
3847    'CREATE TABLESPACE': Array('create-tablespace'),
3848    'CREATE TRIGGER': Array('create-trigger'),
3849    'CREATE VIEW': Array('create-view'),
3850    'DROP DATABASE': Array('drop-database'),
3851    'DROP EVENT': Array('drop-event'),
3852    'DROP FUNCTION': Array('drop-function'),
3853    'DROP INDEX': Array('drop-index'),
3854    'DROP LOGFILE GROUP': Array('drop-logfile-group'),
3855    'DROP PROCEDURE': Array('drop-procedure'),
3856    'DROP SERVER': Array('drop-server'),
3857    'DROP TABLE': Array('drop-table'),
3858    'DROP TABLESPACE': Array('drop-tablespace'),
3859    'DROP TRIGGER': Array('drop-trigger'),
3860    'DROP VIEW': Array('drop-view'),
3861    'RENAME TABLE': Array('rename-table'),
3862    'TRUNCATE TABLE': Array('truncate-table'),
3863
3864    /* Statements */
3865    'SELECT': Array('select'),
3866    'SET': Array('set'),
3867    'EXPLAIN': Array('explain'),
3868    'DESCRIBE': Array('describe'),
3869    'DELETE': Array('delete'),
3870    'SHOW': Array('show'),
3871    'UPDATE': Array('update'),
3872    'INSERT': Array('insert'),
3873    'REPLACE': Array('replace'),
3874    'CALL': Array('call'),
3875    'DO': Array('do'),
3876    'HANDLER': Array('handler'),
3877    'COLLATE': Array('charset-collations'),
3878
3879    /* Functions */
3880    'ABS': Array('mathematical-functions', 'function_abs'),
3881    'ACOS': Array('mathematical-functions', 'function_acos'),
3882    'ADDDATE': Array('date-and-time-functions', 'function_adddate'),
3883    'ADDTIME': Array('date-and-time-functions', 'function_addtime'),
3884    'AES_DECRYPT': Array('encryption-functions', 'function_aes_decrypt'),
3885    'AES_ENCRYPT': Array('encryption-functions', 'function_aes_encrypt'),
3886    'AND': Array('logical-operators', 'operator_and'),
3887    'ASCII': Array('string-functions', 'function_ascii'),
3888    'ASIN': Array('mathematical-functions', 'function_asin'),
3889    'ATAN2': Array('mathematical-functions', 'function_atan2'),
3890    'ATAN': Array('mathematical-functions', 'function_atan'),
3891    'AVG': Array('group-by-functions', 'function_avg'),
3892    'BENCHMARK': Array('information-functions', 'function_benchmark'),
3893    'BIN': Array('string-functions', 'function_bin'),
3894    'BINARY': Array('cast-functions', 'operator_binary'),
3895    'BIT_AND': Array('group-by-functions', 'function_bit_and'),
3896    'BIT_COUNT': Array('bit-functions', 'function_bit_count'),
3897    'BIT_LENGTH': Array('string-functions', 'function_bit_length'),
3898    'BIT_OR': Array('group-by-functions', 'function_bit_or'),
3899    'BIT_XOR': Array('group-by-functions', 'function_bit_xor'),
3900    'CASE': Array('control-flow-functions', 'operator_case'),
3901    'CAST': Array('cast-functions', 'function_cast'),
3902    'CEIL': Array('mathematical-functions', 'function_ceil'),
3903    'CEILING': Array('mathematical-functions', 'function_ceiling'),
3904    'CHAR_LENGTH': Array('string-functions', 'function_char_length'),
3905    'CHAR': Array('string-functions', 'function_char'),
3906    'CHARACTER_LENGTH': Array('string-functions', 'function_character_length'),
3907    'CHARSET': Array('information-functions', 'function_charset'),
3908    'COALESCE': Array('comparison-operators', 'function_coalesce'),
3909    'COERCIBILITY': Array('information-functions', 'function_coercibility'),
3910    'COLLATION': Array('information-functions', 'function_collation'),
3911    'COMPRESS': Array('encryption-functions', 'function_compress'),
3912    'CONCAT_WS': Array('string-functions', 'function_concat_ws'),
3913    'CONCAT': Array('string-functions', 'function_concat'),
3914    'CONNECTION_ID': Array('information-functions', 'function_connection_id'),
3915    'CONV': Array('mathematical-functions', 'function_conv'),
3916    'CONVERT_TZ': Array('date-and-time-functions', 'function_convert_tz'),
3917    'Convert': Array('cast-functions', 'function_convert'),
3918    'COS': Array('mathematical-functions', 'function_cos'),
3919    'COT': Array('mathematical-functions', 'function_cot'),
3920    'COUNT': Array('group-by-functions', 'function_count'),
3921    'CRC32': Array('mathematical-functions', 'function_crc32'),
3922    'CURDATE': Array('date-and-time-functions', 'function_curdate'),
3923    'CURRENT_DATE': Array('date-and-time-functions', 'function_current_date'),
3924    'CURRENT_TIME': Array('date-and-time-functions', 'function_current_time'),
3925    'CURRENT_TIMESTAMP': Array('date-and-time-functions', 'function_current_timestamp'),
3926    'CURRENT_USER': Array('information-functions', 'function_current_user'),
3927    'CURTIME': Array('date-and-time-functions', 'function_curtime'),
3928    'DATABASE': Array('information-functions', 'function_database'),
3929    'DATE_ADD': Array('date-and-time-functions', 'function_date_add'),
3930    'DATE_FORMAT': Array('date-and-time-functions', 'function_date_format'),
3931    'DATE_SUB': Array('date-and-time-functions', 'function_date_sub'),
3932    'DATE': Array('date-and-time-functions', 'function_date'),
3933    'DATEDIFF': Array('date-and-time-functions', 'function_datediff'),
3934    'DAY': Array('date-and-time-functions', 'function_day'),
3935    'DAYNAME': Array('date-and-time-functions', 'function_dayname'),
3936    'DAYOFMONTH': Array('date-and-time-functions', 'function_dayofmonth'),
3937    'DAYOFWEEK': Array('date-and-time-functions', 'function_dayofweek'),
3938    'DAYOFYEAR': Array('date-and-time-functions', 'function_dayofyear'),
3939    'DECLARE': Array('declare', 'declare'),
3940    'DECODE': Array('encryption-functions', 'function_decode'),
3941    'DEFAULT': Array('miscellaneous-functions', 'function_default'),
3942    'DEGREES': Array('mathematical-functions', 'function_degrees'),
3943    'DES_DECRYPT': Array('encryption-functions', 'function_des_decrypt'),
3944    'DES_ENCRYPT': Array('encryption-functions', 'function_des_encrypt'),
3945    'DIV': Array('arithmetic-functions', 'operator_div'),
3946    'ELT': Array('string-functions', 'function_elt'),
3947    'ENCODE': Array('encryption-functions', 'function_encode'),
3948    'ENCRYPT': Array('encryption-functions', 'function_encrypt'),
3949    'EXP': Array('mathematical-functions', 'function_exp'),
3950    'EXPORT_SET': Array('string-functions', 'function_export_set'),
3951    'EXTRACT': Array('date-and-time-functions', 'function_extract'),
3952    'ExtractValue': Array('xml-functions', 'function_extractvalue'),
3953    'FIELD': Array('string-functions', 'function_field'),
3954    'FIND_IN_SET': Array('string-functions', 'function_find_in_set'),
3955    'FLOOR': Array('mathematical-functions', 'function_floor'),
3956    'FORMAT': Array('string-functions', 'function_format'),
3957    'FOUND_ROWS': Array('information-functions', 'function_found_rows'),
3958    'FROM_DAYS': Array('date-and-time-functions', 'function_from_days'),
3959    'FROM_UNIXTIME': Array('date-and-time-functions', 'function_from_unixtime'),
3960    'GET_FORMAT': Array('date-and-time-functions', 'function_get_format'),
3961    'GET_LOCK': Array('miscellaneous-functions', 'function_get_lock'),
3962    'GREATEST': Array('comparison-operators', 'function_greatest'),
3963    'GROUP_CONCAT': Array('group-by-functions', 'function_group_concat'),
3964    'HEX': Array('string-functions', 'function_hex'),
3965    'HOUR': Array('date-and-time-functions', 'function_hour'),
3966    'IF': Array('control-flow-functions', 'function_if'),
3967    'IFNULL': Array('control-flow-functions', 'function_ifnull'),
3968    'IN': Array('comparison-operators', 'function_in'),
3969    'INET_ATON': Array('miscellaneous-functions', 'function_inet_aton'),
3970    'INET_NTOA': Array('miscellaneous-functions', 'function_inet_ntoa'),
3971    'INSTR': Array('string-functions', 'function_instr'),
3972    'INTERVAL': Array('comparison-operators', 'function_interval'),
3973    'IS_FREE_LOCK': Array('miscellaneous-functions', 'function_is_free_lock'),
3974    'IS_USED_LOCK': Array('miscellaneous-functions', 'function_is_used_lock'),
3975    'IS': Array('comparison-operators', 'operator_is'),
3976    'ISNULL': Array('comparison-operators', 'function_isnull'),
3977    'LAST_DAY': Array('date-and-time-functions', 'function_last_day'),
3978    'LAST_INSERT_ID': Array('information-functions', 'function_last_insert_id'),
3979    'LCASE': Array('string-functions', 'function_lcase'),
3980    'LEAST': Array('comparison-operators', 'function_least'),
3981    'LEFT': Array('string-functions', 'function_left'),
3982    'LENGTH': Array('string-functions', 'function_length'),
3983    'LIKE': Array('string-comparison-functions', 'operator_like'),
3984    'LN': Array('mathematical-functions', 'function_ln'),
3985    'LOAD_FILE': Array('string-functions', 'function_load_file'),
3986    'LOCALTIME': Array('date-and-time-functions', 'function_localtime'),
3987    'LOCALTIMESTAMP': Array('date-and-time-functions', 'function_localtimestamp'),
3988    'LOCATE': Array('string-functions', 'function_locate'),
3989    'LOG10': Array('mathematical-functions', 'function_log10'),
3990    'LOG2': Array('mathematical-functions', 'function_log2'),
3991    'LOG': Array('mathematical-functions', 'function_log'),
3992    'LOWER': Array('string-functions', 'function_lower'),
3993    'LPAD': Array('string-functions', 'function_lpad'),
3994    'LTRIM': Array('string-functions', 'function_ltrim'),
3995    'MAKE_SET': Array('string-functions', 'function_make_set'),
3996    'MAKEDATE': Array('date-and-time-functions', 'function_makedate'),
3997    'MAKETIME': Array('date-and-time-functions', 'function_maketime'),
3998    'MASTER_POS_WAIT': Array('miscellaneous-functions', 'function_master_pos_wait'),
3999    'MATCH': Array('fulltext-search', 'function_match'),
4000    'MAX': Array('group-by-functions', 'function_max'),
4001    'MD5': Array('encryption-functions', 'function_md5'),
4002    'MICROSECOND': Array('date-and-time-functions', 'function_microsecond'),
4003    'MID': Array('string-functions', 'function_mid'),
4004    'MIN': Array('group-by-functions', 'function_min'),
4005    'MINUTE': Array('date-and-time-functions', 'function_minute'),
4006    'MOD': Array('mathematical-functions', 'function_mod'),
4007    'MONTH': Array('date-and-time-functions', 'function_month'),
4008    'MONTHNAME': Array('date-and-time-functions', 'function_monthname'),
4009    'NAME_CONST': Array('miscellaneous-functions', 'function_name_const'),
4010    'NOT': Array('logical-operators', 'operator_not'),
4011    'NOW': Array('date-and-time-functions', 'function_now'),
4012    'NULLIF': Array('control-flow-functions', 'function_nullif'),
4013    'OCT': Array('mathematical-functions', 'function_oct'),
4014    'OCTET_LENGTH': Array('string-functions', 'function_octet_length'),
4015    'OLD_PASSWORD': Array('encryption-functions', 'function_old_password'),
4016    'OR': Array('logical-operators', 'operator_or'),
4017    'ORD': Array('string-functions', 'function_ord'),
4018    'PASSWORD': Array('encryption-functions', 'function_password'),
4019    'PERIOD_ADD': Array('date-and-time-functions', 'function_period_add'),
4020    'PERIOD_DIFF': Array('date-and-time-functions', 'function_period_diff'),
4021    'PI': Array('mathematical-functions', 'function_pi'),
4022    'POSITION': Array('string-functions', 'function_position'),
4023    'POW': Array('mathematical-functions', 'function_pow'),
4024    'POWER': Array('mathematical-functions', 'function_power'),
4025    'QUARTER': Array('date-and-time-functions', 'function_quarter'),
4026    'QUOTE': Array('string-functions', 'function_quote'),
4027    'RADIANS': Array('mathematical-functions', 'function_radians'),
4028    'RAND': Array('mathematical-functions', 'function_rand'),
4029    'REGEXP': Array('regexp', 'operator_regexp'),
4030    'RELEASE_LOCK': Array('miscellaneous-functions', 'function_release_lock'),
4031    'REPEAT': Array('string-functions', 'function_repeat'),
4032    'REVERSE': Array('string-functions', 'function_reverse'),
4033    'RIGHT': Array('string-functions', 'function_right'),
4034    'RLIKE': Array('regexp', 'operator_rlike'),
4035    'ROUND': Array('mathematical-functions', 'function_round'),
4036    'ROW_COUNT': Array('information-functions', 'function_row_count'),
4037    'RPAD': Array('string-functions', 'function_rpad'),
4038    'RTRIM': Array('string-functions', 'function_rtrim'),
4039    'SCHEMA': Array('information-functions', 'function_schema'),
4040    'SEC_TO_TIME': Array('date-and-time-functions', 'function_sec_to_time'),
4041    'SECOND': Array('date-and-time-functions', 'function_second'),
4042    'SESSION_USER': Array('information-functions', 'function_session_user'),
4043    'SHA': Array('encryption-functions', 'function_sha1'),
4044    'SHA1': Array('encryption-functions', 'function_sha1'),
4045    'SIGN': Array('mathematical-functions', 'function_sign'),
4046    'SIN': Array('mathematical-functions', 'function_sin'),
4047    'SLEEP': Array('miscellaneous-functions', 'function_sleep'),
4048    'SOUNDEX': Array('string-functions', 'function_soundex'),
4049    'SPACE': Array('string-functions', 'function_space'),
4050    'SQRT': Array('mathematical-functions', 'function_sqrt'),
4051    'STD': Array('group-by-functions', 'function_std'),
4052    'STDDEV_POP': Array('group-by-functions', 'function_stddev_pop'),
4053    'STDDEV_SAMP': Array('group-by-functions', 'function_stddev_samp'),
4054    'STDDEV': Array('group-by-functions', 'function_stddev'),
4055    'STR_TO_DATE': Array('date-and-time-functions', 'function_str_to_date'),
4056    'STRCMP': Array('string-comparison-functions', 'function_strcmp'),
4057    'SUBDATE': Array('date-and-time-functions', 'function_subdate'),
4058    'SUBSTR': Array('string-functions', 'function_substr'),
4059    'SUBSTRING_INDEX': Array('string-functions', 'function_substring_index'),
4060    'SUBSTRING': Array('string-functions', 'function_substring'),
4061    'SUBTIME': Array('date-and-time-functions', 'function_subtime'),
4062    'SUM': Array('group-by-functions', 'function_sum'),
4063    'SYSDATE': Array('date-and-time-functions', 'function_sysdate'),
4064    'SYSTEM_USER': Array('information-functions', 'function_system_user'),
4065    'TAN': Array('mathematical-functions', 'function_tan'),
4066    'TIME_FORMAT': Array('date-and-time-functions', 'function_time_format'),
4067    'TIME_TO_SEC': Array('date-and-time-functions', 'function_time_to_sec'),
4068    'TIME': Array('date-and-time-functions', 'function_time'),
4069    'TIMEDIFF': Array('date-and-time-functions', 'function_timediff'),
4070    'TIMESTAMP': Array('date-and-time-functions', 'function_timestamp'),
4071    'TIMESTAMPADD': Array('date-and-time-functions', 'function_timestampadd'),
4072    'TIMESTAMPDIFF': Array('date-and-time-functions', 'function_timestampdiff'),
4073    'TO_DAYS': Array('date-and-time-functions', 'function_to_days'),
4074    'TRIM': Array('string-functions', 'function_trim'),
4075    'TRUNCATE': Array('mathematical-functions', 'function_truncate'),
4076    'UCASE': Array('string-functions', 'function_ucase'),
4077    'UNCOMPRESS': Array('encryption-functions', 'function_uncompress'),
4078    'UNCOMPRESSED_LENGTH': Array('encryption-functions', 'function_uncompressed_length'),
4079    'UNHEX': Array('string-functions', 'function_unhex'),
4080    'UNIX_TIMESTAMP': Array('date-and-time-functions', 'function_unix_timestamp'),
4081    'UpdateXML': Array('xml-functions', 'function_updatexml'),
4082    'UPPER': Array('string-functions', 'function_upper'),
4083    'USER': Array('information-functions', 'function_user'),
4084    'UTC_DATE': Array('date-and-time-functions', 'function_utc_date'),
4085    'UTC_TIME': Array('date-and-time-functions', 'function_utc_time'),
4086    'UTC_TIMESTAMP': Array('date-and-time-functions', 'function_utc_timestamp'),
4087    'UUID_SHORT': Array('miscellaneous-functions', 'function_uuid_short'),
4088    'UUID': Array('miscellaneous-functions', 'function_uuid'),
4089    'VALUES': Array('miscellaneous-functions', 'function_values'),
4090    'VAR_POP': Array('group-by-functions', 'function_var_pop'),
4091    'VAR_SAMP': Array('group-by-functions', 'function_var_samp'),
4092    'VARIANCE': Array('group-by-functions', 'function_variance'),
4093    'VERSION': Array('information-functions', 'function_version'),
4094    'WEEK': Array('date-and-time-functions', 'function_week'),
4095    'WEEKDAY': Array('date-and-time-functions', 'function_weekday'),
4096    'WEEKOFYEAR': Array('date-and-time-functions', 'function_weekofyear'),
4097    'XOR': Array('logical-operators', 'operator_xor'),
4098    'YEAR': Array('date-and-time-functions', 'function_year'),
4099    'YEARWEEK': Array('date-and-time-functions', 'function_yearweek'),
4100    'SOUNDS_LIKE': Array('string-functions', 'operator_sounds-like'),
4101    'IS_NOT_NULL': Array('comparison-operators', 'operator_is-not-null'),
4102    'IS_NOT': Array('comparison-operators', 'operator_is-not'),
4103    'IS_NULL': Array('comparison-operators', 'operator_is-null'),
4104    'NOT_LIKE': Array('string-comparison-functions', 'operator_not-like'),
4105    'NOT_REGEXP': Array('regexp', 'operator_not-regexp'),
4106    'COUNT_DISTINCT': Array('group-by-functions', 'function_count-distinct'),
4107    'NOT_IN': Array('comparison-operators', 'function_not-in')
4108};
4109
4110var mysql_doc_builtin = {
4111    'TINYINT': Array('numeric-types'),
4112    'SMALLINT': Array('numeric-types'),
4113    'MEDIUMINT': Array('numeric-types'),
4114    'INT': Array('numeric-types'),
4115    'BIGINT': Array('numeric-types'),
4116    'DECIMAL': Array('numeric-types'),
4117    'FLOAT': Array('numeric-types'),
4118    'DOUBLE': Array('numeric-types'),
4119    'REAL': Array('numeric-types'),
4120    'BIT': Array('numeric-types'),
4121    'BOOLEAN': Array('numeric-types'),
4122    'SERIAL': Array('numeric-types'),
4123    'DATE': Array('date-and-time-types'),
4124    'DATETIME': Array('date-and-time-types'),
4125    'TIMESTAMP': Array('date-and-time-types'),
4126    'TIME': Array('date-and-time-types'),
4127    'YEAR': Array('date-and-time-types'),
4128    'CHAR': Array('string-types'),
4129    'VARCHAR': Array('string-types'),
4130    'TINYTEXT': Array('string-types'),
4131    'TEXT': Array('string-types'),
4132    'MEDIUMTEXT': Array('string-types'),
4133    'LONGTEXT': Array('string-types'),
4134    'BINARY': Array('string-types'),
4135    'VARBINARY': Array('string-types'),
4136    'TINYBLOB': Array('string-types'),
4137    'MEDIUMBLOB': Array('string-types'),
4138    'BLOB': Array('string-types'),
4139    'LONGBLOB': Array('string-types'),
4140    'ENUM': Array('string-types'),
4141    'SET': Array('string-types')
4142};
4143;
4144
4145/* vim: set expandtab sw=4 ts=4 sts=4: */
4146/**
4147 * general function, usually for data manipulation pages
4148 *
4149 */
4150
4151/**
4152 * @var sql_box_locked lock for the sqlbox textarea in the querybox
4153 */
4154var sql_box_locked = false;
4155
4156/**
4157 * @var array holds elements which content should only selected once
4158 */
4159var only_once_elements = [];
4160
4161/**
4162 * @var   int   ajax_message_count   Number of AJAX messages shown since page load
4163 */
4164var ajax_message_count = 0;
4165
4166/**
4167 * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab
4168 */
4169var codemirror_editor = false;
4170
4171/**
4172 * @var codemirror_editor object containing CodeMirror editor of the inline query editor
4173 */
4174var codemirror_inline_editor = false;
4175
4176/**
4177 * @var sql_autocomplete_in_progress bool shows if Table/Column name autocomplete AJAX is in progress
4178 */
4179var sql_autocomplete_in_progress = false;
4180
4181/**
4182 * @var sql_autocomplete object containing list of columns in each table
4183 */
4184var sql_autocomplete = false;
4185
4186/**
4187 * @var sql_autocomplete_default_table string containing default table to autocomplete columns
4188 */
4189var sql_autocomplete_default_table = '';
4190
4191/**
4192 * @var central_column_list array to hold the columns in central list per db.
4193 */
4194var central_column_list = [];
4195
4196/**
4197 * @var primary_indexes array to hold 'Primary' index columns.
4198 */
4199var primary_indexes = [];
4200
4201/**
4202 * @var unique_indexes array to hold 'Unique' index columns.
4203 */
4204var unique_indexes = [];
4205
4206/**
4207 * @var indexes array to hold 'Index' columns.
4208 */
4209var indexes = [];
4210
4211/**
4212 * @var fulltext_indexes array to hold 'Fulltext' columns.
4213 */
4214var fulltext_indexes = [];
4215
4216/**
4217 * @var spatial_indexes array to hold 'Spatial' columns.
4218 */
4219var spatial_indexes = [];
4220
4221/**
4222 * Make sure that ajax requests will not be cached
4223 * by appending a random variable to their parameters
4224 */
4225$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
4226    var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000);
4227    if (typeof options.data == "string") {
4228        options.data += "&_nocache=" + nocache;
4229    } else if (typeof options.data == "object") {
4230        options.data = $.extend(originalOptions.data, {'_nocache' : nocache});
4231    }
4232});
4233
4234/*
4235 * Adds a date/time picker to an element
4236 *
4237 * @param object  $this_element   a jQuery object pointing to the element
4238 */
4239function PMA_addDatepicker($this_element, type, options)
4240{
4241    var showTimepicker = true;
4242    if (type=="date") {
4243        showTimepicker = false;
4244    }
4245
4246    var defaultOptions = {
4247        showOn: 'button',
4248        buttonImage: themeCalendarImage, // defined in js/messages.php
4249        buttonImageOnly: true,
4250        stepMinutes: 1,
4251        stepHours: 1,
4252        showSecond: true,
4253        showMillisec: true,
4254        showMicrosec: true,
4255        showTimepicker: showTimepicker,
4256        showButtonPanel: false,
4257        dateFormat: 'yy-mm-dd', // yy means year with four digits
4258        timeFormat: 'HH:mm:ss.lc',
4259        constrainInput: false,
4260        altFieldTimeOnly: false,
4261        showAnim: '',
4262        beforeShow: function (input, inst) {
4263            // Remember that we came from the datepicker; this is used
4264            // in tbl_change.js by verificationsAfterFieldChange()
4265            $this_element.data('comes_from', 'datepicker');
4266            if ($(input).closest('.cEdit').length > 0) {
4267                setTimeout(function () {
4268                    inst.dpDiv.css({
4269                        top: 0,
4270                        left: 0,
4271                        position: 'relative'
4272                    });
4273                }, 0);
4274            }
4275            // Fix wrong timepicker z-index, doesn't work without timeout
4276            setTimeout(function () {
4277                $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
4278            }, 0);
4279        },
4280        onSelect: function() {
4281            $this_element.data('datepicker').inline = true;
4282        },
4283        onClose: function (dateText, dp_inst) {
4284            // The value is no more from the date picker
4285            $this_element.data('comes_from', '');
4286            if (typeof $this_element.data('datepicker') !== 'undefined') {
4287                $this_element.data('datepicker').inline = false;
4288            }
4289        }
4290    };
4291    if (type == "datetime" || type == "timestamp") {
4292        $this_element.datetimepicker($.extend(defaultOptions, options));
4293    }
4294    else if (type == "date") {
4295        $this_element.datetimepicker($.extend(defaultOptions, options));
4296    }
4297    else if (type == "time") {
4298        $this_element.timepicker($.extend(defaultOptions, options));
4299        // Add a tip regarding entering MySQL allowed-values for TIME data-type
4300        PMA_tooltip($this_element, 'input', PMA_messages.strMysqlAllowedValuesTip);
4301    }
4302}
4303
4304/**
4305 * Add a date/time picker to each element that needs it
4306 * (only when jquery-ui-timepicker-addon.js is loaded)
4307 */
4308function addDateTimePicker() {
4309    if ($.timepicker !== undefined) {
4310        $('input.timefield, input.datefield, input.datetimefield').each(function () {
4311
4312            var decimals = $(this).parent().attr('data-decimals');
4313            var type = $(this).parent().attr('data-type');
4314
4315            var showMillisec = false;
4316            var showMicrosec = false;
4317            var timeFormat = 'HH:mm:ss';
4318            // check for decimal places of seconds
4319            if (decimals > 0 && type.indexOf('time') != -1){
4320                if (decimals > 3) {
4321                    showMillisec = true;
4322                    showMicrosec = true;
4323                    timeFormat = 'HH:mm:ss.lc';
4324                } else {
4325                    showMillisec = true;
4326                    timeFormat = 'HH:mm:ss.l';
4327                }
4328            }
4329            PMA_addDatepicker($(this), type, {
4330                showMillisec: showMillisec,
4331                showMicrosec: showMicrosec,
4332                timeFormat: timeFormat
4333            });
4334
4335            // Add a tip regarding entering MySQL allowed-values
4336            // for TIME and DATE data-type
4337            if ($(this).hasClass('timefield')) {
4338                PMA_tooltip($(this), 'input', PMA_messages.strMysqlAllowedValuesTipTime);
4339            } else if ($(this).hasClass('datefield')) {
4340                PMA_tooltip($(this), 'input', PMA_messages.strMysqlAllowedValuesTipDate);
4341            }
4342        });
4343    }
4344}
4345
4346/**
4347 * Handle redirect and reload flags sent as part of AJAX requests
4348 *
4349 * @param data ajax response data
4350 */
4351function PMA_handleRedirectAndReload(data) {
4352    if (parseInt(data.redirect_flag) == 1) {
4353        // add one more GET param to display session expiry msg
4354        if (window.location.href.indexOf('?') === -1) {
4355            window.location.href += '?session_expired=1';
4356        } else {
4357            window.location.href += '&session_expired=1';
4358        }
4359        window.location.reload();
4360    } else if (parseInt(data.reload_flag) == 1) {
4361        // remove the token param and reload
4362        window.location.href = window.location.href.replace(/&?token=[^&#]*/g, "");
4363        window.location.reload();
4364    }
4365}
4366
4367/**
4368 * Creates an SQL editor which supports auto completing etc.
4369 *
4370 * @param $textarea   jQuery object wrapping the textarea to be made the editor
4371 * @param options     optional options for CodeMirror
4372 * @param resize      optional resizing ('vertical', 'horizontal', 'both')
4373 * @param lintOptions additional options for lint
4374 */
4375function PMA_getSQLEditor($textarea, options, resize, lintOptions) {
4376    if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') {
4377
4378        // merge options for CodeMirror
4379        var defaults = {
4380            lineNumbers: true,
4381            matchBrackets: true,
4382            extraKeys: {"Ctrl-Space": "autocomplete"},
4383            hintOptions: {"completeSingle": false, "completeOnSingleClick": true},
4384            indentUnit: 4,
4385            mode: "text/x-mysql",
4386            lineWrapping: true
4387        };
4388
4389        if (CodeMirror.sqlLint) {
4390            $.extend(defaults, {
4391                gutters: ["CodeMirror-lint-markers"],
4392                lint: {
4393                    "getAnnotations": CodeMirror.sqlLint,
4394                    "async": true,
4395                    "lintOptions": lintOptions
4396                }
4397            });
4398        }
4399
4400        $.extend(true, defaults, options);
4401
4402        // create CodeMirror editor
4403        var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults);
4404        // allow resizing
4405        if (! resize) {
4406            resize = 'vertical';
4407        }
4408        var handles = '';
4409        if (resize == 'vertical') {
4410            handles = 's';
4411        }
4412        if (resize == 'both') {
4413            handles = 'all';
4414        }
4415        if (resize == 'horizontal') {
4416            handles = 'e, w';
4417        }
4418        $(codemirrorEditor.getWrapperElement())
4419            .css('resize', resize)
4420            .resizable({
4421                handles: handles,
4422                resize: function() {
4423                    codemirrorEditor.setSize($(this).width(), $(this).height());
4424                }
4425            });
4426        // enable autocomplete
4427        codemirrorEditor.on("inputRead", codemirrorAutocompleteOnInputRead);
4428
4429        return codemirrorEditor;
4430    }
4431    return null;
4432}
4433
4434/**
4435 * Clear text selection
4436 */
4437function PMA_clearSelection() {
4438    if (document.selection && document.selection.empty) {
4439        document.selection.empty();
4440    } else if (window.getSelection) {
4441        var sel = window.getSelection();
4442        if (sel.empty) {
4443            sel.empty();
4444        }
4445        if (sel.removeAllRanges) {
4446            sel.removeAllRanges();
4447        }
4448    }
4449}
4450
4451/**
4452 * Create a jQuery UI tooltip
4453 *
4454 * @param $elements     jQuery object representing the elements
4455 * @param item          the item
4456 *                      (see https://api.jqueryui.com/tooltip/#option-items)
4457 * @param myContent     content of the tooltip
4458 * @param additionalOptions to override the default options
4459 *
4460 */
4461function PMA_tooltip($elements, item, myContent, additionalOptions)
4462{
4463    if ($('#no_hint').length > 0) {
4464        return;
4465    }
4466
4467    var defaultOptions = {
4468        content: myContent,
4469        items:  item,
4470        tooltipClass: "tooltip",
4471        track: true,
4472        show: false,
4473        hide: false
4474    };
4475
4476    $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
4477}
4478
4479/**
4480 * HTML escaping
4481 */
4482
4483function escapeHtml(unsafe) {
4484    if (typeof(unsafe) != 'undefined') {
4485        return unsafe
4486            .toString()
4487            .replace(/&/g, "&amp;")
4488            .replace(/</g, "&lt;")
4489            .replace(/>/g, "&gt;")
4490            .replace(/"/g, "&quot;")
4491            .replace(/'/g, "&#039;");
4492    } else {
4493        return false;
4494    }
4495}
4496
4497function escapeJsString(unsafe) {
4498    if (typeof(unsafe) != 'undefined') {
4499        return unsafe
4500            .toString()
4501            .replace("\000", '')
4502            .replace('\\', '\\\\')
4503            .replace('\'', '\\\'')
4504            .replace("&#039;", "\\\&#039;")
4505            .replace('"', '\"')
4506            .replace("&quot;", "\&quot;")
4507            .replace("\n", '\n')
4508            .replace("\r", '\r')
4509            .replace(/<\/script/gi, '</\' + \'script')
4510    } else {
4511        return false;
4512    }
4513}
4514
4515function PMA_sprintf() {
4516    return sprintf.apply(this, arguments);
4517}
4518
4519/**
4520 * Hides/shows the default value input field, depending on the default type
4521 * Ticks the NULL checkbox if NULL is chosen as default value.
4522 */
4523function PMA_hideShowDefaultValue($default_type)
4524{
4525    if ($default_type.val() == 'USER_DEFINED') {
4526        $default_type.siblings('.default_value').show().focus();
4527    } else {
4528        $default_type.siblings('.default_value').hide();
4529        if ($default_type.val() == 'NULL') {
4530            var $null_checkbox = $default_type.closest('tr').find('.allow_null');
4531            $null_checkbox.prop('checked', true);
4532        }
4533    }
4534}
4535
4536/**
4537 * Hides/shows the input field for column expression based on whether
4538 * VIRTUAL/PERSISTENT is selected
4539 *
4540 * @param $virtuality virtuality dropdown
4541 */
4542function PMA_hideShowExpression($virtuality)
4543{
4544    if ($virtuality.val() === '') {
4545        $virtuality.siblings('.expression').hide();
4546    } else {
4547        $virtuality.siblings('.expression').show();
4548    }
4549}
4550
4551/**
4552 * Show notices for ENUM columns; add/hide the default value
4553 *
4554 */
4555function PMA_verifyColumnsProperties()
4556{
4557    $("select.column_type").each(function () {
4558        PMA_showNoticeForEnum($(this));
4559    });
4560    $("select.default_type").each(function () {
4561        PMA_hideShowDefaultValue($(this));
4562    });
4563    $('select.virtuality').each(function () {
4564        PMA_hideShowExpression($(this));
4565    });
4566}
4567
4568/**
4569 * Add a hidden field to the form to indicate that this will be an
4570 * Ajax request (only if this hidden field does not exist)
4571 *
4572 * @param $form object   the form
4573 */
4574function PMA_prepareForAjaxRequest($form)
4575{
4576    if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
4577        $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
4578    }
4579}
4580
4581/**
4582 * Generate a new password and copy it to the password input areas
4583 *
4584 * @param passwd_form object   the form that holds the password fields
4585 *
4586 * @return boolean  always true
4587 */
4588function suggestPassword(passwd_form)
4589{
4590    // restrict the password to just letters and numbers to avoid problems:
4591    // "editors and viewers regard the password as multiple words and
4592    // things like double click no longer work"
4593    var pwchars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ";
4594    var passwordlength = 16;    // do we want that to be dynamic?  no, keep it simple :)
4595    var passwd = passwd_form.generated_pw;
4596    var randomWords = new Int32Array(passwordlength);
4597
4598    passwd.value = '';
4599
4600    // First we're going to try to use a built-in CSPRNG
4601    if (window.crypto && window.crypto.getRandomValues) {
4602        window.crypto.getRandomValues(randomWords);
4603    }
4604    // Because of course IE calls it msCrypto instead of being standard
4605    else if (window.msCrypto && window.msCrypto.getRandomValues) {
4606        window.msCrypto.getRandomValues(randomWords);
4607    } else {
4608        // Fallback to Math.random
4609        for (var i = 0; i < passwordlength; i++) {
4610            randomWords[i] = Math.floor(Math.random() * pwchars.length);
4611        }
4612    }
4613
4614    for (var i = 0; i < passwordlength; i++) {
4615        passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
4616    }
4617
4618    passwd_form.text_pma_pw.value = passwd.value;
4619    passwd_form.text_pma_pw2.value = passwd.value;
4620    return true;
4621}
4622
4623/**
4624 * Version string to integer conversion.
4625 */
4626function parseVersionString(str)
4627{
4628    if (typeof(str) != 'string') { return false; }
4629    var add = 0;
4630    // Parse possible alpha/beta/rc/
4631    var state = str.split('-');
4632    if (state.length >= 2) {
4633        if (state[1].substr(0, 2) == 'rc') {
4634            add = - 20 - parseInt(state[1].substr(2), 10);
4635        } else if (state[1].substr(0, 4) == 'beta') {
4636            add =  - 40 - parseInt(state[1].substr(4), 10);
4637        } else if (state[1].substr(0, 5) == 'alpha') {
4638            add =  - 60 - parseInt(state[1].substr(5), 10);
4639        } else if (state[1].substr(0, 3) == 'dev') {
4640            /* We don't handle dev, it's git snapshot */
4641            add = 0;
4642        }
4643    }
4644    // Parse version
4645    var x = str.split('.');
4646    // Use 0 for non existing parts
4647    var maj = parseInt(x[0], 10) || 0;
4648    var min = parseInt(x[1], 10) || 0;
4649    var pat = parseInt(x[2], 10) || 0;
4650    var hotfix = parseInt(x[3], 10) || 0;
4651    return  maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
4652}
4653
4654/**
4655 * Indicates current available version on main page.
4656 */
4657function PMA_current_version(data)
4658{
4659    if (data && data.version && data.date) {
4660        var current = parseVersionString($('span.version').text());
4661        var latest = parseVersionString(data.version);
4662        var url = 'https://web.phpmyadmin.net/files/' + escapeHtml(encodeURIComponent(data.version)) + '/';
4663        var version_information_message = '<span class="latest">' +
4664            PMA_messages.strLatestAvailable +
4665            ' <a href="' + url + '" class="disableAjax">' + escapeHtml(data.version) + '</a>' +
4666            '</span>';
4667        if (latest > current) {
4668            var message = PMA_sprintf(
4669                PMA_messages.strNewerVersion,
4670                escapeHtml(data.version),
4671                escapeHtml(data.date)
4672            );
4673            var htmlClass = 'notice';
4674            if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
4675                /* Security update */
4676                htmlClass = 'error';
4677            }
4678            $('#newer_version_notice').remove();
4679            $('#maincontainer').after('<div id="newer_version_notice" class="' + htmlClass + '"><a href="' + url + '" class="disableAjax">' + message + '</a></div>');
4680        }
4681        if (latest === current) {
4682            version_information_message = ' (' + PMA_messages.strUpToDate + ')';
4683        }
4684        var $liPmaVersion = $('#li_pma_version');
4685        $liPmaVersion.find('span.latest').remove();
4686        $liPmaVersion.append(version_information_message);
4687    }
4688}
4689
4690/**
4691 * Loads Git revision data from ajax for index.php
4692 */
4693function PMA_display_git_revision()
4694{
4695    $('#is_git_revision').remove();
4696    $('#li_pma_version_git').remove();
4697    $.get(
4698        "index.php",
4699        {
4700            "server": PMA_commonParams.get('server'),
4701            "token": PMA_commonParams.get('token'),
4702            "git_revision": true,
4703            "ajax_request": true,
4704            "no_debug": true
4705        },
4706        function (data) {
4707            if (typeof data !== 'undefined' && data.success === true) {
4708                $(data.message).insertAfter('#li_pma_version');
4709            }
4710        }
4711    );
4712}
4713
4714/**
4715 * for libraries/display_change_password.lib.php
4716 *     libraries/user_password.php
4717 *
4718 */
4719
4720function displayPasswordGenerateButton()
4721{
4722    $('#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>');
4723    $('#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>');
4724}
4725
4726/**
4727 * selects the content of a given object, f.e. a textarea
4728 *
4729 * @param element     object  element of which the content will be selected
4730 * @param lock        var     variable which holds the lock for this element
4731 *                              or true, if no lock exists
4732 * @param only_once   boolean if true this is only done once
4733 *                              f.e. only on first focus
4734 */
4735function selectContent(element, lock, only_once)
4736{
4737    if (only_once && only_once_elements[element.name]) {
4738        return;
4739    }
4740
4741    only_once_elements[element.name] = true;
4742
4743    if (lock) {
4744        return;
4745    }
4746
4747    element.select();
4748}
4749
4750/**
4751 * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
4752 * This function is called while clicking links
4753 *
4754 * @param theLink     object the link
4755 * @param theSqlQuery object the sql query to submit
4756 *
4757 * @return boolean  whether to run the query or not
4758 */
4759function confirmLink(theLink, theSqlQuery)
4760{
4761    // Confirmation is not required in the configuration file
4762    // or browser is Opera (crappy js implementation)
4763    if (PMA_messages.strDoYouReally === '' || typeof(window.opera) != 'undefined') {
4764        return true;
4765    }
4766
4767    var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, theSqlQuery));
4768    if (is_confirmed) {
4769        if ($(theLink).hasClass('formLinkSubmit')) {
4770            var name = 'is_js_confirmed';
4771
4772            if ($(theLink).attr('href').indexOf('usesubform') != -1) {
4773                var matches = $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i);
4774                if (matches != null) {
4775                    name = 'subform[' + matches[1] + '][is_js_confirmed]';
4776                }
4777            }
4778
4779            $(theLink).parents('form').append('<input type="hidden" name="' + name + '" value="1" />');
4780        } else if (typeof(theLink.href) != 'undefined') {
4781            theLink.href += '&is_js_confirmed=1';
4782        } else if (typeof(theLink.form) != 'undefined') {
4783            theLink.form.action += '?is_js_confirmed=1';
4784        }
4785    }
4786
4787    return is_confirmed;
4788} // end of the 'confirmLink()' function
4789
4790/**
4791 * Confirms a "DROP/DELETE/ALTER" query before
4792 * submitting it if required.
4793 * This function is called by the 'checkSqlQuery()' js function.
4794 *
4795 * @param theForm1 object   the form
4796 * @param sqlQuery1 object  the sql query textarea
4797 *
4798 * @return boolean  whether to run the query or not
4799 *
4800 * @see     checkSqlQuery()
4801 */
4802function confirmQuery(theForm1, sqlQuery1)
4803{
4804    // Confirmation is not required in the configuration file
4805    if (PMA_messages.strDoYouReally === '') {
4806        return true;
4807    }
4808
4809    // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
4810    //
4811    // TODO: find a way (if possible) to use the parser-analyser
4812    // for this kind of verification
4813    // For now, I just added a ^ to check for the statement at
4814    // beginning of expression
4815
4816    var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|PROCEDURE)\\s', 'i');
4817    var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
4818    var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
4819    var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
4820
4821    if (do_confirm_re_0.test(sqlQuery1.value) ||
4822        do_confirm_re_1.test(sqlQuery1.value) ||
4823        do_confirm_re_2.test(sqlQuery1.value) ||
4824        do_confirm_re_3.test(sqlQuery1.value)) {
4825        var message;
4826        if (sqlQuery1.value.length > 100) {
4827            message = sqlQuery1.value.substr(0, 100) + '\n    ...';
4828        } else {
4829            message = sqlQuery1.value;
4830        }
4831        var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, message));
4832        // statement is confirmed -> update the
4833        // "is_js_confirmed" form field so the confirm test won't be
4834        // run on the server side and allows to submit the form
4835        if (is_confirmed) {
4836            theForm1.elements.is_js_confirmed.value = 1;
4837            return true;
4838        }
4839        // statement is rejected -> do not submit the form
4840        else {
4841            window.focus();
4842            sqlQuery1.focus();
4843            return false;
4844        } // end if (handle confirm box result)
4845    } // end if (display confirm box)
4846
4847    return true;
4848} // end of the 'confirmQuery()' function
4849
4850/**
4851 * Displays an error message if the user submitted the sql query form with no
4852 * sql query, else checks for "DROP/DELETE/ALTER" statements
4853 *
4854 * @param theForm object the form
4855 *
4856 * @return boolean  always false
4857 *
4858 * @see     confirmQuery()
4859 */
4860function checkSqlQuery(theForm)
4861{
4862    // get the textarea element containing the query
4863    var sqlQuery;
4864    if (codemirror_editor) {
4865        codemirror_editor.save();
4866        sqlQuery = codemirror_editor.getValue();
4867    } else {
4868        sqlQuery = theForm.elements.sql_query.value;
4869    }
4870    var isEmpty  = 1;
4871    var space_re = new RegExp('\\s+');
4872    if (typeof(theForm.elements.sql_file) != 'undefined' &&
4873            theForm.elements.sql_file.value.replace(space_re, '') !== '') {
4874        return true;
4875    }
4876    if (isEmpty && typeof(theForm.elements.id_bookmark) != 'undefined' &&
4877            (theForm.elements.id_bookmark.value !== null || theForm.elements.id_bookmark.value !== '') &&
4878            theForm.elements.id_bookmark.selectedIndex !== 0) {
4879        return true;
4880    }
4881    // Checks for "DROP/DELETE/ALTER" statements
4882    if (sqlQuery.replace(space_re, '') !== '') {
4883        return confirmQuery(theForm, sqlQuery);
4884    }
4885    theForm.reset();
4886    isEmpty = 1;
4887
4888    if (isEmpty) {
4889        alert(PMA_messages.strFormEmpty);
4890        codemirror_editor.focus();
4891        return false;
4892    }
4893
4894    return true;
4895} // end of the 'checkSqlQuery()' function
4896
4897/**
4898 * Check if a form's element is empty.
4899 * An element containing only spaces is also considered empty
4900 *
4901 * @param object   the form
4902 * @param string   the name of the form field to put the focus on
4903 *
4904 * @return boolean  whether the form field is empty or not
4905 */
4906function emptyCheckTheField(theForm, theFieldName)
4907{
4908    var theField = theForm.elements[theFieldName];
4909    var space_re = new RegExp('\\s+');
4910    return theField.value.replace(space_re, '') === '';
4911} // end of the 'emptyCheckTheField()' function
4912
4913/**
4914 * Ensures a value submitted in a form is numeric and is in a range
4915 *
4916 * @param object   the form
4917 * @param string   the name of the form field to check
4918 * @param integer  the minimum authorized value
4919 * @param integer  the maximum authorized value
4920 *
4921 * @return boolean  whether a valid number has been submitted or not
4922 */
4923function checkFormElementInRange(theForm, theFieldName, message, min, max)
4924{
4925    var theField         = theForm.elements[theFieldName];
4926    var val              = parseInt(theField.value, 10);
4927
4928    if (typeof(min) == 'undefined') {
4929        min = 0;
4930    }
4931    if (typeof(max) == 'undefined') {
4932        max = Number.MAX_VALUE;
4933    }
4934
4935    // It's not a number
4936    if (isNaN(val)) {
4937        theField.select();
4938        alert(PMA_messages.strEnterValidNumber);
4939        theField.focus();
4940        return false;
4941    }
4942    // It's a number but it is not between min and max
4943    else if (val < min || val > max) {
4944        theField.select();
4945        alert(PMA_sprintf(message, val));
4946        theField.focus();
4947        return false;
4948    }
4949    // It's a valid number
4950    else {
4951        theField.value = val;
4952    }
4953    return true;
4954
4955} // end of the 'checkFormElementInRange()' function
4956
4957
4958function checkTableEditForm(theForm, fieldsCnt)
4959{
4960    // TODO: avoid sending a message if user just wants to add a line
4961    // on the form but has not completed at least one field name
4962
4963    var atLeastOneField = 0;
4964    var i, elm, elm2, elm3, val, id;
4965
4966    for (i = 0; i < fieldsCnt; i++) {
4967        id = "#field_" + i + "_2";
4968        elm = $(id);
4969        val = elm.val();
4970        if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') {
4971            elm2 = $("#field_" + i + "_3");
4972            val = parseInt(elm2.val(), 10);
4973            elm3 = $("#field_" + i + "_1");
4974            if (isNaN(val) && elm3.val() !== "") {
4975                elm2.select();
4976                alert(PMA_messages.strEnterValidLength);
4977                elm2.focus();
4978                return false;
4979            }
4980        }
4981
4982        if (atLeastOneField === 0) {
4983            id = "field_" + i + "_1";
4984            if (!emptyCheckTheField(theForm, id)) {
4985                atLeastOneField = 1;
4986            }
4987        }
4988    }
4989    if (atLeastOneField === 0) {
4990        var theField = theForm.elements.field_0_1;
4991        alert(PMA_messages.strFormEmpty);
4992        theField.focus();
4993        return false;
4994    }
4995
4996    // at least this section is under jQuery
4997    var $input = $("input.textfield[name='table']");
4998    if ($input.val() === "") {
4999        alert(PMA_messages.strFormEmpty);
5000        $input.focus();
5001        return false;
5002    }
5003
5004    return true;
5005} // enf of the 'checkTableEditForm()' function
5006
5007/**
5008 * True if last click is to check a row.
5009 */
5010var last_click_checked = false;
5011
5012/**
5013 * Zero-based index of last clicked row.
5014 * Used to handle the shift + click event in the code above.
5015 */
5016var last_clicked_row = -1;
5017
5018/**
5019 * Zero-based index of last shift clicked row.
5020 */
5021var last_shift_clicked_row = -1;
5022
5023var _idleSecondsCounter = 0;
5024var IncInterval;
5025var updateTimeout;
5026AJAX.registerTeardown('functions.js', function () {
5027    clearTimeout(updateTimeout);
5028    clearInterval(IncInterval);
5029    $(document).off('mousemove');
5030});
5031
5032AJAX.registerOnload('functions.js', function () {
5033    document.onclick = function() {
5034        _idleSecondsCounter = 0;
5035    };
5036    $(document).on('mousemove',function() {
5037        _idleSecondsCounter = 0;
5038    });
5039    document.onkeypress = function() {
5040        _idleSecondsCounter = 0;
5041    };
5042
5043    function SetIdleTime() {
5044        _idleSecondsCounter++;
5045    }
5046    function UpdateIdleTime() {
5047        var href = 'index.php';
5048        var params = {
5049                'ajax_request' : true,
5050                'token' : PMA_commonParams.get('token'),
5051                'server' : PMA_commonParams.get('server'),
5052                'db' : PMA_commonParams.get('db'),
5053                'access_time':_idleSecondsCounter
5054            };
5055        $.ajax({
5056                type: 'POST',
5057                url: href,
5058                data: params,
5059                success: function (data) {
5060                    if (data.success) {
5061                        var remaining = PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter;
5062                        if (remaining > 5) {
5063                            // max value for setInterval() function
5064                            var interval = Math.min(remaining * 1000, Math.pow(2, 31) - 1);
5065                            updateTimeout = window.setTimeout(UpdateIdleTime, interval);
5066                        } else if (remaining > 0) {
5067                            // We're close to session expiry
5068                            updateTimeout = window.setTimeout(UpdateIdleTime, 2000);
5069                        }
5070                    } else { //timeout occurred
5071                        if(isStorageSupported('sessionStorage')){
5072                            window.sessionStorage.clear();
5073                        }
5074                        window.location.reload(true);
5075                        clearInterval(IncInterval);
5076                    }
5077                }
5078            });
5079    }
5080    if (PMA_commonParams.get('logged_in') && PMA_commonParams.get('auth_type') == 'cookie') {
5081        IncInterval = window.setInterval(SetIdleTime, 1000);
5082        var interval = (PMA_commonParams.get('LoginCookieValidity') - 5) * 1000;
5083        if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function
5084            interval = Math.pow(2, 31) - 1;
5085        }
5086        updateTimeout = window.setTimeout(UpdateIdleTime, interval);
5087    }
5088});
5089/**
5090 * Unbind all event handlers before tearing down a page
5091 */
5092AJAX.registerTeardown('functions.js', function () {
5093    $(document).off('click', 'input:checkbox.checkall');
5094});
5095AJAX.registerOnload('functions.js', function () {
5096    /**
5097     * Row marking in horizontal mode (use "on" so that it works also for
5098     * next pages reached via AJAX); a tr may have the class noclick to remove
5099     * this behavior.
5100     */
5101
5102    $(document).on('click', 'input:checkbox.checkall', function (e) {
5103        $this = $(this);
5104        var $tr = $this.closest('tr');
5105        var $table = $this.closest('table');
5106
5107        if (!e.shiftKey || last_clicked_row == -1) {
5108            // usual click
5109
5110            var $checkbox = $tr.find(':checkbox.checkall');
5111            var checked = $this.prop('checked');
5112            $checkbox.prop('checked', checked).trigger('change');
5113            if (checked) {
5114                $tr.addClass('marked');
5115            } else {
5116                $tr.removeClass('marked');
5117            }
5118            last_click_checked = checked;
5119
5120            // remember the last clicked row
5121            last_clicked_row = last_click_checked ? $table.find('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1;
5122            last_shift_clicked_row = -1;
5123        } else {
5124            // handle the shift click
5125            PMA_clearSelection();
5126            var start, end;
5127
5128            // clear last shift click result
5129            if (last_shift_clicked_row >= 0) {
5130                if (last_shift_clicked_row >= last_clicked_row) {
5131                    start = last_clicked_row;
5132                    end = last_shift_clicked_row;
5133                } else {
5134                    start = last_shift_clicked_row;
5135                    end = last_clicked_row;
5136                }
5137                $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
5138                    .slice(start, end + 1)
5139                    .removeClass('marked')
5140                    .find(':checkbox')
5141                    .prop('checked', false)
5142                    .trigger('change');
5143            }
5144
5145            // handle new shift click
5146            var curr_row = $table.find('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr);
5147            if (curr_row >= last_clicked_row) {
5148                start = last_clicked_row;
5149                end = curr_row;
5150            } else {
5151                start = curr_row;
5152                end = last_clicked_row;
5153            }
5154            $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
5155                .slice(start, end + 1)
5156                .addClass('marked')
5157                .find(':checkbox')
5158                .prop('checked', true)
5159                .trigger('change');
5160
5161            // remember the last shift clicked row
5162            last_shift_clicked_row = curr_row;
5163        }
5164    });
5165
5166    addDateTimePicker();
5167
5168    /**
5169     * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
5170     */
5171    if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
5172        $('input[type=text]').attr('autocapitalize', 'off').attr('autocorrect', 'off');
5173    }
5174});
5175
5176/**
5177 * Row highlighting in horizontal mode (use "on"
5178 * so that it works also for pages reached via AJAX)
5179 */
5180/*AJAX.registerOnload('functions.js', function () {
5181    $(document).on('hover', 'tr.odd, tr.even',function (event) {
5182        var $tr = $(this);
5183        $tr.toggleClass('hover',event.type=='mouseover');
5184        $tr.children().toggleClass('hover',event.type=='mouseover');
5185    });
5186})*/
5187
5188/**
5189 * marks all rows and selects its first checkbox inside the given element
5190 * the given element is usually a table or a div containing the table or tables
5191 *
5192 * @param container    DOM element
5193 */
5194function markAllRows(container_id)
5195{
5196
5197    $("#" + container_id).find("input:checkbox:enabled").prop('checked', true)
5198    .trigger("change")
5199    .parents("tr").addClass("marked");
5200    return true;
5201}
5202
5203/**
5204 * marks all rows and selects its first checkbox inside the given element
5205 * the given element is usually a table or a div containing the table or tables
5206 *
5207 * @param container    DOM element
5208 */
5209function unMarkAllRows(container_id)
5210{
5211
5212    $("#" + container_id).find("input:checkbox:enabled").prop('checked', false)
5213    .trigger("change")
5214    .parents("tr").removeClass("marked");
5215    return true;
5216}
5217
5218/**
5219  * Checks/unchecks all options of a <select> element
5220  *
5221  * @param string   the form name
5222  * @param string   the element name
5223  * @param boolean  whether to check or to uncheck options
5224  *
5225  * @return boolean  always true
5226  */
5227function setSelectOptions(the_form, the_select, do_check)
5228{
5229    $("form[name='" + the_form + "'] select[name='" + the_select + "']").find("option").prop('selected', do_check);
5230    return true;
5231} // end of the 'setSelectOptions()' function
5232
5233/**
5234 * Sets current value for query box.
5235 */
5236function setQuery(query)
5237{
5238    if (codemirror_editor) {
5239        codemirror_editor.setValue(query);
5240        codemirror_editor.focus();
5241    } else {
5242        document.sqlform.sql_query.value = query;
5243        document.sqlform.sql_query.focus();
5244    }
5245}
5246
5247/**
5248 * Handles 'Simulate query' button on SQL query box.
5249 *
5250 * @return void
5251 */
5252function PMA_handleSimulateQueryButton()
5253{
5254    var update_re = new RegExp('^\\s*UPDATE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+SET\\s', 'i');
5255    var delete_re = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
5256    var query = '';
5257
5258    if (codemirror_editor) {
5259        query = codemirror_editor.getValue();
5260    } else {
5261        query = $('#sqlquery').val();
5262    }
5263
5264    var $simulateDml = $('#simulate_dml');
5265    if (update_re.test(query) || delete_re.test(query)) {
5266        if (! $simulateDml.length) {
5267            $('#button_submit_query')
5268            .before('<input type="button" id="simulate_dml"' +
5269                'tabindex="199" value="' +
5270                PMA_messages.strSimulateDML +
5271                '" />');
5272        }
5273    } else {
5274        if ($simulateDml.length) {
5275            $simulateDml.remove();
5276        }
5277    }
5278}
5279
5280/**
5281  * Create quick sql statements.
5282  *
5283  */
5284function insertQuery(queryType)
5285{
5286    if (queryType == "clear") {
5287        setQuery('');
5288        return;
5289    } else if (queryType == "format") {
5290        if (codemirror_editor) {
5291            $('#querymessage').html(PMA_messages.strFormatting +
5292                '&nbsp;<img class="ajaxIcon" src="' +
5293                pmaThemeImage + 'ajax_clock_small.gif" alt="">');
5294            var href = 'db_sql_format.php';
5295            var params = {
5296                'ajax_request': true,
5297                'token': PMA_commonParams.get('token'),
5298                'sql': codemirror_editor.getValue()
5299            };
5300            $.ajax({
5301                type: 'POST',
5302                url: href,
5303                data: params,
5304                success: function (data) {
5305                    if (data.success) {
5306                        codemirror_editor.setValue(data.sql);
5307                    }
5308                    $('#querymessage').html('');
5309                }
5310            });
5311        }
5312        return;
5313    } else if (queryType == "saved") {
5314        if (isStorageSupported('localStorage') && typeof window.localStorage.auto_saved_sql != 'undefined') {
5315            setQuery(window.localStorage.auto_saved_sql);
5316        } else if ($.cookie('auto_saved_sql')) {
5317            setQuery($.cookie('auto_saved_sql'));
5318        } else {
5319            PMA_ajaxShowMessage(PMA_messages.strNoAutoSavedQuery);
5320        }
5321        return;
5322    }
5323
5324    var query = "";
5325    var myListBox = document.sqlform.dummy;
5326    var table = document.sqlform.table.value;
5327
5328    if (myListBox.options.length > 0) {
5329        sql_box_locked = true;
5330        var columnsList = "";
5331        var valDis = "";
5332        var editDis = "";
5333        var NbSelect = 0;
5334        for (var i = 0; i < myListBox.options.length; i++) {
5335            NbSelect++;
5336            if (NbSelect > 1) {
5337                columnsList += ", ";
5338                valDis += ",";
5339                editDis += ",";
5340            }
5341            columnsList += myListBox.options[i].value;
5342            valDis += "[value-" + NbSelect + "]";
5343            editDis += myListBox.options[i].value + "=[value-" + NbSelect + "]";
5344        }
5345        if (queryType == "selectall") {
5346            query = "SELECT * FROM `" + table + "` WHERE 1";
5347        } else if (queryType == "select") {
5348            query = "SELECT " + columnsList + " FROM `" + table + "` WHERE 1";
5349        } else if (queryType == "insert") {
5350            query = "INSERT INTO `" + table + "`(" + columnsList + ") VALUES (" + valDis + ")";
5351        } else if (queryType == "update") {
5352            query = "UPDATE `" + table + "` SET " + editDis + " WHERE 1";
5353        } else if (queryType == "delete") {
5354            query = "DELETE FROM `" + table + "` WHERE 0";
5355        }
5356        setQuery(query);
5357        sql_box_locked = false;
5358    }
5359}
5360
5361
5362/**
5363  * Inserts multiple fields.
5364  *
5365  */
5366function insertValueQuery()
5367{
5368    var myQuery = document.sqlform.sql_query;
5369    var myListBox = document.sqlform.dummy;
5370
5371    if (myListBox.options.length > 0) {
5372        sql_box_locked = true;
5373        var columnsList = "";
5374        var NbSelect = 0;
5375        for (var i = 0; i < myListBox.options.length; i++) {
5376            if (myListBox.options[i].selected) {
5377                NbSelect++;
5378                if (NbSelect > 1) {
5379                    columnsList += ", ";
5380                }
5381                columnsList += myListBox.options[i].value;
5382            }
5383        }
5384
5385        /* CodeMirror support */
5386        if (codemirror_editor) {
5387            codemirror_editor.replaceSelection(columnsList);
5388        //IE support
5389        } else if (document.selection) {
5390            myQuery.focus();
5391            var sel = document.selection.createRange();
5392            sel.text = columnsList;
5393            document.sqlform.insert.focus();
5394        }
5395        //MOZILLA/NETSCAPE support
5396        else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart == "0") {
5397            var startPos = document.sqlform.sql_query.selectionStart;
5398            var endPos = document.sqlform.sql_query.selectionEnd;
5399            var SqlString = document.sqlform.sql_query.value;
5400
5401            myQuery.value = SqlString.substring(0, startPos) + columnsList + SqlString.substring(endPos, SqlString.length);
5402        } else {
5403            myQuery.value += columnsList;
5404        }
5405        sql_box_locked = false;
5406    }
5407}
5408
5409/**
5410 * Updates the input fields for the parameters based on the query
5411 */
5412function updateQueryParameters() {
5413
5414    if ($('#parameterized').is(':checked')) {
5415        var query = codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val();
5416
5417        var allParameters = query.match(/:[a-zA-Z0-9_]+/g);
5418        var parameters = [];
5419        // get unique parameters
5420        if (allParameters) {
5421            $.each(allParameters, function(i, parameter){
5422                if ($.inArray(parameter, parameters) === -1) {
5423                    parameters.push(parameter);
5424                }
5425            });
5426        } else {
5427            $('#parametersDiv').text(PMA_messages.strNoParam);
5428            return;
5429        }
5430
5431        var $temp = $('<div />');
5432        $temp.append($('#parametersDiv').children());
5433        $('#parametersDiv').empty();
5434
5435        $.each(parameters, function (i, parameter) {
5436            var paramName = parameter.substring(1);
5437            var $param = $temp.find('#paramSpan_' + paramName );
5438            if (! $param.length) {
5439                $param = $('<span class="parameter" id="paramSpan_' + paramName + '" />');
5440                $('<label for="param_' + paramName + '" />').text(parameter).appendTo($param);
5441                $('<input type="text" name="parameters[' + parameter + ']" id="param_' + paramName + '" />').appendTo($param);
5442            }
5443            $('#parametersDiv').append($param);
5444        });
5445    } else {
5446        $('#parametersDiv').empty();
5447    }
5448}
5449
5450/**
5451  * Refresh/resize the WYSIWYG scratchboard
5452  */
5453function refreshLayout()
5454{
5455    var $elm = $('#pdflayout');
5456    var orientation = $('#orientation_opt').val();
5457    var paper = 'A4';
5458    var $paperOpt = $('#paper_opt');
5459    if ($paperOpt.length == 1) {
5460        paper = $paperOpt.val();
5461    }
5462    var posa = 'y';
5463    var posb = 'x';
5464    if (orientation == 'P') {
5465        posa = 'x';
5466        posb = 'y';
5467    }
5468    $elm.css('width', pdfPaperSize(paper, posa) + 'px');
5469    $elm.css('height', pdfPaperSize(paper, posb) + 'px');
5470}
5471
5472/**
5473 * Initializes positions of elements.
5474 */
5475function TableDragInit() {
5476    $('.pdflayout_table').each(function () {
5477        var $this = $(this);
5478        var number = $this.data('number');
5479        var x = $('#c_table_' + number + '_x').val();
5480        var y = $('#c_table_' + number + '_y').val();
5481        $this.css('left', x + 'px');
5482        $this.css('top', y + 'px');
5483        /* Make elements draggable */
5484        $this.draggable({
5485            containment: "parent",
5486            drag: function (evt, ui) {
5487                var number = $this.data('number');
5488                $('#c_table_' + number + '_x').val(parseInt(ui.position.left, 
548810));
5489                $('#c_table_' + number + '_y').val(parseInt(ui.position.top, 10));
5490            }
5491        });
5492    });
5493}
5494
5495/**
5496 * Resets drag and drop positions.
5497 */
5498function resetDrag() {
5499    $('.pdflayout_table').each(function () {
5500        var $this = $(this);
5501        var x = $this.data('x');
5502        var y = $this.data('y');
5503        $this.css('left', x + 'px');
5504        $this.css('top', y + 'px');
5505    });
5506}
5507
5508/**
5509 * User schema handlers.
5510 */
5511$(function () {
5512    /* Move in scratchboard on manual change */
5513    $(document).on('change', '.position-change', function () {
5514        var $this = $(this);
5515        var $elm = $('#table_' + $this.data('number'));
5516        $elm.css($this.data('axis'), $this.val() + 'px');
5517    });
5518    /* Refresh on paper size/orientation change */
5519    $(document).on('change', '.paper-change', function () {
5520        var $elm = $('#pdflayout');
5521        if ($elm.css('visibility') == 'visible') {
5522            refreshLayout();
5523            TableDragInit();
5524        }
5525    });
5526    /* Show/hide the WYSIWYG scratchboard */
5527    $(document).on('click', '#toggle-dragdrop', function () {
5528        var $elm = $('#pdflayout');
5529        if ($elm.css('visibility') == 'hidden') {
5530            refreshLayout();
5531            TableDragInit();
5532            $elm.css('visibility', 'visible');
5533            $elm.css('display', 'block');
5534            $('#showwysiwyg').val('1');
5535        } else {
5536            $elm.css('visibility', 'hidden');
5537            $elm.css('display', 'none');
5538            $('#showwysiwyg').val('0');
5539        }
5540    });
5541    /* Reset scratchboard */
5542    $(document).on('click', '#reset-dragdrop', function () {
5543        resetDrag();
5544    });
5545});
5546
5547/**
5548 * Returns paper sizes for a given format
5549 */
5550function pdfPaperSize(format, axis)
5551{
5552    switch (format.toUpperCase()) {
5553    case '4A0':
5554        if (axis == 'x') {
5555            return 4767.87;
5556        } else {
5557            return 6740.79;
5558        }
5559        break;
5560    case '2A0':
5561        if (axis == 'x') {
5562            return 3370.39;
5563        } else {
5564            return 4767.87;
5565        }
5566        break;
5567    case 'A0':
5568        if (axis == 'x') {
5569            return 2383.94;
5570        } else {
5571            return 3370.39;
5572        }
5573        break;
5574    case 'A1':
5575        if (axis == 'x') {
5576            return 1683.78;
5577        } else {
5578            return 2383.94;
5579        }
5580        break;
5581    case 'A2':
5582        if (axis == 'x') {
5583            return 1190.55;
5584        } else {
5585            return 1683.78;
5586        }
5587        break;
5588    case 'A3':
5589        if (axis == 'x') {
5590            return 841.89;
5591        } else {
5592            return 1190.55;
5593        }
5594        break;
5595    case 'A4':
5596        if (axis == 'x') {
5597            return 595.28;
5598        } else {
5599            return 841.89;
5600        }
5601        break;
5602    case 'A5':
5603        if (axis == 'x') {
5604            return 419.53;
5605        } else {
5606            return 595.28;
5607        }
5608        break;
5609    case 'A6':
5610        if (axis == 'x') {
5611            return 297.64;
5612        } else {
5613            return 419.53;
5614        }
5615        break;
5616    case 'A7':
5617        if (axis == 'x') {
5618            return 209.76;
5619        } else {
5620            return 297.64;
5621        }
5622        break;
5623    case 'A8':
5624        if (axis == 'x') {
5625            return 147.40;
5626        } else {
5627            return 209.76;
5628        }
5629        break;
5630    case 'A9':
5631        if (axis == 'x') {
5632            return 104.88;
5633        } else {
5634            return 147.40;
5635        }
5636        break;
5637    case 'A10':
5638        if (axis == 'x') {
5639            return 73.70;
5640        } else {
5641            return 104.88;
5642        }
5643        break;
5644    case 'B0':
5645        if (axis == 'x') {
5646            return 2834.65;
5647        } else {
5648            return 4008.19;
5649        }
5650        break;
5651    case 'B1':
5652        if (axis == 'x') {
5653            return 2004.09;
5654        } else {
5655            return 2834.65;
5656        }
5657        break;
5658    case 'B2':
5659        if (axis == 'x') {
5660            return 1417.32;
5661        } else {
5662            return 2004.09;
5663        }
5664        break;
5665    case 'B3':
5666        if (axis == 'x') {
5667            return 1000.63;
5668        } else {
5669            return 1417.32;
5670        }
5671        break;
5672    case 'B4':
5673        if (axis == 'x') {
5674            return 708.66;
5675        } else {
5676            return 1000.63;
5677        }
5678        break;
5679    case 'B5':
5680        if (axis == 'x') {
5681            return 498.90;
5682        } else {
5683            return 708.66;
5684        }
5685        break;
5686    case 'B6':
5687        if (axis == 'x') {
5688            return 354.33;
5689        } else {
5690            return 498.90;
5691        }
5692        break;
5693    case 'B7':
5694        if (axis == 'x') {
5695            return 249.45;
5696        } else {
5697            return 354.33;
5698        }
5699        break;
5700    case 'B8':
5701        if (axis == 'x') {
5702            return 175.75;
5703        } else {
5704            return 249.45;
5705        }
5706        break;
5707    case 'B9':
5708        if (axis == 'x') {
5709            return 124.72;
5710        } else {
5711            return 175.75;
5712        }
5713        break;
5714    case 'B10':
5715        if (axis == 'x') {
5716            return 87.87;
5717        } else {
5718            return 124.72;
5719        }
5720        break;
5721    case 'C0':
5722        if (axis == 'x') {
5723            return 2599.37;
5724        } else {
5725            return 3676.54;
5726        }
5727        break;
5728    case 'C1':
5729        if (axis == 'x') {
5730            return 1836.85;
5731        } else {
5732            return 2599.37;
5733        }
5734        break;
5735    case 'C2':
5736        if (axis == 'x') {
5737            return 1298.27;
5738        } else {
5739            return 1836.85;
5740        }
5741        break;
5742    case 'C3':
5743        if (axis == 'x') {
5744            return 918.43;
5745        } else {
5746            return 1298.27;
5747        }
5748        break;
5749    case 'C4':
5750        if (axis == 'x') {
5751            return 649.13;
5752        } else {
5753            return 918.43;
5754        }
5755        break;
5756    case 'C5':
5757        if (axis == 'x') {
5758            return 459.21;
5759        } else {
5760            return 649.13;
5761        }
5762        break;
5763    case 'C6':
5764        if (axis == 'x') {
5765            return 323.15;
5766        } else {
5767            return 459.21;
5768        }
5769        break;
5770    case 'C7':
5771        if (axis == 'x') {
5772            return 229.61;
5773        } else {
5774            return 323.15;
5775        }
5776        break;
5777    case 'C8':
5778        if (axis == 'x') {
5779            return 161.57;
5780        } else {
5781            return 229.61;
5782        }
5783        break;
5784    case 'C9':
5785        if (axis == 'x') {
5786            return 113.39;
5787        } else {
5788            return 161.57;
5789        }
5790        break;
5791    case 'C10':
5792        if (axis == 'x') {
5793            return 79.37;
5794        } else {
5795            return 113.39;
5796        }
5797        break;
5798    case 'RA0':
5799        if (axis == 'x') {
5800            return 2437.80;
5801        } else {
5802            return 3458.27;
5803        }
5804        break;
5805    case 'RA1':
5806        if (axis == 'x') {
5807            return 1729.13;
5808        } else {
5809            return 2437.80;
5810        }
5811        break;
5812    case 'RA2':
5813        if (axis == 'x') {
5814            return 1218.90;
5815        } else {
5816            return 1729.13;
5817        }
5818        break;
5819    case 'RA3':
5820        if (axis == 'x') {
5821            return 864.57;
5822        } else {
5823            return 1218.90;
5824        }
5825        break;
5826    case 'RA4':
5827        if (axis == 'x') {
5828            return 609.45;
5829        } else {
5830            return 864.57;
5831        }
5832        break;
5833    case 'SRA0':
5834        if (axis == 'x') {
5835            return 2551.18;
5836        } else {
5837            return 3628.35;
5838        }
5839        break;
5840    case 'SRA1':
5841        if (axis == 'x') {
5842            return 1814.17;
5843        } else {
5844            return 2551.18;
5845        }
5846        break;
5847    case 'SRA2':
5848        if (axis == 'x') {
5849            return 1275.59;
5850        } else {
5851            return 1814.17;
5852        }
5853        break;
5854    case 'SRA3':
5855        if (axis == 'x') {
5856            return 907.09;
5857        } else {
5858            return 1275.59;
5859        }
5860        break;
5861    case 'SRA4':
5862        if (axis == 'x') {
5863            return 637.80;
5864        } else {
5865            return 907.09;
5866        }
5867        break;
5868    case 'LETTER':
5869        if (axis == 'x') {
5870            return 612.00;
5871        } else {
5872            return 792.00;
5873        }
5874        break;
5875    case 'LEGAL':
5876        if (axis == 'x') {
5877            return 612.00;
5878        } else {
5879            return 1008.00;
5880        }
5881        break;
5882    case 'EXECUTIVE':
5883        if (axis == 'x') {
5884            return 521.86;
5885        } else {
5886            return 756.00;
5887        }
5888        break;
5889    case 'FOLIO':
5890        if (axis == 'x') {
5891            return 612.00;
5892        } else {
5893            return 936.00;
5894        }
5895        break;
5896    } // end switch
5897
5898    return 0;
5899}
5900
5901/**
5902 * Get checkbox for foreign key checks
5903 *
5904 * @return string
5905 */
5906function getForeignKeyCheckboxLoader() {
5907    var html = '';
5908    html    += '<div>';
5909    html    += '<div class="load-default-fk-check-value">';
5910    html    += PMA_getImage('ajax_clock_small.gif');
5911    html    += '</div>';
5912    html    += '</div>';
5913    return html;
5914}
5915
5916function loadForeignKeyCheckbox() {
5917    // Load default foreign key check value
5918    var params = {
5919        'ajax_request': true,
5920        'token': PMA_commonParams.get('token'),
5921        'server': PMA_commonParams.get('server'),
5922        'get_default_fk_check_value': true
5923    };
5924    $.get('sql.php', params, function (data) {
5925        var html = '<input type="hidden" name="fk_checks" value="
59250" />' +
5926            '<input type="checkbox" name="fk_checks" id="fk_checks"' +
5927            (data.default_fk_check_value ? ' checked="checked"' : '') + ' />' +
5928            '<label for="fk_checks">' + PMA_messages.strForeignKeyCheck + '</label>';
5929        $('.load-default-fk-check-value').replaceWith(html);
5930    });
5931}
5932
5933function getJSConfirmCommonParam(elem) {
5934    return {
5935        'is_js_confirmed' : 1,
5936        'ajax_request' : true,
5937        'fk_checks': $(elem).find('#fk_checks').is(':checked') ? 1 : 0
5938    };
5939}
5940
5941/**
5942 * Unbind all event handlers before tearing down a page
5943 */
5944AJAX.registerTeardown('functions.js', function () {
5945    $(document).off('click', "a.inline_edit_sql");
5946    $(document).off('click', "input#sql_query_edit_save");
5947    $(document).off('click', "input#sql_query_edit_discard");
5948    $('input.sqlbutton').unbind('click');
5949    if (codemirror_editor) {
5950        codemirror_editor.off('blur');
5951    } else {
5952        $(document).off('blur', '#sqlquery');
5953    }
5954    $(document).off('change', '#parameterized');
5955    $('#sqlquery').unbind('keydown');
5956    $('#sql_query_edit').unbind('keydown');
5957
5958    if (codemirror_inline_editor) {
5959        // Copy the sql query to the text area to preserve it.
5960        $('#sql_query_edit').text(codemirror_inline_editor.getValue());
5961        $(codemirror_inline_editor.getWrapperElement()).unbind('keydown');
5962        codemirror_inline_editor.toTextArea();
5963        codemirror_inline_editor = false;
5964    }
5965    if (codemirror_editor) {
5966        $(codemirror_editor.getWrapperElement()).unbind('keydown');
5967    }
5968});
5969
5970/**
5971 * Jquery Coding for inline editing SQL_QUERY
5972 */
5973AJAX.registerOnload('functions.js', function () {
5974    // If we are coming back to the page by clicking forward button
5975    // of the browser, bind the code mirror to inline query editor.
5976    bindCodeMirrorToInlineEditor();
5977    $(document).on('click', "a.inline_edit_sql", function () {
5978        if ($('#sql_query_edit').length) {
5979            // An inline query editor is already open,
5980            // we don't want another copy of it
5981            return false;
5982        }
5983
5984        var $form = $(this).prev('form');
5985        var sql_query  = $form.find("input[name='sql_query']").val().trim();
5986        var $inner_sql = $(this).parent().prev().find('code.sql');
5987        var old_text   = $inner_sql.html();
5988
5989        var new_content = "<textarea name=\"sql_query_edit\" id=\"sql_query_edit\">" + escapeHtml(sql_query) + "</textarea>\n";
5990        new_content    += getForeignKeyCheckboxLoader();
5991        new_content    += "<input type=\"submit\" id=\"sql_query_edit_save\" class=\"button btnSave\" value=\"" + PMA_messages.strGo + "\"/>\n";
5992        new_content    += "<input type=\"button\" id=\"sql_query_edit_discard\" class=\"button btnDiscard\" value=\"" + PMA_messages.strCancel + "\"/>\n";
5993        var $editor_area = $('div#inline_editor');
5994        if ($editor_area.length === 0) {
5995            $editor_area = $('<div id="inline_editor_outer"></div>');
5996            $editor_area.insertBefore($inner_sql);
5997        }
5998        $editor_area.html(new_content);
5999        loadForeignKeyCheckbox();
6000        $inner_sql.hide();
6001
6002        bindCodeMirrorToInlineEditor();
6003        return false;
6004    });
6005
6006    $(document).on('click', "input#sql_query_edit_save", function () {
6007        $(".success").hide();
6008        //hide already existing success message
6009        var sql_query;
6010        if (codemirror_inline_editor) {
6011            codemirror_inline_editor.save();
6012            sql_query = codemirror_inline_editor.getValue();
6013        } else {
6014            sql_query = $(this).parent().find('#sql_query_edit').val();
6015        }
6016        var fk_check = $(this).parent().find('#fk_checks').is(':checked');
6017
6018        var $form = $("a.inline_edit_sql").prev('form');
6019        var $fake_form = $('<form>', {action: 'import.php', method: 'post'})
6020                .append($form.find("input[name=server], input[name=db], input[name=table], input[name=token]").clone())
6021                .append($('<input/>', {type: 'hidden', name: 'show_query', value: 1}))
6022                .append($('<input/>', {type: 'hidden', name: 'is_js_confirmed', value: 0}))
6023                .append($('<input/>', {type: 'hidden', name: 'sql_query', value: sql_query}))
6024                .append($('<input/>', {type: 'hidden', name: 'fk_checks', value: fk_check ? 1 : 0}));
6025        if (! checkSqlQuery($fake_form[0])) {
6026            return false;
6027        }
6028        $fake_form.appendTo($('body')).submit();
6029    });
6030
6031    $(document).on('click', "input#sql_query_edit_discard", function () {
6032        var $divEditor = $('div#inline_editor_outer');
6033        $divEditor.siblings('code.sql').show();
6034        $divEditor.remove();
6035    });
6036
6037    $('input.sqlbutton').click(function (evt) {
6038        insertQuery(evt.target.id);
6039        PMA_handleSimulateQueryButton();
6040        return false;
6041    });
6042
6043    $(document).on('change', '#parameterized', updateQueryParameters);
6044
6045    var $inputUsername = $('#input_username');
6046    if ($inputUsername) {
6047        if ($inputUsername.val() === '') {
6048            $inputUsername.focus();
6049        } else {
6050            $('#input_password').focus();
6051        }
6052    }
6053});
6054
6055/**
6056 * "inputRead" event handler for CodeMirror SQL query editors for autocompletion
6057 */
6058function codemirrorAutocompleteOnInputRead(instance) {
6059    if (!sql_autocomplete_in_progress
6060        && (!instance.options.hintOptions.tables || !sql_autocomplete)) {
6061
6062        if (!sql_autocomplete) {
6063            // Reset after teardown
6064            instance.options.hintOptions.tables = false;
6065            instance.options.hintOptions.defaultTable = '';
6066
6067            sql_autocomplete_in_progress = true;
6068
6069            var href = 'db_sql_autocomplete.php';
6070            var params = {
6071                'ajax_request': true,
6072                'token': PMA_commonParams.get('token'),
6073                'server': PMA_commonParams.get('server'),
6074                'db': PMA_commonParams.get('db'),
6075                'no_debug': true
6076            };
6077
6078            var columnHintRender = function(elem, self, data) {
6079                $('<div class="autocomplete-column-name">')
6080                    .text(data.columnName)
6081                    .appendTo(elem);
6082                $('<div class="autocomplete-column-hint">')
6083                    .text(data.columnHint)
6084                    .appendTo(elem);
6085            };
6086
6087            $.ajax({
6088                type: 'POST',
6089                url: href,
6090                data: params,
6091                success: function (data) {
6092                    if (data.success) {
6093                        var tables = $.parseJSON(data.tables);
6094                        sql_autocomplete_default_table = PMA_commonParams.get('table');
6095                        sql_autocomplete = [];
6096                        for (var table in tables) {
6097                            if (tables.hasOwnProperty(table)) {
6098                                var columns = tables[table];
6099                                table = {
6100                                    text: table,
6101                                    columns: []
6102                                };
6103                                for (var column in columns) {
6104                                    if (columns.hasOwnProperty(column)) {
6105                                        var displayText = columns[column].Type;
6106                                        if (columns[column].Key == 'PRI') {
6107                                            displayText += ' | Primary';
6108                                        } else if (columns[column].Key == 'UNI') {
6109                                            displayText += ' | Unique';
6110                                        }
6111                                        table.columns.push({
6112                                            text: column,
6113                                            displayText: column + " | " +  displayText,
6114                                            columnName: column,
6115                                            columnHint: displayText,
6116                                            render: columnHintRender
6117                                        });
6118                                    }
6119                                }
6120                            }
6121                            sql_autocomplete.push(table);
6122                        }
6123                        instance.options.hintOptions.tables = sql_autocomplete;
6124                        instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
6125                    }
6126                },
6127                complete: function () {
6128                    sql_autocomplete_in_progress = false;
6129                }
6130            });
6131        }
6132        else {
6133            instance.options.hintOptions.tables = sql_autocomplete;
6134            instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
6135        }
6136    }
6137    if (instance.state.completionActive) {
6138        return;
6139    }
6140    var cur = instance.getCursor();
6141    var token = instance.getTokenAt(cur);
6142    var string = '';
6143    if (token.string.match(/^[.`\w@]\w*$/)) {
6144        string = token.string;
6145    }
6146    if (string.length > 0) {
6147        CodeMirror.commands.autocomplete(instance);
6148    }
6149}
6150
6151/**
6152 * Remove autocomplete information before tearing down a page
6153 */
6154AJAX.registerTeardown('functions.js', function () {
6155    sql_autocomplete = false;
6156    sql_autocomplete_default_table = '';
6157});
6158
6159/**
6160 * Binds the CodeMirror to the text area used to inline edit a query.
6161 */
6162function bindCodeMirrorToInlineEditor() {
6163    var $inline_editor = $('#sql_query_edit');
6164    if ($inline_editor.length > 0) {
6165        if (typeof CodeMirror !== 'undefined') {
6166            var height = $inline_editor.css('height');
6167            codemirror_inline_editor = PMA_getSQLEditor($inline_editor);
6168            codemirror_inline_editor.getWrapperElement().style.height = height;
6169            codemirror_inline_editor.refresh();
6170            codemirror_inline_editor.focus();
6171            $(codemirror_inline_editor.getWrapperElement())
6172                .bind('keydown', catchKeypressesFromSqlInlineEdit);
6173        } else {
6174            $inline_editor
6175                .focus()
6176                .bind('keydown', catchKeypressesFromSqlInlineEdit);
6177        }
6178    }
6179}
6180
6181function catchKeypressesFromSqlInlineEdit(event) {
6182    // ctrl-enter is 10 in chrome and ie, but 13 in ff
6183    if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) {
6184        $("#sql_query_edit_save").trigger('click');
6185    }
6186}
6187
6188/**
6189 * Adds doc link to single highlighted SQL element
6190 */
6191function PMA_doc_add($elm, params)
6192{
6193    if (typeof mysql_doc_template == 'undefined') {
6194        return;
6195    }
6196
6197    var url = PMA_sprintf(
6198        decodeURIComponent(mysql_doc_template),
6199        params[0]
6200    );
6201    if (params.length > 1) {
6202        url += '#' + params[1];
6203    }
6204    var content = $elm.text();
6205    $elm.text('');
6206    $elm.append('<a target="mysql_doc" class="cm-sql-doc" href="' + url + '">' + content + '</a>');
6207}
6208
6209/**
6210 * Generates doc links for keywords inside highlighted SQL
6211 */
6212function PMA_doc_keyword(idx, elm)
6213{
6214    var $elm = $(elm);
6215    /* Skip already processed ones */
6216    if ($elm.find('a').length > 0) {
6217        return;
6218    }
6219    var keyword = $elm.text().toUpperCase();
6220    var $next = $elm.next('.cm-keyword');
6221    if ($next) {
6222        var next_keyword = $next.text().toUpperCase();
6223        var full = keyword + ' ' + next_keyword;
6224
6225        var $next2 = $next.next('.cm-keyword');
6226        if ($next2) {
6227            var next2_keyword = $next2.text().toUpperCase();
6228            var full2 = full + ' ' + next2_keyword;
6229            if (full2 in mysql_doc_keyword) {
6230                PMA_doc_add($elm, mysql_doc_keyword[full2]);
6231                PMA_doc_add($next, mysql_doc_keyword[full2]);
6232                PMA_doc_add($next2, mysql_doc_keyword[full2]);
6233                return;
6234            }
6235        }
6236        if (full in mysql_doc_keyword) {
6237            PMA_doc_add($elm, mysql_doc_keyword[full]);
6238            PMA_doc_add($next, mysql_doc_keyword[full]);
6239            return;
6240        }
6241    }
6242    if (keyword in mysql_doc_keyword) {
6243        PMA_doc_add($elm, mysql_doc_keyword[keyword]);
6244    }
6245}
6246
6247/**
6248 * Generates doc links for builtins inside highlighted SQL
6249 */
6250function PMA_doc_builtin(idx, elm)
6251{
6252    var $elm = $(elm);
6253    var builtin = $elm.text().toUpperCase();
6254    if (builtin in mysql_doc_builtin) {
6255        PMA_doc_add($elm, mysql_doc_builtin[builtin]);
6256    }
6257}
6258
6259/**
6260 * Higlights SQL using CodeMirror.
6261 */
6262function PMA_highlightSQL($base)
6263{
6264    var $elm = $base.find('code.sql');
6265    $elm.each(function () {
6266        var $sql = $(this);
6267        var $pre = $sql.find('pre');
6268        /* We only care about visible elements to avoid double processing */
6269        if ($pre.is(":visible")) {
6270            var $highlight = $('<div class="sql-highlight cm-s-default"></div>');
6271            $sql.append($highlight);
6272            if (typeof CodeMirror != 'undefined') {
6273                CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]);
6274                $pre.hide();
6275                $highlight.find('.cm-keyword').each(PMA_doc_keyword);
6276                $highlight.find('.cm-builtin').each(PMA_doc_builtin);
6277            }
6278        }
6279    });
6280}
6281
6282/**
6283 * Updates an element containing code.
6284 *
6285 * @param jQuery Object $base base element which contains the raw and the
6286 *                            highlighted code.
6287 *
6288 * @param string htmlValue    code in HTML format, displayed if code cannot be
6289 *                            highlighted
6290 *
6291 * @param string rawValue     raw code, used as a parameter for highlighter
6292 *
6293 * @return bool               whether content was updated or not
6294 */
6295function PMA_updateCode($base, htmlValue, rawValue)
6296{
6297    var $code = $base.find('code');
6298    if ($code.length === 0) {
6299        return false;
6300    }
6301
6302    // Determines the type of the content and appropriate CodeMirror mode.
6303    var type = '', mode = '';
6304    if  ($code.hasClass('json')) {
6305        type = 'json';
6306        mode = 'application/json';
6307    } else if ($code.hasClass('sql')) {
6308        type = 'sql';
6309        mode = 'text/x-mysql';
6310    } else if ($code.hasClass('xml')) {
6311        type = 'xml';
6312        mode = 'application/xml';
6313    } else {
6314        return false;
6315    }
6316
6317    // Element used to display unhighlighted code.
6318    var $notHighlighted = $('<pre>' + htmlValue + '</pre>');
6319
6320    // Tries to highlight code using CodeMirror.
6321    if (typeof CodeMirror != 'undefined') {
6322        var $highlighted = $('<div class="' + type + '-highlight cm-s-default"></div>');
6323        CodeMirror.runMode(rawValue, mode, $highlighted[0]);
6324        $notHighlighted.hide();
6325        $code.html('').append($notHighlighted, $highlighted[0]);
6326    } else {
6327        $code.html('').append($notHighlighted);
6328    }
6329
6330    return true;
6331}
6332
6333/**
6334 * Show a message on the top of the page for an Ajax request
6335 *
6336 * Sample usage:
6337 *
6338 * 1) var $msg = PMA_ajaxShowMessage();
6339 * This will show a message that reads "Loading...". Such a message will not
6340 * disappear automatically and cannot be dismissed by the user. To remove this
6341 * message either the PMA_ajaxRemoveMessage($msg) function must be called or
6342 * another message must be show with PMA_ajaxShowMessage() function.
6343 *
6344 * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
6345 * This is a special case. The behaviour is same as above,
6346 * just with a different message
6347 *
6348 * 3) var $msg = PMA_ajaxShowMessage('The operation was successful');
6349 * This will show a message that will disappear automatically and it can also
6350 * be dismissed by the user.
6351 *
6352 * 4) var $msg = PMA_ajaxShowMessage('Some error', false);
6353 * This will show a message that will not disappear automatically, but it
6354 * can be dismissed by the user after he has finished reading it.
6355 *
6356 * @param string  message     string containing the message to be shown.
6357 *                              optional, defaults to 'Loading...'
6358 * @param mixed   timeout     number of milliseconds for the message to be visible
6359 *                              optional, defaults to 5000. If set to 'false', the
6360 *                              notification will never disappear
6361 * @return jQuery object       jQuery Element that holds the message div
6362 *                              this object can be passed to PMA_aja
6362xRemoveMessage()
6363 *                              to remove the notification
6364 */
6365function PMA_ajaxShowMessage(message, timeout)
6366{
6367    /**
6368     * @var self_closing Whether the notification will automatically disappear
6369     */
6370    var self_closing = true;
6371    /**
6372     * @var dismissable Whether the user will be able to remove
6373     *                  the notification by clicking on it
6374     */
6375    var dismissable = true;
6376    // Handle the case when a empty data.message is passed.
6377    // We don't want the empty message
6378    if (message === '') {
6379        return true;
6380    } else if (! message) {
6381        // If the message is undefined, show the default
6382        message = PMA_messages.strLoading;
6383        dismissable = false;
6384        self_closing = false;
6385    } else if (message == PMA_messages.strProcessingRequest) {
6386        // This is another case where the message should not disappear
6387        dismissable = false;
6388        self_closing = false;
6389    }
6390    // Figure out whether (or after how long) to remove the notification
6391    if (timeout === undefined) {
6392        timeout = 5000;
6393    } else if (timeout === false) {
6394        self_closing = false;
6395    }
6396    // Create a parent element for the AJAX messages, if necessary
6397    if ($('#loading_parent').length === 0) {
6398        $('<div id="loading_parent"></div>')
6399        .prependTo("#page_content");
6400    }
6401    // Update message count to create distinct message elements every time
6402    ajax_message_count++;
6403    // Remove all old messages, if any
6404    $("span.ajax_notification[id^=ajax_message_num]").remove();
6405    /**
6406     * @var    $retval    a jQuery object containing the reference
6407     *                    to the created AJAX message
6408     */
6409    var $retval = $(
6410            '<span class="ajax_notification" id="ajax_message_num_' +
6411            ajax_message_count +
6412            '"></span>'
6413    )
6414    .hide()
6415    .appendTo("#loading_parent")
6416    .html(message)
6417    .show();
6418    // If the notification is self-closing we should create a callback to remove it
6419    if (self_closing) {
6420        $retval
6421        .delay(timeout)
6422        .fadeOut('medium', function () {
6423            if ($(this).is(':data(tooltip)')) {
6424                $(this).tooltip('destroy');
6425            }
6426            // Remove the notification
6427            $(this).remove();
6428        });
6429    }
6430    // If the notification is dismissable we need to add the relevant class to it
6431    // and add a tooltip so that the users know that it can be removed
6432    if (dismissable) {
6433        $retval.addClass('dismissable').css('cursor', 'pointer');
6434        /**
6435         * Add a tooltip to the notification to let the user know that (s)he
6436         * can dismiss the ajax notification by clicking on it.
6437         */
6438        PMA_tooltip(
6439            $retval,
6440            'span',
6441            PMA_messages.strDismiss
6442        );
6443    }
6444    PMA_highlightSQL($retval);
6445
6446    return $retval;
6447}
6448
6449/**
6450 * Removes the message shown for an Ajax operation when it's completed
6451 *
6452 * @param jQuery object   jQuery Element that holds the notification
6453 *
6454 * @return nothing
6455 */
6456function PMA_ajaxRemoveMessage($this_msgbox)
6457{
6458    if ($this_msgbox !== undefined && $this_msgbox instanceof jQuery) {
6459        $this_msgbox
6460        .stop(true, true)
6461        .fadeOut('medium');
6462        if ($this_msgbox.is(':data(tooltip)')) {
6463            $this_msgbox.tooltip('destroy');
6464        } else {
6465            $this_msgbox.remove();
6466        }
6467    }
6468}
6469
6470/**
6471 * Requests SQL for previewing before executing.
6472 *
6473 * @param jQuery Object $form Form containing query data
6474 *
6475 * @return void
6476 */
6477function PMA_previewSQL($form)
6478{
6479    var form_url = $form.attr('action');
6480    var form_data = $form.serialize() +
6481        '&do_save_data=1' +
6482        '&preview_sql=1' +
6483        '&ajax_request=1';
6484    var $msgbox = PMA_ajaxShowMessage();
6485    $.ajax({
6486        type: 'POST',
6487        url: form_url,
6488        data: form_data,
6489        success: function (response) {
6490            PMA_ajaxRemoveMessage($msgbox);
6491            if (response.success) {
6492                var $dialog_content = $('<div/>')
6493                    .append(response.sql_data);
6494                var button_options = {};
6495                button_options[PMA_messages.strClose] = function () {
6496                    $(this).dialog('close');
6497                };
6498                var $response_dialog = $dialog_content.dialog({
6499                    minWidth: 550,
6500                    maxHeight: 400,
6501                    modal: true,
6502                    buttons: button_options,
6503                    title: PMA_messages.strPreviewSQL,
6504                    close: function () {
6505                        $(this).remove();
6506                    },
6507                    open: function () {
6508                        // Pretty SQL printing.
6509                        PMA_highlightSQL($(this));
6510                    }
6511                });
6512            } else {
6513                PMA_ajaxShowMessage(response.message);
6514            }
6515        },
6516        error: function () {
6517            PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest);
6518        }
6519    });
6520}
6521
6522/**
6523 * check for reserved keyword column name
6524 *
6525 * @param jQuery Object $form Form
6526 *
6527 * @returns true|false
6528 */
6529
6530function PMA_checkReservedWordColumns($form) {
6531    var is_confirmed = true;
6532    $.ajax({
6533        type: 'POST',
6534        url: "tbl_structure.php",
6535        data: $form.serialize() + '&reserved_word_check=1',
6536        success: function (data) {
6537            if (typeof data.success != 'undefined' && data.success === true) {
6538                is_confirmed = confirm(data.message);
6539            }
6540        },
6541        async:false
6542    });
6543    return is_confirmed;
6544}
6545
6546// This event only need to be fired once after the initial page load
6547$(function () {
6548    /**
6549     * Allows the user to dismiss a notification
6550     * created with PMA_ajaxShowMessage()
6551     */
6552    $(document).on('click', 'span.ajax_notification.dismissable', function () {
6553        PMA_ajaxRemoveMessage($(this));
6554    });
6555    /**
6556     * The below two functions hide the "Dismiss notification" tooltip when a user
6557     * is hovering a link or button that is inside an ajax message
6558     */
6559    $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
6560        if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
6561            $(this).parents('span.ajax_notification').tooltip('disable');
6562        }
6563    });
6564    $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
6565        if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
6566            $(this).parents('span.ajax_notification').tooltip('enable');
6567        }
6568    });
6569});
6570
6571/**
6572 * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
6573 */
6574function PMA_showNoticeForEnum(selectElement)
6575{
6576    var enum_notice_id = selectElement.attr("id").split("_")[1];
6577    enum_notice_id += "_" + (parseInt(selectElement.attr("id").split("_")[2], 10) + 1);
6578    var selectedType = selectElement.val();
6579    if (selectedType == "ENUM" || selectedType == "SET") {
6580        $("p#enum_notice_" + enum_notice_id).show();
6581    } else {
6582        $("p#enum_notice_" + enum_notice_id).hide();
6583    }
6584}
6585
6586/**
6587 * Creates a Profiling Chart. Used in sql.js
6588 * and in server_status_monitor.js
6589 */
6590function PMA_createProfilingChart(target, data)
6591{
6592    // create the chart
6593    var factory = new JQPlotChartFactory();
6594    var chart = factory.createChart(ChartType.PIE, target);
6595
6596    // create the data table and add columns
6597    var dataTable = new DataTable();
6598    dataTable.addColumn(ColumnType.STRING, '');
6599    dataTable.addColumn(ColumnType.NUMBER, '');
6600    dataTable.setData(data);
6601
6602    // draw the chart and return the chart object
6603    chart.draw(dataTable, {
6604        seriesDefaults: {
6605            rendererOptions: {
6606                showDataLabels:  true
6607            }
6608        },
6609        highlighter: {
6610            tooltipLocation: 'se',
6611            sizeAdjust: 0,
6612            tooltipAxes: 'pieref',
6613            formatString: '%s, %.9Ps'
6614        },
6615        legend: {
6616            show: true,
6617            location: 'e',
6618            rendererOptions: {
6619                numberColumns: 2
6620            }
6621        },
6622        // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
6623        seriesColors: [
6624            '#fce94f',
6625            '#fcaf3e',
6626            '#e9b96e',
6627            '#8ae234',
6628            '#729fcf',
6629            '#ad7fa8',
6630            '#ef2929',
6631            '#eeeeec',
6632            '#888a85',
6633            '#c4a000',
6634            '#ce5c00',
6635            '#8f5902',
6636            '#4e9a06',
6637            '#204a87',
6638            '#5c3566',
6639            '#a40000',
6640            '#babdb6',
6641            '#2e3436'
6642        ]
6643    });
6644    return chart;
6645}
6646
6647/**
6648 * Formats a profiling duration nicely (in us and ms time).
6649 * Used in server_status_monitor.js
6650 *
6651 * @param  integer    Number to be formatted, should be in the range of microsecond to second
6652 * @param  integer    Accuracy, how many numbers right to the comma should be
6653 * @return string     The formatted number
6654 */
6655function PMA_prettyProfilingNum(num, acc)
6656{
6657    if (!acc) {
6658        acc = 2;
6659    }
6660    acc = Math.pow(10, acc);
6661    if (num * 1000 < 0.1) {
6662        num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
6663    } else if (num < 0.1) {
6664        num = Math.round(acc * (num * 1000)) / acc + 'm';
6665    } else {
6666        num = Math.round(acc * num) / acc;
6667    }
6668
6669    return num + 's';
6670}
6671
6672
6673/**
6674 * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
6675 *
6676 * @param string      Query to be formatted
6677 * @return string      The formatted query
6678 */
6679function PMA_SQLPrettyPrint(string)
6680{
6681    if (typeof CodeMirror == 'undefined') {
6682        return string;
6683    }
6684
6685    var mode = CodeMirror.getMode({}, "text/x-mysql");
6686    var stream = new CodeMirror.StringStream(string);
6687    var state = mode.startState();
6688    var token, tokens = [];
6689    var output = '';
6690    var tabs = function (cnt) {
6691        var ret = '';
6692        for (var i = 0; i < 4 * cnt; i++) {
6693            ret += " ";
6694        }
6695        return ret;
6696    };
6697
6698    // "root-level" statements
6699    var statements = {
6700        'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'],
6701        'update': ['update', 'set', 'where'],
6702        'insert into': ['insert into', 'values']
6703    };
6704    // don't put spaces before these tokens
6705    var spaceExceptionsBefore = {';': true, ',': true, '.': true, '(': true};
6706    // don't put spaces after these tokens
6707    var spaceExceptionsAfter = {'.': true};
6708
6709    // Populate tokens array
6710    var str = '';
6711    while (! stream.eol()) {
6712        stream.start = stream.pos;
6713        token = mode.token(stream, state);
6714        if (token !== null) {
6715            tokens.push([token, stream.current().toLowerCase()]);
6716        }
6717    }
6718
6719    var currentStatement = tokens[0][1];
6720
6721    if (! statements[currentStatement]) {
6722        return string;
6723    }
6724    // Holds all currently opened code blocks (statement, function or generic)
6725    var blockStack = [];
6726    // Holds the type of block from last iteration (the current is in blockStack[0])
6727    var previousBlock;
6728    // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
6729    var newBlock, endBlock;
6730    // How much to indent in the current line
6731    var indentLevel = 0;
6732    // Holds the "root-level" statements
6733    var statementPart, lastStatementPart = statements[currentStatement][0];
6734
6735    blockStack.unshift('statement');
6736
6737    // Iterate through every token and format accordingly
6738    for (var i = 0; i < tokens.length; i++) {
6739        previousBlock = blockStack[0];
6740
6741        // New block => push to stack
6742        if (tokens[i][1] == '(') {
6743            if (i < tokens.length - 1 && tokens[i + 1][0] == 'statement-verb') {
6744                blockStack.unshift(newBlock = 'statement');
6745            } else if (i > 0 && tokens[i - 1][0] == 'builtin') {
6746                blockStack.unshift(newBlock = 'function');
6747            } else {
6748                blockStack.unshift(newBlock = 'generic');
6749            }
6750        } else {
6751            newBlock = null;
6752        }
6753
6754        // Block end => pop from stack
6755        if (tokens[i][1] == ')') {
6756            endBlock = blockStack[0];
6757            blockStack.shift();
6758        } else {
6759            endBlock = null;
6760        }
6761
6762        // A subquery is starting
6763        if (i > 0 && newBlock == 'statement') {
6764            indentLevel++;
6765            output += "\n" + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + "\n" + tabs(indentLevel + 1);
6766            currentStatement = tokens[i + 1][1];
6767            i++;
6768            continue;
6769        }
6770
6771        // A subquery is ending
6772        if (endBlock == 'statement' && indentLevel > 0) {
6773            output += "\n" + tabs(indentLevel);
6774            indentLevel--;
6775        }
6776
6777        // One less indentation for statement parts (from, where, order by, etc.) and a newline
6778        statementPart = statements[currentStatement].indexOf(tokens[i][1]);
6779        if (statementPart != -1) {
6780            if (i > 0) {
6781                output += "\n";
6782            }
6783            output += tabs(indentLevel) + tokens[i][1].toUpperCase();
6784            output += "\n" + tabs(indentLevel + 1);
6785            lastStatementPart = tokens[i][1];
6786        }
6787        // Normal indentation and spaces for everything else
6788        else {
6789            if (! spaceExceptionsBefore[tokens[i][1]] &&
6790               ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) &&
6791               output.charAt(output.length - 1) != ' ') {
6792                output += " ";
6793            }
6794            if (tokens[i][0] == 'keyword') {
6795                output += tokens[i][1].toUpperCase();
6796            } else {
6797                output += tokens[i][1];
6798            }
6799        }
6800
6801        // split columns in select and 'update set' clauses, but only inside statements blocks
6802        if ((lastStatementPart == 'select' || lastStatementPart == 'where'  || lastStatementPart == 'set') &&
6803            tokens[i][1] == ',' && blockStack[0] == 'statement') {
6804
6805            output += "\n" + tabs(indentLevel + 1);
6806        }
6807
6808        // split conditions in where clauses, but only inside statements blocks
6809        if (lastStatementPart == 'where' &&
6810            (tokens[i][1] == 'and' || tokens[i][1] == 'or' || tokens[i][1] == 'xor')) {
6811
6812            if (blockStack[0] == 'statement') {
6813                output += "\n" + tabs(indentLevel + 1);
6814            }
6815            // Todo: Also split and or blocks in newlines & indentation++
6816            //if (blockStack[0] == 'generic')
6817             //   output += ...
6818        }
6819    }
6820    return output;
6821}
6822
6823/**
6824 * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
6825 *  return a jQuery object yet and hence cannot be chained
6826 *
6827 * @param string      question
6828 * @param string      url           URL to be passed to the callbackFn to make
6829 *                                  an Ajax call to
6830 * @param function    callbackFn    callback to execute after user clicks on OK
6831 * @param function    openCallback  optional callback to run when dialog is shown
6832 */
6833
6834jQuery.fn.PMA_confirm = function (question, url, callbackFn, openCallback) {
6835    var confirmState = PMA_commonParams.get('confirm');
6836    if (! confirmState) {
6837        // user does not want to confirm
6838        if ($.isFunction(callbackFn)) {
6839            callbackFn.call(this, url);
6840            return true;
6841        }
6842    }
6843    if (PMA_messages.strDoYouReally === '') {
6844        return true;
6845    }
6846
6847    /**
6848     * @var    button_options  Object that stores the options passed to 
6848jQueryUI
6849     *                          dialog
6850     */
6851    var button_options = [
6852        {
6853            text: PMA_messages.strOK,
6854            'class': 'submitOK',
6855            click: function () {
6856                $(this).dialog("close");
6857                if ($.isFunction(callbackFn)) {
6858                    callbackFn.call(this, url);
6859                }
6860            }
6861        },
6862        {
6863            text: PMA_messages.strCancel,
6864            'class': 'submitCancel',
6865            click: function () {
6866                $(this).dialog("close");
6867            }
6868        }
6869    ];
6870
6871    $('<div/>', {'id': 'confirm_dialog', 'title': PMA_messages.strConfirm})
6872    .prepend(question)
6873    .dialog({
6874        buttons: button_options,
6875        close: function () {
6876            $(this).remove();
6877        },
6878        open: openCallback,
6879        modal: true
6880    });
6881};
6882
6883/**
6884 * jQuery function to sort a table's body after a new row has been appended to it.
6885 * Also fixes the even/odd classes of the table rows at the end.
6886 *
6887 * @param string      text_selector   string to select the sortKey's text
6888 *
6889 * @return jQuery Object for chaining purposes
6890 */
6891jQuery.fn.PMA_sort_table = function (text_selector) {
6892    return this.each(function () {
6893
6894        /**
6895         * @var table_body  Object referring to the table's <tbody> element
6896         */
6897        var table_body = $(this);
6898        /**
6899         * @var rows    Object referring to the collection of rows in {@link table_body}
6900         */
6901        var rows = $(this).find('tr').get();
6902
6903        //get the text of the field that we will sort by
6904        $.each(rows, function (index, row) {
6905            row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase());
6906        });
6907
6908        //get the sorted order
6909        rows.sort(function (a, b) {
6910            if (a.sortKey < b.sortKey) {
6911                return -1;
6912            }
6913            if (a.sortKey > b.sortKey) {
6914                return 1;
6915            }
6916            return 0;
6917        });
6918
6919        //pull out each row from the table and then append it according to it's order
6920        $.each(rows, function (index, row) {
6921            $(table_body).append(row);
6922            row.sortKey = null;
6923        });
6924
6925        //Re-check the classes of each row
6926        $(this).find('tr:odd')
6927        .removeClass('even').addClass('odd')
6928        .end()
6929        .find('tr:even')
6930        .removeClass('odd').addClass('even');
6931    });
6932};
6933
6934/**
6935 * Unbind all event handlers before tearing down a page
6936 */
6937AJAX.registerTeardown('functions.js', function () {
6938    $(document).off('submit', "#create_table_form_minimal.ajax");
6939    $(document).off('submit', "form.create_table_form.ajax");
6940    $(document).off('click', "form.create_table_form.ajax input[name=submit_num_fields]");
6941    $(document).off('keyup', "form.create_table_form.ajax input");
6942    $(document).off('change', "input[name=partition_count],input[name=subpartition_count],select[name=partition_by]");
6943});
6944
6945/**
6946 * jQuery coding for 'Create Table'.  Used on db_operations.php,
6947 * db_structure.php and db_tracking.php (i.e., wherever
6948 * libraries/display_create_table.lib.php is used)
6949 *
6950 * Attach Ajax Event handlers for Create Table
6951 */
6952AJAX.registerOnload('functions.js', function () {
6953    /**
6954     * Attach event handler for submission of create table form (save)
6955     */
6956    $(document).on('submit', "form.create_table_form.ajax", function (event) {
6957        event.preventDefault();
6958
6959        /**
6960         * @var    the_form    object referring to the create table form
6961         */
6962        var $form = $(this);
6963
6964        /*
6965         * First validate the form; if there is a problem, avoid submitting it
6966         *
6967         * checkTableEditForm() needs a pure element and not a jQuery object,
6968         * this is why we pass $form[0] as a parameter (the jQuery object
6969         * is actually an array of DOM elements)
6970         */
6971
6972        if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
6973            PMA_prepareForAjaxRequest($form);
6974            if (PMA_checkReservedWordColumns($form)) {
6975                PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
6976                //User wants to submit the form
6977                $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
6978                    if (typeof data !== 'undefined' && data.success === true) {
6979                        $('#properties_message')
6980                         .removeClass('error')
6981                         .html('');
6982                        PMA_ajaxShowMessage(data.message);
6983                        // Only if the create table dialog (distinct panel) exists
6984                        var $createTableDialog = $("#create_table_dialog");
6985                        if ($createTableDialog.length > 0) {
6986                            $createTableDialog.dialog("close").remove();
6987                        }
6988                        $('#tableslistcontainer').before(data.formatted_sql);
6989
6990                        /**
6991                         * @var tables_table    Object referring to the <tbody> element that holds the list of tables
6992                         */
6993                        var tables_table = $("#tablesForm").find("tbody").not("#tbl_summary_row");
6994                        // this is the first table created in this db
6995                        if (tables_table.length === 0) {
6996                            PMA_commonActions.refreshMain(
6997                                PMA_commonParams.get('opendb_url')
6998                            );
6999                        } else {
7000                            /**
7001                             * @var curr_last_row   Object referring to the last <tr> element in {@link tables_table}
7002                             */
7003                            var curr_last_row = $(tables_table).find('tr:last');
7004                            /**
7005                             * @var curr_last_row_index_string   String containing the index of {@link curr_last_row}
7006                             */
7007                            var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0];
7008                            /**
7009                             * @var curr_last_row_index Index of {@link curr_last_row}
7010                             */
7011                            var curr_last_row_index = parseFloat(curr_last_row_index_string);
7012                            /**
7013                             * @var new_last_row_index   Index of the new row to be appended to {@link tables_table}
7014                             */
7015                            var new_last_row_index = curr_last_row_index + 1;
7016                            /**
7017                             * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table}
7018                             */
7019                            var new_last_row_id = 'checkbox_tbl_' + new_last_row_index;
7020
7021                            data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id);
7022                            //append to table
7023                            $(data.new_table_string)
7024                             .appendTo(tables_table);
7025
7026                            //Sort the table
7027                            $(tables_table).PMA_sort_table('th');
7028
7029                            // Adjust summary row
7030                            PMA_adjustTotals();
7031                        }
7032
7033                        //Refresh navigation as a new table has been added
7034                        PMA_reloadNavigation();
7035                        // Redirect to table structure page on creation of new table
7036                        var params_12 = 'ajax_request=true&ajax_page_request=true';
7037                        if (! (history && history.pushState)) {
7038                            params_12 += PMA_MicroHistory.menus.getRequestParam();
7039                        }
7040                        tblStruct_url = 'tbl_structure.php?server=' + data._params.server +
7041                            '&db='+ data._params.db + '&token=' + data._params.token +
7042                            '&goto=db_structure.php&table=' + data._params.table + '';
7043                        $.get(tblStruct_url, params_12, AJAX.responseHandler);
7044                    } else {
7045                        PMA_ajaxShowMessage(
7046                            '<div class="error">' + data.error + '</div>',
7047                            false
7048                        );
7049                    }
7050                }); // end $.post()
7051            }
7052        } // end if (checkTableEditForm() )
7053    }); // end create table form (save)
7054
7055    /**
7056     * Submits the intermediate changes in the table creation form
7057     * to refresh the UI accordingly
7058     */
7059    function submitChangesInCreateTableForm (actionParam) {
7060
7061        /**
7062         * @var    the_form    object referring to the create table form
7063         */
7064        var $form = $('form.create_table_form.ajax');
7065
7066        var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
7067        PMA_prepareForAjaxRequest($form);
7068
7069        //User wants to add more fields to the table
7070        $.post($form.attr('action'), $form.serialize() + "&" + actionParam, function (data) {
7071            if (typeof data !== 'undefined' && data.success) {
7072                var $pageContent = $("#page_content");
7073                $pageContent.html(data.message);
7074                PMA_highlightSQL($pageContent);
7075                PMA_verifyColumnsProperties();
7076                PMA_hideShowConnection($('.create_table_form select[name=tbl_storage_engine]'));
7077                PMA_ajaxRemoveMessage($msgbox);
7078            } else {
7079                PMA_ajaxShowMessage(data.error);
7080            }
7081        }); //end $.post()
7082    }
7083
7084    /**
7085     * Attach event handler for create table form (add fields)
7086     */
7087    $(document).on('click', "form.create_table_form.ajax input[name=submit_num_fields]", function (event) {
7088        event.preventDefault();
7089
7090        if (!checkFormElementInRange(this.form, 'added_fields', PMA_messages.strLeastColumnError, 1)) {
7091            return;
7092        }
7093
7094        submitChangesInCreateTableForm('submit_num_fields=1');
7095    }); // end create table form (add fields)
7096
7097    $(document).on('keydown', "form.create_table_form.ajax input[name=added_fields]", function (event) {
7098        if (event.keyCode == 13) {
7099            event.preventDefault();
7100            event.stopImmediatePropagation();
7101            $(this)
7102                .closest('form')
7103                .find('input[name=submit_num_fields]')
7104                .click();
7105        }
7106    });
7107
7108    /**
7109     * Attach event handler to manage changes in number of partitions and subpartitions
7110     */
7111    $(document).on('change', "input[name=partition_count],input[name=subpartition_count],select[name=partition_by]", function (event) {
7112        $this = $(this);
7113        $form = $this.parents('form');
7114        if ($form.is(".create_table_form.ajax")) {
7115            submitChangesInCreateTableForm('submit_partition_change=1');
7116        } else {
7117            $form.submit();
7118        }
7119    });
7120
7121    $("input[value=AUTO_INCREMENT]").change(function(){
7122        if (this.checked) {
7123            var col = /\d/.exec($(this).attr('name'));
7124            col = col[0];
7125            var $selectFieldKey = $('select[name="field_key[' + col + ']"]');
7126            if ($selectFieldKey.val() === 'none_'+col) {
7127                $selectFieldKey.val('primary_'+col).change();
7128            }
7129        }
7130    });
7131    $('body')
7132    .off('click', 'input.preview_sql')
7133    .on('click', 'input.preview_sql', function () {
7134        var $form = $(this).closest('form');
7135        PMA_previewSQL($form);
7136    });
7137});
7138
7139
7140/**
7141 * Validates the password field in a form
7142 *
7143 * @see    PMA_messages.strPasswordEmpty
7144 * @see    PMA_messages.strPasswordNotSame
7145 * @param  object $the_form The form to be validated
7146 * @return bool
7147 */
7148function PMA_checkPassword($the_form)
7149{
7150    // Did the user select 'no password'?
7151    if ($the_form.find('#nopass_1').is(':checked')) {
7152        return true;
7153    } else {
7154        var $pred = $the_form.find('#select_pred_password');
7155        if ($pred.length && ($pred.val() == 'none' || $pred.val() == 'keep')) {
7156            return true;
7157        }
7158    }
7159
7160    var $password = $the_form.find('input[name=pma_pw]');
7161    var $password_repeat = $the_form.find('input[name=pma_pw2]');
7162    var alert_msg = false;
7163
7164    if ($password.val() === '') {
7165        alert_msg = PMA_messages.strPasswordEmpty;
7166    } else if ($password.val() != $password_repeat.val()) {
7167        alert_msg = PMA_messages.strPasswordNotSame;
7168    }
7169
7170    if (alert_msg) {
7171        alert(alert_msg);
7172        $password.val('');
7173        $password_repeat.val('');
7174        $password.focus();
7175        return false;
7176    }
7177    return true;
7178}
7179
7180/**
7181 * Unbind all event handlers before tearing down a page
7182 */
7183AJAX.registerTeardown('functions.js', function () {
7184    $(document).off('click', '#change_password_anchor.ajax');
7185});
7186/**
7187 * Attach Ajax event handlers for 'Change Password' on index.php
7188 */
7189AJAX.registerOnload('functions.js', function () {
7190
7191    /* Handler for hostname type */
7192    $(document).on('change', '#select_pred_hostname', function () {
7193        var hostname = $('#pma_hostname');
7194        if (this.value == 'any') {
7195            hostname.val('%');
7196        } else if (this.value == 'localhost') {
7197            hostname.val('localhost');
7198        } else if (this.value == 'thishost' && $(this).data('thishost')) {
7199            hostname.val($(this).data('thishost'));
7200        } else if (this.value == 'hosttable') {
7201            hostname.val('').prop('required', false);
7202        } else if (this.value == 'userdefined') {
7203            hostname.focus().select().prop('required', true);
7204        }
7205    });
7206
7207    /* Handler for editing hostname */
7208    $(document).on('change', '#pma_hostname', function () {
7209        $('#select_pred_hostname').val('userdefined');
7210        $('#pma_hostname').prop('required', true);
7211    });
7212
7213    /* Handler for username type */
7214    $(document).on('change', '#select_pred_username', function () {
7215        if (this.value == 'any') {
7216            $('#pma_username').val('').prop('required', false);
7217            $('#user_exists_warning').css('display', 'none');
7218        } else if (this.value == 'userdefined') {
7219            $('#pma_username').focus().select().prop('required', true);
7220        }
7221    });
7222
7223    /* Handler for editing username */
7224    $(document).on('change', '#pma_username', function () {
7225        $('#select_pred_username').val('userdefined');
7226        $('#pma_username').prop('required', true);
7227    });
7228
7229    /* Handler for password type */
7230    $(document).on('change', '#select_pred_password', function () {
7231        if (this.value == 'none') {
7232            $('#text_pma_pw2').prop('required', false).val('');
7233            $('#text_pma_pw').prop('required', false).val('');
7234        } else if (this.value == 'userdefined') {
7235            $('#text_pma_pw2').prop('required', true);
7236            $('#text_pma_pw').prop('required', true).focus().select();
7237        } else {
7238            $('#text_pma_pw2').prop('required', false);
7239            $('#text_pma_pw').prop('required', false);
7240        }
7241    });
7242
7243    /* Handler for editing password */
7244    $(document).on('change', '#text_pma_pw,#text_pma_pw2', function () {
7245        $('#select_pred_password').val('userdefined');
7246        $('#text_pma_pw2').prop('required', true);
7247        $('#text_pma_pw').prop('required', true);
7248    });
7249
7250    /**
7251     * Attach Ajax event handler on the change password anchor
7252     */
7253    $(document).on('click', '#change_password_anchor.ajax', function (event) {
7254        event.preventDefault();
7255
7256        var $msgbox = PMA_ajaxShowMessage();
7257
7258        /**
7259         * @var button_options  Object containing options to be passed to jQueryUI's dialog
7260         */
7261        var button_options = {};
7262        button_options[PMA_messages.strGo] = function () {
7263
7264            event.preventDefault();
7265
7266            /**
7267             * @var $the_form    Object referring to the change password form
7268             */
7269            var $the_form = $("#change_password_form");
7270
7271            if (! PMA_checkPassword($the_form)) {
7272                return false;
7273            }
7274
7275            /**
7276             * @var this_value  String containing the value of the submit button.
7277             * Need to append this for the change password form on Server Privileges
7278             * page to work
7279             */
7280            var this_value = $(this).val();
7281
7282            var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
7283            $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
7284
7285            $.post($the_form.attr('action'), $the_form.serialize() + '&change_pw=' + this_value, function (data) {
7286                if (typeof data === 'undefined' || data.success !== true) {
7287                    PMA_ajaxShowMessage(data.error, false);
7288                    return;
7289                }
7290
7291                var $pageContent = $("#page_content");
7292                $pageContent.prepend(data.message);
7293                PMA_highlightSQL($pageContent);
7294                $("#change_password_dialog").hide().remove();
7295                $("#edit_user_dialog").dialog("close").remove();
7296                PMA_ajaxRemoveMessage($msgbox);
7297            }); // end $.post()
7298        };
7299
7300        button_options[PMA_messages.strCancel] = function () {
7301            $(this).dialog('close');
7302        };
7303        $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
7304            if (typeof data === 'undefined' || !data.success) {
7305                PMA_ajaxShowMessage(data.error, false);
7306                return;
7307            }
7308
7309            $('<div id="change_password_dialog"></div>')
7310                .dialog({
7311                    title: PMA_messages.strChangePassword,
7312                    width: 600,
7313                    close: function (ev, ui) {
7314                        $(this).remove();
7315                    },
7316                    buttons: button_options,
7317                    modal: true
7318                })
7319                .append(data.message);
7320            // for this dialog, we remove the fieldset wrapping due to double headings
7321            $("fieldset#fieldset_change_password")
7322                .find("legend").remove().end()
7323                .find("table.noclick").unwrap().addClass("some-margin")
7324                .find("input#text_pma_pw").focus();
7325            displayPasswordGenerateButton();
7326            $('#fieldset_change_password_footer').hide();
7327            PMA_ajaxRemoveMessage($msgbox);
7328            $('#change_password_form').bind('submit', function (e) {
7329                e.preventDefault();
7330                $(this)
7331                    .closest('.ui-dialog')
7332                    .find('.ui-dialog-buttonpane .ui-button')
7333                    .first()
7334                    .click();
7335            });
7336        }); // end $.get()
7337    }); // end handler for change password anchor
7338}); // end $() for Change Password
7339
7340/**
7341 * Unbind all event handlers before tearing down a page
7342 */
7343AJAX.registerTeardown('functions.js', function () {
7344    $(document).off('change', "select.column_type");
7345    $(document).off('change', "select.default_type");
7346    $(document).off('change', "select.virtuality");
7347    $(document).off('change', 'input.allow_null');
7348    $(document).off('change', '.create_table_form select[name=tbl_storage_engine]');
7349});
7350/**
7351 * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
7352 * the page loads and when the selected data type changes
7353 */
7354AJAX.registerOnload('functions.js', function () {
7355    // is called here for normal page loads and also when opening
7356    // the Create table dialog
7357    PMA_verifyColumnsProperties();
7358    //
7359    // needs on() to work also in the Create Table dialog
7360    $(document).on('change', "select.column_type", function () {
7361        PMA_showNoticeForEnum($(this));
7362    });
7363    $(document).on('change', "select.default_type", function () {
7364        PMA_hideShowDefaultValue($(this));
7365    });
7366    $(document).on('change', "select.virtuality", function () {
7367        PMA_hideShowExpression($(this));
7368    });
7369    $(document).on('change', 'input.allow_null', function () {
7370        PMA_validateDefaultValue($(this));
7371    });
7372    $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () {
7373        PMA_hideShowConnection($(this));
7374    });
7375});
7376
7377/**
7378 * If the chosen storage engine is FEDERATED show connection field. Hide otherwise
7379 *
7380 * @param $engine_selector storage engine selector
7381 */
7382function PMA_hideShowConnection($engine_selector)
7383{
7384    var $connection = $('.create_table_form input[name=connection]');
7385    var index = $connection.parent('td').index() + 1;
7386    var $labelTh = $connection.parents('tr').prev('tr').children('th:nth-child(' + index + ')');
7387    if ($engine_selector.val() != 'FEDERATED') {
7388        $connection
7389            .prop('disabled', true)
7390            .parent('td').hide();
7391        $labelTh.hide();
7392    } else {
7393        $connection
7394            .prop('disabled', false)
7395            .parent('td').show();
7396        $labelTh.show();
7397    }
7398}
7399
7400/**
7401 * If the column does not allow NULL values, makes sure that default is not NULL
7402 */
7403function PMA_validateDefaultValue($null_checkbox)
7404{
7405    if (! $null_checkbox.prop('checked')) {
7406        var $default = $null_checkbox.closest('tr').find('.default_t
7406ype');
7407        if ($default.val() == 'NULL') {
7408            $default.val('NONE');
7409        }
7410    }
7411}
7412
7413/**
7414 * function to populate the input fields on picking a column from central list
7415 *
7416 * @param string  input_id input id of the name field for the column to be populated
7417 * @param integer offset of the selected column in central list of columns
7418 */
7419function autoPopulate(input_id, offset)
7420{
7421    var db = PMA_commonParams.get('db');
7422    var table = PMA_commonParams.get('table');
7423    input_id = input_id.substring(0, input_id.length - 1);
7424    $('#' + input_id + '1').val(central_column_list[db + '_' + table][offset].col_name);
7425    var col_type = central_column_list[db + '_' + table][offset].col_type.toUpperCase();
7426    $('#' + input_id + '2').val(col_type);
7427    var $input3 = $('#' + input_id + '3');
7428    $input3.val(central_column_list[db + '_' + table][offset].col_length);
7429    if(col_type === 'ENUM' || col_type === 'SET') {
7430        $input3.next().show();
7431    } else {
7432        $input3.next().hide();
7433    }
7434    var col_default = central_column_list[db + '_' + table][offset].col_default.toUpperCase();
7435    var $input4 = $('#' + input_id + '4');
7436    if (col_default !== '' && col_default !== 'NULL' && col_default !== 'CURRENT_TIMESTAMP') {
7437        $input4.val("USER_DEFINED");
7438        $input4.next().next().show();
7439        $input4.next().next().val(central_column_list[db + '_' + table][offset].col_default);
7440    } else {
7441        $input4.val(central_column_list[db + '_' + table][offset].col_default);
7442        $input4.next().next().hide();
7443    }
7444    $('#' + input_id + '5').val(central_column_list[db + '_' + table][offset].col_collation);
7445    var $input6 = $('#' + input_id + '6');
7446    $input6.val(central_column_list[db + '_' + table][offset].col_attribute);
7447    if(central_column_list[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') {
7448        $input6.val(central_column_list[db + '_' + table][offset].col_extra);
7449    }
7450    if(central_column_list[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') {
7451        $('#' + input_id + '9').prop("checked",true).change();
7452    } else {
7453        $('#' + input_id + '9').prop("checked",false);
7454    }
7455    if(central_column_list[db + '_' + table][offset].col_isNull !== '0') {
7456        $('#' + input_id + '7').prop("checked",true);
7457    } else {
7458        $('#' + input_id + '7').prop("checked",false);
7459    }
7460}
7461
7462/**
7463 * Unbind all event handlers before tearing down a page
7464 */
7465AJAX.registerTeardown('functions.js', function () {
7466    $(document).off('click', "a.open_enum_editor");
7467    $(document).off('click', "input.add_value");
7468    $(document).off('click', "#enum_editor td.drop");
7469    $(document).off('click', 'a.central_columns_dialog');
7470});
7471/**
7472 * @var $enum_editor_dialog An object that points to the jQuery
7473 *                          dialog of the ENUM/SET editor
7474 */
7475var $enum_editor_dialog = null;
7476/**
7477 * Opens the ENUM/SET editor and controls its functions
7478 */
7479AJAX.registerOnload('functions.js', function () {
7480    $(document).on('click', "a.open_enum_editor", function () {
7481        // Get the name of the column that is being edited
7482        var colname = $(this).closest('tr').find('input:first').val();
7483        var title;
7484        var i;
7485        // And use it to make up a title for the page
7486        if (colname.length < 1) {
7487            title = PMA_messages.enum_newColumnVals;
7488        } else {
7489            title = PMA_messages.enum_columnVals.replace(
7490                /%s/,
7491                '"' + escapeHtml(decodeURIComponent(colname)) + '"'
7492            );
7493        }
7494        // Get the values as a string
7495        var inputstring = $(this)
7496            .closest('td')
7497            .find("input")
7498            .val();
7499        // Escape html entities
7500        inputstring = $('<div/>')
7501            .text(inputstring)
7502            .html();
7503        // Parse the values, escaping quotes and
7504        // slashes on the fly, into an array
7505        var values = [];
7506        var in_string = false;
7507        var curr, next, buffer = '';
7508        for (i = 0; i < inputstring.length; i++) {
7509            curr = inputstring.charAt(i);
7510            next = i == inputstring.length ? '' : inputstring.charAt(i + 1);
7511            if (! in_string && curr == "'") {
7512                in_string = true;
7513            } else if (in_string && curr == "\\" && next == "\\") {
7514                buffer += "&#92;";
7515                i++;
7516            } else if (in_string && next == "'" && (curr == "'" || curr == "\\")) {
7517                buffer += "&#39;";
7518                i++;
7519            } else if (in_string && curr == "'") {
7520                in_string = false;
7521                values.push(buffer);
7522                buffer = '';
7523            } else if (in_string) {
7524                buffer += curr;
7525            }
7526        }
7527        if (buffer.length > 0) {
7528            // The leftovers in the buffer are the last value (if any)
7529            values.push(buffer);
7530        }
7531        var fields = '';
7532        // If there are no values, maybe the user is about to make a
7533        // new list so we add a few for him/her to get started with.
7534        if (values.length === 0) {
7535            values.push('', '', '', '');
7536        }
7537        // Add the parsed values to the editor
7538        var drop_icon = PMA_getImage('b_drop.png');
7539        for (i = 0; i < values.length; i++) {
7540            fields += "<tr><td>" +
7541                   "<input type='text' value='" + values[i] + "'/>" +
7542                   "</td><td class='drop'>" +
7543                   drop_icon +
7544                   "</td></tr>";
7545        }
7546        /**
7547         * @var dialog HTML code for the ENUM/SET dialog
7548         */
7549        var dialog = "<div id='enum_editor'>" +
7550                   "<fieldset>" +
7551                    "<legend>" + title + "</legend>" +
7552                    "<p>" + PMA_getImage('s_notice.png') +
7553                    PMA_messages.enum_hint + "</p>" +
7554                    "<table class='values'>" + fields + "</table>" +
7555                    "</fieldset><fieldset class='tblFooters'>" +
7556                    "<table class='add'><tr><td>" +
7557                    "<div class='slider'></div>" +
7558                    "</td><td>" +
7559                    "<form><div><input type='submit' class='add_value' value='" +
7560                    PMA_sprintf(PMA_messages.enum_addValue, 1) +
7561                    "'/></div></form>" +
7562                    "</td></tr></table>" +
7563                    "<input type='hidden' value='" + // So we know which column's data is being edited
7564                    $(this).closest('td').find("input").attr("id") +
7565                    "' />" +
7566                    "</fieldset>" +
7567                    "</div>";
7568        /**
7569         * @var  Defines functions to be called when the buttons in
7570         * the buttonOptions jQuery dialog bar are pressed
7571         */
7572        var buttonOptions = {};
7573        buttonOptions[PMA_messages.strGo] = function () {
7574            // When the submit button is clicked,
7575            // put the data back into the original form
7576            var value_array = [];
7577            $(this).find(".values input").each(function (index, elm) {
7578                var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, "''");
7579                value_array.push("'" + val + "'");
7580            });
7581            // get the Length/Values text field where this value belongs
7582            var values_id = $(this).find("input[type='hidden']").val();
7583            $("input#" + values_id).val(value_array.join(","));
7584            $(this).dialog("close");
7585        };
7586        buttonOptions[PMA_messages.strClose] = function () {
7587            $(this).dialog("close");
7588        };
7589        // Show the dialog
7590        var width = parseInt(
7591            (parseInt($('html').css('font-size'), 10) / 13) * 340,
7592            10
7593        );
7594        if (! width) {
7595            width = 340;
7596        }
7597        $enum_editor_dialog = $(dialog).dialog({
7598            minWidth: width,
7599            maxHeight: 450,
7600            modal: true,
7601            title: PMA_messages.enum_editor,
7602            buttons: buttonOptions,
7603            open: function () {
7604                // Focus the "Go" button after opening the dialog
7605                $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
7606            },
7607            close: function () {
7608                $(this).remove();
7609            }
7610        });
7611        // slider for choosing how many fields to add
7612        $enum_editor_dialog.find(".slider").slider({
7613            animate: true,
7614            range: "min",
7615            value: 1,
7616            min: 1,
7617            max: 9,
7618            slide: function (event, ui) {
7619                $(this).closest('table').find('input[type=submit]').val(
7620                    PMA_sprintf(PMA_messages.enum_addValue, ui.value)
7621                );
7622            }
7623        });
7624        // Focus the slider, otherwise it looks nearly transparent
7625        $('a.ui-slider-handle').addClass('ui-state-focus');
7626        return false;
7627    });
7628
7629    $(document).on('click', 'a.central_columns_dialog', function (e) {
7630        var href = "db_central_columns.php";
7631        var db = PMA_commonParams.get('db');
7632        var table = PMA_commonParams.get('table');
7633        var maxRows = $(this).data('maxrows');
7634        var pick = $(this).data('pick');
7635        if (pick !== false) {
7636            pick = true;
7637        }
7638        var params = {
7639            'ajax_request' : true,
7640            'token' : PMA_commonParams.get('token'),
7641            'server' : PMA_commonParams.get('server'),
7642            'db' : PMA_commonParams.get('db'),
7643            'cur_table' : PMA_commonParams.get('table'),
7644            'getColumnList':true
7645        };
7646        var colid = $(this).closest('td').find("input").attr("id");
7647        var fields = '';
7648        if (! (db + '_' + table in central_column_list)) {
7649            central_column_list.push(db + '_' + table);
7650            $.ajax({
7651                type: 'POST',
7652                url: href,
7653                data: params,
7654                success: function (data) {
7655                    central_column_list[db + '_' + table] = $.parseJSON(data.message);
7656                },
7657                async:false
7658            });
7659        }
7660        var i = 0;
7661        var list_size = central_column_list[db + '_' + table].length;
7662        var min = (list_size <= maxRows) ? list_size : maxRows;
7663        for (i = 0; i < min; i++) {
7664
7665            fields += '<tr><td><div><span style="font-weight:bold">' +
7666                escapeHtml(central_column_list[db + '_' + table][i].col_name) +
7667                '</span><br><span style="color:gray">' + central_column_list[db + '_' + table][i].col_type;
7668
7669            if (central_column_list[db + '_' + table][i].col_attribute !== '') {
7670                fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_attribute) + ') ';
7671            }
7672            if (central_column_list[db + '_' + table][i].col_length !== '') {
7673                fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_length) +') ';
7674            }
7675            fields += escapeHtml(central_column_list[db + '_' + table][i].col_extra) + '</span>' +
7676                '</div></td>';
7677            if (pick) {
7678                fields += '<td><input class="pick" style="width:100%" type="submit" value="' +
7679                    PMA_messages.pickColumn + '" onclick="autoPopulate(\'' + colid + '\',' + i + ')"/></td>';
7680            }
7681            fields += '</tr>';
7682        }
7683        var result_pointer = i;
7684        var search_in = '<input type="text" class="filter_rows" placeholder="' + PMA_messages.searchList + '">';
7685        if (fields === '') {
7686            fields = PMA_sprintf(PMA_messages.strEmptyCentralList, "'" + escapeHtml(db) + "'");
7687            search_in = '';
7688        }
7689        var seeMore = '';
7690        if (list_size > maxRows) {
7691            seeMore = "<fieldset class='tblFooters' style='text-align:center;font-weight:bold'>" +
7692                "<a href='#' id='seeMore'>" + PMA_messages.seeMore + "</a></fieldset>";
7693        }
7694        var central_columns_dialog = "<div style='max-height:400px'>" +
7695            "<fieldset>" +
7696            search_in +
7697            "<table id='col_list' style='width:100%' class='values'>" + fields + "</table>" +
7698            "</fieldset>" +
7699            seeMore +
7700            "</div>";
7701
7702        var width = parseInt(
7703            (parseInt($('html').css('font-size'), 10) / 13) * 500,
7704            10
7705        );
7706        if (! width) {
7707            width = 500;
7708        }
7709        var buttonOptions = {};
7710        var $central_columns_dialog = $(central_columns_dialog).dialog({
7711            minWidth: width,
7712            maxHeight: 450,
7713            modal: true,
7714            title: PMA_messages.pickColumnTitle,
7715            buttons: buttonOptions,
7716            open: function () {
7717                $('#col_list').on("click", ".pick", function (){
7718                    $central_columns_dialog.remove();
7719                });
7720                $(".filter_rows").on("keyup", function () {
7721                    $.uiTableFilter($("#col_list"), $(this).val());
7722                });
7723                $("#seeMore").click(function() {
7724                    fields = '';
7725                    min = (list_size <= maxRows + result_pointer) ? list_size : maxR
7725ows + result_pointer;
7726                    for (i = result_pointer; i < min; i++) {
7727
7728                        fields += '<tr><td><div><span style="font-weight:bold">' +
7729                            central_column_list[db + '_' + table][i].col_name +
7730                            '</span><br><span style="color:gray">' +
7731                            central_column_list[db + '_' + table][i].col_type;
7732
7733                        if (central_column_list[db + '_' + table][i].col_attribute !== '') {
7734                            fields += '(' + central_column_list[db + '_' + table][i].col_attribute + ') ';
7735                        }
7736                        if (central_column_list[db + '_' + table][i].col_length !== '') {
7737                            fields += '(' + central_column_list[db + '_' + table][i].col_length + ') ';
7738                        }
7739                        fields += central_column_list[db + '_' + table][i].col_extra + '</span>' +
7740                            '</div></td>';
7741                        if (pick) {
7742                            fields += '<td><input class="pick" style="width:100%" type="submit" value="' +
7743                                PMA_messages.pickColumn + '" onclick="autoPopulate(\'' + colid + '\',' + i + ')"/></td>';
7744                        }
7745                        fields += '</tr>';
7746                    }
7747                    $("#col_list").append(fields);
7748                    result_pointer = i;
7749                    if (result_pointer === list_size) {
7750                        $('.tblFooters').hide();
7751                    }
7752                    return false;
7753                });
7754                $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
7755            },
7756            close: function () {
7757                $('#col_list').off("click", ".pick");
7758                $(".filter_rows").off("keyup");
7759                $(this).remove();
7760            }
7761        });
7762        return false;
7763    });
7764
7765   // $(document).on('click', 'a.show_central_list',function(e) {
7766
7767   // });
7768    // When "add a new value" is clicked, append an empty text field
7769    $(document).on('click', "input.add_value", function (e) {
7770        e.preventDefault();
7771        var num_new_rows = $enum_editor_dialog.find("div.slider").slider('value');
7772        while (num_new_rows--) {
7773            $enum_editor_dialog.find('.values')
7774                .append(
7775                    "<tr style='display: none;'><td>" +
7776                    "<input type='text' />" +
7777                    "</td><td class='drop'>" +
7778                    PMA_getImage('b_drop.png') +
7779                    "</td></tr>"
7780                )
7781                .find('tr:last')
7782                .show('fast');
7783        }
7784    });
7785
7786    // Removes the specified row from the enum editor
7787    $(document).on('click', "#enum_editor td.drop", function () {
7788        $(this).closest('tr').hide('fast', function () {
7789            $(this).remove();
7790        });
7791    });
7792});
7793
7794/**
7795 * Ensures indexes names are valid according to their type and, for a primary
7796 * key, lock index name to 'PRIMARY'
7797 * @param string   form_id  Variable which parses the form name as
7798 *                            the input
7799 * @return boolean  false    if there is no index form, true else
7800 */
7801function checkIndexName(form_id)
7802{
7803    if ($("#" + form_id).length === 0) {
7804        return false;
7805    }
7806
7807    // Gets the elements pointers
7808    var $the_idx_name = $("#input_index_name");
7809    var $the_idx_choice = $("#select_index_choice");
7810
7811    // Index is a primary key
7812    if ($the_idx_choice.find("option:selected").val() == 'PRIMARY') {
7813        $the_idx_name.val('PRIMARY');
7814        $the_idx_name.prop("disabled", true);
7815    }
7816
7817    // Other cases
7818    else {
7819        if ($the_idx_name.val() == 'PRIMARY') {
7820            $the_idx_name.val("");
7821        }
7822        $the_idx_name.prop("disabled", false);
7823    }
7824
7825    return true;
7826} // end of the 'checkIndexName()' function
7827
7828AJAX.registerTeardown('functions.js', function () {
7829    $(document).off('click', '#index_frm input[type=submit]');
7830});
7831AJAX.registerOnload('functions.js', function () {
7832    /**
7833     * Handler for adding more columns to an index in the editor
7834     */
7835    $(document).on('click', '#index_frm input[type=submit]', function (event) {
7836        event.preventDefault();
7837        var rows_to_add = $(this)
7838            .closest('fieldset')
7839            .find('.slider')
7840            .slider('value');
7841
7842        var tempEmptyVal = function () {
7843            $(this).val('');
7844        };
7845
7846        var tempSetFocus = function () {
7847            if ($(this).find("option:selected").val() === '') {
7848                return true;
7849            }
7850            $(this).closest("tr").find("input").focus();
7851        };
7852
7853        while (rows_to_add--) {
7854            var $indexColumns = $('#index_columns');
7855            var $newrow = $indexColumns
7856                .find('tbody > tr:first')
7857                .clone()
7858                .appendTo(
7859                    $indexColumns.find('tbody')
7860                );
7861            $newrow.find(':input').each(tempEmptyVal);
7862            // focus index size input on column picked
7863            $newrow.find('select').change(tempSetFocus);
7864        }
7865    });
7866});
7867
7868function indexEditorDialog(url, title, callback_success, callback_failure)
7869{
7870    /*Remove the hidden dialogs if there are*/
7871    var $editIndexDialog = $('#edit_index_dialog');
7872    if ($editIndexDialog.length !== 0) {
7873        $editIndexDialog.remove();
7874    }
7875    var $div = $('<div id="edit_index_dialog"></div>');
7876
7877    /**
7878     * @var button_options Object that stores the options
7879     *                     passed to jQueryUI dialog
7880     */
7881    var button_options = {};
7882    button_options[PMA_messages.strGo] = function () {
7883        /**
7884         * @var    the_form    object referring to the export form
7885         */
7886        var $form = $("#index_frm");
7887        var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
7888        PMA_prepareForAjaxRequest($form);
7889        //User wants to submit the form
7890        $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
7891            var $sqlqueryresults = $(".sqlqueryresults");
7892            if ($sqlqueryresults.length !== 0) {
7893                $sqlqueryresults.remove();
7894            }
7895            if (typeof data !== 'undefined' && data.success === true) {
7896                PMA_ajaxShowMessage(data.message);
7897                var $resultQuery = $('.result_query');
7898                if ($resultQuery.length) {
7899                    $resultQuery.remove();
7900                }
7901                if (data.sql_query) {
7902                    $('<div class="result_query"></div>')
7903                        .html(data.sql_query)
7904                        .prependTo('#page_content');
7905                    PMA_highlightSQL($('#page_content'));
7906                }
7907                $(".result_query .notice").remove();
7908                $resultQuery.prepend(data.message);
7909                /*Reload the field form*/
7910                $("#table_index").remove();
7911                $("<div id='temp_div'><div>")
7912                    .append(data.index_table)
7913                    .find("#table_index")
7914                    .insertAfter("#index_header");
7915                var $editIndexDialog = $("#edit_index_dialog");
7916                if ($editIndexDialog.length > 0) {
7917                    $editIndexDialog.dialog("close");
7918                }
7919                $('div.no_indexes_defined').hide();
7920                if (callback_success) {
7921                    callback_success();
7922                }
7923                PMA_reloadNavigation();
7924            } else {
7925                var $temp_div = $("<div id='temp_div'><div>").append(data.error);
7926                var $error;
7927                if ($temp_div.find(".error code").length !== 0) {
7928                    $error = $temp_div.find(".error code").addClass("error");
7929                } else {
7930                    $error = $temp_div;
7931                }
7932                if (callback_failure) {
7933                    callback_failure();
7934                }
7935                PMA_ajaxShowMessage($error, false);
7936            }
7937        }); // end $.post()
7938    };
7939    button_options[PMA_messages.strPreviewSQL] = function () {
7940        // Function for Previewing SQL
7941        var $form = $('#index_frm');
7942        PMA_previewSQL($form);
7943    };
7944    button_options[PMA_messages.strCancel] = function () {
7945        $(this).dialog('close');
7946    };
7947    var $msgbox = PMA_ajaxShowMessage();
7948    $.get("tbl_indexes.php", url, function (data) {
7949        if (typeof data !== 'undefined' && data.success === false) {
7950            //in the case of an error, show the error message returned.
7951            PMA_ajaxShowMessage(data.error, false);
7952        } else {
7953            PMA_ajaxRemoveMessage($msgbox);
7954            // Show dialog if the request was successful
7955            $div
7956            .append(data.message)
7957            .dialog({
7958                title: title,
7959                width: 450,
7960                height: 350,
7961                open: PMA_verifyColumnsProperties,
7962                modal: true,
7963                buttons: button_options,
7964                close: function () {
7965                    $(this).remove();
7966                }
7967            });
7968            $div.find('.tblFooters').remove();
7969            showIndexEditDialog($div);
7970        }
7971    }); // end $.get()
7972}
7973
7974function showIndexEditDialog($outer)
7975{
7976    checkIndexType();
7977    checkIndexName("index_frm");
7978    var $indexColumns = $('#index_columns');
7979    $indexColumns.find('td').each(function () {
7980        $(this).css("width", $(this).width() + 'px');
7981    });
7982    $indexColumns.find('tbody').sortable({
7983        axis: 'y',
7984        containment: $indexColumns.find("tbody"),
7985        tolerance: 'pointer'
7986    });
7987    PMA_showHints($outer);
7988    PMA_init_slider();
7989    // Add a slider for selecting how many columns to add to the index
7990    $outer.find('.slider').slider({
7991        animate: true,
7992        value: 1,
7993        min: 1,
7994        max: 16,
7995        slide: function (event, ui) {
7996            $(this).closest('fieldset').find('input[type=submit]').val(
7997                PMA_sprintf(PMA_messages.strAddToIndex, ui.value)
7998            );
7999        }
8000    });
8001    $('div.add_fields').removeClass('hide');
8002    // focus index size input on column picked
8003    $outer.find('table#index_columns select').change(function () {
8004        if ($(this).find("option:selected").val() === '') {
8005            return true;
8006        }
8007        $(this).closest("tr").find("input").focus();
8008    });
8009    // Focus the slider, otherwise it looks nearly transparent
8010    $('a.ui-slider-handle').addClass('ui-state-focus');
8011    // set focus on index name input, if empty
8012    var input = $outer.find('input#input_index_name');
8013    if (! input.val()) {
8014        input.focus();
8015    }
8016}
8017
8018/**
8019 * Function to display tooltips that were
8020 * generated on the PHP side by PMA\libraries\Util::showHint()
8021 *
8022 * @param object $div a div jquery object which specifies the
8023 *                    domain for searching for tooltips. If we
8024 *                    omit this parameter the function searches
8025 *                    in the whole body
8026 **/
8027function PMA_showHints($div)
8028{
8029    if ($div === undefined || ! $div instanceof jQuery || $div.length === 0) {
8030        $div = $("body");
8031    }
8032    $div.find('.pma_hint').each(function () {
8033        PMA_tooltip(
8034            $(this).children('img'),
8035            'img',
8036            $(this).children('span').html()
8037        );
8038    });
8039}
8040
8041AJAX.registerOnload('functions.js', function () {
8042    PMA_showHints();
8043});
8044
8045function PMA_mainMenuResizerCallback() {
8046    // 5 px margin for jumping menu in Chrome
8047    return $(document.body).width() - 5;
8048}
8049// This must be fired only once after the initial page load
8050$(function () {
8051    // Initialise the menu resize plugin
8052    $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
8053    // register resize event
8054    $(window).resize(function () {
8055        $('#topmenu').menuResizer('resize');
8056    });
8057});
8058
8059/**
8060 * Get the row number from the classlist (for example, row_1)
8061 */
8062function PMA_getRowNumber(classlist)
8063{
8064    return parseInt(classlist.split(/\s+row_/)[1], 10);
8065}
8066
8067/**
8068 * Changes status of slider
8069 */
8070function PMA_set_status_label($element)
8071{
8072    var text;
8073    if ($element.css('display') == 'none') {
8074        text = '+ ';
8075    } else {
8076        text = '- ';
8077    }
8078    $element.closest('.slide-wrapper').prev().find('span').text(text);
8079}
8080
8081/**
8082 * var  toggleButton  This is a function that creates a toggle
8083 *                    sliding button given a jQuery reference
8084 *                    to the correct DOM element
8085 */
8086var toggleButton = function ($obj) {
8087    // In rtl mode the toggle switch is flipped horizontally
8088    // so we need to take that into account
8089    var right;
8090    if ($('span.text_direction', $obj).text() == 'ltr') {
8091        right = 'right';
8092    } else {
8093        right = 'left';
8094    }
8095    /**
8096     *  var  h  Height of the button, used to scale the
8097     *          background image and position the layers
8098     */
8099    var h = $obj.height();
8100    $('img', $obj).height(h);
8101    $('table', $obj).css('bottom', h - 1);
8102    /**
8103     *  var  on   Width of the "ON" part of the toggle switch
8104     *  var  off  Width of the "OFF" part of the toggle switch
8105     */
8106    var on  = $('td.toggleOn', $obj).width();
8107    var off = $('td.toggleOff', $obj).width();
8108    // Make the "ON" and "OFF" parts of the switch the same size
8109    // + 2 pixels to avoid overflowed
8110    $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
8111    $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
8112    /**
8113     *  var  w  Width of the central part of the switch
8114     */
8115    var w = parseInt(($('img', $obj).height() / 16) * 22, 10);
8116    // Resize the central part of the switch on the top
8117    // layer to match the background
8118    $('table td:nth-child(2) > div', $obj).width(w);
8119    /**
8120     *  var  imgw    Width of the background image
8121     *  var  tblw    Width of the foreground layer
8122     *  var  offset  By how many pixels to move the background
8123     *               image, so that it matches the top layer
8124     */
8125    var imgw = $('img', $obj).width();
8126    var tblw = $('table', $obj).width();
8127    var offset = parseInt(((imgw - tblw) / 2), 10);
8128    // Move the background to match the layout of the top layer
8129    $obj.find('img').css(right, offset);
8130    /**
8131     *  var  offw    Outer width of the "ON" part of the toggle switch
8132     *  var  btnw    Outer width of the central part of the switch
8133     */
8134    var offw = $('td.toggleOff', $obj).outerWidth();
8135    var btnw = $('table td:nth-child(2)', $obj).outerWidth();
8136    // Resize the main div so that exactly one side of
8137    // the switch plus the central part fit into it.
8138    $obj.width(offw + btnw + 2);
8139    /**
8140     *  var  move  How many pixels to move the
8141     *             switch by when toggling
8142     */
8143    var move = $('td.toggleOff', $obj).outerWidth();
8144    // If the switch is initialized to the
8145    // OFF state we need to move it now.
8146    if ($('div.container', $obj).hasClass('off')) {
8147        if (right == 'right') {
8148            $('div.container', $obj).animate({'left': '-=' + move + 'px'}, 0);
8149        } else {
8150            $('div.container', $obj).animate({'left': '+=' + move + 'px'}, 0);
8151        }
8152    }
8153    // Attach an 'onclick' event to the switch
8154    $('div.container', $obj).click(function () {
8155        if ($(this).hasClass('isActive')) {
8156            return false;
8157        } else {
8158            $(this).addClass('isActive');
8159        }
8160        var $msg = PMA_ajaxShowMessage();
8161        var $container = $(this);
8162        var callback = $('span.callback', this).text();
8163        var operator, url, removeClass, addClass;
8164        // Perform the actual toggle
8165        if ($(this).hasClass('on')) {
8166            if (right == 'right') {
8167                operator = '-=';
8168            } else {
8169                operator = '+=';
8170            }
8171            url = $(this).find('td.toggleOff > span').text();
8172            removeClass = 'on';
8173            addClass = 'off';
8174        } else {
8175            if (right == 'right') {
8176                operator = '+=';
8177            } else {
8178                operator = '-=';
8179            }
8180            url = $(this).find('td.toggleOn > span').text();
8181            removeClass = 'off';
8182            addClass = 'on';
8183        }
8184        $.post(url, {'ajax_request': true}, function (data) {
8185            if (typeof data !== 'undefined' && data.success === true) {
8186                PMA_ajaxRemoveMessage($msg);
8187                $container
8188                .removeClass(removeClass)
8189                .addClass(addClass)
8190                .animate({'left': operator + move + 'px'}, function () {
8191                    $container.removeClass('isActive');
8192                });
8193                eval(callback);
8194            } else {
8195                PMA_ajaxShowMessage(data.error, false);
8196                $container.removeClass('isActive');
8197            }
8198        });
8199    });
8200};
8201
8202/**
8203 * Unbind all event handlers before tearing down a page
8204 */
8205AJAX.registerTeardown('functions.js', function () {
8206    $('div.container').unbind('click');
8207});
8208/**
8209 * Initialise all toggle buttons
8210 */
8211AJAX.registerOnload('functions.js', function () {
8212    $('div.toggleAjax').each(function () {
8213        var $button = $(this).show();
8214        $button.find('img').each(function () {
8215            if (this.complete) {
8216                toggleButton($button);
8217            } else {
8218                $(this).load(function () {
8219                    toggleButton($button);
8220                });
8221            }
8222        });
8223    });
8224});
8225
8226/**
8227 * Unbind all event handlers before tearing down a page
8228 */
8229AJAX.registerTeardown('functions.js', function () {
8230    $(document).off('change', 'select.pageselector');
8231    $(document).off('click', 'a.formLinkSubmit');
8232    $('#update_recent_tables').unbind('ready');
8233    $('#sync_favorite_tables').unbind('ready');
8234});
8235
8236AJAX.registerOnload('functions.js', function () {
8237
8238    /**
8239     * Autosubmit page selector
8240     */
8241    $(document).on('change', 'select.pageselector', function (event) {
8242        event.stopPropagation();
8243        // Check where to load the new content
8244        if ($(this).closest("#pma_navigation").length === 0) {
8245            // For the main page we don't need to do anything,
8246            $(this).closest("form").submit();
8247        } else {
8248            // but for the navigation we need to manually replace the content
8249            PMA_navigationTreePagination($(this));
8250        }
8251    });
8252
8253    /**
8254     * Load version information asynchronously.
8255     */
8256    if ($('li.jsversioncheck').length > 0) {
8257        $.ajax({
8258            dataType: "json",
8259            url: 'version_check.php',
8260            method: "POST",
8261            data: {
8262                "server": PMA_commonParams.get('server'),
8263                "token": PMA_commonParams.get('token'),
8264            },
8265            success: PMA_current_version
8266        });
8267    }
8268
8269    if ($('#is_git_revision').length > 0) {
8270        setTimeout(PMA_display_git_revision, 10);
8271    }
8272
8273    /**
8274     * Slider effect.
8275     */
8276    PMA_init_slider();
8277
8278    /**
8279     * Enables the text generated by PMA\libraries\Util::linkOrButton() to be clickable
8280     */
8281    $(document).on('click', 'a.formLinkSubmit', function (e) {
8282        if (! $(this).hasClass('requireConfirm')) {
8283            submitFormLink($(this));
8284            return false;
8285        }
8286    });
8287
8288    var $updateRecentTables = $('#update_recent_tables');
8289    if ($updateRecentTables.length) {
8290        $.get(
8291            $updateRecentTables.attr('href'),
8292            {no_debug: true},
8293            function (data) {
8294                if (typeof data !== 'undefined' && data.success === true) {
8295                    $('#pma_recent_list').html(data.list);
8296                }
8297            }
8298        );
8299    }
8300
8301    // Sync favorite tables from localStorage to pmadb.
8302    if ($('#sync_favorite_tables').length) {
8303        $.ajax({
8304            url: $('#sync_favorite_tables').attr("href"),
8305            cache: false,
8306            type: 'POST',
8307            data: {
8308                favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
8309                    ? window.localStorage.favorite_tables
8310                    : '',
8311                no_debug: true
8312            },
8313            success: function (data) {
8314                // Update localStorage.
8315                if (isStorageSupported('localStorage')) {
8316                    window.localStorage.favorite_tables = data.favorite_tables;
8317                }
8318                $('#pma_favorite_list').html(data.list);
8319            }
8320        });
8321    }
8322}); // end of $()
8323
8324/**
8325 * Submits the form placed in place of a link due to the excessive url length
8326 *
8327 * @param $link anchor
8328 * @returns {Boolean}
8329 */
8330function submitFormLink($link)
8331{
8332    if ($link.attr('href').indexOf('=') != -1) {
8333        var data = $link.attr('href').substr($link.attr('href').indexOf('#') + 1).split('=', 2);
8334        $link.parents('form').append('<input type="hidden" name="' + data[0] + '" value="' + data[1] + '"/>');
8335    }
8336    $link.parents('form').submit();
8337}
8338
8339/**
8340 * Initializes slider effect.
8341 */
8342function PMA_init_slider()
8343{
8344    $('div.pma_auto_slider').each(function () {
8345        var $this = $(this);
8346        if ($this.data('slider_init_done')) {
8347            return;
8348        }
8349        var $wrapper = $('<div>', {'class': 'slide-wrapper'});
8350        $wrapper.toggle($this.is(':visible'));
8351        $('<a>', {href: '#' + this.id, "class": 'ajax'})
8352            .text($this.attr('title'))
8353            .prepend($('<span>'))
8354            .insertBefore($this)
8355            .click(function () {
8356                var $wrapper = $this.closest('.slide-wrapper');
8357                var visible = $this.is(':visible');
8358                if (!visible) {
8359                    $wrapper.show();
8360                }
8361                $this[visible ? 'hide' : 'show']('blind', function () {
8362                    $wrapper.toggle(!visible);
8363                    $wrapper.parent().toggleClass("print_ignore", visible);
8364                    PMA_set_status_label($this);
8365                });
8366                return false;
8367            });
8368        $this.wrap($wrapper);
8369        $this.removeAttr('title');
8370        PMA_set_status_label($this);
8371        $this.data('slider_init_done', 1);
8372    });
8373}
8374
8375/**
8376 * Initializes slider effect.
8377 */
8378AJAX.registerOnload('functions.js', function () {
8379    PMA_init_slider();
8380});
8381
8382/**
8383 * Restores sliders to the state they were in before initialisation.
8384 */
8385AJAX.registerTeardown('functions.js', function () {
8386    $('div.pma_auto_slider').each(function () {
8387        var $this = $(this);
8388        $this.removeData();
8389        $this.parent().replaceWith($this);
8390        $this.parent().children('a').remove();
8391    });
8392});
8393
8394/**
8395 * Creates a message inside an object with a sliding effect
8396 *
8397 * @param msg    A string containing the text to display
8398 * @param $obj   a jQuery object containing the reference
8399 *                 to the element where to put the message
8400 *                 This is optional, if no element is
8401 *                 provided, one will be created below the
8402 *                 navigation links at the top of the page
8403 *
8404 * @return bool   True on success, false on failure
8405 */
8406function PMA_slidingMessage(msg, $obj)
8407{
8408    if (msg === undefined || msg.length === 0) {
8409        // Don't show an empty message
8410        return false;
8411    }
8412    if ($obj === undefined || ! $obj instanceof jQuery || $obj.length === 0) {
8413        // If the second argument was not supplied,
8414        // we might have to create a new DOM node.
8415        if ($('#PMA_slidingMessage').length === 0) {
8416            $('#page_content').prepend(
8417                '<span id="PMA_slidingMessage" ' +
8418                'style="display: inline-block;"></span>'
8419            );
8420        }
8421        $obj = $('#PMA_slidingMessage');
8422    }
8423    if ($obj.has('div').length > 0) {
8424        // If there already is a message inside the
8425        // target object, we must get rid of it
8426        $obj
8427        .find('div')
8428        .first()
8429        .fadeOut(function () {
8430            $obj
8431            .children()
8432            .remove();
8433            $obj
8434            .append('<div>' + msg + '</div>');
8435            // highlight any sql before taking height;
8436            PMA_highlightSQL($obj);
8437            $obj.find('div')
8438                .first()
8439                .hide();
8440            $obj
8441            .animate({
8442                height: $obj.find('div').first().height()
8443            })
8444            .find('div')
8445            .first()
8446            .fadeIn();
8447        });
8448    } else {
8449        // Object does not already have a message
8450        // inside it, so we simply slide it down
8451        $obj.width('100%')
8452            .html('<div>' + msg + '</div>');
8453        // highlight any sql before taking height;
8454        PMA_highlightSQL($obj);
8455        var h = $obj
8456            .find('div')
8457            .first()
8458            .hide()
8459            .height();
8460        $obj
8461        .find('div')
8462        .first()
8463        .css('height', 0)
8464        .show()
8465        .animate({
8466                height: h
8467            }, function () {
8468            // Set the height of the parent
8469            // to the height of the child
8470                $obj
8471                .height(
8472                    $obj
8473                    .find('div')
8474                    .first()
8475                    .height()
8476                );
8477            });
8478    }
8479    return true;
8480} // end PMA_slidingMessage()
8481
8482/**
8483 * Attach CodeMirror2 editor to SQL edit area.
8484 */
8485AJAX.registerOnload('functions.js', function () {
8486    var $elm = $('#sqlquery');
8487    if ($elm.length > 0) {
8488        if (typeof CodeMirror != 'undefined') {
8489            codemirror_editor = PMA_getSQLEditor($elm);
8490            codemirror_editor.focus();
8491            codemirror_editor.on("blur", updateQueryParameters);
8492        } else {
8493            // without codemirror
8494            $elm.focus()
8495                .bind('blur', updateQueryParameters);
8496        }
8497    }
8498    PMA_highlightSQL($('body'));
8499});
8500AJAX.registerTeardown('functions.js', function () {
8501    if (codemirror_editor) {
8502        $('#sqlquery').text(codemirror_editor.getValue());
8503        codemirror_editor.toTextArea();
8504        codemirror_editor = false;
8505    }
8506});
8507AJAX.registerOnload('functions.js', function () {
8508    // initializes all lock-page elements lock-id and
8509    // val-hash data property
8510    $('#page_content form.lock-page textarea, ' +
8511            '#page_content form.lock-page input[type="text"], '+
8512            '#page_content form.lock-page input[type="number"], '+
8513            '#page_content form.lock-page select').each(function (i) {
8514        $(this).data('lock-id', i);
8515        // val-hash is the hash of default value of the field
8516        // so that it can be compared with new value hash
8517        // to check whether field was modified or not.
8518        $(this).data('val-hash', AJAX.hash($(this).val()));
8519    });
8520
8521    // initializes lock-page elements (input types checkbox and radio buttons)
8522    // lock-id and val-hash data property
8523    $('#page_content form.lock-page input[type="checkbox"], ' +
8524            '#page_content form.lock-page input[type="radio"]').each(function (i) {
8525        $(this).data('lock-id', i);
8526        $(this).data('val-hash', AJAX.hash($(this).is(":checked")));
8527    });
8528});
8529
8530/**
8531 * jQuery plugin to correctly filter input fields by value, needed
8532 * because some nasty values may break selector syntax
8533 */
8534(function ($) {
8535    $.fn.filterByValue = function (value) {
8536        return this.filter(function () {
8537            return $(this).val() === value;
8538        });
8539    };
8540})(jQuery);
8541
8542/**
8543 * Return value of a cell in a table.
8544 */
8545function PMA_getCellValue(td) {
8546    var $td = $(td);
8547    if ($td.is('.null')) {
8548        return '';
8549    } else if ((! $td.is('.to_be_saved')
8550        || $td.is('.set'))
8551        && $td.data('original_data')
8552    ) {
8553        return $td.data('original_data');
8554    } else {
8555        return $td.text();
8556    }
8557}
8558
8559$(window).on('popstate', function (event, data) {
8560    $('#printcss').attr('media','print');
8561    return true;
8562});
8563
8564/**
8565 * Unbind all event handlers before tearing down a page
8566 */
8567AJAX.registerTeardown('functions.js', function () {
8568    $(document).off('click', 'a.themeselect');
8569    $(document).off('change', '.autosubmit');
8570    $('a.take_theme').unbind('click');
8571});
8572
8573AJAX.registerOnload('functions.js', function () {
8574    /**
8575     * Theme selector.
8576     */
8577    $(document).on('click', 'a.themeselect', function (e) {
8578        window.open(
8579            e.target,
8580            'themes',
8581            'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes'
8582            );
8583        return false;
8584    });
8585
8586    /**
8587     * Automatic form submission on change.
8588     */
8589    $(document).on('change', '.autosubmit', function (e) {
8590        $(this).closest('form').submit();
8591    });
8592
8593    /**
8594     * Theme changer.
8595     */
8596    $('a.take_theme').click(function (e) {
8597        var what = this.name;
8598        if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) {
8599            window.opener.document.forms.setTheme.elements.set_theme.value = what;
8600            window.opener.document.forms.setTheme.submit();
8601            window.close();
8602            return false;
8603        }
8604        return true;
8605    });
8606});
8607
8608/**
8609 * Produce print preview
8610 */
8611function printPreview()
8612{
8613    $('#printcss').attr('media','all');
8614    createPrintAndBackButtons();
8615}
8616
8617/**
8618 * Create print and back buttons in preview page
8619 */
8620function createPrintAndBackButtons() {
8621
8622    var back_button = $("<input/>",{
8623        type: 'button',
8624        value: PMA_messages.back,
8625        id: 'back_button_print_view'
8626    });
8627    back_button.click(removePrintAndBackButton);
8628    back_button.appendTo('#page_content');
8629    var print_button = $("<input/>",{
8630        type: 'button',
8631        value: PMA_messages.print,
8632        id: 'print_button_print_view'
8633    });
8634    print_button.click(printPage);
8635    print_button.appendTo('#page_content');
8636}
8637
8638/**
8639 * Remove print and back buttons and revert to normal view
8640 */
8641function removePrintAndBackButton(){
8642    $('#printcss').attr('media','print');
8643    $('#back_button_print_view').remove();
8644    $('#print_button_print_view').remove();
8645}
8646
8647/**
8648 * Print page
8649 */
8650function printPage(){
8651    if (typeof(window.print) != 'undefined') {
8652        window.print();
8653    }
8654}
8655
8656/**
8657 * Print button
8658 */
8659function copyToClipboard()
8660{
8661    var textArea = document.createElement("textarea");
8662
8663    //
8664    // *** This styling is an extra step which is likely not required. ***
8665    //
8666    // Why is it here? To ensure:
8667    // 1. the element is able to have focus and selection.
8668    // 2. if element was to flash render it has minimal visual impact.
8669    // 3. less flakyness with selection and copying which **might** occur if
8670    //    the textarea element is not visible.
8671    //
8672    // The likelihood is the element won't even render, not even a flash,
8673    // so some of these are just precautions. However in IE the element
8674    // is visible whilst the popup box asking the user for permission for
8675    // the web page to copy to the clipboard.
8676    //
8677
8678    // Place in top-left corner of screen regardless of scroll position.
8679    textArea.style.position = 'fixed';
8680    textArea.style.top = 0;
8681    textArea.style.left = 0;
8682
8683    // Ensure it has a small width and height. Setting to 1px / 1em
8684    // doesn't work as this gives a negative w/h on some browsers.
8685    textArea.style.width = '2em';
8686    textArea.style.height = '2em';
8687
8688    // We don't need padding, reducing the size if it does flash render.
8689    textArea.style.padding = 0;
8690
8691    // Clean up any borders.
8692    textArea.style.border = 'none';
8693    textArea.style.outline = 'none';
8694    textArea.style.boxShadow = 'none';
8695
8696    // Avoid flash of white box if rendered for any reason.
8697    textArea.style.background = 'transparent';
8698
8699    textArea.value = '';
8700
8701    var elementList = $('#serverinfo a');
8702
8703    elementList.each(function(){
8704        textArea.value += $(this).text().split(':')[1].trim() + '/';
8705    });
8706    textArea.value += '\t\t' + window.location.href;
8707    textArea.value += '\n';
8708
8709    elementList = $('.notice,.success');
8710
8711    elementList.each(function(){
8712        textArea.value += $(this).clone().children().remove().end().text() + '\n\n';
8713    });
8714
8715    elementList = $('.sql pre');
8716
8717    elementList.each(function() {
8718        textArea.value += $(this).text() + '\n\n';
8719    });
8720
8721    elementList = $('.table_results .column_heading a');
8722
8723    elementList.each(function() {
8724        textArea.value += $(this).clone().children().remove().end().text() + '\t';
8725    });
8726
8727    textArea.value += '\n';
8728    elementList = $('tbody .odd,tbody .even');
8729    elementList.each(function() {
8730        var childElementList = $(this).find('.data span');
8731        childElementList.each(function(){
8732            textArea.value += $(this).clone().children().remove().end().text() + '\t';
8733        });
8734        textArea.value += '\n';
8735    });
8736
8737    document.body.appendChild(textArea);
8738
8739    textArea.select();
8740
8741    try {
8742        document.execCommand('copy');
8743    } catch (err) {
8744        alert('Sorry! Unable to copy');
8745    }
8746
8747    document.body.removeChild(textArea);
8748}
8749
8750/**
8751 * Unbind all event handlers before tearing down a page
8752 */
8753AJAX.registerTeardown('functions.js', function () {
8754    $('input#print').unbind('click');
8755    $(document).off('click', 'a.create_view.ajax');
8756    $(document).off('keydown', '#createViewDialog input, #createViewDialog select');
8757    $(document).off('change', '#fkc_checkbox');
8758});
8759
8760AJAX.registerOnload('functions.js', function () {
8761    $('input#print').click(printPage);
8762    /**
8763     * Ajaxification for the "Create View" action
8764     */
8765    $(document).on('click', 'a.create_view.ajax', function (e) {
8766        e.preventDefault();
8767        PMA_createViewDialog($(this));
8768    });
8769    /**
8770     * Attach Ajax event handlers for input fields in the editor
8771     * and used to submit the Ajax request when the ENTER key is pressed.
8772     */
8773    if ($('#createViewDialog').length !== 0) {
8774        $(document).on('keydown', '#createViewDialog input, #createViewDialog select', function (e) {
8775            if (e.which === 13) { // 13 is the ENTER key
8776                e.preventDefault();
8777
8778                // with preventing default, selection by <select> tag
8779                // was also prevented in IE
8780                $(this).blur();
8781
8782                $(this).closest('.ui-dialog').find('.ui-button:first').click();
8783            }
8784        }); // end $(document).on()
8785    }
8786
8787    syntaxHighlighter = PMA_getSQLEditor($('textarea[name="view[as]"]'));
8788
8789});
8790
8791function PMA_createViewDialog($this)
8792{
8793    var $msg = PMA_ajaxShowMessage();
8794    var syntaxHighlighter = null;
8795    $.get($this.attr('href') + '&ajax_request=1&ajax_dialog=1', function (data) {
8796        if (typeof data !== 'undefined' && data.success === true) {
8797            PMA_ajaxRemoveMessage($msg);
8798            var buttonOptions = {};
8799            buttonOptions[PMA_messages.strGo] = function () {
8800                if (typeof CodeMirror !== 'undefined') {
8801                    syntaxHighlighter.save();
8802                }
8803                $msg = PMA_ajaxShowMessage();
8804                $.post('view_create.php', $('#createViewDialog').find('form').serialize(), function (data) {
8805                    PMA_ajaxRemoveMessage($msg);
8806                    if (typeof data !== 'undefined' && data.success === true) {
8807                        $('#createViewDialog').dialog("close");
8808                        $('.result_query').html(data.message);
8809                        PMA_reloadNavigation();
8810                    } else {
8811                        PMA_ajaxShowMessage(data.error, false);
8812                    }
8813                });
8814            };
8815            buttonOptions[PMA_messages.strClose] = function () {
8816                $(this).dialog("close");
8817            };
8818            var $dialog = $('<div/>').attr('id', 'createViewDialog').append(data.message).dialog({
8819                width: 600,
8820                minWidth: 400,
8821                modal: true,
8822                buttons: buttonOptions,
8823                title: PMA_messages.strCreateView,
8824                close: function () {
8825                    $(this).remove();
8826                }
8827            });
8828            // Attach syntax highlighted editor
8829            syntaxHighlighter = PMA_getSQLEditor($dialog.find('textarea'));
8830            $('input:visible[type=text]', $dialog).first().focus();
8831        } else {
8832            PMA_ajaxShowMessage(data.error);
8833        }
8834    });
8835}
8836
8837/**
8838 * Makes the breadcrumbs and the menu bar float at the top of the viewport
8839 */
8840$(function () {
8841    if ($("#floating_menubar").length && $('#PMA_disable_floating_menubar').length === 0) {
8842        var left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
8843        $("#floating_menubar")
8844            .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width())
8845            .css(left, 0)
8846            .css({
8847                'position': 'fixed',
8848                'top': 0,
8849                'width': '100%',
8850                'z-index': 99
8851            })
8852            .append($('#serverinfo'))
8853            .append($('#topmenucontainer'));
8854        // Allow the DOM to render, then adjust the padding on the body
8855        setTimeout(function () {
8856            $('body').css(
8857                'padding-top',
8858                $('#floating_menubar').outerHeight(true)
8859            );
8860            $('#topmenu').menuResizer('resize');
8861        }, 4);
8862    }
8863});
8864
8865/**
8866 * Scrolls the page to the top if clicking the serverinfo bar
8867 */
8868$(function () {
8869    $(document).delegate("#serverinfo, #goto_pagetop", "click", function (event) {
8870        event.preventDefault();
8871        $('html, body').animate({scrollTop: 0}, 'fast');
8872    });
8873});
8874
8875var checkboxes_sel = "input.checkall:checkbox:enabled";
8876/**
8877 * Watches checkboxes in a form to set the checkall box accordingly
8878 */
8879var checkboxes_changed = function () {
8880    var $form = $(this.form);
8881    // total number of checkboxes in current form
8882    var total_boxes = $form.find(checkboxes_sel).length;
8883    // number of checkboxes checked in current form
8884    var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
8885    var $checkall = $form.find("input.checkall_box");
8886    if (total_boxes == checked_boxes) {
8887        $checkall.prop({checked: true, indeterminate: false});
8888    }
8889    else if (checked_boxes > 0) {
8890        $checkall.prop({checked: true, indeterminate: true});
8891    }
8892    else {
8893        $checkall.prop({checked: false, indeterminate: false});
8894    }
8895};
8896$(document).on("change", checkboxes_sel, checkboxes_changed);
8897
8898$(document).on("change", "input.checkall_box", function () {
8899    var is_checked = $(this).is(":checked");
8900    $(this.form).find(checkboxes_sel).prop("checked", is_checked)
8901    .parents("tr").toggleClass("marked", is_checked);
8902});
8903
8904/**
8905 * Watches checkboxes in a sub form to set the sub checkall box accordingly
8906 */
8907var sub_checkboxes_changed = function () {
8908    var $form = $(this).parent().parent();
8909    // total number of checkboxes in current sub form
8910    var total_boxes = $form.find(checkboxes_sel).length;
8911    // number of checkboxes checked in current sub form
8912    var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
8913    var $checkall = $form.find("input.sub_checkall_box");
8914    if (total_boxes == checked_boxes) {
8915        $checkall.prop({checked: true, indeterminate: false});
8916    }
8917    else if (checked_boxes > 0) {
8918        $checkall.prop({checked: true, indeterminate: true});
8919    }
8920    else {
8921        $checkall.prop({checked: false, indeterminate: false});
8922    }
8923};
8924$(document).on("change", checkboxes_sel + ", input.checkall_box:checkbox:enabled", sub_checkboxes_changed);
8925
8926$(document).on("change", "input.sub_checkall_box", function () {
8927    var is_checked = $(this).is(":checked");
8928    var $form = $(this).parent().parent();
8929    $form.find(checkboxes_sel).prop("checked", is_checked)
8930    .parents("tr").toggleClass("marked", is_checked);
8931});
8932
8933/**
8934 * Toggles row colors of a set of 'tr' elements starting from a given element
8935 *
8936 * @param $start Starting element
8937 */
8938function toggleRowColors($start)
8939{
8940    for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) {
8941        if ($curr_row.hasClass('odd')) {
8942            $curr_row.removeClass('odd').addClass('even');
8943        } else if ($curr_row.hasClass('even')) {
8944            $curr_row.removeClass('even').addClass('odd');
8945        }
8946    }
8947}
8948
8949/**
8950 * Formats a byte number to human-readable form
8951 *
8952 * @param bytes the bytes to format
8953 * @param optional subdecimals the number of digits after the point
8954 * @param optional pointchar the char to use as decimal point
8955 */
8956function formatBytes(bytes, subdecimals, pointchar) {
8957    if (!subdecimals) {
8958        subdecimals = 0;
8959    }
8960    if (!pointchar) {
8961        pointchar = '.';
8962    }
8963    var units = ['B', 'KiB', 'MiB', 'GiB'];
8964    for (var i = 0; bytes > 1024 && i < units.length; i++) {
8965        bytes /= 1024;
8966    }
8967    var factor = Math.pow(10, subdecimals);
8968    bytes = Math.round(bytes * factor) / factor;
8969    bytes = bytes.toString().split('.').join(pointchar);
8970    return bytes + ' ' + units[i];
8971}
8972
8973AJAX.registerOnload('functions.js', function () {
8974    /**
8975     * Reveal the login form to users with JS enabled
8976     * and focus the appropriate input field
8977     */
8978    var $loginform = $('#loginform');
8979    if ($loginform.length) {
8980        $loginform.find('.js-show').show();
8981        if ($('#input_username').val()) {
8982            $('#input_password').focus();
8983        } else {
8984            $('#input_username').focus();
8985        }
8986    }
8987});
8988
8989/**
8990 * Dynamically adjust the width of the boxes
8991 * on the table and db operations pages
8992 */
8993(function () {
8994    function DynamicBoxes() {
8995        var $boxContainer = $('#boxContainer');
8996        if ($boxContainer.length) {
8997            var minWidth = $boxContainer.data('box-width');
8998            var viewport = $(window).width() - $('#pma_navigation').width();
8999            var slots = Math.floor(viewport / minWidth);
9000            $boxContainer.children()
9001            .each(function () {
9002                if (viewport < minWidth) {
9003                    $(this).width(minWidth);
9004                } else {
9005                    $(this).css('width', ((1 /  slots) * 100) + "%");
9006                }
9007            })
9008            .removeClass('clearfloat')
9009            .filter(':nth-child(' + slots + 'n+1)')
9010            .addClass('clearfloat');
9011        }
9012    }
9013    AJAX.registerOnload('functions.js', function () {
9014        DynamicBoxes();
9015    });
9016    $(function () {
9017        $(window).resize(DynamicBoxes);
9018    });
9019})();
9020
9021/**
9022 * Formats timestamp for display
9023 */
9024function PMA_formatDateTime(date, seconds) {
9025    var result = $.datepicker.formatDate('yy-mm-dd', date);
9026    var timefmt = 'HH:mm';
9027    if (seconds) {
9028        timefmt = 'HH:mm:ss';
9029    }
9030    return result + ' ' + $.datepicker.formatTime(
9031        timefmt, {
9032            hour: date.getHours(),
9033            minute: date.getMinutes(),
9034            second: date.getSeconds()
9035        }
9036    );
9037}
9038
9039/**
9040 * Check than forms have less fields than max allowed by PHP.
9041 */
9042function checkNumberOfFields() {
9043    if (typeof maxInputVars === 'undefined') {
9044        return false;
9045    }
9046    if (false === maxInputVars) {
9047        return false;
9048    }
9049    $('form').each(function() {
9050        var nbInputs = $(this).find(':input').length;
9051        if (nbInputs > maxInputVars) {
9052            var warning = PMA_sprintf(PMA_messages.strTooManyInputs, maxInputVars);
9053            PMA_ajaxShowMessage(warning);
9054            return false;
9055        }
9056        return true;
9057    });
9058
9059    return true;
9060}
9061
9062/**
9063 * Ignore the displayed php errors.
9064 * Simply removes the displayed errors.
9065 *
9066 * @param  clearPrevErrors whether to clear errors stored
9067 *             in $_SESSION['prev_errors'] at server
9068 *
9069 */
9070function PMA_ignorePhpErrors(clearPrevErrors){
9071    if (typeof(clearPrevErrors) === "undefined" ||
9072        clearPrevErrors === null
9073    ) {
9074        str = false;
9075    }
9076    // send AJAX request to error_report.php with send_error_report=0, exception_type=php & token.
9077    // It clears the prev_errors stored in session.
9078    if(clearPrevErrors){
9079        var $pmaReportErrorsForm = $('#pma_report_errors_form');
9080        $pmaReportErrorsForm.find('input[name="send_error_report"]').val(0); // change send_error_report to '0'
9081        $pmaReportErrorsForm.submit();
9082    }
9083
9084    // remove displayed errors
9085    var $pmaErrors = $('#pma_errors');
9086    $pmaErrors.fadeOut( "slow");
9087    $pmaErrors.remove();
9088}
9089
9090/**
9091 * Toggle the Datetimepicker UI if the date value entered
9092 * by the user in the 'text box' is not going to be accepted
9093 * by the Datetimepicker plugin (but is accepted by MySQL)
9094 */
9095function toggleDatepickerIfInvalid($td, $input_field) {
9096    // Regex allowed by the Datetimepicker UI
9097    var dtexpDate = new RegExp(['^([0-9]{4})',
9098        '-(((01|03|05|07|08|10|12)-((0[1-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)',
9099        '-((0[1-9])|([1-2][0-9])|30)))$'].join(''));
9100    var dtexpTime = new RegExp(['^(([0-1][0-9])|(2[0-3]))',
9101        ':((0[0-9])|([1-5][0-9]))',
9102        ':((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$'].join(''));
9103
9104    // If key-ed in Time or Date values are unsupported by the UI, close it
9105    if ($td.attr('data-type') === 'date' && ! dtexpDate.test($input_field.val())) {
9106        $input_field.datepicker('hide');
9107    } else if ($td.attr('data-type') === 'time' && ! dtexpTime.test($input_field.val())) {
9108        $input_field.datepicker('hide');
9109    } else {
9110        $input_field.datepicker('show');
9111    }
9112}
9113
9114/**
9115 * Unbind all event handlers before tearing down a page
9116 */
9117AJAX.registerTeardown('functions.js', function(){
9118    $(document).off('keydown', 'form input, form textarea, form select');
9119});
9120
9121AJAX.registerOnload('functions.js', function () {
9122    /**
9123     * Handle 'Ctrl/Alt + Enter' form submits
9124     */
9125    $('form input, form textarea, form select').on('keydown', function(e){
9126        if((e.ctrlKey && e.which == 13) || (e.altKey && e.which == 13)) {
9127            $form = $(this).closest('form');
9128            if (! $form.find('input[type="submit"]') ||
9129                ! $form.find('input[type="submit"]').click()
9130            ) {
9131                $form.submit();
9132            }
9133        }
9134    });
9135});
9136
9137/**
9138 * Unbind all event handlers before tearing down a page
9139 */
9140AJAX.registerTeardown('functions.js', function(){
9141    $(document).off('change', 'input[type=radio][name="pw_hash"]');
9142});
9143
9144AJAX.registerOnload('functions.js', function(){
9145    /*
9146     * Display warning regarding SSL when sha256_password
9147     * method is selected
9148     * Used in user_password.php (Change Password link on index.php)
9149     */
9150    $(document).on("change", 'select#select_authentication_plugin_cp', function() {
9151        if (this.value === 'sha256_password') {
9152            $('#ssl_reqd_warning_cp').show();
9153        } else {
9154            $('#ssl_reqd_warning_cp').hide();
9155        }
9156    });
9157});
9158;
9159
9160/* vim: set expandtab sw=4 ts=4 sts=4: */
9161/**
9162 * function used in or for navigation panel
9163 *
9164 * @package phpMyAdmin-Navigation
9165 */
9166
9167/**
9168 * updates the tree state in sessionStorage
9169 *
9170 * @returns void
9171 */
9172function navTreeStateUpdate() {
9173    // update if session storage is supported
9174    if (isStorageSupported('sessionStorage')) {
9175        var storage = window.sessionStorage;
9176        // try catch necessary here to detect whether
9177        // content to be stored exceeds storage capacity
9178        try {
9179            storage.setItem('navTreePaths', JSON.stringify(traverseNavigationForPaths()));
9180            storage.setItem('server', PMA_commonParams.get('server'));
9181            storage.setItem('token', PMA_commonParams.get('token'));
9182        } catch(error) {
9183            // storage capacity exceeded & old navigation tree
9184            // state is no more valid, so remove it
9185            storage.removeItem('navTreePaths');
9186            storage.removeItem('server');
9187            storage.removeItem('token');
9188        }
9189    }
9190}
9191
9192/**
9193 * Loads child items of a node and executes a given callback
9194 *
9195 * @param isNode
9196 * @param $expandElem expander
9197 * @param callback    callback function
9198 *
9199 * @returns void
9200 */
9201function loadChildNodes(isNode, $expandElem, callback) {
9202
9203    var $destination = null;
9204    var params = null;
9205
9206    if (isNode) {
9207        if (!$expandElem.hasClass('expander')) {
9208            return;
9209        }
9210        $destination = $expandElem.closest('li');
9211        params = {
9212            aPath: $expandElem.find('span.aPath').text(),
9213            vPath: $expandElem.find('span.vPath').text(),
9214            pos: $expandElem.find('span.pos').text(),
9215            pos2_name: $expandElem.find('span.pos2_name').text(),
9216            pos2_value: $expandElem.find('span.pos2_value').text(),
9217            searchClause: '',
9218            searchClause2: ''
9219        };
9220        if ($expandElem.closest('ul').hasClass('search_results')) {
9221            params.searchClause = PMA_fastFilter.getSearchClause();
9222            params.searchClause2 = PMA_fastFilter.getSearchClause2($expandElem);
9223        }
9224    } else {
9225        $destination = $('#pma_navigation_tree_content');
9226        params = {
9227            aPath: $expandElem.attr('aPath'),
9228            vPath: $expandElem.attr('vPath'),
9229            pos: $expandElem.attr('pos'),
9230            pos2_name: '',
9231            pos2_value: '',
9232            searchClause: '',
9233            searchClause2: ''
9234        };
9235    }
9236
9237    var url = $('#pma_navigation').find('a.navigation_url').attr('href');
9238    $.get(url, params, function (data) {
9239        if (typeof data !== 'undefined' && data.success === true) {
9240            $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
9241            if (isNode) {
9242                $destination.append(data.message);
9243                $expandElem.addClass('loaded');
9244            } else {
9245                $destination.html(data.message);
9246                $destination.children()
9247                    .first()
9248                    .css({
9249                        border: '0px',
9250                        margin: '0em',
9251                        padding : '0em'
9252                    })
9253                    .slideDown('slow');
9254            }
9255            if (data._errors) {
9256                var $errors = $(data._errors);
9257                if ($errors.children().length > 0) {
9258                    $('#pma_errors').replaceWith(data._errors);
9259                }
9260            }
9261            if (callback && typeof callback == 'function') {
9262                callback(data);
9263            }
9264        } else if(data.redirect_flag == "1") {
9265            if (window.location.href.indexOf('?') === -1) {
9266                window.location.href += '?session_expired=1';
9267            } else {
9268                window.location.href += '&session_expired=1';
9269            }
9270            window.location.reload();
9271        } else {
9272            var $throbber = $expandElem.find('img.throbber');
9273            $throbber.hide();
9274            var $icon = $expandElem.find('img.ic_b_plus');
9275            $icon.show();
9276            PMA_ajaxShowMessage(data.error, false);
9277        }
9278    });
9279}
9280
9281/**
9282 * Collapses a node in navigation tree.
9283 *
9284 * @param $expandElem expander
9285 *
9286 * @returns void
9287 */
9288function collapseTreeNode($expandElem) {
9289    var $children = $expandElem.closest('li').children('div.list_container');
9290    var $icon = $expandElem.find('img');
9291    if ($expandElem.hasClass('loaded')) {
9292        if ($icon.is('.ic_b_minus')) {
9293            $icon.removeClass('ic_b_minus').addClass('ic_b_plus');
9294            $children.slideUp('fast');
9295        }
9296    }
9297    $expandElem.blur();
9298    $children.promise().done(navTreeStateUpdate);
9299}
9300
9301/**
9302 * Traverse the navigation tree backwards to generate all the actual
9303 * and virtual paths, as well as the positions in the pagination at
9304 * various levels, if necessary.
9305 *
9306 * @return Object
9307 */
9308function traverseNavigationForPaths() {
9309    var params = {
9310        pos: $('#pma_navigation_tree').find('div.dbselector select').val()
9311    };
9312    if ($('#navi_db_select').length) {
9313        return params;
9314    }
9315    var count = 0;
9316    $('#pma_navigation_tree').find('a.expander:visible').each(function () {
9317        if ($(this).find('img').is('.ic_b_minus') &&
9318            $(this).closest('li').find('div.list_container .ic_b_minus').length === 0
9319        ) {
9320            params['n' + count + '_aPath'] = $(this).find('span.aPath').text();
9321            params['n' + count + '_vPath'] = $(this).find('span.vPath').text();
9322
9323            var pos2_name = $(this).find('span.pos2_name').text();
9324            if (! pos2_name) {
9325                pos2_name = $(this)
9326                    .parent()
9327                    .parent()
9328                    .find('span.pos2_name:last')
9329                    .text();
9330            }
9331            var pos2_value = $(this).find('span.pos2_value').text();
9332            if (! pos2_value) {
9333                pos2_value = $(this)
9334                    .parent()
9335                    .parent()
9336                    .find('span.pos2_value:last')
9337                    .text();
9338            }
9339
9340            params['n' + count + '_pos2_name'] = pos2_name;
9341            params['n' + count + '_pos2_value'] = pos2_value;
9342
9343            params['n' + count + '_pos3_name'] = $(this).find('span.pos3_name').text();
9344            params['n' + count + '_pos3_value'] = $(this).find('span.pos3_value').text();
9345            count++;
9346        }
9347    });
9348    return params;
9349}
9350
9351/**
9352 * Executed on page load
9353 */
9354$(function () {
9355    if (! $('#pma_navigation').length) {
9356        // Don't bother running any code if the navigation is not even on the page
9357        return;
9358    }
9359
9360    // Do not let the page reload on submitting the fast filter
9361    $(document).on('submit', '.fast_filter', function (event) {
9362        event.preventDefault();
9363    });
9364
9365    // Fire up the resize handlers
9366    new ResizeHandler();
9367
9368    /**
9369     * opens/closes (hides/shows) tree elements
9370     * loads data via ajax
9371     */
9372    $(document).on('click', '#pma_navigation_tree a.expander', function (event) {
9373        event.preventDefault();
9374        event.stopImmediatePropagation();
9375        var $icon = $(this).find('img');
9376        if ($icon.is('.ic_b_plus')) {
9377            expandTreeNode($(this));
9378        } else {
9379            collapseTreeNode($(this));
9380        }
9381    });
9382
9383    /**
9384     * Register event handler for click on the reload
9385     * navigation icon at the top of the panel
9386     */
9387    $(document).on('click', '#pma_navigation_reload', function (event) {
9388        event.preventDefault();
9389        // reload icon object
9390        var $icon = $(this).find('img');
9391        // source of the hidden throbber icon
9392        var icon_throbber_src = $('#pma_navigation').find('.throbber').attr('src');
9393        // source of the reload icon
9394        var icon_reload_src = $icon.attr('src');
9395        // replace the source of the reload icon with the one for throbber
9396        $icon.attr('src', icon_throbber_src);
9397        PMA_reloadNavigation();
9398        // after one second, put back the reload icon
9399        setTimeout(function () {
9400            $icon.attr('src', icon_reload_src);
9401        }, 1000);
9402    });
9403
9404    $(document).on("change", '#navi_db_select',  function (event) {
9405        if (! $(this).val()) {
9406            PMA_commonParams.set('db', '');
9407            PMA_reloadNavigation();
9408        }
9409        $(this).closest('form').trigger('submit');
9410    });
9411
9412    /**
9413     * Register event handler for click on the collapse all
9414     * navigation icon at the top of the navigation tree
9415     */
9416    $(document).on('click', '#pma_navigation_collapse', function (event) {
9417        event.preventDefault();
9418        $('#pma_navigation_tree').find('a.expander').each(function() {
9419            var $icon = $(this).find('img');
9420            if ($icon.is('.ic_b_minus')) {
9421                $(this).click();
9422            }
9423        });
9424    });
9425
9426    /**
9427     * Register event handler to toggle
9428     * the 'link with main panel' icon on mouseenter.
9429     */
9430    $(document).on('mouseenter', '#pma_navigation_sync', function (event) {
9431        event.preventDefault();
9432        var synced = $('#pma_navigation_tree').hasClass('synced');
9433        var $img = $('#pma_navigation_sync').children('img');
9434        if (synced) {
9435            $img.removeClass('ic_s_link').addClass('ic_s_unlink');
9436        } else {
9437            $img.removeClass('ic_s_unlink').addClass('ic_s_link');
9438        }
9439    });
9440
9441    /**
9442     * Register event handler to toggle
9443     * the 'link with main panel' icon on mouseout.
9444     */
9445    $(document).on('mouseout', '#pma_navigation_sync', function (event) {
9446        event.preventDefault();
9447        var synced = $('#pma_navigation_tree').hasClass('synced');
9448        var $img = $('#pma_navigation_sync').children('img');
9449        if (synced) {
9450            $img.removeClass('ic_s_unlink').addClass('ic_s_link');
9451        } else {
9452            $img.removeClass('ic_s_link').addClass('ic_s_unlink');
9453        }
9454    });
9455
9456    /**
9457     * Register event handler to toggle
9458     * the linking with main panel behavior
9459     */
9460    $(document).on('click', '#pma_navigation_sync', function (event) {
9461        event.preventDefault();
9462        var synced = $('#pma_navigation_tree').hasClass('synced');
9463        var $img = $('#pma_navigation_sync').children('img');
9464        if (synced) {
9465            $img
9466                .removeClass('ic_s_unlink')
9467                .addClass('ic_s_link')
9468                .attr('alt', PMA_messages.linkWithMain)
9469                .attr('title', PMA_messages.linkWithMain);
9470            $('#pma_navigation_tree')
9471                .removeClass('synced')
9472                .find('li.selected')
9473                .removeClass('selected');
9474        } else {
9475            $img
9476                .removeClass('ic_s_link')
9477                .addClass('ic_s_unlink')
9478                .attr('alt', PMA_messages.unlinkWithMain)
9479                .attr('title', PMA_messages.unlinkWithMain);
9480            $('#pma_navigation_tree').addClass('synced');
9481            PMA_showCurrentNavigation();
9482        }
9483    });
9484
9485    /**
9486     * Bind all "fast filter" events
9487     */
9488    $(document).on('click', '#pma_navigation_tree li.fast_filter span', PMA_fastFilter.events.clear);
9489    $(document).on('focus', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.focus);
9490    $(document).on('blur', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.blur);
9491    $(document).on('keyup', '#pma_navigation_tree li.fast_filter input.searchClause', PMA_fastFilter.events.keyup);
9492
9493    /**
9494     * Ajax handler for pagination
9495     */
9496    $(document).on('click', '#pma_navigation_tree div.pageselector a.ajax', function (event) {
9497        event.preventDefault();
9498        PMA_navigationTreePagination($(this));
9499    });
9500
9501    /**
9502     * Node highlighting
9503     */
9504    $(document).on(
9505        'mouseover',
9506        '#pma_navigation_tree.highlight li:not(.fast_filter)',
9507        function () {
9508            if ($('li:visible', this).length === 0) {
9509                $(this).addClass('activePointer');
9510            }
9511        }
9512    );
9513    $(document).on(
9514        'mouseout',
9515        '#pma_navigation_tree.highlight li:not(.fast_filter)',
9516        function () {
9517            $(this).removeClass('activePointer');
9518        }
9519    );
9520
9521    /** Create a Routine, Trigger or Event */
9522    $(document).on('click', 'li.new_procedure a.ajax, li.new_function a.ajax', function (event) {
9523        event.preventDefault();
9524        var dialog = new RTE.object('routine');
9525        dialog.editorDialog(1, $(this));
9526    });
9527    $(document).on('click', 'li.new_trigger a.ajax', function (event) {
9528        event.preventDefault();
9529        var dialog = new RTE.object('trigger');
9530        dialog.editorDialog(1, $(this));
9531    });
9532    $(document).on('click', 'li.new_event a.ajax', function (event) {
9533        event.preventDefault();
9534        var dialog = new RTE.object('event');
9535        dialog.editorDialog(1, $(this));
9536    });
9537
9538    /** Edit Routines, Triggers or Events */
9539    $(document).on('click', 'li.procedure > a.ajax, li.function > a.ajax', function (event) {
9540        event.preventDefault();
9541        var dialog = new RTE.object('routine');
9542        dialog.editorDialog(0, $(this));
9543    });
9544    $(document).on('click', 'li.trigger > a.ajax', function (event) {
9545        event.preventDefault();
9546        var dialog = new RTE.object('trigger');
9547        dialog.editorDialog(0, $(this));
9548    });
9549    $(document).on('click', 'li.event > a.ajax', function (event) {
9550        event.preventDefault();
9551        var dialog = new RTE.object('event');
9552        dialog.editorDialog(0, $(this));
9553    });
9554
9555    /** Execute Routines */
9556    $(document).on('click', 'li.procedure div a.ajax img,' +
9557        ' li.function div a.ajax img', function (event) {
9558        event.preventDefault();
9559        var dialog = new RTE.object('routine');
9560        dialog.executeDialog($(this).parent());
9561    });
9562    /** Export Triggers and Events */
9563    $(document).on('click', 'li.trigger div:eq(1) a.ajax img,' +
9564        ' li.event div:eq(1) a.ajax img', function (event) {
9565        event.preventDefault();
9566        var dialog = new RTE.object();
9567        dialog.exportDialog($(this).parent());
9568    });
9569
9570    /** New index */
9571    $(document).on('click', '#pma_navigation_tree li.new_index a.ajax', function (event) {
9572        event.preventDefault();
9573        var url = $(this).attr('href').substr(
9574            $(this).attr('href').indexOf('?') + 1
9575        ) + '&ajax_request=true';
9576        var title = PMA_messages.strAddIndex;
9577        indexEditorDialog(url, title);
9578    });
9579
9580    /** Edit index */
9581    $(document).on('click', 'li.index a.ajax', function (event) {
9582        event.preventDefault();
9583        var url = $(this).attr('href').substr(
9584            $(this).attr('href').indexOf('?') + 1
9585        ) + '&ajax_request=true';
9586        var title = PMA_messages.strEditIndex;
9587        indexEditorDialog(url, title);
9588    });
9589
9590    /** New view */
9591    $(document).on('click', 'li.new_view a.ajax', function (event) {
9592        event.preventDefault();
9593        PMA_createViewDialog($(this));
9594    });
9595
9596    /** Hide navigation tree item */
9597    $(document).on('click', 'a.hideNavItem.ajax', function (event) {
9598        event.preventDefault();
9599        $.ajax({
9600            type: 'POST',
9601            url: $(this).attr('href') + '&ajax_request=true',
9602            success: function (data) {
9603                if (typeof data !== 'undefined' && data.success === true) {
9604                    PMA_reloadNavigation();
9605                } else {
9606                    PMA_ajaxShowMessage(data.error);
9607                }
9608            }
9609        });
9610    });
9611
9612    /** Display a dialog to choose hidden navigation items to show */
9613    $(document).on('click', 'a.showUnhide.ajax', function (event) {
9614        event.preventDefault();
9615        var $msg = PMA_ajaxShowMessage();
9616        $.get($(this).attr('href') + '&ajax_request=1', function (data) {
9617            if (typeof data !== 'undefined' && data.success === true) {
9618                PMA_ajaxRemoveMessage($msg);
9619                var buttonOptions = {};
9620                buttonOptions[PMA_messages.strClose] = function () {
9621                    $(this).dialog("close");
9622                };
9623                $('<div/>')
9624                    .attr('id', 'unhideNavItemDialog')
9625                    .append(data.message)
9626                    .dialog({
9627                        width: 400,
9628                        minWidth: 200,
9629                        modal: true,
9630                        buttons: buttonOptions,
9631                        title: PMA_messages.strUnhideNavItem,
9632                        close: function () {
9633                            $(this).remove();
9634                        }
9635                    });
9636            } else {
9637                PMA_ajaxShowMessage(data.error);
9638            }
9639        });
9640    });
9641
9642    /** Show a hidden navigation tree item */
9643    $(document).on('click', 'a.unhideNavItem.ajax', function (event) {
9644        event.preventDefault();
9645        var $tr = $(this).parents('tr');
9646        var $msg = PMA_ajaxShowMessage();
9647        $.ajax({
9648            type: 'POST',
9649            url: $(this).attr('href') + '&ajax_request=true',
9650            success: function (data) {
9651                PMA_ajaxRemoveMessage($msg);
9652                if (typeof data !== 'undefined' && data.success === true) {
9653                    $tr.remove();
9654                    PMA_reloadNavigation();
9655                } else {
9656                    PMA_ajaxShowMessage(data.error);
9657                }
9658            }
9659        });
9660    });
9661
9662    // Add/Remove favorite table using Ajax.
9663    $(document).on("click", ".favorite_table_anchor", function (event) {
9664        event.preventDefault();
9665        $self = $(this);
9666        var anchor_id = $self.attr("id");
9667        if($self.data("favtargetn") !== null) {
9668            if($('a[data-favtargets="' + $self.data("favtargetn") + '"]').length > 0)
9669            {
9670                $('a[data-favtargets="' + $self.data("favtargetn") + '"]').trigger('click');
9671                return;
9672            }
9673        }
9674
9675        $.ajax({
9676            url: $self.attr('href'),
9677            cache: false,
9678            type: 'POST',
9679            data: {
9680                favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
9681                    ? window.localStorage.favorite_tables
9682                    : ''
9683            },
9684            success: function (data) {
9685                if (data.changes) {
9686                    $('#pma_favorite_list').html(data.list);
9687                    $('#' + anchor_id).parent().html(data.anchor);
9688                    PMA_tooltip(
9689                        $('#' + anchor_id),
9690                        'a',
9691                        $('#' + anchor_id).attr("title")
9692                    );
9693                    // Update localStorage.
9694                    if (isStorageSupported('localStorage')) {
9695                        window.localStorage.favorite_tables = data.favorite_tables;
9696                    }
9697                } else {
9698                    PMA_ajaxShowMessage(data.message);
9699                }
9700            }
9701        });
9702    });
9703    // Check if session storage is supported
9704    if (isStorageSupported('sessionStorage')) {
9705        var storage = window.sessionStorage;
9706        // remove tree from storage if Navi_panel config form is submitted
9707        $(document).on('submit', 'form.config-form', function(event) {
9708            storage.removeItem('navTreePaths');
9709        });
9710        // Initialize if no previous state is defined
9711        if ($('#pma_navigation_tree_content').length &&
9712            typeof storage.navTreePaths === 'undefined'
9713        ) {
9714            PMA_reloadNavigation();
9715        } else if (PMA_commonParams.get('server') === storage.server &&
9716            PMA_commonParams.get('token') === storage.token
9717        ) {
9718            // Reload the tree to the state before page refresh
9719            PMA_reloadNavigation(null, JSON.parse(storage.navTreePaths));
9720        } else {
9721            // If the user is different
9722            navTreeStateUpdate();
9723        }
9724    }
9725});
9726
9727/**
9728 * Expands a node in navigation tree.
9729 *
9730 * @param $expandElem expander
9731 * @param callback    callback function
9732 *
9733 * @returns void
9734 */
9735function expandTreeNode($expandElem, callback) {
9736    var $children = $expandElem.closest('li').children('div.list_container');
9737    var $icon = $expandElem.find('img');
9738    if ($expandElem.hasClass('loaded')) {
9739        if ($icon.is('.ic_b_plus')) {
9740            $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
9741            $children.slideDown('fast');
9742        }
9743        if (callback && typeof callback == 'function') {
9744            callback.call();
9745        }
9746        $children.promise().done(navTreeStateUpdate);
9747    } else {
9748        var $throbber = $('#pma_navigation').find('.throbber')
9749            .first()
9750            .clone()
9751            .css({visibility: 'visible', display: 'block'})
9752            .click(false);
9753        $icon.hide();
9754        $throbber.insertBefore($icon);
9755
9756        loadChildNodes(true, $expandElem, function (data) {
9757            if (typeof data !== 'undefined' && data.success === true) {
9758                var $destination = $expandElem.closest('li');
9759                $icon.removeClass('ic_b_plus').addClass('ic_b_minus');
9760                $children = $destination.children('div.list_container');
9761                $children.slideDown('fast');
9762                if ($destination.find('ul > li').length == 1) {
9763                    $destination.find('ul > li')
9764                        .find('a.expander.container')
9765                        .click();
9766                }
9767                if (callback && typeof callback == 'function') {
9768                    callback.call();
9769                }
9770                PMA_showFullName($destination);
9771            } else {
9772                PMA_ajaxShowMessage(data.error, false);
9773            }
9774            $icon.show();
9775            $throbber.remove();
9776            $children.promise().done(navTreeStateUpdate);
9777        });
9778    }
9779    $expandElem.blur();
9780}
9781
9782/**
9783 * Auto-scrolls the newly chosen database
9784 *
9785 * @param  object   $element    The element to set to view
9786 * @param  boolean  $forceToTop Whether to force scroll to top
9787 *
9788 */
9789function scrollToView($element, $forceToTop) {
9790    var $container = $('#pma_navigation_tree_content');
9791    var elemTop = $element.offset().top - $container.offset().top;
9792    var textHeight = 20;
9793    var scrollPadding = 20; // extra padding from top of bottom when scrolling to view
9794    if (elemTop < 0 || $forceToTop) {
9795        $container.stop().animate({
9796            scrollTop: elemTop + $container.scrollTop() - scrollPadding
9797        });
9798    } else if (elemTop + textHeight > $container.height()) {
9799        $container.stop().animate({
9800            scrollTop: elemTop + textHeight - $container.height() + $container.scrollTop() + scrollPadding
9801        });
9802    }
9803}
9804
9805/**
9806 * Expand the navigation and highlight the current database or table/view
9807 *
9808 * @returns void
9809 */
9810function PMA_showCurrentNavigation() {
9811    var db = PMA_commonParams.get('db');
9812    var table = PMA_commonParams.get('table');
9813    $('#pma_navigation_tree')
9814        .find('li.selected')
9815        .removeClass('selected');
9816    if (db) {
9817        var $dbItem = findLoadedItem(
9818            $('#pma_navigation_tree').find('> div'), db, 'database', !table
9819        );
9820        if ($('#navi_db_select').length &&
9821            $('option:selected', $('#navi_db_select')).length
9822        ) {
9823            if (! PMA_selectCurrentDb()) {
9824                return;
9825            }
9826            // If loaded database in navigation is not same as current one
9827            if ($('#pma_navigation_tree_content').find('span.loaded_db:first').text()
9828                !== $('#navi_db_select').val()
9829            ) {
9830                loadChildNodes(false, $('option:selected', $('#navi_db_select')), function (data) {
9831                    handleTableOrDb(table, $('#pma_navigation_tree_content'));
9832                    var $children = $('#pma_navigation_tree_content').children('div.list_container');
9833                    $children.promise().done(navTreeStateUpdate);
9834                });
9835            } else {
9836                handleTableOrDb(table, $('#pma_navigation_tree_content'));
9837            }
9838        } else if ($dbItem) {
9839            var $expander = $dbItem.children('div:first').children('a.expander');
9840            // if not loaded or loaded but collapsed
9841            if (! $expander.hasClass('loaded') ||
9842                $expander.find('img').is('.ic_b_plus')
9843            ) {
9844                expandTreeNode($expander, function () {
9845                    handleTableOrDb(table, $dbItem);
9846                });
9847            } else {
9848                handleTableOrDb(table, $dbItem);
9849            }
9850        }
9851    } else if ($('#navi_db_select').length && $('#navi_db_select').val()) {
9852        $('#navi_db_select').val('').hide().trigger('change');
9853    }
9854    PMA_showFullName($('#pma_navigation_tree'));
9855
9856    function handleTableOrDb(table, $dbItem) {
9857        if (table) {
9858            loadAndHighlightTableOrView($dbItem, table);
9859        } else {
9860            var $container = $dbItem.children('div.list_container');
9861            var $tableContainer = $container.children('ul').children('li.tableContainer');
9862            if ($tableContainer.length > 0) {
9863                var $expander = $tableContainer.children('div:first').children('a.expander');
9864                $tableContainer.addClass('selected');
9865                expandTreeNode($expander, function () {
9866                    scrollToView($dbItem, true);
9867                });
9868            } else {
9869                scrollToView($dbItem, true);
9870            }
9871        }
9872    }
9873
9874    function findLoadedItem($container, name, clazz, doSelect) {
9875        var ret = false;
9876        $container.children('ul').children('li').each(function () {
9877            var $li = $(this);
9878            // this is a navigation group, recurse
9879            if ($li.is('.navGroup')) {
9880                var $container = $li.children('div.list_container');
9881                var $childRet = findLoadedItem(
9882                    $container, name, clazz, doSelect
9883                );
9884                if ($childRet) {
9885                    ret = $childRet;
9886                    return false;
9887                }
9888            } else { // this is a real navigation item
9889                // name and class matches
9890                if (((clazz && $li.is('.' + clazz)) || ! clazz) &&
9891                        $li.children('a').text() == name) {
9892                    if (doSelect) {
9893                        $li.addClass('selected');
9894                    }
9895                    // taverse up and expand and parent navigation groups
9896                    $li.parents('.navGroup').each(function () {
9897                        var $cont = $(this).children('div.list_container');
9898                        if (! $cont.is(':visible')) {
9899                            $(this)
9900                                .children('div:first')
9901                                .children('a.expander')
9902                                .click();
9903                        }
9904                    });
9905                    ret = $li;
9906                    return false;
9907                }
9908            }
9909        });
9910        return ret;
9911    }
9912
9913    function loadAndHighlightTableOrView($dbItem, itemName) {
9914        var $container = $dbItem.children('div.list_container');
9915        var $expander;
9916        var $whichItem = isItemInContainer($container, itemName, 'li.table, li.view');
9917        //If item already there in some container
9918        if ($whichItem) {
9919            //get the relevant container while may also be a subcontainer
9920            var $relatedContainer = $whichItem.closest('li.subContainer').length
9921                ? $whichItem.closest('li.subContainer')
9922                : $dbItem;
9923            $whichItem = findLoadedItem(
9924                $relatedContainer.children('div.list_container'),
9925                itemName, null, true
9926            );
9927            //Show directly
9928            showTableOrView($whichItem, $relatedContainer.children('div:first').children('a.expander'));
9929        //else if item not there, try loading once
9930        } else {
9931            var $sub_containers = $dbItem.find('.subContainer');
9932            //If there are subContainers i.e. tableContainer or viewContainer
9933            if($sub_containers.length > 0) {
9934                var $containers = [];
9935                $sub_containers.each(function (index) {
9936                    $containers[index] = $(this);
9937                    $expander = $containers[index]
9938                        .children('div:first')
9939                        .children('a.expander');
9940                    if (! $expander.hasClass('loaded')) {
9941                        loadAndShowTableOrView($expander, $containers[index], itemName);
9942                    }
9943                });
9944            // else if no subContainers
9945            } else {
9946                $expander = $dbItem
9947                    .children('div:first')
9948                    .children('a.expander');
9949                if (! $expander.hasClass('loaded')) {
9950                    loadAndShowTableOrView($expander, $dbItem, itemName);
9951                }
9952            }
9953        }
9954    }
9955
9956    function loadAndShowTableOrView($expander, $relatedContainer, itemName) {
9957        loadChildNodes(true, $expander, function (data) {
9958            var $whichItem = findLoadedItem(
9959                $relatedContainer.children('div.list_container'),
9960                itemName, null, true
9961            );
9962            if ($whichItem) {
9963                showTableOrView($whichItem, $expander);
9964            }
9965        });
9966    }
9967
9968    function showTableOrView($whichItem, $expander) {
9969        expandTreeNode($expander, function (data) {
9970            if ($whichItem) {
9971                scrollToView($whichItem, false);
9972            }
9973        });
9974    }
9975
9976    function isItemInContainer($container, name, clazz)
9977    {
9978        var $whichItem = null;
9979        $items = $container.find(clazz);
9980        var found = false;
9981        $items.each(function () {
9982            if ($(this).children('a').text() == name) {
9983                $whichItem = $(this);
9984                return false;
9985            }
9986        });
9987        return $whichItem;
9988    }
9989}
9990
9991/**
9992 * Disable navigation panel settings
9993 *
9994 * @return void
9995 */
9996function PMA_disableNaviSettings() {
9997    $('#pma_navigation_settings_icon').addClass('hide');
9998    $('#pma_navigation_settings').remove();
9999}
10000
10001/**
10002 * Ensure that navigation panel settings is properly setup.
10003 * If not, set it up
10004 *
10005 * @return void
10006 */
10007function PMA_ensureNaviSettings(selflink) {
10008    $('#pma_navigation_settings_icon').removeClass('hide');
10009
10010    if (!$('#pma_navigation_settings').length) {
10011        var params = {
10012            getNaviSettings: true
10013        };
10014        var url = $('#pma_navigation').find('a.navigation_url').attr('href');
10015        $.post(url, params, function (data) {
10016            if (typeof data !== 'undefined' && data.success) {
10017                $('#pma_navi_settings_container').html(data.message);
10018                setupRestoreField();
10019                setupValidation();
10020                setupConfigTabs();
10021                $('#pma_navigation_settings').find('form').attr('action', selflink);
10022            } else {
10023                PMA_ajaxShowMessage(data.error);
10024            }
10025        });
10026    } else {
10027        $('#pma_navigation_settings').find('form').attr('action', selflink);
10028    }
10029}
10030
10031/**
10032 * Reloads the whole navigation tree while preserving its state
10033 *
10034 * @param  function     the callback function
10035 * @param  Object       stored navigation paths
10036 *
10037 * @return void
10038 */
10039function PMA_reloadNavigation(callback, paths) {
10040    var params = {
10041        reload: true,
10042        no_debug: true
10043    };
10044    paths = paths || traverseNavigationForPaths();
10045    $.extend(params, paths);
10046    if ($('#navi_db_select').length) {
10047        params.db = PMA_commonParams.get('db');
10048        requestNaviReload(params);
10049        return;
10050    }
10051    requestNaviReload(params);
10052
10053    function requestNaviReload(params) {
10054        var url = $('#pma_navigation').find('a.navigation_url').attr('href');
10055        $.post(url, params, function (data) {
10056            if (typeof data !== 'undefined' && data.success) {
10057                $('#pma_navigation_tree').html(data.message).children('div').show();
10058                if ($('#pma_navigation_tree').hasClass('synced')) {
10059                    PMA_selectCurrentDb();
10060                    PMA_showCurrentNavigation();
10061                }
10062                // Fire the callback, if any
10063                if (typeof callback === 'function') {
10064                    callback.call();
10065                }
10066                navTreeStateUpdate();
10067            } else {
10068                PMA_ajaxShowMessage(data.error);
10069            }
10070        });
10071    }
10072}
10073
10074function PMA_selectCurrentDb() {
10075    var $naviDbSelect = $('#navi_db_select');
10076
10077    if (!$naviDbSelect.length) {
10078        return false;
10079    }
10080
10081    if (PMA_commonParams.get('db')) { // db selected
10082        $naviDbSelect.show();
10083    }
10084
10085    $naviDbSelect.val(PMA_commonParams.get('db'));
10086    return $naviDbSelect.val() === PMA_commonParams.get('db');
10087
10088}
10089
10090/**
10091 * Handles any requests to change the page in a branch of a tree
10092 *
10093 * This can be called from link click or select change event handlers
10094 *
10095 * @param object $this A jQuery object that points to the element that
10096 * initiated the action of changing the page
10097 *
10098 * @return void
10099 */
10100function PMA_navigationTreePagination($this) {
10101    var $msgbox = PMA_ajaxShowMessage();
10102    var isDbSelector = $this.closest('div.pageselector').is('.dbselector');
10103    var url, params;
10104    if ($this[0].tagName == 'A') {
10105        url = $this.attr('href');
10106        params = 'ajax_request=true';
10107    } else { // tagName == 'SELECT'
10108        url = 'navigation.php';
10109        params = $this.closest("form").serialize() + '&ajax_request=true';
10110    }
10111    var searchClause = PMA_fastFilter.getSearchClause();
10112    if (searchClause) {
10113        params += '&searchClause=' + encodeURIComponent(searchClause);
10114    }
10115    if (isDbSelector) {
10116        params += '&full=true';
10117    } else {
10118        var searchClause2 = PMA_fastFilter.getSearchClause2($this);
10119        if (searchClause2) {
10120            params += '&searchClause2=' + encodeURIComponent(searchClause2);
10121        }
10122    }
10123    $.post(url, params, function (data) {
10124        if (typeof data !== 'undefined' && data.success) {
10125            PMA_ajaxRemoveMessage($msgbox);
10126            if (isDbSelector) {
10127                var val = PMA_fastFilter.getSearchClause();
10128                $('#pma_navigation_tree')
10129                    .html(data.message)
10130                    .children('div')
10131                    .show();
10132                if (val) {
10133                    $('#pma_navigation_tree')
10134                        .find('li.fast_filter input.searchClause')
10135                        .val(val);
10136                }
10137            } else {
10138                var $parent = $this.closest('div.list_container').parent();
10139                var val = PMA_fastFilter.getSearchClause2($this);
10140                $this.closest('div.list_container').html(
10141                    $(data.message).children().show()
10142                );
10143                if (val) {
10144                    $parent.find('li.fast_filter input.searchClause').val(val);
10145                }
10146                $parent.find('span.pos2_value:first').text(
10147                    $parent.find('span.pos2_value:last').text()
10148                );
10149                $parent.find('span.pos3_value:first').text(
10150                    $parent.find('span.pos3_value:last').text()
10151                );
10152            }
10153        } else {
10154            PMA_ajaxShowMessage(data.error);
10155            PMA_handleRedirectAndReload(data);
10156        }
10157        navTreeStateUpdate();
10158    });
10159}
10160
10161/**
10162 * @var ResizeHandler Custom object that manages the resizing of the navigation
10163 *
10164 * XXX: Must only be ever instanciated once
10165 * XXX: Inside event handlers the 'this' object is accessed as 'event.data.resize_handler'
10166 */
10167var ResizeHandler = function () {
10168    /**
10169     * @var int panel_width Used by the collapser to know where to go
10170     *                      back to when uncollapsing the panel
10171     */
10172    this.panel_width = 0;
10173    /**
10174     * @var string left Used to provide support for RTL languages
10175     */
10176    this.left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
10177    /**
10178     * Adjusts the width of the navigation panel to the specified value
10179     *
10180     * @param int pos Navigation width in pixels
10181     *
10182     * @return void
10183     */
10184    this.setWidth = function (pos) {
10185        var $resizer = $('#pma_navigation_resizer');
10186        var resizer_width = $resizer.width();
10187        var $collapser = $('#pma_navigation_collapser');
10188        $('#pma_navigation').width(pos);
10189        $('body').css('margin-' + this.left, pos + 'px');
10190        $("#floating_menubar, #pma_console")
10191            .css('margin-' + this.left, (pos + resizer_width) + 'px');
10192        $resizer.css(this.left, pos + 'px');
10193        if (pos === 0) {
10194            $collapser
10195                .css(this.left, pos + resizer_width)
10196                .html(this.getSymbol(pos))
10197                .prop('title', PMA_messages.strShowPanel);
10198        } else {
10199            $collapser
10200                .css(this.left, pos)
10201                .html(this.getSymbol(pos))
10202                .prop('title', PMA_messages.strHidePanel);
10203        }
10204        setTimeout(function () {
10205            $(window).trigger('resize');
10206        }, 4);
10207    };
10208    /**
10209     * Returns the horizontal position of the mouse,
10210     * relative to the outer side of the navigation panel
10211     *
10212     * @param int pos Navigation width in pixels
10213     *
10214     * @return void
10215     */
10216    this.getPos = function (event) {
10217        var pos = event.pageX;
10218        var windowWidth = $(window).width();
10219        var windowScroll = $(window).scrollLeft();
10220        pos = pos - windowScroll;
10221        if (this.left != 'left') {
10222            pos = windowWidth - event.pageX;
10223        }
10224        if (pos < 0) {
10225            pos = 0;
10226        } else if (pos + 100 >= windowWidth) {
10227            pos = windowWidth - 100;
10228        } else {
10229            this.panel_width = 0;
10230        }
10231        return pos;
10232    };
10233    /**
10234     * Returns the HTML code for the arrow symbol used in the collapser
10235     *
10236     * @param int width The width of the panel
10237     *
10238     * @return string
10239     */
10240    this.getSymbol = function (width) {
10241        if (this.left == 'left') {
10242            if (width === 0) {
10243                return '&rarr;';
10244            } else {
10245                return '&larr;';
10246            }
10247        } else {
10248            if (width === 0) {
10249                return '&larr;';
10250            } else {
10251                return '&rarr;';
10252            }
10253        }
10254    };
10255    /**
10256     * Event handler for initiating a resize of the panel
10257     *
10258     * @param object e Event data (contains a reference to resizeHandler)
10259     *
10260     * @return void
10261     */
10262    this.mousedown = function (event) {
10263        event.preventDefault();
10264        $(document)
10265            .bind('mousemove', {'resize_handler': event.data.resize_handler},
10266                $.throttle(event.data.resize_handler.mousemove, 4))
10267            .bind('mouseup', {'resize_handler': event.data.resize_handler},
10268                event.data.resize_handler.mouseup);
10269        $('body').css('cursor', 'col-resize');
10270    };
10271    /**
10272     * Event handler for terminating a resize of the panel
10273     *
10274     * @param object e Event data (contains a reference to resizeHandler)
10275     *
10276     * @return void
10277     */
10278    this.mouseup = function (event) {
10279        $('body').css('cursor', '');
10280        $.cookie('pma_navi_width', event.data.resize_handler.getPos(event));
10281        $('#topmenu').menuResizer('resize');
10282        $(document)
10283            .unbind('mousemove')
10284            .unbind('mouseup');
10285    };
10286    /**
10287     * Event handler for updating the panel during a resize operation
10288     *
10289     * @param object e Event data (contains a reference to resizeHandler)
10290     *
10291     * @return void
10292     */
10293    this.mousemove = function (event) {
10294        event.preventDefault();
10295        var pos = event.data.resize_handler.getPos(event);
10296        event.data.resize_handler.setWidth(pos);
10297        if ($('.sticky_columns').length !== 0) {
10298            handleAllStickyColumns();
10299        }
10300    };
10301    /**
10302     * Event handler for collapsing the panel
10303     *
10304     * @param object e Event data (contains a reference to resizeHandler)
10305     *
10306     * @return void
10307     */
10308    this.collapse = function (event) {
10309        event.preventDefault();
10310        var panel_width = event.data.resize_handler.panel_width;
10311        var width = $('#pma_navigation').width();
10312        if (width === 0 && panel_width === 0) {
10313            panel_width = 240;
10314        }
10315        event.data.resize_handler.setWidth(panel_width);
10316        event.data.resize_handler.panel_width = width;
10317    };
10318    /**
10319     * Event handler for resizing the navigation tree height on window resize
10320     *
10321     * @return void
10322     */
10323    this.treeResize = function (event) {
10324        var $nav        = $("#pma_navigation"),
10325            $nav_tree   = $("#pma_navigation_tree"),
10326            $nav_header = $("#pma_navigation_header"),
10327            $nav_tree_content = $("#pma_navigation_tree_content");
10328        $nav_tree.height($nav.height() - $nav_header.height());
10329        if ($nav_tree_content.length > 0) {
10330            $nav_tree_content.height($nav_tree.height() - $nav_tree_content.position().top);
10331        } else {
10332            //TODO: in fast filter search response there is no #pma_navigation_tree_content, needs to be added in php
10333            $nav_tree.css({
10334                'overflow-y': 'auto'
10335            });
10336        }
10337        // Set content bottom space beacuse of console
10338        $('body').css('margin-bottom', $('#pma_console').height() + 'px');
10339    };
10340    /* Initialisation section begins here */
10341    if ($.cookie('pma_navi_width')) {
10342        // If we have a cookie, set the width of the panel to its value
10343        var pos = Math.abs(parseInt($.cookie('pma_navi_width'), 10) || 0);
10344        this.setWidth(pos);
10345        $('#topmenu').menuResizer('resize');
10346    }
10347    // Register the events for the resizer and the collapser
10348    $(document).on('mousedown', '#pma_navigation_resizer', {'resize_handler': this}, this.mousedown);
10349    $(document).on('click', '#pma_navigation_collapser', {'resize_handler': this}, this.collapse);
10350
10351    // Add the correct arrow symbol to the collapser
10352    $('#pma_navigation_collapser').html(this.getSymbol($('#pma_navigation').width()));
10353    // Fix navigation tree height
10354    $(window).on('resize', this.treeResize);
10355    // need to call this now and then, browser might decide
10356    // to show/hide horizontal scrollbars depending on page content width
10357    setInterval(this.treeResize, 2000);
10358    this.treeResize();
10359}; // End of ResizeHandler
10360
10361/**
10362 * @var object PMA_fastFilter Handles the functionality that allows filtering
10363 *                            of the items in a branch of the navigation tree
10364 */
10365var PMA_fastFilter = {
10366    /**
10367     * Construct for the asynchronous fast filter functionality
10368     *
10369     * @param object $this        A jQuery object pointing to the list container
10370     *                            which is the nearest parent of the fast filter
10371     * @param string searchClause The query string for the filter
10372     *
10373     * @return new PMA_fastFilter.filter object
10374     */
10375    filter: function ($this, searchClause) {
10376        /**
10377         * @var object $this A jQuery object pointing to the list container
10378         *                   which is the nearest parent of the fast filter
10379         */
10380        this.$this = $this;
10381        /**
10382         * @var bool searchClause The query string for the filter
10383         */
10384        this.searchClause = searchClause;
10385        /**
10386         * @var object $clone A clone of the original contents
10387         *                    of the navigation branch before
10388         *                    the fast filter was applied
10389         */
10390        this.$clone = $this.clone();
10391        /**
10392         * @var object xhr A reference to the ajax request that is currently running
10393         */
10394        this.xhr = null;
10395        /**
10396         * @var int timeout Used to delay the request for asynchronous search
10397         */
10398        this.timeout = null;
10399
10400        var $filterInput = $this.find('li.fast_filter input.searchClause');
10401        if ($filterInput.length !== 0 &&
10402            $filterInput.val() !== '' &&
10403            $filterInput.val() != $filterInput[0].defaultValue
10404        ) {
10405            this.request();
10406        }
10407    },
10408    /**
10409     * Gets the query string from the database fast filter form
10410     *
10411     * @return string
10412     */
10413    getSearchClause: function () {
10414        var retval = '';
10415        var $input = $('#pma_navigation_tree')
10416            .find('li.fast_filter.db_fast_filter input.searchClause');
10417        if ($input.length && $input.val() != $input[0].defaultValue) {
10418            retval = $input.val();
10419        }
10420        return retval;
10421    },
10422    /**
10423     * Gets the query string from a second level item's fast filter form
10424     * The retrieval is done by trasversing the navigation tree backwards
10425     *
10426     * @return string
10427     */
10428    getSearchClause2: function ($this) {
10429        var $filterContainer = $this.closest('div.list_container');
10430        var $filterInput = $([]);
10431        if ($filterContainer
10432            .find('li.fast_filter:not(.db_fast_filter) input.searchClause')
10433            .length !== 0) {
10434            $filterInput = $filterContainer
10435                .find('li.fast_filter:not(.db_fast_filter) input.searchClause');
10436        }
10437        var searchClause2 = '';
10438        if ($filterInput.length !== 0 &&
10439            $filterInput.first().val() != $filterInput[0].defaultValue
10440        ) {
10441            searchClause2 = $filterInput.val();
10442        }
10443        return searchClause2;
10444    },
10445    /**
10446     * @var hash events A list of functions that are bound to DOM events
10447     *                  at the top of this file
10448     */
10449    events: {
10450        focus: function (event) {
10451            var $obj = $(this).closest('div.list_container');
10452            if (! $obj.data('fastFilter')) {
10453                $obj.data(
10454                    'fastFilter',
10455                    new PMA_fastFilter.filter($obj, $(this).val())
10456                );
10457            }
10458            if ($(this).val() == this.defaultValue) {
10459                $(this).val('');
10460            } else {
10461                $(this).select();
10462            }
10463        },
10464        blur: function (event) {
10465            if ($(this).val() === '') {
10466                $(this).val(this.defaultValue);
10467            }
10468            var $obj = $(this).closest('div.list_container');
10469            if ($(this).val() == this.defaultValue && $obj.data('fastFilter')) {
10470                $obj.data('fastFilter').restore();
10471            }
10472        },
10473        keyup: function (event) {
10474            var $obj = $(this).closest('div.list_container');
10475            var str = '';
10476            if ($(this).val() != this.defaultValue && $(this).val() !== '') {
10477                $obj.find('div.pageselector').hide();
10478                str = $(this).val();
10479            }
10480
10481            /**
10482             * FIXME at the server level a value match is done while on
10483             * the client side it is a regex match. These two should be aligned
10484             */
10485
10486            // regex used for filtering.
10487            var regex;
10488            try {
10489                regex = new RegExp(str, 'i');
10490            } catch (err) {
10491                return;
10492            }
10493
10494            // this is the div that houses the items to be filtered by this filter.
10495            var outerContainer;
10496            if ($(this).closest('li.fast_filter').is('.db_fast_filter')) {
10497                outerContainer = $('#pma_navigation_tree_content');
10498            } else {
10499                outerContainer = $obj;
10500            }
10501
10502            // filters items that are directly under the div as well as grouped in
10503            // groups. Does not filter child items (i.e. a database search does
10504            // not filter tables)
10505            var item_filter = function($curr) {
10506                $curr.children('ul').children('li.navGroup').each(function() {
10507                    $(this).children('div.list_container').each(function() {
10508                        item_filter($(this)); // recursive
10509                    });
10510                });
10511                $curr.children('ul').children('li').children('a').not('.container').each(function() {
10512                    if (regex.test($(this).text())) {
10513                        $(this).parent().show().removeClass('hidden');
10514                    } else {
10515                        $(this).parent().hide().addClass('hidden');
10516                    }
10517                });
10518            };
10519            item_filter(outerContainer);
10520
10521            // hides containers that does not have any visible children
10522            var container_filter = function ($curr) {
10523                $curr.children('ul').children('li.navGroup').each(function() {
10524                    var $group = $(this);
10525                    $group.children('div.list_container').each(function() {
10526                        container_filter($(this)); // recursive
10527                    });
10528                    $group.show().removeClass('hidden');
10529                    if ($group.children('div.list_container').children('ul')
10530                            .children('li').not('.hidden').length === 0) {
10531                        $group.hide().addClass('hidden');
10532                    }
10533                });
10534            };
10535            container_filter(outerContainer);
10536
10537            if ($(this).val() != this.defaultValue && $(this).val() !== '') {
10538                if (! $obj.data('fastFilter')) {
10539                    $obj.data(
10540                        'fastFilter',
10541                        new PMA_fastFilter.filter($obj, $(this).val())
10542                    );
10543                } else {
10544                    if (event.keyCode == 13) {
10545                        $obj.data('fastFilter').update($(this).val());
10546                    }
10547                }
10548            } else if ($obj.data('fastFilter')) {
10549                $obj.data('fastFilter').restore(true);
10550            }
10551        },
10552        clear: function (event) {
10553            event.stopPropagation();
10554            // Clear the input and apply the fast filter with empty input
10555            var filter = $(this).closest('div.list_container').data('fastFilter');
10556            if (filter) {
10557                filter.restore();
10558            }
10559            var value = $(this).prev()[0].defaultValue;
10560            $(this).prev().val(value).trigger('keyup');
10561        }
10562    }
10563};
10564/**
10565 * Handles a change in the search clause
10566 *
10567 * @param string searchClause The query string for the filter
10568 *
10569 * @return void
10570 */
10571PMA_fastFilter.filter.prototype.update = function (searchClause) {
10572    if (this.searchClause != searchClause) {
10573        this.searchClause = searchClause;
10574        this.request();
10575    }
10576};
10577/**
10578 * After a delay of 250mS, initiates a request to retrieve search results
10579 * Multiple calls to this function will always abort the previous request
10580 *
10581 * @return void
10582 */
10583PMA_fastFilter.filter.prototype.request = function () {
10584    var self = this;
10585    if (self.$this.find('li.fast_filter').find('img.throbber').length === 0) {
10586        self.$this.find('li.fast_filter').append(
10587            $('<div class="throbber"></div>').append(
10588                $('#pma_navigation_content')
10589                    .find('img.throbber')
10590                    .clone()
10591                    .css({visibility: 'visible', display: 'block'})
10592            )
10593        );
10594    }
10595    if (self.xhr) {
10596        self.xhr.abort();
10597    }
10598    var url = $('#pma_navigation').find('a.navigation_url').attr('href');
10599    var params = self.$this.find('> ul > li > form.fast_filter').first().serialize();
10600    if (self.$this.find('> ul > li > form.fast_filter:first input[name=searchClause]').length === 0) {
10601        var $input = $('#pma_navigation_tree').find('li.fast_filter.db_fast_filter input.searchClause');
10602        if ($input.length && $input.val() != $input[0].defaultValue) {
10603            params += '&searchClause=' + encodeURIComponent($input.val());
10604        }
10605    }
10606    self.xhr = $.ajax({
10607        url: url,
10608        type: 'post',
10609        dataType: 'json',
10610        data: params,
10611        complete: function (jqXHR, status) {
10612            if (status != 'abort') {
10613                var data = $.parseJSON(jqXHR.responseText);
10614                self.$this.find('li.fast_filter').find('div.throbber').remove();
10615                if (data && data.results) {
10616                    self.swap.apply(self, [data.message]);
10617                }
10618            }
10619        }
10620    });
10621};
10622/**
10623 * Replaces the contents of the navigation branch with the search results
10624 *
10625 * @param string list The search results
10626 *
10627 * @return void
10628 */
10629PMA_fastFilter.filter.prototype.swap = function (list) {
10630    this.$this
10631        .html($(list).html())
10632        .children()
10633        .show()
10634        .end()
10635        .find('li.fast_filter input.searchClause')
10636        .val(this.searchClause);
10637    this.$this.data('fastFilter', this);
10638};
10639/**
10640 * Restores the navigation to the original state after the fast filter is cleared
10641 *
10642 * @param bool focus Whether to also focus the input box of the fast filter
10643 *
10644 * @return void
10645 */
10646PMA_fastFilter.filter.prototype.restore = function (focus) {
10647    if(this.$this.children('ul').first().hasClass('search_results')) {
10648        this.$this.html(this.$clone.html()).children().show();
10649        this.$this.data('fastFilter', this);
10650        if (focus) {
10651            this.$this.find('li.fast_filter input.searchClause').focus();
10652        }
10653    }
10654    this.searchClause = '';
10655    this.$this.find('div.pageselector').show();
10656    this.$this.find('div.throbber').remove();
10657};
10658
10659/**
10660 * Show full name when cursor hover and name not shown completely
10661 *
10662 * @param object $containerELem Container element
10663 *
10664 * @return void
10665 */
10666function PMA_showFullName($containerELem) {
10667
10668    $containerELem.find('.hover_show_full').mouseenter(function() {
10669        /** mouseenter */
10670        var $this = $(this);
10671        var thisOffset = $this.offset();
10672        if($this.text() === '') {
10673            return;
10674        }
10675        var $parent = $this.parent();
10676        if(  ($parent.offset().left + $parent.outerWidth())
10677           < (thisOffset.left + $this.outerWidth()))
10678        {
10679            var $fullNameLayer = $('#full_name_layer');
10680            if($fullNameLayer.length === 0)
10681            {
10682                $('body').append('<div id="full_name_layer" class="hide"></div>');
10683                $('#full_name_layer').mouseleave(function() {
10684                    /** mouseleave */
10685                    $(this).addClass('hide')
10686                           .removeClass('hovering');
10687                }).mouseenter(function() {
10688                    /** mouseenter */
10689                    $(this).addClass('hovering');
10690                });
10691                $fullNameLayer = $('#full_name_layer');
10692            }
10693            $fullNameLayer.removeClass('hide');
10694            $fullNameLayer.css({left: thisOffset.left, top: thisOffset.top});
10695            $fullNameLayer.html($this.clone());
10696            setTimeout(function() {
10697                if(! $fullNameLayer.hasClass('hovering'))
10698                {
10699                    $fullNameLayer.trigger('mouseleave');
10700                }
10701            }, 200);
10702        }
10703    });
10704}
10705;
10706

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