/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* general function, usually for data manipulation pages
*
*/
/**
* @var $table_clone reference to the action links on the tbl_structure page
*/
var $table_clone = false;
/**
* @var sql_box_locked lock for the sqlbox textarea in the querybox/querywindow
*/
var sql_box_locked = false;
/**
* @var array holds elements which content should only selected once
*/
var only_once_elements = [];
/**
* @var int ajax_message_count Number of AJAX messages shown since page load
*/
var ajax_message_count = 0;
/**
* @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab
*/
var codemirror_editor = false;
/**
* @var codemirror_editor object containing CodeMirror editor of the inline query editor
*/
var codemirror_inline_editor = false;
/**
* @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests
*/
var chart_activeTimeouts = {};
/**
* Make sure that ajax requests will not be cached
* by appending a random variable to their parameters
*/
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000);
if (typeof options.data == "string") {
options.data += "&_nocache=" + nocache;
} else if (typeof options.data == "object") {
options.data = $.extend(originalOptions.data, {'_nocache':nocache});
}
});
/**
* Add a hidden field to the form to indicate that this will be an
* Ajax request (only if this hidden field does not exist)
*
* @param object the form
*/
function PMA_prepareForAjaxRequest($form)
{
if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
$form.append('');
}
}
/**
* Generate a new password and copy it to the password input areas
*
* @param object the form that holds the password fields
*
* @return boolean always true
*/
function suggestPassword(passwd_form)
{
// restrict the password to just letters and numbers to avoid problems:
// "editors and viewers regard the password as multiple words and
// things like double click no longer work"
var pwchars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ";
var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :)
var passwd = passwd_form.generated_pw;
var randomWords = new Int32Array(passwordlength);
passwd.value = '';
// First we're going to try to use a built-in CSPRNG
if (window.crypto && window.crypto.getRandomValues) {
window.crypto.getRandomValues(randomWords);
}
// Because of course IE calls it msCrypto instead of being standard
else if (window.msCrypto && window.msCrypto.getRandomValues) {
window.msCrypto.getRandomValues(randomWords);
} else {
// Fallback to Math.random
for (var i = 0; i < passwordlength; i++) {
randomWords[i] = Math.floor(Math.random() * pwchars.length);
}
}
for (var i = 0; i < passwordlength; i++) {
passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
}
passwd_form.text_pma_pw.value = passwd.value;
passwd_form.text_pma_pw2.value = passwd.value;
return true;
}
/**
* Version string to integer conversion.
*/
function parseVersionString (str)
{
if (typeof(str) != 'string') { return false; }
var add = 0;
// Parse possible alpha/beta/rc/
var state = str.split('-');
if (state.length >= 2) {
if (state[1].substr(0, 2) == 'rc') {
add = - 20 - parseInt(state[1].substr(2));
} else if (state[1].substr(0, 4) == 'beta') {
add = - 40 - parseInt(state[1].substr(4));
} else if (state[1].substr(0, 5) == 'alpha') {
add = - 60 - parseInt(state[1].substr(5));
} else if (state[1].substr(0, 3) == 'dev') {
/* We don't handle dev, it's git snapshot */
add = 0;
}
}
// Parse version
var x = str.split('.');
// Use 0 for non existing parts
var maj = parseInt(x[0]) || 0;
var min = parseInt(x[1]) || 0;
var pat = parseInt(x[2]) || 0;
var hotfix = parseInt(x[3]) || 0;
return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
}
/**
* Indicates current available version on main page.
*/
function PMA_current_version(data)
{
if (data && data.version && data.date) {
var current = parseVersionString(pmaversion);
var latest = parseVersionString(data['version']);
var version_information_message = PMA_messages['strLatestAvailable'] + ' ' + escapeHtml(data['version']);
if (latest > current) {
var message = $.sprintf(
PMA_messages['strNewerVersion'],
escapeHtml(data['version']),
escapeHtml(data['date'])
);
if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
/* Security update */
var klass = 'error';
} else {
var klass = 'notice';
}
$('#maincontainer').after('
' + message + '
');
}
if (latest === current) {
version_information_message = ' (' + PMA_messages['strUpToDate'] + ')';
}
$('#li_pma_version').append(version_information_message);
}
}
/**
* Loads Git revision data from ajax for index.php
*/
function PMA_display_git_revision()
{
$('#is_git_revision').remove();
$.get(
"index.php",
{
"server": PMA_commonParams.get('server'),
"token": PMA_commonParams.get('token'),
"git_revision": true,
"ajax_request": true
},
function (data) {
if (data.success == true) {
$(data.message).insertAfter('#li_pma_version');
}
}
);
}
/**
* for libraries/display_change_password.lib.php
* libraries/user_password.php
*
*/
function displayPasswordGenerateButton()
{
$('#tr_element_before_generate_password').parent().append('
');
}
/*
* Adds a date/time picker to an element
*
* @param object $this_element a jQuery object pointing to the element
*/
function PMA_addDatepicker($this_element, options)
{
var showTimeOption = false;
if ($this_element.is('.datetimefield')) {
showTimeOption = true;
}
var defaultOptions = {
showOn: 'button',
buttonImage: themeCalendarImage, // defined in js/messages.php
buttonImageOnly: true,
stepMinutes: 1,
stepHours: 1,
showSecond: true,
showTimepicker: showTimeOption,
showButtonPanel: false,
dateFormat: 'yy-mm-dd', // yy means year with four digits
timeFormat: 'HH:mm:ss',
altFieldTimeOnly: false,
showAnim: '',
beforeShow: function(input, inst) {
// Remember that we came from the datepicker; this is used
// in tbl_change.js by verificationsAfterFieldChange()
$this_element.data('comes_from', 'datepicker');
// Fix wrong timepicker z-index, doesn't work without timeout
setTimeout(function() {
$('#ui-timepicker-div').css('z-index',$('#ui-datepicker-div').css('z-index'));
}, 0);
},
onClose: function(dateText, dp_inst) {
// The value is no more from the date picker
$this_element.data('comes_from', '');
}
};
if ( showTimeOption || (typeof(options) != 'undefined' && options.showTimepicker) ) {
$this_element.datetimepicker($.extend(defaultOptions, options));
} else {
$this_element.datepicker($.extend(defaultOptions, options));
}
}
/**
* selects the content of a given object, f.e. a textarea
*
* @param object element element of which the content will be selected
* @param var lock variable which holds the lock for this element
* or true, if no lock exists
* @param boolean only_once if true this is only done once
* f.e. only on first focus
*/
function selectContent( element, lock, only_once )
{
if ( only_once && only_once_elements[element.name] ) {
return;
}
only_once_elements[element.name] = true;
if ( lock ) {
return;
}
element.select();
}
/**
* Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
* This function is called while clicking links
*
* @param object the link
* @param object the sql query to submit
*
* @return boolean whether to run the query or not
*/
function confirmLink(theLink, theSqlQuery)
{
// Confirmation is not required in the configuration file
// or browser is Opera (crappy js implementation)
if (PMA_messages['strDoYouReally'] == '' || typeof(window.opera) != 'undefined') {
return true;
}
var is_confirmed = confirm($.sprintf(PMA_messages['strDoYouReally'], theSqlQuery));
if (is_confirmed) {
if ( $(theLink).hasClass('formLinkSubmit') ) {
var name = 'is_js_confirmed';
if ($(theLink).attr('href').indexOf('usesubform') != -1) {
name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]';
}
$(theLink).parents('form').append('');
} else if ( typeof(theLink.href) != 'undefined' ) {
theLink.href += '&is_js_confirmed=1';
} else if ( typeof(theLink.form) != 'undefined' ) {
theLink.form.action += '?is_js_confirmed=1';
}
}
return is_confirmed;
} // end of the 'confirmLink()' function
/**
* Displays an error message if a "DROP DATABASE" statement is submitted
* while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before
* sumitting it if required.
* This function is called by the 'checkSqlQuery()' js function.
*
* @param object the form
* @param object the sql query textarea
*
* @return boolean whether to run the query or not
*
* @see checkSqlQuery()
*/
function confirmQuery(theForm1, sqlQuery1)
{
// Confirmation is not required in the configuration file
if (PMA_messages['strDoYouReally'] == '') {
return true;
}
// "DROP DATABASE" statement isn't allowed
if (PMA_messages['strNoDropDatabases'] != '') {
var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i');
if (drop_re.test(sqlQuery1.value)) {
alert(PMA_messages['strNoDropDatabases']);
theForm1.reset();
sqlQuery1.focus();
return false;
} // end if
} // end if
// Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
//
// TODO: find a way (if possible) to use the parser-analyser
// for this kind of verification
// For now, I just added a ^ to check for the statement at
// beginning of expression
var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i');
var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
if (do_confirm_re_0.test(sqlQuery1.value)
|| do_confirm_re_1.test(sqlQuery1.value)
|| do_confirm_re_2.test(sqlQuery1.value)
|| do_confirm_re_3.test(sqlQuery1.value)) {
var message = (sqlQuery1.value.length > 100)
? sqlQuery1.value.substr(0, 100) + '\n ...'
: sqlQuery1.value;
var is_confirmed = confirm($.sprintf(PMA_messages['strDoYouReally'], message));
// statement is confirmed -> update the
// "is_js_confirmed" form field so the confirm test won't be
// run on the server side and allows to submit the form
if (is_confirmed) {
theForm1.elements['is_js_confirmed'].value = 1;
return true;
}
// statement is rejected -> do not submit the form
else {
window.focus();
sqlQuery1.focus();
return false;
} // end if (handle confirm box result)
} // end if (display confirm box)
return true;
} // end of the 'confirmQuery()' function
/**
* Displays an error message if the user submitted the sql query form with no
* sql query, else checks for "DROP/DELETE/ALTER" statements
*
* @param object the form
*
* @return boolean always false
*
* @see confirmQuery()
*/
function checkSqlQuery(theForm)
{
// First check if codemirror is active.
if (codemirror_editor) {
theForm.elements['sql_query'].value = codemirror_editor.getValue();
}
var sqlQuery = theForm.elements['sql_query'];
var isEmpty = 1;
var space_re = new RegExp('\\s+');
if (typeof(theForm.elements['sql_file']) != 'undefined' &&
theForm.elements['sql_file'].value.replace(space_re, '') != '') {
return true;
}
if (typeof(theForm.elements['sql_localfile']) != 'undefined' &&
theForm.elements['sql_localfile'].value.replace(space_re, '') != '') {
return true;
}
if (isEmpty && typeof(theForm.elements['id_bookmark']) != 'undefined' &&
(theForm.elements['id_bookmark'].value != null || theForm.elements['id_bookmark'].value != '') &&
theForm.elements['id_bookmark'].selectedIndex != 0
) {
return true;
}
// Checks for "DROP/DELETE/ALTER" statements
if (sqlQuery.value.replace(space_re, '') != '') {
if (confirmQuery(theForm, sqlQuery)) {
return true;
} else {
return false;
}
}
theForm.reset();
isEmpty = 1;
if (isEmpty) {
sqlQuery.select();
alert(PMA_messages['strFormEmpty']);
sqlQuery.focus();
return false;
}
return true;
} // end of the 'checkSqlQuery()' function
/**
* Check if a form's element is empty.
* An element containing only spaces is also considered empty
*
* @param object the form
* @param string the name of the form field to put the focus on
*
* @return boolean whether the form field is empty or not
*/
function emptyCheckTheField(theForm, theFieldName)
{
var theField = theForm.elements[theFieldName];
var space_re = new RegExp('\\s+');
return (theField.value.replace(space_re, '') == '') ? 1 : 0;
} // end of the 'emptyCheckTheField()' function
/**
* Check whether a form field is empty or not
*
* @param object the form
* @param string the name of the form field to put the focus on
*
* @return boolean whether the form field is empty or not
*/
function emptyFormElements(theForm, theFieldName)
{
var theField = theForm.elements[theFieldName];
var isEmpty = emptyCheckTheField(theForm, theFieldName);
return isEmpty;
} // end of the 'emptyFormElements()' function
/**
* Ensures a value submitted in a form is numeric and is in a range
*
* @param object the form
* @param string the name of the form field to check
* @param integer the minimum authorized value
* @param integer the maximum authorized value
*
* @return boolean whether a valid number has been submitted or not
*/
function checkFormElementInRange(theForm, theFieldName, message, min, max)
{
var theField = theForm.elements[theFieldName];
var val = parseInt(theField.value);
if (typeof(min) == 'undefined') {
min = 0;
}
if (typeof(max) == 'undefined') {
max = Number.MAX_VALUE;
}
// It's not a number
if (isNaN(val)) {
theField.select();
alert(PMA_messages['strNotNumber']);
theField.focus();
return false;
}
// It's a number but it is not between min and max
else if (val < min || val > max) {
theField.select();
alert($.sprintf(message, val));
theField.focus();
return false;
}
// It's a valid number
else {
theField.value = val;
}
return true;
} // end of the 'checkFormElementInRange()' function
function checkTableEditForm(theForm, fieldsCnt)
{
// TODO: avoid sending a message if user just wants to add a line
// on the form but has not completed at least one field name
var atLeastOneField = 0;
var i, elm, elm2, elm3, val, id;
for (i=0; i (label and checkbox), so we need to handle this differently
var $checkbox = $tr.find(':checkbox');
if ($checkbox.length) {
// checkbox in a row, add or remove class depending on checkbox state
var checked = $checkbox.prop('checked');
if (!$(e.target).is(':checkbox, label')) {
checked = !checked;
$checkbox.prop('checked', checked).trigger('change');
}
if (checked) {
$tr.addClass('marked');
} else {
$tr.removeClass('marked');
}
last_click_checked = checked;
} else {
// normal data table, just toggle class
$tr.toggleClass('marked');
last_click_checked = false;
}
// remember the last clicked row
last_clicked_row = last_click_checked ? $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1;
last_shift_clicked_row = -1;
} else {
// handle the shift click
PMA_clearSelection();
var start, end;
// clear last shift click result
if (last_shift_clicked_row >= 0) {
if (last_shift_clicked_row >= last_clicked_row) {
start = last_clicked_row;
end = last_shift_clicked_row;
} else {
start = last_shift_clicked_row;
end = last_clicked_row;
}
$tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
.slice(start, end + 1)
.removeClass('marked')
.find(':checkbox')
.prop('checked', false)
.trigger('change');
}
// handle new shift click
var curr_row = $('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr);
if (curr_row >= last_clicked_row) {
start = last_clicked_row;
end = curr_row;
} else {
start = curr_row;
end = last_clicked_row;
}
$tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
.slice(start, end + 1)
.addClass('marked')
.find(':checkbox')
.prop('checked', true)
.trigger('change');
// remember the last shift clicked row
last_shift_clicked_row = curr_row;
}
});
addDateTimePicker();
/**
* Add attribute to text boxes for iOS devices (based on bugID: 3508912)
*/
if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
$('input[type=text]').attr('autocapitalize','off').attr('autocorrect','off');
}
});
/**
* True if last click is to check a row.
*/
var last_click_checked = false;
/**
* Zero-based index of last clicked row.
* Used to handle the shift + click event in the code above.
*/
var last_clicked_row = -1;
/**
* Zero-based index of last shift clicked row.
*/
var last_shift_clicked_row = -1;
/**
* Row highlighting in horizontal mode (use "live"
* so that it works also for pages reached via AJAX)
*/
/*AJAX.registerOnload('functions.js', function() {
$('tr.odd, tr.even').live('hover',function(event) {
var $tr = $(this);
$tr.toggleClass('hover',event.type=='mouseover');
$tr.children().toggleClass('hover',event.type=='mouseover');
});
})*/
/**
* This array is used to remember mark status of rows in browse mode
*/
var marked_row = [];
/**
* marks all rows and selects its first checkbox inside the given element
* the given element is usaly a table or a div containing the table or tables
*
* @param container DOM element
*/
function markAllRows(container_id)
{
$("#" + container_id).find("input:checkbox:enabled").prop('checked', true)
.trigger("change")
.parents("tr").addClass("marked");
return true;
}
/**
* marks all rows and selects its first checkbox inside the given element
* the given element is usaly a table or a div containing the table or tables
*
* @param container DOM element
*/
function unMarkAllRows(container_id)
{
$("#" + container_id).find("input:checkbox:enabled").prop('checked', false)
.trigger("change")
.parents("tr").removeClass("marked");
return true;
}
/**
* Checks/unchecks all checkbox in given conainer (f.e. a form, fieldset or div)
*
* @param string container_id the container id
* @param boolean state new value for checkbox (true or false)
* @return boolean always true
*/
function setCheckboxes(container_id, state)
{
$("#" + container_id).find("input:checkbox").prop('checked', state);
return true;
} // end of the 'setCheckboxes()' function
/**
* Checks/unchecks all options of a