// Copyright (C) 2010-2024 Combodo SAS // // This file is part of iTop. // // iTop is free software; you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // iTop is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with iTop. If not, see // ID of the (hidden) form field used to store the JSON representation of the // object being edited in this page var sJsonFieldId = 'json_object'; // The memory representation of the object var oObj = {}; // Mapping between the fields of the form and the attribute of the current object // If aFieldsMap[2] contains 'foo' it means that oObj.foo corresponds to the field // of Id 'att_2' in the form var aFieldsMap = new Array; window.bInSubmit = false; // For handling form cancellation via OnBeforeUnload events // Update the whole object from the form and also update its // JSON (serialized) representation in the (hidden) field function UpdateObjectFromForm(aFieldsMap, oObj) { for(i=0; i iCurrentStep) { // Check the values when moving forward if (CheckFields('wizStep'+iCurrentStep, true)) { oCurrentStep.style.display = 'none'; ActivateStep(iNextStep); } } else { oCurrentStep.style.display = 'none'; ActivateStep(iNextStep); } } function ActivateStep(iTargetStep) { UpdateObjectFromForm(aFieldsMap, oObj); var oNextStep = document.getElementById('wizStep'+(iTargetStep)); window.location.href='#step'+iTargetStep; // If a handler for entering this step exists, call it if (typeof(this['OnEnterStep'+iTargetStep]) == 'function') { eval( 'OnEnterStep'+iTargetStep+'();'); } oNextStep.style.display = ''; G_iCurrentStep = iTargetStep; //$('#wizStep'+(iTargetStep)).block({ message: null }); } function OnUnload(sTransactionId, sObjClass, iObjKey, sToken) { if (!window.bInSubmit) { // If it's not a submit, then it's a "cancel" (Pressing the Cancel button, closing the window, using the back button...) var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'; var oFormData = new FormData(); oFormData.append('operation', 'on_form_cancel'); oFormData.append('transaction_id', sTransactionId); oFormData.append('obj_class', sObjClass); oFormData.append('obj_key', iObjKey); oFormData.append('token', sToken); navigator.sendBeacon(sUrl, oFormData); } } function OnSubmit(sFormId) { if($('#'+sFormId).attr('data-form-state') === 'onsubmit') { return false; } $('#'+sFormId).attr('data-form-state','onsubmit'); window.bInSubmit=true; // This is a submit, make sure that when the page gets unloaded we don't cancel the action if ($('#'+sFormId).data('force_submit')) { return true; } var bResult = CheckFields(sFormId, true); if (!bResult) { window.bInSubmit = false; // Submit is/will be canceled $('#'+sFormId).attr('data-form-state', 'default'); } return bResult; } // Store the result of the form validation... there may be several forms per page, beware var oFormErrors = { err_form0: 0 }; function CheckFields(sFormId, bDisplayAlert) { // if some fields are in wait, no submit is allowed if ($('#'+sFormId+' .blockMsg').length>0) { CombodoModal.OpenWarningModal(Dict.S('UI:Button:Wait')); return false; } $('#'+sFormId+' :submit').prop('disable', true); $('#'+sFormId+' :button[type=submit]').prop('disable', true); firstErrorId = ''; // The two 'fields' below will be updated when the 'validate' event is processed oFormErrors['err_'+sFormId] = 0; // Number of errors encountered when validating the form oFormErrors['input_'+sFormId] = null; // First 'input' with an error, to set the focus to it $('#'+sFormId+' :input').each( function() { // this is synchronous ! // each field should register this event to launch ValidateField() if needed validateEventResult = $(this).trigger('validate', sFormId); } ); if(oFormErrors['err_'+sFormId] > 0) { if (bDisplayAlert) { activateFirstTabWithError(sFormId); CombodoModal.OpenErrorModal(Dict.S('UI:FillAllMandatoryFields')); } $('#'+sFormId+' :submit').prop('disable', false); $('#'+sFormId+' :button[type=submit]').prop('disable', false); if (oFormErrors['input_'+sFormId] != null) { $('#'+oFormErrors['input_'+sFormId]).trigger('focus'); } } return (oFormErrors['err_'+sFormId] == 0); // If no error, submit the form } function activateFirstTabWithError(sFormId) { var $form = $("#"+sFormId), $tabsContainer = $form.find(".ui-widget.ui-widget-content"), $tabs = $tabsContainer.find(".ui-tabs-panel"); $tabs.each(function (index, element) { var $fieldsWithError = $(element).find(".form_validation"); if ($fieldsWithError.length > 0 && ($tabsContainer.tabs("instance") !== undefined)) { $tabsContainer.tabs("option", "active", index); return false; } }); } function ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain) { if (bValid) { // Visual feedback - none when it's Ok $('#field_'+sFieldId+' .ibo-input-wrapper').removeClass('is-error') $('#v_'+sFieldId).html(''); $('#'+sFieldId+'[data-validate*="dependencies"]').trigger('change.dependencies').removeAttr('data-validate'); } else { // Report the error... oFormErrors['err_'+sFormId]++; if (oFormErrors['input_'+sFormId] == null) { // Let's remember the first input with an error, so that we can put back the focus on it later oFormErrors['input_'+sFormId] = sFieldId; } if($('#field_'+sFieldId+' .ibo-input-wrapper').attr('data-validation') === 'untouched') { $('#field_'+sFieldId+' .ibo-input-wrapper').removeAttr('data-validation'); } else{ $('#field_'+sFieldId+' .ibo-input-wrapper').addClass('is-error'); } if ($('#v_'+sFieldId).text() == '') { $('#v_'+sFieldId).html(sExplain); } } } /** * To be launched on each field from normal event (click, change, ...) and 'validate' event for form submission. * Calls ReportFieldValidationStatus() to update global vars containing fields status * @param sFieldId * @param sPattern * @param bMandatory * @param sFormId * @param nullValue * @param originalValue * @returns {boolean} * @constructor */ function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue) { var bValid = true; var sExplain = ''; if ($('#'+sFieldId).prop('disabled')) { bValid = true; // disabled fields are not checked } else { var currentVal = $('#'+sFieldId).val(); if (currentVal == '$$NULL$$') // Convention to indicate a non-valid value since it may have to be passed as text { bValid = false; } else if (bMandatory && (currentVal == nullValue)) { bValid = false; sExplain = Dict.S('UI:ValueMustBeSet'); } else if ((originalValue != undefined) && (currentVal == originalValue)) { bValid = false; if (originalValue == nullValue) { sExplain = Dict.S('UI:ValueMustBeSet'); } else { sExplain = Dict.S('UI:ValueMustBeChanged'); } } else if (currentVal == nullValue) { // An empty field is Ok... bValid = true; } else if (sPattern != '') { re = new RegExp(sPattern); //console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern ); bValid = re.test(currentVal); sExplain = Dict.S('UI:ValueInvalidFormat'); } } ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain); //console.log('Form: '+sFormId+' Validating field: '+sFieldId + ' current value: '+currentVal+' pattern: '+sPattern+' result: '+bValid ); return true; // Do not stop propagation ?? } function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue) { let oField = $('#'+sFieldId); if (oField.length === 0) { return false; } let oCKEditor = CombodoCKEditorHandler.GetInstanceSynchronous('#'+sFieldId); var bValid; var sExplain = ''; if (oField.prop('disabled')) { bValid = true; // disabled fields are not checked } else { // If the CKEditor is not yet loaded, we need to wait for it to be ready // but as we need this function to be synchronous, we need to call it again when the CKEditor is ready if (oCKEditor === undefined){ CombodoCKEditorHandler.GetInstance('#'+sFieldId).then((oCKEditor) => { ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue); }); return; } let sTextContent; let sFormattedContent = oCKEditor.getData(); // Get the contents without the tags // Check if we have a formatted content that is HTML, otherwise we just have plain text, and we can use it directly sTextContent = $(sFormattedContent).length > 0 ? $(sFormattedContent).text() : sFormattedContent; if (sTextContent === '') { // No plain text, maybe there is just an image let oImg = $(sFormattedContent).find("img"); if (oImg.length !== 0) { sTextContent = 'image'; } } // Get the original value without the tags let oFormattedOriginalContents = (originalValue !== undefined) ? $('
').html(originalValue) : undefined; let sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined; if (bMandatory && (sTextContent === nullValue)) { bValid = false; sExplain = Dict.S('UI:ValueMustBeSet'); } else if ((sTextOriginalContents !== undefined) && (sTextContent === sTextOriginalContents)) { bValid = false; if (sTextOriginalContents === nullValue) { sExplain = Dict.S('UI:ValueMustBeSet'); } else { // Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value. sExplain = Dict.S('UI:ValueMustBeChanged'); } } else { bValid = true; } // Put and event to check the field when the content changes, remove the event right after as we'll call this same function again, and we don't want to call the event more than once (especially not ^2 times on each call) oCKEditor.model.document.once('change:data', (event) => { ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue); }); } ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain); return bValid; } function ResetPwd(id) { // Reset the values of the password fields $('#'+id).val('*****'); $('#'+id+'_confirm').val('*****'); // And reset the flag, to tell it that the password remains unchanged $('#'+id+'_changed').val(0); // Visual feedback, None when it's Ok $('#v_'+id).html(''); } // Called whenever the content of a one way encrypted password changes function PasswordFieldChanged(id) { // Set the flag, to tell that the password changed $('#'+id+'_changed').val(1); } // Special validation function for one way encrypted password fields function ValidatePasswordField(id, sFormId) { var bChanged = $('#'+id+'_changed').val(); if (bChanged) { if ($('#'+id).val() != $('#'+id+'_confirm').val()) { oFormErrors['err_'+sFormId]++; if (oFormErrors['input_'+sFormId] == null) { // Let's remember the first input with an error, so that we can put back the focus on it later oFormErrors['input_'+sFormId] = id; } // Visual feedback $('#v_'+id).html(Dict.S('UI:Component:Input:Password:DoesNotMatch')); $('#field_'+id +' .ibo-input-wrapper').addClass('is-error'); return false; } } $('#v_'+id).html(''); $('#field_'+id +' .ibo-input-wrapper').removeClass('is-error'); return true; } //Special validation function for case log fields, taking into account the history // to determine if the field is empty or not function ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue) { var bValid = true; var sExplain = ''; var sTextContent; if ($('#'+sFieldId).prop('disabled')) { bValid = true; // disabled fields are not checked } else { // Get the contents (with tags) // Note: For CaseLog we can't retrieve the formatted contents from CKEditor (unlike in ValidateCKEditorField() method) because of the place holder. sTextContent = $('#' + sFieldId).val(); var count = $('#'+sFieldId+'_count').val(); if (bMandatory && (count == 0) && (sTextContent == nullValue)) { // No previous entry and no content typed bValid = false; sExplain = Dict.S('UI:ValueMustBeSet'); } else if ((originalValue != undefined) && (sTextContent == originalValue)) { bValid = false; sExplain = Dict.S('UI:ValueMustBeChanged'); } } ReportFieldValidationStatus(sFieldId, sFormId, bValid, '' /* sExplain */); // We need to check periodically as CKEditor doesn't trigger our events. More details in UIHTMLEditorWidget::Display() @ line 92 setTimeout(function(){ValidateCaseLogField(sFieldId, bMandatory, sFormId, nullValue, originalValue);}, 500); } // Validate the inputs depending on the current setting function ValidateRedundancySettings(sFieldId, sFormId) { var bValid = true; var sExplain = ''; $('#'+sFieldId+' :input[type="radio"]:checked').parent().find(':input[type="string"]').each(function (){ var sValue = $(this).val().trim(); if (sValue == '') { bValid = false; sExplain = Dict.S('UI:ValueMustBeSet'); } else { // There is something... check if it is a number re = new RegExp('^[0-9]+$'); bValid = re.test(sValue); if (bValid) { var iValue = parseInt(sValue , 10); if ($(this).hasClass('redundancy-min-up-percent')) { // A percentage if ((iValue < 0) || (iValue > 100)) { bValid = false; } } else if ($(this).hasClass('redundancy-min-up-count')) { // A count if (iValue < 0) { bValid = false; } } } if (!bValid) { sExplain = Dict.S('UI:ValueInvalidFormat'); } } }); ReportFieldValidationStatus(sFieldId, sFormId, bValid, sExplain); return bValid; } //Special validation function for custom fields function ValidateCustomFields(sFieldId, sFormId) { var oFieldSet = $('#'+sFieldId+'_console_form').console_form_handler('option', 'field_set'); bValid = oFieldSet.triggerHandler('validate'); ReportFieldValidationStatus(sFieldId, sFormId, bValid, ''); return bValid; } // Manage a 'duration' field function UpdateDuration(iId) { var iDays = parseInt($('#'+iId+'_d').val(), 10); var iHours = parseInt($('#'+iId+'_h').val(), 10); var iMinutes = parseInt($('#'+iId+'_m').val(), 10); var iSeconds = parseInt($('#'+iId+'_s').val(), 10); var iDuration = (((iDays*24)+ iHours)*60+ iMinutes)*60 + iSeconds; $('#'+iId).val(iDuration); $('#'+iId).trigger('change'); return true; } // Called when filling an autocomplete field //deprecated in 2.8 function OnAutoComplete(id, event, data, formatted) { if (data) { // A valid match was found: data[0] => label, data[1] => value if (data[1] != $('#'+id).val()) { $('#'+id).val(data[1]); $('#'+id).trigger('change'); $('#'+id).trigger('extkeychange'); } } else { if ($('#label_'+id).val() == '') { $('#'+id).val(''); // Empty value } else { $('#'+id).val('$$NULL$$'); // Convention: not a valid value } $('#'+id).trigger('change'); } }