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