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