Translated using Weblate (Persian)
[phpmyadmin.git] / js / console.js
blob2ec580e4b94e21772dad0f26274f6ea605bf0244
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * Used in or for console
4  *
5  * @package phpMyAdmin-Console
6  */
8 /* global debugSQLInfo */ // libraries/classes/Footer.php
10 /**
11  * Console object
12  */
13 var Console = {
14     /**
15      * @var object, jQuery object, selector is '#pma_console>.content'
16      * @access private
17      */
18     $consoleContent: null,
19     /**
20      * @var object, jQuery object, selector is '#pma_console .content',
21      *  used for resizer
22      * @access private
23      */
24     $consoleAllContents: null,
25     /**
26      * @var object, jQuery object, selector is '#pma_console .toolbar'
27      * @access private
28      */
29     $consoleToolbar: null,
30     /**
31      * @var object, jQuery object, selector is '#pma_console .template'
32      * @access private
33      */
34     $consoleTemplates: null,
35     /**
36      * @var object, jQuery object, form for submit
37      * @access private
38      */
39     $requestForm: null,
40     /**
41      * @var object, contain console config
42      * @access private
43      */
44     config: null,
45     /**
46      * @var bool, if console element exist, it'll be true
47      * @access public
48      */
49     isEnabled: false,
50     /**
51      * @var bool, make sure console events bind only once
52      * @access private
53      */
54     isInitialized: false,
55     /**
56      * Used for console initialize, reinit is ok, just some variable assignment
57      *
58      * @return void
59      */
60     initialize: function () {
61         if ($('#pma_console').length === 0) {
62             return;
63         }
65         Console.config = Functions.configGet('Console', false);
67         Console.isEnabled = true;
69         // Vars init
70         Console.$consoleToolbar = $('#pma_console').find('>.toolbar');
71         Console.$consoleContent = $('#pma_console').find('>.content');
72         Console.$consoleAllContents = $('#pma_console').find('.content');
73         Console.$consoleTemplates = $('#pma_console').find('>.templates');
75         // Generate a from for post
76         Console.$requestForm = $('<form method="post" action="import.php">' +
77             '<input name="is_js_confirmed" value="0">' +
78             '<textarea name="sql_query"></textarea>' +
79             '<input name="console_message_id" value="0">' +
80             '<input name="server" value="">' +
81             '<input name="db" value="">' +
82             '<input name="table" value="">' +
83             '<input name="token" value="">' +
84             '</form>'
85         );
86         Console.$requestForm.children('[name=token]').val(CommonParams.get('token'));
87         Console.$requestForm.on('submit', AJAX.requestHandler);
89         // Event binds shouldn't run again
90         if (Console.isInitialized === false) {
91             // Load config first
92             if (Console.config.AlwaysExpand === true) {
93                 $('#pma_console_options input[name=always_expand]').prop('checked', true);
94             }
95             if (Console.config.StartHistory === true) {
96                 $('#pma_console_options').find('input[name=start_history]').prop('checked', true);
97             }
98             if (Console.config.CurrentQuery === true) {
99                 $('#pma_console_options').find('input[name=current_query]').prop('checked', true);
100             }
101             if (Console.config.EnterExecutes === true) {
102                 $('#pma_console_options').find('input[name=enter_executes]').prop('checked', true);
103             }
104             if (Console.config.DarkTheme === true) {
105                 $('#pma_console_options').find('input[name=dark_theme]').prop('checked', true);
106                 $('#pma_console').find('>.content').addClass('console_dark_theme');
107             }
109             ConsoleResizer.initialize();
110             ConsoleInput.initialize();
111             ConsoleMessages.initialize();
112             ConsoleBookmarks.initialize();
113             ConsoleDebug.initialize();
115             Console.$consoleToolbar.children('.console_switch').on('click', Console.toggle);
117             $('#pma_console').find('.toolbar').children().on('mousedown', function (event) {
118                 event.preventDefault();
119                 event.stopImmediatePropagation();
120             });
122             $('#pma_console').find('.button.clear').on('click', function () {
123                 ConsoleMessages.clear();
124             });
126             $('#pma_console').find('.button.history').on('click', function () {
127                 ConsoleMessages.showHistory();
128             });
130             $('#pma_console').find('.button.options').on('click', function () {
131                 Console.showCard('#pma_console_options');
132             });
134             $('#pma_console').find('.button.debug').on('click', function () {
135                 Console.showCard('#debug_console');
136             });
138             Console.$consoleContent.on('click', function (event) {
139                 if (event.target === this) {
140                     ConsoleInput.focus();
141                 }
142             });
144             $('#pma_console').find('.mid_layer').on('click', function () {
145                 Console.hideCard($(this).parent().children('.card'));
146             });
147             $('#debug_console').find('.switch_button').on('click', function () {
148                 Console.hideCard($(this).closest('.card'));
149             });
150             $('#pma_bookmarks').find('.switch_button').on('click', function () {
151                 Console.hideCard($(this).closest('.card'));
152             });
153             $('#pma_console_options').find('.switch_button').on('click', function () {
154                 Console.hideCard($(this).closest('.card'));
155             });
157             $('#pma_console_options').find('input[type=checkbox]').on('change', function () {
158                 Console.updateConfig();
159             });
161             $('#pma_console_options').find('.button.default').on('click', function () {
162                 $('#pma_console_options input[name=always_expand]').prop('checked', false);
163                 $('#pma_console_options').find('input[name=start_history]').prop('checked', false);
164                 $('#pma_console_options').find('input[name=current_query]').prop('checked', true);
165                 $('#pma_console_options').find('input[name=enter_executes]').prop('checked', false);
166                 $('#pma_console_options').find('input[name=dark_theme]').prop('checked', false);
167                 Console.updateConfig();
168             });
170             $('#pma_console_options').find('input[name=enter_executes]').on('change', function () {
171                 ConsoleMessages.showInstructions(Console.config.EnterExecutes);
172             });
174             $(document).ajaxComplete(function (event, xhr, ajaxOptions) {
175                 if (ajaxOptions.dataType && ajaxOptions.dataType.indexOf('json') !== -1) {
176                     return;
177                 }
178                 if (xhr.status !== 200) {
179                     return;
180                 }
181                 try {
182                     var data = JSON.parse(xhr.responseText);
183                     Console.ajaxCallback(data);
184                 } catch (e) {
185                     // eslint-disable-next-line no-console
186                     console.trace();
187                     // eslint-disable-next-line no-console
188                     console.log('Failed to parse JSON: ' + e.message);
189                 }
190             });
192             Console.isInitialized = true;
193         }
195         // Change console mode from cookie
196         switch (Console.config.Mode) {
197         case 'collapse':
198             Console.collapse();
199             break;
200         case 'info':
201             Console.info();
202             break;
203         case 'show':
204             Console.show(true);
205             Console.scrollBottom();
206             break;
207         default:
208             Console.setConfig('Mode', 'info');
209             Console.info();
210         }
211     },
212     /**
213      * Execute query and show results in console
214      *
215      * @return void
216      */
217     execute: function (queryString, options) {
218         if (typeof(queryString) !== 'string' || ! /[a-z]|[A-Z]/.test(queryString)) {
219             return;
220         }
221         Console.$requestForm.children('textarea').val(queryString);
222         Console.$requestForm.children('[name=server]').attr('value', CommonParams.get('server'));
223         if (options && options.db) {
224             Console.$requestForm.children('[name=db]').val(options.db);
225             if (options.table) {
226                 Console.$requestForm.children('[name=table]').val(options.table);
227             } else {
228                 Console.$requestForm.children('[name=table]').val('');
229             }
230         } else {
231             Console.$requestForm.children('[name=db]').val(
232                 (CommonParams.get('db').length > 0 ? CommonParams.get('db') : ''));
233         }
234         Console.$requestForm.find('[name=profiling]').remove();
235         if (options && options.profiling === true) {
236             Console.$requestForm.append('<input name="profiling" value="on">');
237         }
238         if (! Functions.confirmQuery(Console.$requestForm[0], Console.$requestForm.children('textarea')[0].value)) {
239             return;
240         }
241         Console.$requestForm.children('[name=console_message_id]')
242             .val(ConsoleMessages.appendQuery({ 'sql_query': queryString }).message_id);
243         Console.$requestForm.trigger('submit');
244         ConsoleInput.clear();
245         Navigation.reload();
246     },
247     ajaxCallback: function (data) {
248         if (data && data.console_message_id) {
249             ConsoleMessages.updateQuery(data.console_message_id, data.success,
250                 (data.reloadQuerywindow ? data.reloadQuerywindow : false));
251         } else if (data && data.reloadQuerywindow) {
252             if (data.reloadQuerywindow.sql_query.length > 0) {
253                 ConsoleMessages.appendQuery(data.reloadQuerywindow, 'successed')
254                     .$message.addClass(Console.config.CurrentQuery ? '' : 'hide');
255             }
256         }
257     },
258     /**
259      * Change console to collapse mode
260      *
261      * @return void
262      */
263     collapse: function () {
264         Console.setConfig('Mode', 'collapse');
265         var pmaConsoleHeight = Math.max(92, Console.config.Height);
267         Console.$consoleToolbar.addClass('collapsed');
268         Console.$consoleAllContents.height(pmaConsoleHeight);
269         Console.$consoleContent.stop();
270         Console.$consoleContent.animate({ 'margin-bottom': -1 * Console.$consoleContent.outerHeight() + 'px' },
271             'fast', 'easeOutQuart', function () {
272                 Console.$consoleContent.css({ display:'none' });
273                 $(window).trigger('resize');
274             });
275         Console.hideCard();
276     },
277     /**
278      * Show console
279      *
280      * @param bool inputFocus If true, focus the input line after show()
281      * @return void
282      */
283     show: function (inputFocus) {
284         Console.setConfig('Mode', 'show');
286         var pmaConsoleHeight = Math.max(92, Console.config.Height);
287         pmaConsoleHeight = Math.min(Console.config.Height, (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - 25);
288         Console.$consoleContent.css({ display:'block' });
289         if (Console.$consoleToolbar.hasClass('collapsed')) {
290             Console.$consoleToolbar.removeClass('collapsed');
291         }
292         Console.$consoleAllContents.height(pmaConsoleHeight);
293         Console.$consoleContent.stop();
294         Console.$consoleContent.animate({ 'margin-bottom': 0 },
295             'fast', 'easeOutQuart', function () {
296                 $(window).trigger('resize');
297                 if (inputFocus) {
298                     ConsoleInput.focus();
299                 }
300             });
301     },
302     /**
303      * Change console to SQL information mode
304      * this mode shows current SQL query
305      * This mode is the default mode
306      *
307      * @return void
308      */
309     info: function () {
310         // Under construction
311         Console.collapse();
312     },
313     /**
314      * Toggle console mode between collapse/show
315      * Used for toggle buttons and shortcuts
316      *
317      * @return void
318      */
319     toggle: function () {
320         switch (Console.config.Mode) {
321         case 'collapse':
322         case 'info':
323             Console.show(true);
324             break;
325         case 'show':
326             Console.collapse();
327             break;
328         }
329     },
330     /**
331      * Scroll console to bottom
332      *
333      * @return void
334      */
335     scrollBottom: function () {
336         Console.$consoleContent.scrollTop(Console.$consoleContent.prop('scrollHeight'));
337     },
338     /**
339      * Show card
340      *
341      * @param string cardSelector Selector, select string will be "#pma_console " + cardSelector
342      * this param also can be JQuery object, if you need.
343      *
344      * @return void
345      */
346     showCard: function (cardSelector) {
347         var $card = null;
348         if (typeof(cardSelector) !== 'string') {
349             if (cardSelector.length > 0) {
350                 $card = cardSelector;
351             } else {
352                 return;
353             }
354         } else {
355             $card = $('#pma_console ' + cardSelector);
356         }
357         if ($card.length === 0) {
358             return;
359         }
360         $card.parent().children('.mid_layer').show().fadeTo(0, 0.15);
361         $card.addClass('show');
362         ConsoleInput.blur();
363         if ($card.parents('.card').length > 0) {
364             Console.showCard($card.parents('.card'));
365         }
366     },
367     /**
368      * Scroll console to bottom
369      *
370      * @param object $targetCard Target card JQuery object, if it's empty, function will hide all cards
371      * @return void
372      */
373     hideCard: function ($targetCard) {
374         if (! $targetCard) {
375             $('#pma_console').find('.mid_layer').fadeOut(140);
376             $('#pma_console').find('.card').removeClass('show');
377         } else if ($targetCard.length > 0) {
378             $targetCard.parent().find('.mid_layer').fadeOut(140);
379             $targetCard.find('.card').removeClass('show');
380             $targetCard.removeClass('show');
381         }
382     },
383     /**
384      * Used for update console config
385      *
386      * @return void
387      */
388     updateConfig: function () {
389         Console.setConfig('AlwaysExpand', $('#pma_console_options input[name=always_expand]').prop('checked'));
390         Console.setConfig('StartHistory', $('#pma_console_options').find('input[name=start_history]').prop('checked'));
391         Console.setConfig('CurrentQuery', $('#pma_console_options').find('input[name=current_query]').prop('checked'));
392         Console.setConfig('EnterExecutes', $('#pma_console_options').find('input[name=enter_executes]').prop('checked'));
393         Console.setConfig('DarkTheme', $('#pma_console_options').find('input[name=dark_theme]').prop('checked'));
394         /* Setting the dark theme of the console*/
395         if (Console.config.DarkTheme) {
396             $('#pma_console').find('>.content').addClass('console_dark_theme');
397         } else {
398             $('#pma_console').find('>.content').removeClass('console_dark_theme');
399         }
400     },
401     setConfig: function (key, value) {
402         Console.config[key] = value;
403         Functions.configSet('Console/' + key, value);
404     },
405     isSelect: function (queryString) {
406         var regExp = /^SELECT\s+/i;
407         return regExp.test(queryString);
408     }
412  * Resizer object
413  * Careful: this object UI logics highly related with functions under Console
414  * Resizing min-height is 32, if small than it, console will collapse
415  */
416 var ConsoleResizer = {
417     posY: 0,
418     height: 0,
419     resultHeight: 0,
420     /**
421      * Mousedown event handler for bind to resizer
422      *
423      * @return void
424      */
425     mouseDown: function (event) {
426         if (Console.config.Mode !== 'show') {
427             return;
428         }
429         ConsoleResizer.posY = event.pageY;
430         ConsoleResizer.height = Console.$consoleContent.height();
431         $(document).on('mousemove', ConsoleResizer.mouseMove);
432         $(document).on('mouseup', ConsoleResizer.mouseUp);
433         // Disable text selection while resizing
434         $(document).on('selectstart', function () {
435             return false;
436         });
437     },
438     /**
439      * Mousemove event handler for bind to resizer
440      *
441      * @return void
442      */
443     mouseMove: function (event) {
444         if (event.pageY < 35) {
445             event.pageY = 35;
446         }
447         ConsoleResizer.resultHeight = ConsoleResizer.height + (ConsoleResizer.posY - event.pageY);
448         // Content min-height is 32, if adjusting height small than it we'll move it out of the page
449         if (ConsoleResizer.resultHeight <= 32) {
450             Console.$consoleAllContents.height(32);
451             Console.$consoleContent.css('margin-bottom', ConsoleResizer.resultHeight - 32);
452         } else {
453             // Logic below makes viewable area always at bottom when adjusting height and content already at bottom
454             if (Console.$consoleContent.scrollTop() + Console.$consoleContent.innerHeight() + 16
455                 >= Console.$consoleContent.prop('scrollHeight')) {
456                 Console.$consoleAllContents.height(ConsoleResizer.resultHeight);
457                 Console.scrollBottom();
458             } else {
459                 Console.$consoleAllContents.height(ConsoleResizer.resultHeight);
460             }
461         }
462     },
463     /**
464      * Mouseup event handler for bind to resizer
465      *
466      * @return void
467      */
468     mouseUp: function () {
469         Console.setConfig('Height', ConsoleResizer.resultHeight);
470         Console.show();
471         $(document).off('mousemove');
472         $(document).off('mouseup');
473         $(document).off('selectstart');
474     },
475     /**
476      * Used for console resizer initialize
477      *
478      * @return void
479      */
480     initialize: function () {
481         $('#pma_console').find('.toolbar').off('mousedown');
482         $('#pma_console').find('.toolbar').on('mousedown', ConsoleResizer.mouseDown);
483     }
487  * Console input object
488  */
489 var ConsoleInput = {
490     /**
491      * @var array, contains Codemirror objects or input jQuery objects
492      * @access private
493      */
494     inputs: null,
495     /**
496      * @var bool, if codemirror enabled
497      * @access private
498      */
499     codeMirror: false,
500     /**
501      * @var int, count for history navigation, 0 for current input
502      * @access private
503      */
504     historyCount: 0,
505     /**
506      * @var string, current input when navigating through history
507      * @access private
508      */
509     historyPreserveCurrent: null,
510     /**
511      * Used for console input initialize
512      *
513      * @return void
514      */
515     initialize: function () {
516         // _cm object can't be reinitialize
517         if (ConsoleInput.inputs !== null) {
518             return;
519         }
520         if (typeof CodeMirror !== 'undefined') {
521             ConsoleInput.codeMirror = true;
522         }
523         ConsoleInput.inputs = [];
524         if (ConsoleInput.codeMirror) {
525             // eslint-disable-next-line new-cap
526             ConsoleInput.inputs.console = CodeMirror($('#pma_console').find('.console_query_input')[0], {
527                 theme: 'pma',
528                 mode: 'text/x-sql',
529                 lineWrapping: true,
530                 extraKeys: { 'Ctrl-Space': 'autocomplete' },
531                 hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
532                 gutters: ['CodeMirror-lint-markers'],
533                 lint: {
534                     'getAnnotations': CodeMirror.sqlLint,
535                     'async': true,
536                 }
537             });
538             ConsoleInput.inputs.console.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
539             ConsoleInput.inputs.console.on('keydown', function (instance, event) {
540                 ConsoleInput.historyNavigate(event);
541             });
542             if ($('#pma_bookmarks').length !== 0) {
543                 // eslint-disable-next-line new-cap
544                 ConsoleInput.inputs.bookmark = CodeMirror($('#pma_console').find('.bookmark_add_input')[0], {
545                     theme: 'pma',
546                     mode: 'text/x-sql',
547                     lineWrapping: true,
548                     extraKeys: { 'Ctrl-Space': 'autocomplete' },
549                     hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true },
550                     gutters: ['CodeMirror-lint-markers'],
551                     lint: {
552                         'getAnnotations': CodeMirror.sqlLint,
553                         'async': true,
554                     }
555                 });
556                 ConsoleInput.inputs.bookmark.on('inputRead', Functions.codeMirrorAutoCompleteOnInputRead);
557             }
558         } else {
559             ConsoleInput.inputs.console =
560                 $('<textarea>').appendTo('#pma_console .console_query_input')
561                     .on('keydown', ConsoleInput.historyNavigate);
562             if ($('#pma_bookmarks').length !== 0) {
563                 ConsoleInput.inputs.bookmark =
564                     $('<textarea>').appendTo('#pma_console .bookmark_add_input');
565             }
566         }
567         $('#pma_console').find('.console_query_input').on('keydown', ConsoleInput.keyDown);
568     },
569     historyNavigate: function (event) {
570         if (event.keyCode === 38 || event.keyCode === 40) {
571             var upPermitted = false;
572             var downPermitted = false;
573             var editor = ConsoleInput.inputs.console;
574             var cursorLine;
575             var totalLine;
576             if (ConsoleInput.codeMirror) {
577                 cursorLine = editor.getCursor().line;
578                 totalLine = editor.lineCount();
579             } else {
580                 // Get cursor position from textarea
581                 var text = ConsoleInput.getText();
582                 cursorLine = text.substr(0, editor.prop('selectionStart')).split('\n').length - 1;
583                 totalLine = text.split(/\r*\n/).length;
584             }
585             if (cursorLine === 0) {
586                 upPermitted = true;
587             }
588             if (cursorLine === totalLine - 1) {
589                 downPermitted = true;
590             }
591             var nextCount;
592             var queryString = false;
593             if (upPermitted && event.keyCode === 38) {
594                 // Navigate up in history
595                 if (ConsoleInput.historyCount === 0) {
596                     ConsoleInput.historyPreserveCurrent = ConsoleInput.getText();
597                 }
598                 nextCount = ConsoleInput.historyCount + 1;
599                 queryString = ConsoleMessages.getHistory(nextCount);
600             } else if (downPermitted && event.keyCode === 40) {
601                 // Navigate down in history
602                 if (ConsoleInput.historyCount === 0) {
603                     return;
604                 }
605                 nextCount = ConsoleInput.historyCount - 1;
606                 if (nextCount === 0) {
607                     queryString = ConsoleInput.historyPreserveCurrent;
608                 } else {
609                     queryString = ConsoleMessages.getHistory(nextCount);
610                 }
611             }
612             if (queryString !== false) {
613                 ConsoleInput.historyCount = nextCount;
614                 ConsoleInput.setText(queryString, 'console');
615                 if (ConsoleInput.codeMirror) {
616                     editor.setCursor(editor.lineCount(), 0);
617                 }
618                 event.preventDefault();
619             }
620         }
621     },
622     /**
623      * Mousedown event handler for bind to input
624      * Shortcut is Ctrl+Enter key or just ENTER, depending on console's
625      * configuration.
626      *
627      * @return void
628      */
629     keyDown: function (event) {
630         // Execute command
631         if (Console.config.EnterExecutes) {
632             // Enter, but not in combination with Shift (which writes a new line).
633             if (!event.shiftKey && event.keyCode === 13) {
634                 ConsoleInput.execute();
635             }
636         } else {
637             // Ctrl+Enter
638             if (event.ctrlKey && event.keyCode === 13) {
639                 ConsoleInput.execute();
640             }
641         }
642         // Clear line
643         if (event.ctrlKey && event.keyCode === 76) {
644             ConsoleInput.clear();
645         }
646         // Clear console
647         if (event.ctrlKey && event.keyCode === 85) {
648             ConsoleMessages.clear();
649         }
650     },
651     /**
652      * Used for send text to Console.execute()
653      *
654      * @return void
655      */
656     execute: function () {
657         if (ConsoleInput.codeMirror) {
658             Console.execute(ConsoleInput.inputs.console.getValue());
659         } else {
660             Console.execute(ConsoleInput.inputs.console.val());
661         }
662     },
663     /**
664      * Used for clear the input
665      *
666      * @param string target, default target is console input
667      * @return void
668      */
669     clear: function (target) {
670         ConsoleInput.setText('', target);
671     },
672     /**
673      * Used for set focus to input
674      *
675      * @return void
676      */
677     focus: function () {
678         ConsoleInput.inputs.console.focus();
679     },
680     /**
681      * Used for blur input
682      *
683      * @return void
684      */
685     blur: function () {
686         if (ConsoleInput.codeMirror) {
687             ConsoleInput.inputs.console.getInputField().blur();
688         } else {
689             ConsoleInput.inputs.console.blur();
690         }
691     },
692     /**
693      * Used for set text in input
694      *
695      * @param string text
696      * @param string target
697      * @return void
698      */
699     setText: function (text, target) {
700         if (ConsoleInput.codeMirror) {
701             switch (target) {
702             case 'bookmark':
703                 Console.execute(ConsoleInput.inputs.bookmark.setValue(text));
704                 break;
705             default:
706             case 'console':
707                 Console.execute(ConsoleInput.inputs.console.setValue(text));
708             }
709         } else {
710             switch (target) {
711             case 'bookmark':
712                 Console.execute(ConsoleInput.inputs.bookmark.val(text));
713                 break;
714             default:
715             case 'console':
716                 Console.execute(ConsoleInput.inputs.console.val(text));
717             }
718         }
719     },
720     getText: function (target) {
721         if (ConsoleInput.codeMirror) {
722             switch (target) {
723             case 'bookmark':
724                 return ConsoleInput.inputs.bookmark.getValue();
725             default:
726             case 'console':
727                 return ConsoleInput.inputs.console.getValue();
728             }
729         } else {
730             switch (target) {
731             case 'bookmark':
732                 return ConsoleInput.inputs.bookmark.val();
733             default:
734             case 'console':
735                 return ConsoleInput.inputs.console.val();
736             }
737         }
738     }
743  * Console messages, and message items management object
744  */
745 var ConsoleMessages = {
746     /**
747      * Used for clear the messages
748      *
749      * @return void
750      */
751     clear: function () {
752         $('#pma_console').find('.content .console_message_container .message:not(.welcome)').addClass('hide');
753         $('#pma_console').find('.content .console_message_container .message.failed').remove();
754         $('#pma_console').find('.content .console_message_container .message.expanded').find('.action.collapse').trigger('click');
755     },
756     /**
757      * Used for show history messages
758      *
759      * @return void
760      */
761     showHistory: function () {
762         $('#pma_console').find('.content .console_message_container .message.hide').removeClass('hide');
763     },
764     /**
765      * Used for getting a perticular history query
766      *
767      * @param int nthLast get nth query message from latest, i.e 1st is last
768      * @return string message
769      */
770     getHistory: function (nthLast) {
771         var $queries = $('#pma_console').find('.content .console_message_container .query');
772         var length = $queries.length;
773         var $query = $queries.eq(length - nthLast);
774         if (!$query || (length - nthLast) < 0) {
775             return false;
776         } else {
777             return $query.text();
778         }
779     },
780     /**
781      * Used to show the correct message depending on which key
782      * combination executes the query (Ctrl+Enter or Enter).
783      *
784      * @param bool enterExecutes Only Enter has to be pressed to execute query.
785      * @return void
786      */
787     showInstructions: function (enterExecutes) {
788         var enter = +enterExecutes || 0; // conversion to int
789         var $welcomeMsg = $('#pma_console').find('.content .console_message_container .message.welcome span');
790         $welcomeMsg.children('[id^=instructions]').hide();
791         $welcomeMsg.children('#instructions-' + enter).show();
792     },
793     /**
794      * Used for log new message
795      *
796      * @param string msgString Message to show
797      * @param string msgType Message type
798      * @return object, {message_id, $message}
799      */
800     append: function (msgString, msgType) {
801         if (typeof(msgString) !== 'string') {
802             return false;
803         }
804         // Generate an ID for each message, we can find them later
805         var msgId = Math.round(Math.random() * (899999999999) + 100000000000);
806         var now = new Date();
807         var $newMessage =
808             $('<div class="message ' +
809                 (Console.config.AlwaysExpand ? 'expanded' : 'collapsed') +
810                 '" msgid="' + msgId + '"><div class="action_content"></div></div>');
811         switch (msgType) {
812         case 'query':
813             $newMessage.append('<div class="query highlighted"></div>');
814             if (ConsoleInput.codeMirror) {
815                 CodeMirror.runMode(msgString,
816                     'text/x-sql', $newMessage.children('.query')[0]);
817             } else {
818                 $newMessage.children('.query').text(msgString);
819             }
820             $newMessage.children('.action_content')
821                 .append(Console.$consoleTemplates.children('.query_actions').html());
822             break;
823         default:
824         case 'normal':
825             $newMessage.append('<div>' + msgString + '</div>');
826         }
827         ConsoleMessages.messageEventBinds($newMessage);
828         $newMessage.find('span.text.query_time span')
829             .text(now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds())
830             .parent().attr('title', now);
831         return {
832             'message_id': msgId,
833             $message: $newMessage.appendTo('#pma_console .content .console_message_container')
834         };
835     },
836     /**
837      * Used for log new query
838      *
839      * @param string queryData Struct should be
840      * {sql_query: "Query string", db: "Target DB", table: "Target Table"}
841      * @param string state Message state
842      * @return object, {message_id: string message id, $message: JQuery object}
843      */
844     appendQuery: function (queryData, state) {
845         var targetMessage = ConsoleMessages.append(queryData.sql_query, 'query');
846         if (! targetMessage) {
847             return false;
848         }
849         if (queryData.db && queryData.table) {
850             targetMessage.$message.attr('targetdb', queryData.db);
851             targetMessage.$message.attr('targettable', queryData.table);
852             targetMessage.$message.find('.text.targetdb span').text(queryData.db);
853         }
854         if (Console.isSelect(queryData.sql_query)) {
855             targetMessage.$message.addClass('select');
856         }
857         switch (state) {
858         case 'failed':
859             targetMessage.$message.addClass('failed');
860             break;
861         case 'successed':
862             targetMessage.$message.addClass('successed');
863             break;
864         default:
865         case 'pending':
866             targetMessage.$message.addClass('pending');
867         }
868         return targetMessage;
869     },
870     messageEventBinds: function ($target) {
871         // Leave unbinded elements, remove binded.
872         var $targetMessage = $target.filter(':not(.binded)');
873         if ($targetMessage.length === 0) {
874             return;
875         }
876         $targetMessage.addClass('binded');
878         $targetMessage.find('.action.expand').on('click', function () {
879             $(this).closest('.message').removeClass('collapsed');
880             $(this).closest('.message').addClass('expanded');
881         });
882         $targetMessage.find('.action.collapse').on('click', function () {
883             $(this).closest('.message').addClass('collapsed');
884             $(this).closest('.message').removeClass('expanded');
885         });
886         $targetMessage.find('.action.edit').on('click', function () {
887             ConsoleInput.setText($(this).parent().siblings('.query').text());
888             ConsoleInput.focus();
889         });
890         $targetMessage.find('.action.requery').on('click', function () {
891             var query = $(this).parent().siblings('.query').text();
892             var $message = $(this).closest('.message');
893             if (confirm(Messages.strConsoleRequeryConfirm + '\n' +
894                 (query.length < 100 ? query : query.slice(0, 100) + '...'))
895             ) {
896                 Console.execute(query, { db: $message.attr('targetdb'), table: $message.attr('targettable') });
897             }
898         });
899         $targetMessage.find('.action.bookmark').on('click', function () {
900             var query = $(this).parent().siblings('.query').text();
901             var $message = $(this).closest('.message');
902             ConsoleBookmarks.addBookmark(query, $message.attr('targetdb'));
903             Console.showCard('#pma_bookmarks .card.add');
904         });
905         $targetMessage.find('.action.edit_bookmark').on('click', function () {
906             var query = $(this).parent().siblings('.query').text();
907             var $message = $(this).closest('.message');
908             var isShared = $message.find('span.bookmark_label').hasClass('shared');
909             var label = $message.find('span.bookmark_label').text();
910             ConsoleBookmarks.addBookmark(query, $message.attr('targetdb'), label, isShared);
911             Console.showCard('#pma_bookmarks .card.add');
912         });
913         $targetMessage.find('.action.delete_bookmark').on('click', function () {
914             var $message = $(this).closest('.message');
915             if (confirm(Messages.strConsoleDeleteBookmarkConfirm + '\n' + $message.find('.bookmark_label').text())) {
916                 $.post('import.php',
917                     {
918                         'server': CommonParams.get('server'),
919                         'action_bookmark': 2,
920                         'ajax_request': true,
921                         'id_bookmark': $message.attr('bookmarkid')
922                     },
923                     function () {
924                         ConsoleBookmarks.refresh();
925                     });
926             }
927         });
928         $targetMessage.find('.action.profiling').on('click', function () {
929             var $message = $(this).closest('.message');
930             Console.execute($(this).parent().siblings('.query').text(),
931                 { db: $message.attr('targetdb'),
932                     table: $message.attr('targettable'),
933                     profiling: true });
934         });
935         $targetMessage.find('.action.explain').on('click', function () {
936             var $message = $(this).closest('.message');
937             Console.execute('EXPLAIN ' + $(this).parent().siblings('.query').text(),
938                 { db: $message.attr('targetdb'),
939                     table: $message.attr('targettable') });
940         });
941         $targetMessage.find('.action.dbg_show_trace').on('click', function () {
942             var $message = $(this).closest('.message');
943             if (!$message.find('.trace').length) {
944                 ConsoleDebug.getQueryDetails(
945                     $message.data('queryInfo'),
946                     $message.data('totalTime'),
947                     $message
948                 );
949                 ConsoleMessages.messageEventBinds($message.find('.message:not(.binded)'));
950             }
951             $message.addClass('show_trace');
952             $message.removeClass('hide_trace');
953         });
954         $targetMessage.find('.action.dbg_hide_trace').on('click', function () {
955             var $message = $(this).closest('.message');
956             $message.addClass('hide_trace');
957             $message.removeClass('show_trace');
958         });
959         $targetMessage.find('.action.dbg_show_args').on('click', function () {
960             var $message = $(this).closest('.message');
961             $message.addClass('show_args expanded');
962             $message.removeClass('hide_args collapsed');
963         });
964         $targetMessage.find('.action.dbg_hide_args').on('click', function () {
965             var $message = $(this).closest('.message');
966             $message.addClass('hide_args collapsed');
967             $message.removeClass('show_args expanded');
968         });
969         if (ConsoleInput.codeMirror) {
970             $targetMessage.find('.query:not(.highlighted)').each(function (index, elem) {
971                 CodeMirror.runMode($(elem).text(),
972                     'text/x-sql', elem);
973                 $(this).addClass('highlighted');
974             });
975         }
976     },
977     msgAppend: function (msgId, msgString) {
978         var $targetMessage = $('#pma_console').find('.content .console_message_container .message[msgid=' + msgId + ']');
979         if ($targetMessage.length === 0 || isNaN(parseInt(msgId)) || typeof(msgString) !== 'string') {
980             return false;
981         }
982         $targetMessage.append('<div>' + msgString + '</div>');
983     },
984     updateQuery: function (msgId, isSuccessed, queryData) {
985         var $targetMessage = $('#pma_console').find('.console_message_container .message[msgid=' + parseInt(msgId) + ']');
986         if ($targetMessage.length === 0 || isNaN(parseInt(msgId))) {
987             return false;
988         }
989         $targetMessage.removeClass('pending failed successed');
990         if (isSuccessed) {
991             $targetMessage.addClass('successed');
992             if (queryData) {
993                 $targetMessage.children('.query').text('');
994                 $targetMessage.removeClass('select');
995                 if (Console.isSelect(queryData.sql_query)) {
996                     $targetMessage.addClass('select');
997                 }
998                 if (ConsoleInput.codeMirror) {
999                     CodeMirror.runMode(queryData.sql_query, 'text/x-sql', $targetMessage.children('.query')[0]);
1000                 } else {
1001                     $targetMessage.children('.query').text(queryData.sql_query);
1002                 }
1003                 $targetMessage.attr('targetdb', queryData.db);
1004                 $targetMessage.attr('targettable', queryData.table);
1005                 $targetMessage.find('.text.targetdb span').text(queryData.db);
1006             }
1007         } else {
1008             $targetMessage.addClass('failed');
1009         }
1010     },
1011     /**
1012      * Used for console messages initialize
1013      *
1014      * @return void
1015      */
1016     initialize: function () {
1017         ConsoleMessages.messageEventBinds($('#pma_console').find('.message:not(.binded)'));
1018         if (Console.config.StartHistory) {
1019             ConsoleMessages.showHistory();
1020         }
1021         ConsoleMessages.showInstructions(Console.config.EnterExecutes);
1022     }
1026  * Console bookmarks card, and bookmarks items management object
1027  */
1028 var ConsoleBookmarks = {
1029     bookmarks: [],
1030     addBookmark: function (queryString, targetDb, label, isShared) {
1031         $('#pma_bookmarks').find('.add [name=shared]').prop('checked', false);
1032         $('#pma_bookmarks').find('.add [name=label]').val('');
1033         $('#pma_bookmarks').find('.add [name=targetdb]').val('');
1034         $('#pma_bookmarks').find('.add [name=id_bookmark]').val('');
1035         ConsoleInput.setText('', 'bookmark');
1037         if (typeof queryString !== 'undefined') {
1038             ConsoleInput.setText(queryString, 'bookmark');
1039         }
1040         if (typeof targetDb !== 'undefined') {
1041             $('#pma_bookmarks').find('.add [name=targetdb]').val(targetDb);
1042         }
1043         if (typeof label !== 'undefined') {
1044             $('#pma_bookmarks').find('.add [name=label]').val(label);
1045         }
1046         if (typeof isShared !== 'undefined') {
1047             $('#pma_bookmarks').find('.add [name=shared]').prop('checked', isShared);
1048         }
1049     },
1050     refresh: function () {
1051         $.get('import.php',
1052             {
1053                 'ajax_request': true,
1054                 'server': CommonParams.get('server'),
1055                 'console_bookmark_refresh': 'refresh'
1056             },
1057             function (data) {
1058                 if (data.console_message_bookmark) {
1059                     $('#pma_bookmarks').find('.content.bookmark').html(data.console_message_bookmark);
1060                     ConsoleMessages.messageEventBinds($('#pma_bookmarks').find('.message:not(.binded)'));
1061                 }
1062             });
1063     },
1064     /**
1065      * Used for console bookmarks initialize
1066      * message events are already binded by ConsoleMsg.messageEventBinds
1067      *
1068      * @return void
1069      */
1070     initialize: function () {
1071         if ($('#pma_bookmarks').length === 0) {
1072             return;
1073         }
1074         $('#pma_console').find('.button.bookmarks').on('click', function () {
1075             Console.showCard('#pma_bookmarks');
1076         });
1077         $('#pma_bookmarks').find('.button.add').on('click', function () {
1078             Console.showCard('#pma_bookmarks .card.add');
1079         });
1080         $('#pma_bookmarks').find('.card.add [name=submit]').on('click', function () {
1081             if ($('#pma_bookmarks').find('.card.add [name=label]').val().length === 0
1082                 || ConsoleInput.getText('bookmark').length === 0) {
1083                 alert(Messages.strFormEmpty);
1084                 return;
1085             }
1086             $(this).prop('disabled', true);
1087             $.post('import.php',
1088                 {
1089                     'ajax_request': true,
1090                     'console_bookmark_add': 'true',
1091                     'label': $('#pma_bookmarks').find('.card.add [name=label]').val(),
1092                     'server': CommonParams.get('server'),
1093                     'db': $('#pma_bookmarks').find('.card.add [name=targetdb]').val(),
1094                     'bookmark_query': ConsoleInput.getText('bookmark'),
1095                     'shared': $('#pma_bookmarks').find('.card.add [name=shared]').prop('checked')
1096                 },
1097                 function () {
1098                     ConsoleBookmarks.refresh();
1099                     $('#pma_bookmarks').find('.card.add [name=submit]').prop('disabled', false);
1100                     Console.hideCard($('#pma_bookmarks').find('.card.add'));
1101                 });
1102         });
1103         $('#pma_console').find('.button.refresh').on('click', function () {
1104             ConsoleBookmarks.refresh();
1105         });
1106     }
1109 var ConsoleDebug = {
1110     config: {
1111         groupQueries: false,
1112         orderBy: 'exec', // Possible 'exec' => Execution order, 'time' => Time taken, 'count'
1113         order: 'asc' // Possible 'asc', 'desc'
1114     },
1115     lastDebugInfo: {
1116         debugInfo: null,
1117         url: null
1118     },
1119     initialize: function () {
1120         // Try to get debug info after every AJAX request
1121         $(document).ajaxSuccess(function (event, xhr, settings, data) {
1122             if (data.debug) {
1123                 ConsoleDebug.showLog(data.debug, settings.url);
1124             }
1125         });
1127         if (Console.config.GroupQueries) {
1128             $('#debug_console').addClass('grouped');
1129         } else {
1130             $('#debug_console').addClass('ungrouped');
1131             if (Console.config.OrderBy === 'count') {
1132                 $('#debug_console').find('.button.order_by.sort_exec').addClass('active');
1133             }
1134         }
1135         var orderBy = Console.config.OrderBy;
1136         var order = Console.config.Order;
1137         $('#debug_console').find('.button.order_by.sort_' + orderBy).addClass('active');
1138         $('#debug_console').find('.button.order.order_' + order).addClass('active');
1140         // Initialize actions in toolbar
1141         $('#debug_console').find('.button.group_queries').on('click', function () {
1142             $('#debug_console').addClass('grouped');
1143             $('#debug_console').removeClass('ungrouped');
1144             Console.setConfig('GroupQueries', true);
1145             ConsoleDebug.refresh();
1146             if (Console.config.OrderBy === 'count') {
1147                 $('#debug_console').find('.button.order_by.sort_exec').removeClass('active');
1148             }
1149         });
1150         $('#debug_console').find('.button.ungroup_queries').on('click', function () {
1151             $('#debug_console').addClass('ungrouped');
1152             $('#debug_console').removeClass('grouped');
1153             Console.setConfig('GroupQueries', false);
1154             ConsoleDebug.refresh();
1155             if (Console.config.OrderBy === 'count') {
1156                 $('#debug_console').find('.button.order_by.sort_exec').addClass('active');
1157             }
1158         });
1159         $('#debug_console').find('.button.order_by').on('click', function () {
1160             var $this = $(this);
1161             $('#debug_console').find('.button.order_by').removeClass('active');
1162             $this.addClass('active');
1163             if ($this.hasClass('sort_time')) {
1164                 Console.setConfig('OrderBy', 'time');
1165             } else if ($this.hasClass('sort_exec')) {
1166                 Console.setConfig('OrderBy', 'exec');
1167             } else if ($this.hasClass('sort_count')) {
1168                 Console.setConfig('OrderBy', 'count');
1169             }
1170             ConsoleDebug.refresh();
1171         });
1172         $('#debug_console').find('.button.order').on('click', function () {
1173             var $this = $(this);
1174             $('#debug_console').find('.button.order').removeClass('active');
1175             $this.addClass('active');
1176             if ($this.hasClass('order_asc')) {
1177                 Console.setConfig('Order', 'asc');
1178             } else if ($this.hasClass('order_desc')) {
1179                 Console.setConfig('Order', 'desc');
1180             }
1181             ConsoleDebug.refresh();
1182         });
1184         // Show SQL debug info for first page load
1185         if (typeof debugSQLInfo !== 'undefined' && debugSQLInfo !== 'null') {
1186             $('#pma_console').find('.button.debug').removeClass('hide');
1187         } else {
1188             return;
1189         }
1190         ConsoleDebug.showLog(debugSQLInfo);
1191     },
1192     formatFunctionCall: function (dbgStep) {
1193         var functionName = '';
1194         if ('class' in dbgStep) {
1195             functionName += dbgStep.class;
1196             functionName += dbgStep.type;
1197         }
1198         functionName += dbgStep.function;
1199         if (dbgStep.args && dbgStep.args.length) {
1200             functionName += '(...)';
1201         } else {
1202             functionName += '()';
1203         }
1204         return functionName;
1205     },
1206     formatFunctionArgs: function (dbgStep) {
1207         var $args = $('<div>');
1208         if (dbgStep.args.length) {
1209             $args.append('<div class="message welcome">')
1210                 .append(
1211                     $('<div class="message welcome">')
1212                         .text(
1213                             Functions.sprintf(
1214                                 Messages.strConsoleDebugArgsSummary,
1215                                 dbgStep.args.length
1216                             )
1217                         )
1218                 );
1219             for (var i = 0; i < dbgStep.args.length; i++) {
1220                 $args.append(
1221                     $('<div class="message">')
1222                         .html(
1223                             '<pre>' +
1224                         Functions.escapeHtml(JSON.stringify(dbgStep.args[i], null, '  ')) +
1225                         '</pre>'
1226                         )
1227                 );
1228             }
1229         }
1230         return $args;
1231     },
1232     formatFileName: function (dbgStep) {
1233         var fileName = '';
1234         if ('file' in dbgStep) {
1235             fileName += dbgStep.file;
1236             fileName += '#' + dbgStep.line;
1237         }
1238         return fileName;
1239     },
1240     formatBackTrace: function (dbgTrace) {
1241         var $traceElem = $('<div class="trace">');
1242         $traceElem.append(
1243             $('<div class="message welcome">')
1244         );
1245         var step;
1246         var $stepElem;
1247         for (var stepId in dbgTrace) {
1248             if (dbgTrace.hasOwnProperty(stepId)) {
1249                 step = dbgTrace[stepId];
1250                 if (!Array.isArray(step) && typeof step !== 'object') {
1251                     $stepElem =
1252                         $('<div class="message traceStep collapsed hide_args">')
1253                             .append(
1254                                 $('<span>').text(step)
1255                             );
1256                 } else {
1257                     if (typeof step.args === 'string' && step.args) {
1258                         step.args = [step.args];
1259                     }
1260                     $stepElem =
1261                         $('<div class="message traceStep collapsed hide_args">')
1262                             .append(
1263                                 $('<span class="function">').text(this.formatFunctionCall(step))
1264                             )
1265                             .append(
1266                                 $('<span class="file">').text(this.formatFileName(step))
1267                             );
1268                     if (step.args && step.args.length) {
1269                         $stepElem
1270                             .append(
1271                                 $('<span class="args">').html(this.formatFunctionArgs(step))
1272                             )
1273                             .prepend(
1274                                 $('<div class="action_content">')
1275                                     .append(
1276                                         '<span class="action dbg_show_args">' +
1277                                 Messages.strConsoleDebugShowArgs +
1278                                 '</span> '
1279                                     )
1280                                     .append(
1281                                         '<span class="action dbg_hide_args">' +
1282                                 Messages.strConsoleDebugHideArgs +
1283                                 '</span> '
1284                                     )
1285                             );
1286                     }
1287                 }
1288                 $traceElem.append($stepElem);
1289             }
1290         }
1291         return $traceElem;
1292     },
1293     formatQueryOrGroup: function (queryInfo, totalTime) {
1294         var grouped;
1295         var queryText;
1296         var queryTime;
1297         var count;
1298         var i;
1299         if (Array.isArray(queryInfo)) {
1300             // It is grouped
1301             grouped = true;
1303             queryText = queryInfo[0].query;
1305             queryTime = 0;
1306             for (i in queryInfo) {
1307                 queryTime += queryInfo[i].time;
1308             }
1310             count = queryInfo.length;
1311         } else {
1312             queryText = queryInfo.query;
1313             queryTime = queryInfo.time;
1314         }
1316         var $query = $('<div class="message collapsed hide_trace">')
1317             .append(
1318                 $('#debug_console').find('.templates .debug_query').clone()
1319             )
1320             .append(
1321                 $('<div class="query">')
1322                     .text(queryText)
1323             )
1324             .data('queryInfo', queryInfo)
1325             .data('totalTime', totalTime);
1326         if (grouped) {
1327             $query.find('.text.count').removeClass('hide');
1328             $query.find('.text.count span').text(count);
1329         }
1330         $query.find('.text.time span').text(queryTime + 's (' + ((queryTime * 100) / totalTime).toFixed(3) + '%)');
1332         return $query;
1333     },
1334     appendQueryExtraInfo: function (query, $elem) {
1335         if ('error' in query) {
1336             $elem.append(
1337                 $('<div>').html(query.error)
1338             );
1339         }
1340         $elem.append(this.formatBackTrace(query.trace));
1341     },
1342     getQueryDetails: function (queryInfo, totalTime, $query) {
1343         if (Array.isArray(queryInfo)) {
1344             var $singleQuery;
1345             for (var i in queryInfo) {
1346                 $singleQuery = $('<div class="message welcome trace">')
1347                     .text((parseInt(i) + 1) + '.')
1348                     .append(
1349                         $('<span class="time">').text(
1350                             Messages.strConsoleDebugTimeTaken +
1351                         ' ' + queryInfo[i].time + 's' +
1352                         ' (' + ((queryInfo[i].time * 100) / totalTime).toFixed(3) + '%)'
1353                         )
1354                     );
1355                 this.appendQueryExtraInfo(queryInfo[i], $singleQuery);
1356                 $query
1357                     .append('<div class="message welcome trace">')
1358                     .append($singleQuery);
1359             }
1360         } else {
1361             this.appendQueryExtraInfo(queryInfo, $query);
1362         }
1363     },
1364     showLog: function (debugInfo, url) {
1365         this.lastDebugInfo.debugInfo = debugInfo;
1366         this.lastDebugInfo.url = url;
1368         $('#debug_console').find('.debugLog').empty();
1369         $('#debug_console').find('.debug>.welcome').empty();
1371         var debugJson = false;
1372         var i;
1373         if (typeof debugInfo === 'object' && 'queries' in debugInfo) {
1374             // Copy it to debugJson, so that it doesn't get changed
1375             if (!('queries' in debugInfo)) {
1376                 debugJson = false;
1377             } else {
1378                 debugJson = { queries: [] };
1379                 for (i in debugInfo.queries) {
1380                     debugJson.queries[i] = debugInfo.queries[i];
1381                 }
1382             }
1383         } else if (typeof debugInfo === 'string') {
1384             try {
1385                 debugJson = JSON.parse(debugInfo);
1386             } catch (e) {
1387                 debugJson = false;
1388             }
1389             if (debugJson && !('queries' in debugJson)) {
1390                 debugJson = false;
1391             }
1392         }
1393         if (debugJson === false) {
1394             $('#debug_console').find('.debug>.welcome').text(
1395                 Messages.strConsoleDebugError
1396             );
1397             return;
1398         }
1399         var allQueries = debugJson.queries;
1400         var uniqueQueries = {};
1402         var totalExec = allQueries.length;
1404         // Calculate total time and make unique query array
1405         var totalTime = 0;
1406         for (i = 0; i < totalExec; ++i) {
1407             totalTime += allQueries[i].time;
1408             if (!(allQueries[i].hash in uniqueQueries)) {
1409                 uniqueQueries[allQueries[i].hash] = [];
1410             }
1411             uniqueQueries[allQueries[i].hash].push(allQueries[i]);
1412         }
1413         // Count total unique queries, convert uniqueQueries to Array
1414         var totalUnique = 0;
1415         var uniqueArray = [];
1416         for (var hash in uniqueQueries) {
1417             if (uniqueQueries.hasOwnProperty(hash)) {
1418                 ++totalUnique;
1419                 uniqueArray.push(uniqueQueries[hash]);
1420             }
1421         }
1422         uniqueQueries = uniqueArray;
1423         // Show summary
1424         $('#debug_console').find('.debug>.welcome').append(
1425             $('<span class="debug_summary">').text(
1426                 Functions.sprintf(
1427                     Messages.strConsoleDebugSummary,
1428                     totalUnique,
1429                     totalExec,
1430                     totalTime
1431                 )
1432             )
1433         );
1434         if (url) {
1435             $('#debug_console').find('.debug>.welcome').append(
1436                 $('<span class="script_name">').text(url.split('?')[0])
1437             );
1438         }
1440         // For sorting queries
1441         function sortByTime (a, b) {
1442             var order = ((Console.config.Order === 'asc') ? 1 : -1);
1443             if (Array.isArray(a) && Array.isArray(b)) {
1444                 // It is grouped
1445                 var timeA = 0;
1446                 var timeB = 0;
1447                 var i;
1448                 for (i in a) {
1449                     timeA += a[i].time;
1450                 }
1451                 for (i in b) {
1452                     timeB += b[i].time;
1453                 }
1454                 return (timeA - timeB) * order;
1455             } else {
1456                 return (a.time - b.time) * order;
1457             }
1458         }
1460         function sortByCount (a, b) {
1461             var order = ((Console.config.Oorder === 'asc') ? 1 : -1);
1462             return (a.length - b.length) * order;
1463         }
1465         var orderBy = Console.config.OrderBy;
1466         var order = Console.config.Order;
1468         if (Console.config.GroupQueries) {
1469             // Sort queries
1470             if (orderBy === 'time') {
1471                 uniqueQueries.sort(sortByTime);
1472             } else if (orderBy === 'count') {
1473                 uniqueQueries.sort(sortByCount);
1474             } else if (orderBy === 'exec' && order === 'desc') {
1475                 uniqueQueries.reverse();
1476             }
1477             for (i in uniqueQueries) {
1478                 if (orderBy === 'time') {
1479                     uniqueQueries[i].sort(sortByTime);
1480                 } else if (orderBy === 'exec' && order === 'desc') {
1481                     uniqueQueries[i].reverse();
1482                 }
1483                 $('#debug_console').find('.debugLog').append(this.formatQueryOrGroup(uniqueQueries[i], totalTime));
1484             }
1485         } else {
1486             if (orderBy === 'time') {
1487                 allQueries.sort(sortByTime);
1488             } else if (order === 'desc') {
1489                 allQueries.reverse();
1490             }
1491             for (i = 0; i < totalExec; ++i) {
1492                 $('#debug_console').find('.debugLog').append(this.formatQueryOrGroup(allQueries[i], totalTime));
1493             }
1494         }
1496         ConsoleMessages.messageEventBinds($('#debug_console').find('.message:not(.binded)'));
1497     },
1498     refresh: function () {
1499         var last = this.lastDebugInfo;
1500         ConsoleDebug.showLog(last.debugInfo, last.url);
1501     }
1504 /** s
1505  * Executed on page load
1506  */
1507 $(function () {
1508     Console.initialize();