/*
 *  Copyright 2018 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
 
 /**
 * Search criteria widget
 * This widget is registered for fields of type Ametys.form.WidgetManager#TYPE_STRING
 */
Ext.define('Ametys.web.form.widget.SearchCriteria', {
    extend: 'Ametys.form.AbstractFieldsWrapper',
    mixins: { servercaller: 'Ametys.data.ServerCaller' },
    
    /**
     * @cfg {String/String[]} fieldsReloadingWidget the names of the fields (separated by ',') which will launch a reload of this widget when their value change
     */
    
    statics: {
        /**
         * @property {String} MODE_COMBOBOX_FIELD_ITEM_ID The item id of the 'mode' combobox.
         * @static
         * @readonly
         */
        MODE_COMBOBOX_FIELD_ITEM_ID: 'mode',
        /**
         * @property {String} MODE_COMBOBOX_FIELD_CHOICE_STATIC The choice for 'static' in the 'mode' combobox.
         * @static
         * @readonly
         */
        MODE_COMBOBOX_FIELD_CHOICE_STATIC: 'STATIC',
        /**
         * @property {String} MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT The choice for 'user input' in the 'mode' combobox.
         * @static
         * @readonly
         */
        MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT: 'USER_INPUT',
        /**
         * @property {String} MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT The choice for 'restricted user input' in the 'mode' combobox.
         * @static
         * @readonly
         */
        MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT: 'RESTRICTED_USER_INPUT',
        /**
         * @property {String} MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT The choice for 'profiled groups tags_input' in the 'mode' combobox.
         * @static
         * @readonly
         */
        MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT: 'PROFILED_GROUPS_TAGS_INPUT',
        /**
         * @property {String} USER_INPUT_HINT_FIELD_ITEM_ID The item id of the 'user-input-hint' display field.
         * @static
         * @readonly
         */
        USER_INPUT_HINT_FIELD_ITEM_ID: 'user-input-hint',
        /**
         * @property {String} RESTRICTED_USER_INPUT_FIELD_ITEM_ID The item id of the 'restricted-user-input' widget.
         * @static
         * @readonly
         */
        RESTRICTED_USER_INPUT_FIELD_ITEM_ID: 'restrictedValues',
        /**
         * @property {String} PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID The item id of the 'profiled-groups-tags' display field for group tags.
         * @static
         * @readonly
         */
        PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID: 'profiled-groups-tags'
    },
     
    /**
     * @private
     * @property {Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel} _advancedSearchFormPanel The advanced search form panel
     */
    /**
     * @private
     * @property {Number} _suspendGetValue A counter incremented when calls of #getValue() should be cancelled and not computed
     */
    _suspendGetValue: 0,
    /**
     * @private
     * @property {Boolean/Number} _advancedSearchFormPanelReinitializing true (just for the initial value) or a Number greater than 0 if the advanced search form panel is reinitializing. A Number equals to 0 otherwise.
     */
    _advancedSearchFormPanelReinitializing: true,
    
    getServerRole: function()
    {
        return "org.ametys.core.ui.widgets.WidgetsManager";
    },
    
    getServerId: function()
    {
        return "edition.search-criteria";
    },
    
    initComponent: function()
    {
        this._createAdvancedSearchFormPanel();
        this._advancedSearchFormPanel.initialize({
            'advanced-criteria': {
                criteria: {}
            }
        });
        
        if (this.form && Ext.isFunction(this.form.onRelativeFieldsChange))
        {
            // returnables will launch the reinit when it will change
            this.form.onRelativeFieldsChange('returnables', this, this._onReturnablesChange);
            
            // given fields will launch the reinit when they will change
            var fieldsReloadingWidget = this.getInitialConfig('fieldsReloadingWidget');
            fieldsReloadingWidget = Ext.isArray(fieldsReloadingWidget) ? fieldsReloadingWidget : fieldsReloadingWidget.split(',');
            Ext.Array.forEach(fieldsReloadingWidget, function(fieldName) {
                this.form.onRelativeFieldsChange(fieldName, this, this._onListenedParamChange);
            }, this);
        }
        
        this.items = [this._advancedSearchFormPanel];
        
        this.callParent(arguments);
    },
    
    /**
     * @private
     * Creates and returns an advanced search form panel
     * @return {Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel} the created panel
     */
    _createAdvancedSearchFormPanel: function()
    {
        this._advancedSearchFormPanel = Ext.create('Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel', {
            flex: 1,
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            border: false,
            bodyPadding: 10,
            scrollable: true,
            allowBlank: true,
            
            additionalFields: [{
                itemId: this.statics().MODE_COMBOBOX_FIELD_ITEM_ID,
                after: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.OPERATOR_FIELD_ITEM_ID,
                getValueFn: function(field)
                {
                    return field.getValue();
                },
                config: Ext.bind(this._getModeComboboxConfig, this)
            }, {
                itemId: this.statics().USER_INPUT_HINT_FIELD_ITEM_ID,
                after: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID,
                config: {
                    xtype: 'displayfield',
                    hidden: true,
                    value: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_USER_INPUT_HINT}}",
                    flex: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_FLEX,
                    style: {
                        paddingLeft: '6px'
                    },
//                    fieldCls:  'ametys-field-hint',
                    fieldStyle: 'color: #666; font-style: italic;'
                }
            }, {
                itemId: this.statics().RESTRICTED_USER_INPUT_FIELD_ITEM_ID,
                after: this.statics().USER_INPUT_HINT_FIELD_ITEM_ID,
                getValueFn: function(field)
                {
                    return field.getValue();
                },
                config: Ext.bind(this._getRestrictedUserInputFieldConfig, this)
            }, {
                itemId: this.statics().PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID,
                after: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID,
                getValueFn: function(field)
                {
                    return field.getValue();
                },
                config: {
                    xtype: 'displayfield',
                    hidden: true,
                    value: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_PROFILED_GROUPS_TAGS_INPUT_HINT}}",
                    flex: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_FLEX,
                    style: {
                        paddingLeft: '6px'
                    },
                    fieldStyle: 'color: #666; font-style: italic;'
                }
            }]
        });
        
        this._overrideAdvancedSearchFormPanel();
        
        return this._advancedSearchFormPanel;
    },
    
    /**
     * @private
     * Custom override of the advanced search form panel
     */
    _overrideAdvancedSearchFormPanel: function()
    {
        var searchCriteriaField = this;
        Ext.override(this._advancedSearchFormPanel, {
            initForm: function(values, language)
            {
                searchCriteriaField._suspendGetValue++;
                this.callParent(arguments);
                searchCriteriaField._suspendGetValue--;
            },
            
            addCriterion: function(position)
            {
                searchCriteriaField._suspendGetValue++;
                this.callParent(arguments);
                searchCriteriaField._suspendGetValue--;

                // force computing of valid state
                searchCriteriaField.isValid();
                searchCriteriaField.checkChange();
            },
            
            _removeRow: function(button, eOpts)
            {
                searchCriteriaField._suspendGetValue++;
                this.callParent(arguments);
                searchCriteriaField._suspendGetValue--;
                
                // force computing of valid state
                searchCriteriaField.isValid();
                searchCriteriaField.checkChange();
            },
            
            _insertRowUp: function(button, eOpts)
            {
                searchCriteriaField._suspendGetValue++;
                this.callParent(arguments);
                searchCriteriaField._suspendGetValue--;
                
                // force computing of valid state
                searchCriteriaField.isValid();
                searchCriteriaField.checkChange();
            },
            
            _moveRowUp: function(button)
            {
                searchCriteriaField._suspendGetValue++;
                this.callParent(arguments);
                searchCriteriaField._suspendGetValue--;
                
                // force computing of valid state
                searchCriteriaField.isValid();
                searchCriteriaField.checkChange();
            },
            
            _moveRowDown: function(button)
            {
                searchCriteriaField._suspendGetValue++;
                this.callParent(arguments);
                searchCriteriaField._suspendGetValue--;
                
                // force computing of valid state
                searchCriteriaField.isValid();
                searchCriteriaField.checkChange();
            },
            
            getOperators: function(type)
            {
                var operators = this.callParent(arguments);
                
                // Is empty operator should not appear on this service
                operators = operators.filter(operator => operator.value != "is-empty" && operator.value != "is-not-empty")
                                     .map(function(operatorObject) 
                {
                    var resultOperatorObject = Ext.apply({}, operatorObject);
                    
                    if (type == 'date' && resultOperatorObject.value == 'ge')
                    {
                        resultOperatorObject.label = "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_OP_DATE_AFTER_INCLUDED}}";
                        resultOperatorObject.shortLabel = resultOperatorObject.label;
                        resultOperatorObject.labelMultiple = resultOperatorObject.label;
                        resultOperatorObject.shortLabelMultiple = resultOperatorObject.label;
                    }
                    else if (type == 'date' && resultOperatorObject.value == 'le')
                    {
                        resultOperatorObject.label = "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_OP_DATE_BEFORE_INCLUDED}}";
                        resultOperatorObject.shortLabel = resultOperatorObject.label;
                        resultOperatorObject.labelMultiple = resultOperatorObject.label;
                        resultOperatorObject.shortLabelMultiple = resultOperatorObject.label;
                    }
                    
                    // transform 1st letter to lower case to render a prettier and smoother sentence line
                    function lowerFirstChar(s) {
                        return s.charAt(0).toLowerCase() + s.substring(1);
                    }
                    resultOperatorObject.shortLabel = lowerFirstChar(resultOperatorObject.shortLabel);
                    resultOperatorObject.shortLabelMultiple = lowerFirstChar(resultOperatorObject.shortLabelMultiple);
                    
                    return resultOperatorObject;
                });
                
                return operators;
            }
        });
    },
    
    /**
     * @private
     * The function giving the configuration object of the mode combobox.
     * @param {Number} rowIndex The index of the row
     * @param {String} criterionName the new name of the criterion field
     * @return {Object} the configuration object of the mode combobox
     */
    _getModeComboboxConfig: function(rowIndex, criterionName)
    {
        function showAndHide(fieldsToShow, fieldsToHide)
        {
            fieldsToShow.forEach(function(field) {
                field.enable();
                field.show();
            });
            fieldsToHide.forEach(function(field) {
                field.disable();
                field.hide();
            });
        }
        
        var store = [];
        if (criterionName == "ContentSearchable$profiled-groups-tags")
        {
            store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_PROFILED_GROUPS_TAGS_INPUT}}"]);
        }
        else
        {
	        store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_STATIC, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_STATIC}}"]);
	        
	        var criterionObj = this._advancedSearchFormPanel.getCriteria()[criterionName];
	        if (criterionObj)
	        {
	            if (criterionObj['allowDisplayAllValues'] === true)
	            {
	                store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_USER_INPUT}}"]);
	            }
	            
	            if (criterionObj['canBeRestricted'] === true)
	            {
	                store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_RESTRICTED_USER_INPUT}}"]);
	            }
	        }
        }
        
        function showAndHideFields(modeCombobox, newVal)
        {
            var container = modeCombobox.up('fieldcontainer'),
                valueField = container.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID),
                userInputHintCmpt = container.items.get(Ametys.web.form.widget.SearchCriteria.USER_INPUT_HINT_FIELD_ITEM_ID),
                restrictedUserInputField = container.items.get(Ametys.web.form.widget.SearchCriteria.RESTRICTED_USER_INPUT_FIELD_ITEM_ID);
                userGroupsInputField = container.items.get(Ametys.web.form.widget.SearchCriteria.PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID);
            
            switch (newVal)
            {
                case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT:
                    showAndHide([userInputHintCmpt], [valueField, restrictedUserInputField, userGroupsInputField]);
                    break;
                case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT:
                    showAndHide([restrictedUserInputField], [valueField, userInputHintCmpt, userGroupsInputField]);
                    break;
                case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT:
                    showAndHide([userGroupsInputField], [valueField, userInputHintCmpt, restrictedUserInputField]);
                    break;
                case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_STATIC:
                default:
                    showAndHide([valueField], [userInputHintCmpt, restrictedUserInputField, userGroupsInputField]);
                    break;
            }
        }
        
        var defaultValue = this._hasUserInputChoice(store)
                ? Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT
                : store[0][0];

        return {
            xtype: 'combobox',
            store: store,
            value: defaultValue,
            flex: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_FLEX,
            queryMode: 'local',
            triggerAction: 'all',
            editable: false,
            forceSelection: true,
            listeners: {
                'beforerender': function(modeCombobox)
                {
                    // Be sure to hide and show the right fields
                    showAndHideFields(modeCombobox, modeCombobox.getValue());
                },
                'change': showAndHideFields,
                scope: this
            }
        };
    },
    
    /**
     * @private
     * True if the store contains the choice "USER_INPUT"
     * @return {boolean} true if the store contains the choice "USER_INPUT"
     */
    _hasUserInputChoice: function(store)
    {
        var found = false;
        for (var crit of store)
        {
            if (crit[0] == Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT)
            {
                found = true;
                break;
            }
        }
        
        return found;
    },
    
    /**
     * @private
     * The function giving the configuration object of the 'restricted user input' field.
     * @param {Number} rowIndex The index of the row
     * @param {String} criterionName the new name of the criterion field
     * @return {Object} the configuration object of the 'restricted user input' field
     */
    _getRestrictedUserInputFieldConfig: function(rowIndex, criterionName)
    {
        var advancedSearchFormPanel = this._advancedSearchFormPanel,
            criterionDefinition = advancedSearchFormPanel.getCriteria()[criterionName];
        if (criterionName == null || criterionDefinition == null || criterionDefinition['canBeRestricted'] !== true)
        {
            return {
                xtype: 'hidden'
            };
        }
        
        var scope = advancedSearchFormPanel,
            modifiedCriterionDefinition = Ext.Object.merge({}, criterionDefinition), // copy object
            args = [rowIndex, modifiedCriterionDefinition, null];
        
        // Modifiy criterionDefinition to be sure to have multiple choice
        modifiedCriterionDefinition.multiple = true;
        return Ext.apply(advancedSearchFormPanel.getValueField.apply(scope, args), {
            hidden: true // hide at beginning because default mode is MODE_COMBOBOX_FIELD_CHOICE_STATIC
        });
    },
    
    /**
     * @private
     * Listener called when the value of returnables field changes and the criteria field needs to be updated. 
     * @param {Ext.form.field.Field} field The field that changed
     * @param {Object} newValue The new value
     * @param {Object} oldValue The old value
     */
    _onReturnablesChange: function(field, newValue, oldValue)
    {
        var returnables = Ext.Array.from(newValue);
        this._reinitCriteria(returnables)
    },
    
    /**
     * @private
     * Listener called when the value of a listened field changes and the criteria field needs to be updated. 
     * @param {Ext.form.field.Field} field The field that changed
     * @param {Object} newValue The new value
     * @param {Object} oldValue The old value
     */
    _onListenedParamChange: function(field, newValue, oldValue)
    {
        var returnables = Ext.Array.from(this.form.getField('returnables').getValue());
        this._reinitCriteria(returnables);
    },
    
    /**
     * @private
     * Reinitialize the advanced search criteria
     * @param {String[]} returnables The returnables
     */
    _reinitCriteria: function(returnables)
    {
        var formValues = this.form.getJsonValues();
        this._advancedSearchFormPanelReinitializing = 1;
        this.serverCall("getCriterionDefinitions", [returnables, formValues], this._getCriterionDefinitionsCb, {
            errorMessage: {
                msg: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_GET_SEARCH_CRITERION_DEFINITION_SERVER_ERROR}}",
                category: Ext.getClassName(this)
            },
            cancelCode: this.$className + "$" + this.getId()
        }, this);
    },
    
    /**
     * @private
     * Callback function called after retrieving the criterion definitions to propose.
     * @param {Object} response The response object containing the values for initializing the advanced search form panel
     * @param {Object} args The callback arguments
     */
    _getCriterionDefinitionsCb: function(response, args)
    {
        // Try to keep current values to re-apply them
        var oldValues = this.getValue();
        
        this._advancedSearchFormPanelReinitializing = 0;
        
        // Reset advanced search form panel
        this.remove(this._advancedSearchFormPanel);
        this._createAdvancedSearchFormPanel();
        this.add(this._advancedSearchFormPanel);
        
        var criteria = {};
        var labelSep = ' > ';
        Ext.Object.each(response, function(critId, critValues) {
            // Enhance label
            var contextPrefixLabels = critValues['contextPrefixLabels'];
            critValues['label'] = critValues['groupLabel']
                                + labelSep
                                + (contextPrefixLabels.length ? (contextPrefixLabels.join(labelSep) + labelSep) : '')
                                + critValues['label'];
            
            // For dates, use special widget
            if (critValues.type == 'date' || critValues.type == 'datetime')
            {
                critValues.widget = "edition.adaptable-date";
                critValues['widget-params'] = critValues['widget-params'] || {};
                Ext.apply(critValues['widget-params'], {
                    allowBlank: false
                });
            }
            
            criteria[critId] = critValues;
        });
        this._advancedSearchFormPanel.initialize({
            'advanced-criteria': {
                criteria: criteria
            }
        });
        
        // Set values
        if (oldValues)
        {
            var value = oldValues;
            if (Ext.isString(value) && !Ext.isEmpty(value))
            {
                value = Ext.decode(value);
            }
            value = value || {};
            this._advancedSearchFormPanel.initForm(value);
        }
    },
    
    setValue: function(value)
    {
        this.callParent(arguments);
        
        if (!this._advancedSearchFormPanelReinitializing)
        {
            if (Ext.isString(value) && !Ext.isEmpty(value))
            {
                value = Ext.decode(value);
            }
            value = value || {};
            this._advancedSearchFormPanel.initForm(value);
        }
    },
    
    getValue: function()
    {
        if (!this._advancedSearchFormPanelReinitializing && !this._suspendGetValue)
        {
            var valueTree = this._advancedSearchFormPanel.getValueTree();
            this.value = Ext.encode(valueTree);
        }
        
        return this.callParent(arguments) || "{}";
    },
    
    getErrors: function(value)
    {
        var errors = this.callParent(arguments),
            hasLineWithEmptyField = false,
            modeFieldItemId = this.statics().MODE_COMBOBOX_FIELD_ITEM_ID,
            restrictedValuesFieldItemId = this.statics().RESTRICTED_USER_INPUT_FIELD_ITEM_ID,
            modeStatic = this.statics().MODE_COMBOBOX_FIELD_CHOICE_STATIC,
            modeRestrictedUserInput = this.statics().MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT;
        
        function isLogicalOperatorInvalid(index, logicalOpField)
        {
            return index > 0 && (logicalOpField == null || logicalOpField.getValue() == null);
        }
        
        function isCriterionInvalid(criterionField)
        {
            return criterionField == null || criterionField.getValue() == null;
        }
        
        function isOperatorInvalid(operatorField)
        {
            return operatorField == null || operatorField.getValue() == null;
        }
        
        function isModeInvalid(mode, valueField, restrictedValuesField)
        {
            return mode == modeStatic && (valueField == null || Ext.isEmpty(valueField.getValue()))
                    || mode == modeRestrictedUserInput && (restrictedValuesField == null || Ext.isEmpty(restrictedValuesField.getValue()));
        }
        
        if (value == null)
        {
            // abort computing of errors
            return errors;
        }
        
        this._advancedSearchFormPanel.items.each(function(line, index, len) {
            var logicalOpField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.LOGICAL_OPERATOR_FIELD_ITEM_ID),
                criterionField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.CRITERION_FIELD_ITEM_ID),
                modeField = line.items.get(modeFieldItemId),
                mode = modeField && modeField.getValue(),
                operatorField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.OPERATOR_FIELD_ITEM_ID),
                valueField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID),
                restrictedValuesField = line.items.get(restrictedValuesFieldItemId);
            if (index == len - 1)
            {
                // continue iteration
                return true;
            }
            else if (isLogicalOperatorInvalid(index, logicalOpField) || isCriterionInvalid(criterionField) || isOperatorInvalid(operatorField) || isModeInvalid(mode, valueField, restrictedValuesField))
            {
                hasLineWithEmptyField = true;
                // break iteration
                return false;
            }
        });
        
        if (hasLineWithEmptyField)
        {
            errors.push("{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_ERROR_INCOMPLETE_CRITERIA}}");
        }
        
        return errors;
    }
});