Passed
Push — master ( 771cdd...c0fb5f )
by Maurício
07:44
created

js/functions.js (1 issue)

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