/* vim: set expandtab sw=4 ts=4 sts=4: */ /** * general function, usually for data manipulation pages * */ /** * @var sql_box_locked lock for the sqlbox textarea in the querybox */ var sql_box_locked = false; /** * @var array holds elements which content should only selected once */ var only_once_elements = []; /** * @var int ajax_message_count Number of AJAX messages shown since page load */ var ajax_message_count = 0; /** * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab */ var codemirror_editor = false; /** * @var codemirror_editor object containing CodeMirror editor of the inline query editor */ var codemirror_inline_editor = false; /** * @var sql_autocomplete_in_progress bool shows if Table/Column name autocomplete AJAX is in progress */ var sql_autocomplete_in_progress = false; /** * @var sql_autocomplete object containing list of columns in each table */ var sql_autocomplete = false; /** * @var sql_autocomplete_default_table string containing default table to autocomplete columns */ var sql_autocomplete_default_table = ''; /** * @var central_column_list array to hold the columns in central list per db. */ var central_column_list = []; /** * @var primary_indexes array to hold 'Primary' index columns. */ var primary_indexes = []; /** * @var unique_indexes array to hold 'Unique' index columns. */ var unique_indexes = []; /** * @var indexes array to hold 'Index' columns. */ var indexes = []; /** * @var fulltext_indexes array to hold 'Fulltext' columns. */ var fulltext_indexes = []; /** * @var spatial_indexes array to hold 'Spatial' columns. */ var spatial_indexes = []; /** * Make sure that ajax requests will not be cached * by appending a random variable to their parameters */ $.ajaxPrefilter(function (options, originalOptions, jqXHR) { var nocache = new Date().getTime() + '' + Math.floor(Math.random() * 1000000); if (typeof options.data === 'string') { options.data += '&_nocache=' + nocache + '&token=' + encodeURIComponent(PMA_commonParams.get('token')); } else if (typeof options.data === 'object') { options.data = $.extend(originalOptions.data, { '_nocache' : nocache, 'token': PMA_commonParams.get('token') }); } }); /* * Adds a date/time picker to an element * * @param object $this_element a jQuery object pointing to the element */ function PMA_addDatepicker ($this_element, type, options) { var showTimepicker = true; if (type === 'date') { showTimepicker = false; } var defaultOptions = { showOn: 'button', buttonImage: themeCalendarImage, // defined in js/messages.php buttonImageOnly: true, stepMinutes: 1, stepHours: 1, showSecond: true, showMillisec: true, showMicrosec: true, showTimepicker: showTimepicker, showButtonPanel: false, dateFormat: 'yy-mm-dd', // yy means year with four digits timeFormat: 'HH:mm:ss.lc', constrainInput: false, altFieldTimeOnly: false, showAnim: '', beforeShow: function (input, inst) { // Remember that we came from the datepicker; this is used // in tbl_change.js by verificationsAfterFieldChange() $this_element.data('comes_from', 'datepicker'); if ($(input).closest('.cEdit').length > 0) { setTimeout(function () { inst.dpDiv.css({ top: 0, left: 0, position: 'relative' }); }, 0); } setTimeout(function () { // Fix wrong timepicker z-index, doesn't work without timeout $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index')); // Integrate tooltip text into dialog var tooltip = $this_element.tooltip('instance'); if (typeof tooltip !== 'undefined') { tooltip.disable(); var $note = $('

'); $note.text(tooltip.option('content')); $('div.ui-datepicker').append($note); } }, 0); }, onSelect: function () { $this_element.data('datepicker').inline = true; }, onClose: function (dateText, dp_inst) { // The value is no more from the date picker $this_element.data('comes_from', ''); if (typeof $this_element.data('datepicker') !== 'undefined') { $this_element.data('datepicker').inline = false; } var tooltip = $this_element.tooltip('instance'); if (typeof tooltip !== 'undefined') { tooltip.enable(); } } }; if (type == "time") { $this_element.timepicker($.extend(defaultOptions, options)); // Add a tip regarding entering MySQL allowed-values for TIME data-type PMA_tooltip($this_element, 'input', PMA_messages.strMysqlAllowedValuesTipTime); } else { $this_element.datetimepicker($.extend(defaultOptions, options)); } } /** * Add a date/time picker to each element that needs it * (only when jquery-ui-timepicker-addon.js is loaded) */ function addDateTimePicker () { if ($.timepicker !== undefined) { $('input.timefield, input.datefield, input.datetimefield').each(function () { var decimals = $(this).parent().attr('data-decimals'); var type = $(this).parent().attr('data-type'); var showMillisec = false; var showMicrosec = false; var timeFormat = 'HH:mm:ss'; var hourMax = 23; // check for decimal places of seconds if (decimals > 0 && type.indexOf('time') !== -1) { if (decimals > 3) { showMillisec = true; showMicrosec = true; timeFormat = 'HH:mm:ss.lc'; } else { showMillisec = true; timeFormat = 'HH:mm:ss.l'; } } if (type === 'time') { hourMax = 99; } PMA_addDatepicker($(this), type, { showMillisec: showMillisec, showMicrosec: showMicrosec, timeFormat: timeFormat, hourMax: hourMax }); // Add a tip regarding entering MySQL allowed-values // for TIME and DATE data-type if ($(this).hasClass('timefield')) { PMA_tooltip($(this), 'input', PMA_messages.strMysqlAllowedValuesTipTime); } else if ($(this).hasClass('datefield')) { PMA_tooltip($(this), 'input', PMA_messages.strMysqlAllowedValuesTipDate); } }); } } /** * Handle redirect and reload flags sent as part of AJAX requests * * @param data ajax response data */ function PMA_handleRedirectAndReload (data) { if (parseInt(data.redirect_flag) === 1) { // add one more GET param to display session expiry msg if (window.location.href.indexOf('?') === -1) { window.location.href += '?session_expired=1'; } else { window.location.href += PMA_commonParams.get('arg_separator') + 'session_expired=1'; } window.location.reload(); } else if (parseInt(data.reload_flag) === 1) { window.location.reload(); } } /** * Creates an SQL editor which supports auto completing etc. * * @param $textarea jQuery object wrapping the textarea to be made the editor * @param options optional options for CodeMirror * @param resize optional resizing ('vertical', 'horizontal', 'both') * @param lintOptions additional options for lint */ function PMA_getSQLEditor ($textarea, options, resize, lintOptions) { if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') { // merge options for CodeMirror var defaults = { lineNumbers: true, matchBrackets: true, extraKeys: { 'Ctrl-Space': 'autocomplete' }, hintOptions: { 'completeSingle': false, 'completeOnSingleClick': true }, indentUnit: 4, mode: 'text/x-mysql', lineWrapping: true }; if (CodeMirror.sqlLint) { $.extend(defaults, { gutters: ['CodeMirror-lint-markers'], lint: { 'getAnnotations': CodeMirror.sqlLint, 'async': true, 'lintOptions': lintOptions } }); } $.extend(true, defaults, options); // create CodeMirror editor var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults); // allow resizing if (! resize) { resize = 'vertical'; } var handles = ''; if (resize === 'vertical') { handles = 's'; } if (resize === 'both') { handles = 'all'; } if (resize === 'horizontal') { handles = 'e, w'; } $(codemirrorEditor.getWrapperElement()) .css('resize', resize) .resizable({ handles: handles, resize: function () { codemirrorEditor.setSize($(this).width(), $(this).height()); } }); // enable autocomplete codemirrorEditor.on('inputRead', codemirrorAutocompleteOnInputRead); // page locking codemirrorEditor.on('change', function (e) { e.data = { value: 3, content: codemirrorEditor.isClean(), }; AJAX.lockPageHandler(e); }); return codemirrorEditor; } return null; } /** * Clear text selection */ function PMA_clearSelection () { if (document.selection && document.selection.empty) { document.selection.empty(); } else if (window.getSelection) { var sel = window.getSelection(); if (sel.empty) { sel.empty(); } if (sel.removeAllRanges) { sel.removeAllRanges(); } } } /** * Create a jQuery UI tooltip * * @param $elements jQuery object representing the elements * @param item the item * (see https://api.jqueryui.com/tooltip/#option-items) * @param myContent content of the tooltip * @param additionalOptions to override the default options * */ function PMA_tooltip ($elements, item, myContent, additionalOptions) { if ($('#no_hint').length > 0) { return; } var defaultOptions = { content: myContent, items: item, tooltipClass: 'tooltip', track: true, show: false, hide: false }; $elements.tooltip($.extend(true, defaultOptions, additionalOptions)); } /** * HTML escaping */ function escapeHtml (unsafe) { if (typeof(unsafe) !== 'undefined') { return unsafe .toString() .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } else { return false; } } function escapeJsString (unsafe) { if (typeof(unsafe) !== 'undefined') { return unsafe .toString() .replace('\x00', '') .replace('\\', '\\\\') .replace('\'', '\\\'') .replace(''', '\\\'') .replace('"', '\"') .replace('"', '\"') .replace('\n', '\n') .replace('\r', '\r') .replace(/<\/script/gi, ''); } } /** * Generate a new password and copy it to the password input areas * * @param passwd_form object the form that holds the password fields * * @return boolean always true */ function suggestPassword (passwd_form) { // restrict the password to just letters and numbers to avoid problems: // "editors and viewers regard the password as multiple words and // things like double click no longer work" var pwchars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ'; var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :) var passwd = passwd_form.generated_pw; var randomWords = new Int32Array(passwordlength); passwd.value = ''; // First we're going to try to use a built-in CSPRNG if (window.crypto && window.crypto.getRandomValues) { window.crypto.getRandomValues(randomWords); } else if (window.msCrypto && window.msCrypto.getRandomValues) { // Because of course IE calls it msCrypto instead of being standard window.msCrypto.getRandomValues(randomWords); } else { // Fallback to Math.random for (var i = 0; i < passwordlength; i++) { randomWords[i] = Math.floor(Math.random() * pwchars.length); } } for (var i = 0; i < passwordlength; i++) { passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length); } $jquery_passwd_form = $(passwd_form); passwd_form.elements.pma_pw.value = passwd.value; passwd_form.elements.pma_pw2.value = passwd.value; meter_obj = $jquery_passwd_form.find('meter[name="pw_meter"]').first(); meter_obj_label = $jquery_passwd_form.find('span[name="pw_strength"]').first(); checkPasswordStrength(passwd.value, meter_obj, meter_obj_label); return true; } /** * Version string to integer conversion. */ function parseVersionString (str) { if (typeof(str) !== 'string') { return false; } var add = 0; // Parse possible alpha/beta/rc/ var state = str.split('-'); if (state.length >= 2) { if (state[1].substr(0, 2) === 'rc') { add = - 20 - parseInt(state[1].substr(2), 10); } else if (state[1].substr(0, 4) === 'beta') { add = - 40 - parseInt(state[1].substr(4), 10); } else if (state[1].substr(0, 5) === 'alpha') { add = - 60 - parseInt(state[1].substr(5), 10); } else if (state[1].substr(0, 3) === 'dev') { /* We don't handle dev, it's git snapshot */ add = 0; } } // Parse version var x = str.split('.'); // Use 0 for non existing parts var maj = parseInt(x[0], 10) || 0; var min = parseInt(x[1], 10) || 0; var pat = parseInt(x[2], 10) || 0; var hotfix = parseInt(x[3], 10) || 0; return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add; } /** * Indicates current available version on main page. */ function PMA_current_version (data) { if (data && data.version && data.date) { var current = parseVersionString($('span.version').text()); var latest = parseVersionString(data.version); var url = 'https://www.phpmyadmin.net/files/' + escapeHtml(encodeURIComponent(data.version)) + '/'; var version_information_message = document.createElement('span'); version_information_message.className = 'latest'; var version_information_message_link = document.createElement('a'); version_information_message_link.href = url; version_information_message_link.className = 'disableAjax'; version_information_message_link_text = document.createTextNode(data.version); version_information_message_link.appendChild(version_information_message_link_text); var prefix_message = document.createTextNode(PMA_messages.strLatestAvailable + ' '); version_information_message.appendChild(prefix_message); version_information_message.appendChild(version_information_message_link); if (latest > current) { var message = PMA_sprintf( PMA_messages.strNewerVersion, escapeHtml(data.version), escapeHtml(data.date) ); var htmlClass = 'notice'; if (Math.floor(latest / 10000) === Math.floor(current / 10000)) { /* Security update */ htmlClass = 'error'; } $('#newer_version_notice').remove(); var maincontainer_div = document.createElement('div'); maincontainer_div.id = 'newer_version_notice'; maincontainer_div.className = htmlClass; var maincontainer_div_link = document.createElement('a'); maincontainer_div_link.href = url; maincontainer_div_link.className = 'disableAjax'; maincontainer_div_link_text = document.createTextNode(message); maincontainer_div_link.appendChild(maincontainer_div_link_text); maincontainer_div.appendChild(maincontainer_div_link); $('#maincontainer').append($(maincontainer_div)); } if (latest === current) { version_information_message = document.createTextNode(' (' + PMA_messages.strUpToDate + ')'); } /* Remove extra whitespace */ var version_info = $('#li_pma_version').contents().get(2); version_info.textContent = $.trim(version_info.textContent); var $liPmaVersion = $('#li_pma_version'); $liPmaVersion.find('span.latest').remove(); $liPmaVersion.append($(version_information_message)); } } /** * Loads Git revision data from ajax for index.php */ function PMA_display_git_revision () { $('#is_git_revision').remove(); $('#li_pma_version_git').remove(); $.get( 'index.php', { 'server': PMA_commonParams.get('server'), 'git_revision': true, 'ajax_request': true, 'no_debug': true }, function (data) { if (typeof data !== 'undefined' && data.success === true) { $(data.message).insertAfter('#li_pma_version'); } } ); } /** * for PhpMyAdmin\Display\ChangePassword * libraries/user_password.php * */ function displayPasswordGenerateButton () { var generatePwdRow = $('').addClass('vmiddle'); var titleCell = $('').html(PMA_messages.strGeneratePassword).appendTo(generatePwdRow); var pwdCell = $('').appendTo(generatePwdRow); var pwdButton = $('') .attr({ type: 'button', id: 'button_generate_password', value: PMA_messages.strGenerate }) .addClass('button') .on('click', function () { suggestPassword(this.form); }); var pwdTextbox = $('') .attr({ type: 'text', name: 'generated_pw', id: 'generated_pw' }); pwdCell.append(pwdButton).append(pwdTextbox); $('#tr_element_before_generate_password').parent().append(generatePwdRow); var generatePwdDiv = $('

').addClass('item'); var titleLabel = $('