Passed
Push — master ( 003da0...69fb1d )
by William
07:21
created

js/functions.js (2 issues)

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