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