001/*
002 *  Copyright 2021 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.forms.question.types;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.regex.Pattern;
022
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.cocoon.xml.XMLUtils;
026import org.apache.commons.lang.StringUtils;
027import org.xml.sax.ContentHandler;
028import org.xml.sax.SAXException;
029
030import org.ametys.core.user.CurrentUserProvider;
031import org.ametys.core.user.User;
032import org.ametys.core.user.UserIdentity;
033import org.ametys.core.user.UserManager;
034import org.ametys.plugins.forms.helper.FormElementDefinitionHelper;
035import org.ametys.plugins.forms.repository.FormQuestion;
036import org.ametys.runtime.config.DisableCondition;
037import org.ametys.runtime.config.DisableCondition.OPERATOR;
038import org.ametys.runtime.config.DisableConditions;
039import org.ametys.runtime.i18n.I18nizableText;
040import org.ametys.runtime.model.ElementDefinition;
041import org.ametys.runtime.model.ModelItem;
042import org.ametys.runtime.model.SimpleViewItemGroup;
043import org.ametys.runtime.model.StaticEnumerator;
044import org.ametys.runtime.model.ViewElement;
045import org.ametys.runtime.model.type.ModelItemTypeConstants;
046import org.ametys.runtime.parameter.DefaultValidator;
047
048/** 
049 * Class for creating simple text questions 
050 */
051public class SimpleTextQuestionType extends AbstractFormQuestionType
052{
053    /** Constant for regexp attribute. */
054    public static final String ATTRIBUTE_REGEXP = "regexp";
055    
056    /** Constant for custom regexp attribute. */
057    public static final String ATTRIBUTE_CUSTOM_REGEX = "custom-regex";
058    
059    /** Constant for autofill attribute. */
060    public static final String ATTRIBUTE_AUTOFILL = "autofill";
061    
062    /** Constant for default value attribute. */
063    public static final String ATTRIBUTE_DEFAULT_VALUE = "default-value";
064    
065    /** Name of empty regexStaticEnumerator entry  */
066    public static final String EMPTY_REGEX_VALUE = " ";
067    
068    /** Name of email regexStaticEnumerator entry  */
069    public static final String EMAIL_REGEX_VALUE = "email";
070    
071    /** Name of phone regexStaticEnumerator entry  */
072    public static final String PHONE_REGEX_VALUE = "phone";
073    
074    /** Name of regexStaticEnumerator entry that enable custom regex field */
075    public static final String CUSTOM_REGEX_VALUE = "custom";
076    
077    /** Name of empty autofillStaticEnumerator entry */
078    public static final String EMPTY_AUTOFILL_VALUE = " ";
079    
080    /** Name of email autofillStaticEnumerator entry */
081    public static final String EMAIL_AUTOFILL_VALUE = "email";
082    
083    /** Name of id autofillStaticEnumerator entry */
084    public static final String ID_AUTOFILL_VALUE = "id";
085    
086    /** Name of fullname autofillStaticEnumerator entry */
087    public static final String FULLNAME_AUTOFILL_VALUE = "fullName";
088    
089    /** Name of firstName autofillStaticEnumerator entry */
090    public static final String FIRSTNAME_AUTOFILL_VALUE = "firstName";
091    
092    /** Name of lastName autofillStaticEnumerator entry */
093    public static final String LASTNAME_AUTOFILL_VALUE = "lastName";
094    
095    /** Name of autofillStaticEnumerator entry that enable default value field */
096    public static final String CUSTOM_AUTOFILL_VALUE = "custom";
097    
098    /** Constant for placeholder attribute. */
099    public static final String ATTRIBUTE_PLACEHOLDER = "placeholder";
100
101    /** Constant for default title */
102    public static final String DEFAULT_TITLE = "PLUGIN_FORMS_QUESTION_DEFAULT_TITLE_SIMPLE_TEXT";
103    
104    /** The current user provider */
105    protected CurrentUserProvider _currentUserProvider;
106    
107    /** The users manager */
108    protected UserManager _userManager;
109    
110    @Override
111    public void service(ServiceManager manager) throws ServiceException
112    {
113        super.service(manager);
114        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
115        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
116    }
117    
118    @Override
119    protected List<ModelItem> _getModelItems()
120    {
121        List<ModelItem> modelItems = super._getModelItems();
122        
123        //PLACEHOLDER
124        ElementDefinition placeholder = FormElementDefinitionHelper.getElementDefinition(ATTRIBUTE_PLACEHOLDER, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_FORMS_QUESTIONS_DIALOG_QUESTION_PLACEHOLDER", "PLUGINS_FORMS_QUESTIONS_DIALOG_QUESTION_PLACEHOLDER_DESC", null);
125        modelItems.add(placeholder);
126        
127        //AUTOFILL
128        ElementDefinition<String> autofill = FormElementDefinitionHelper.getElementDefinition(ATTRIBUTE_AUTOFILL, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_DESC", null);
129
130        StaticEnumerator<String> autofillStaticEnumerator = new StaticEnumerator<>();
131        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_EMPTY_TEXT"), EMPTY_AUTOFILL_VALUE);
132        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_EMAIL"), EMAIL_AUTOFILL_VALUE);
133        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_ID"), ID_AUTOFILL_VALUE);
134        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_FULLNAME"), FULLNAME_AUTOFILL_VALUE);
135        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_FIRSTNAME"), FIRSTNAME_AUTOFILL_VALUE);
136        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_LASTNAME"), LASTNAME_AUTOFILL_VALUE);
137        autofillStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_AUTOFILL_CUSTOM"), CUSTOM_AUTOFILL_VALUE);
138        autofill.setEnumerator(autofillStaticEnumerator);
139        Map<String, I18nizableText> widgetParameters  = new HashMap<>();
140        widgetParameters.put("naturalOrder", new I18nizableText("true"));
141        autofill.setWidgetParameters(widgetParameters);
142        autofill.setDefaultValue(EMPTY_AUTOFILL_VALUE);
143        
144        modelItems.add(autofill);
145        
146        //DEFAULT VALUE
147        ElementDefinition defaultValue = FormElementDefinitionHelper.getElementDefinition(ATTRIBUTE_DEFAULT_VALUE, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_DEFAULT_VALUE", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_DEFAULT_VALUE_DESC", null);
148        DisableConditions disableConditions = new DisableConditions();
149        DisableCondition condition = new DisableCondition(ATTRIBUTE_AUTOFILL, OPERATOR.NEQ, CUSTOM_AUTOFILL_VALUE); 
150        disableConditions.getConditions().add(condition);
151        defaultValue.setDisableConditions(disableConditions);
152        modelItems.add(defaultValue);
153        
154        //REGEX
155        ElementDefinition<String> regexp = FormElementDefinitionHelper.getElementDefinition(ATTRIBUTE_REGEXP, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_DESC", null);
156        
157        StaticEnumerator<String> regexStaticEnumerator = new StaticEnumerator<>();
158        regexStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_EMPTY_TEXT"), EMPTY_REGEX_VALUE);
159        regexStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_EMAIL"), EMAIL_REGEX_VALUE);
160        regexStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_PHONE"), PHONE_REGEX_VALUE);
161        regexStaticEnumerator.add(new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_CUSTOM"), CUSTOM_REGEX_VALUE);
162        regexp.setEnumerator(regexStaticEnumerator);
163        Map<String, I18nizableText> regexpWidgetParameters  = new HashMap<>();
164        regexpWidgetParameters.put("naturalOrder", new I18nizableText("true"));
165        regexp.setWidgetParameters(regexpWidgetParameters);
166        regexp.setDefaultValue(EMPTY_REGEX_VALUE);
167        modelItems.add(regexp);
168        
169        //CUSTOM REGEX
170        ElementDefinition customRegex = FormElementDefinitionHelper.getElementDefinition(ATTRIBUTE_CUSTOM_REGEX, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_CUSTOM", "PLUGINS_FORMS_QUESTIONS_DIALOG_SIMPLE_TEXT_REGEXTYPE_CUSTOM_DESC", new DefaultValidator("^\\/.+\\/[gi]*$", new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_REGEXP_ERROR_DESCRIPTION"), false));
171        DisableConditions regexDisableConditions = new DisableConditions();
172        DisableCondition regexCondition = new DisableCondition(ATTRIBUTE_REGEXP, OPERATOR.NEQ, CUSTOM_REGEX_VALUE); 
173        regexDisableConditions.getConditions().add(regexCondition);
174        customRegex.setDisableConditions(regexDisableConditions);
175        modelItems.add(customRegex);
176        
177        return modelItems;
178    }
179    
180    @Override
181    protected SimpleViewItemGroup _getAdvancedTab()
182    {
183        SimpleViewItemGroup advancedFieldset = super._getAdvancedTab();
184        
185        ViewElement placeholder = new ViewElement();
186        placeholder.setDefinition((ElementDefinition< ? >) getModel().getModelItem(ATTRIBUTE_PLACEHOLDER));
187        advancedFieldset.addViewItem(placeholder);
188        
189        ViewElement autofill = new ViewElement();
190        autofill.setDefinition((ElementDefinition< ? >) getModel().getModelItem(ATTRIBUTE_AUTOFILL));
191        advancedFieldset.addViewItem(autofill);
192        
193        ViewElement defaultValue = new ViewElement();
194        defaultValue.setDefinition((ElementDefinition< ? >) getModel().getModelItem(ATTRIBUTE_DEFAULT_VALUE));
195        advancedFieldset.addViewItem(defaultValue);
196        
197        ViewElement regexp = new ViewElement();
198        regexp.setDefinition((ElementDefinition< ? >) getModel().getModelItem(ATTRIBUTE_REGEXP));
199        advancedFieldset.addViewItem(regexp);
200        
201        ViewElement customRegex = new ViewElement();
202        customRegex.setDefinition((ElementDefinition< ? >) getModel().getModelItem(ATTRIBUTE_CUSTOM_REGEX));
203        advancedFieldset.addViewItem(customRegex);
204        
205        return advancedFieldset;
206    }
207    
208    public String getStorageType(FormQuestion question)
209    {
210        return ModelItemTypeConstants.STRING_TYPE_ID;
211    }
212    
213    @Override
214    protected ModelItem _getEntryModelItem(FormQuestion question)
215    {
216        ModelItem item = super._getEntryModelItem(question);
217        ((ElementDefinition) item).setValidator(new DefaultValidator(_getEntryRegExpPattern(question), isMandatory(question)));
218        return item;
219    }
220
221    @Override
222    public void saxAdditionalInfos(ContentHandler contentHandler, FormQuestion question) throws SAXException
223    {
224        super.saxAdditionalInfos(contentHandler, question);
225        
226        if (StringUtils.isEmpty(question.getValue(ATTRIBUTE_DEFAULT_VALUE)))
227        {
228            UserIdentity userIdentity = _currentUserProvider.getUser();
229            User user = userIdentity != null ? _userManager.getUser(userIdentity) : null;
230            String autofill = user != null ? _getAutofillValue(question, user) : null;
231            if (StringUtils.isNotBlank(autofill))
232            {
233                XMLUtils.createElement(contentHandler, "default-value", autofill);
234            }
235        }
236        else
237        {
238            XMLUtils.createElement(contentHandler, "default-value", question.<String>getValue(ATTRIBUTE_DEFAULT_VALUE));
239        }
240        
241        String pattern = _getRegExpPattern(question);
242        if (StringUtils.isNotEmpty(pattern))
243        {
244            XMLUtils.createElement(contentHandler, "pattern", pattern);
245        }
246    }
247    
248    /**
249     * Get autofill value
250     * @param question the question
251     * @param user the user
252     * @return the autofill value.<code>null</code> if there is not autofill value
253     */
254    protected String _getAutofillValue(FormQuestion question, User user) 
255    {
256        String autofill = question.getValue(ATTRIBUTE_AUTOFILL);
257        if (autofill != null)
258        {
259            switch (autofill)
260            {
261                case "email":
262                    return user.getEmail();
263                case "id":
264                    return user.getIdentity().getLogin(); 
265                case "fullName":
266                    return user.getFullName();
267                case "firstName":
268                    return user.getFirstName();
269                case "lastName":
270                    return user.getLastName();
271                default:
272                    return null;
273            }
274        }
275        return null;
276    }
277    
278    /**
279     * Get the validation pattern.
280     * @param question the question
281     * @return the validation pattern.
282     */
283    protected String _getRegExpPattern (FormQuestion question)
284    {
285        String regexpType = question.getValue(ATTRIBUTE_REGEXP);
286        if (regexpType == null)
287        {
288            return null;
289        }
290        else if ("email".equals(regexpType))
291        {
292            return "/^([a-zA-Z0-9_\\.\\-\\+])+\\@(([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})+$/";
293        }
294        else if ("phone".equals(regexpType))
295        {
296            return "/^(\\+?\\(?[0-9]{1,3}\\)?([\\s]?)(\\(0\\))?|0)([\\s]?)([0-9\\-\\+\\s]{4,})+$/";
297        }
298        else if ("custom".equals(regexpType) && !("".equals(question.getValue(ATTRIBUTE_CUSTOM_REGEX)))) 
299        {
300            return question.getValue(ATTRIBUTE_CUSTOM_REGEX);
301        }
302        
303        return null;
304    }
305    
306    /**
307     * Get the validation pattern for entries.
308     * @param question the question
309     * @return the validation pattern.
310     */
311    protected String _getEntryRegExpPattern (FormQuestion question)
312    {
313        String regexpType = question.getValue(ATTRIBUTE_REGEXP);
314        if (regexpType == null)
315        {
316            return null;
317        }
318        else if ("email".equals(regexpType))
319        {
320            return "^([a-zA-Z0-9_\\.\\-\\+])+\\@(([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})+$";
321        }
322        else if ("phone".equals(regexpType))
323        {
324            return "^(\\+?\\(?[0-9]{1,3}\\)?([\\s]?)(\\(0\\))?|0)([\\s]?)([0-9\\-\\+\\s]{4,})+$";
325        }
326        else if ("custom".equals(regexpType) && !("".equals(question.getValue(ATTRIBUTE_CUSTOM_REGEX)))) 
327        {
328            String custom = question.getValue(ATTRIBUTE_CUSTOM_REGEX);
329            return custom.substring(1, custom.length() - 1);
330        }
331        
332        return null;
333    }
334    
335    @Override
336    public void validateQuestionValues(Map<String, Object> values, Map<String, I18nizableText> errors)
337    {
338        super.validateQuestionValues(values, errors);
339        
340        if (StringUtils.isNotBlank((String) values.get(ATTRIBUTE_CUSTOM_REGEX)) 
341                && !Pattern.matches("^\\/.+\\/[gi]*$", (CharSequence) values.get(ATTRIBUTE_CUSTOM_REGEX)))
342        {
343            errors.put(ATTRIBUTE_CUSTOM_REGEX, new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTIONS_REGEXP_ERROR_DESCRIPTION"));
344        }
345    }
346    
347    public I18nizableText getDefaultTitle()
348    {
349        return new I18nizableText("plugin.forms", DEFAULT_TITLE);
350    }
351    
352    @Override
353    public List<String> getFieldToDisableIfFormPublished(FormQuestion question)
354    {
355        List<String> fieldNames =  super.getFieldToDisableIfFormPublished(question);
356        fieldNames.add(ATTRIBUTE_REGEXP);
357        fieldNames.add(ATTRIBUTE_CUSTOM_REGEX);
358        return fieldNames;
359    }
360}