View Javadoc

1   /**
2    * This file Copyright (c) 2010-2012 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.form.templates.components;
35  
36  import info.magnolia.cms.i18n.I18nContentSupportFactory;
37  import info.magnolia.cms.i18n.Messages;
38  import info.magnolia.cms.i18n.MessagesManager;
39  import info.magnolia.cms.i18n.MessagesUtil;
40  import info.magnolia.cms.security.SilentSessionOp;
41  import info.magnolia.context.MgnlContext;
42  import info.magnolia.jcr.util.NodeTypes.Renderable;
43  import info.magnolia.jcr.util.NodeUtil;
44  import info.magnolia.jcr.util.PropertyUtil;
45  import info.magnolia.module.form.FormModule;
46  import info.magnolia.module.form.engine.FormDataBinder;
47  import info.magnolia.module.form.engine.FormField;
48  import info.magnolia.module.form.engine.FormStepState;
49  import info.magnolia.module.form.validators.ValidationResult;
50  import info.magnolia.module.form.validators.Validator;
51  import info.magnolia.repository.RepositoryConstants;
52  import info.magnolia.util.EscapeUtil;
53  
54  import java.util.Iterator;
55  import java.util.Locale;
56  
57  import javax.jcr.Node;
58  import javax.jcr.Property;
59  import javax.jcr.RepositoryException;
60  import javax.jcr.Session;
61  import javax.jcr.Value;
62  
63  import org.apache.commons.lang.StringUtils;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  /**
68   * Default {@link info.magnolia.module.form.engine.FormDataBinder} that performs binding and validation for the
69   * built-in fields.
70   */
71  public class DefaultFormDataBinder implements FormDataBinder {
72  
73      private static final Logger log = LoggerFactory.getLogger(DefaultFormDataBinder.class);
74  
75      private static final String CONTENT_NAME_TEXT_FIELD_GROUP = "edits";
76  
77      private static final String DEFAULT_PATH = "info.magnolia.module.form.messages";
78  
79      private static final String PATH_TO_TEMPLATES = "/modules/%s/templates/%s";
80  
81      private String i18nBasename;
82  
83      public void setI18nBasename(String i18nBasename) {
84          this.i18nBasename = i18nBasename;
85      }
86  
87      public static String getDefaultPath(){
88          return DEFAULT_PATH;
89      }
90  
91      @Override
92      public FormStepState bindAndValidate(Node component) throws RepositoryException {
93          FormStepState step = new FormStepState();
94          step.setParagraphUuid(NodeUtil.getNodeIdentifierIfPossible(component));
95          if (component.hasNode("fieldsets")) {
96              Iterator<Node> itFieldsets = NodeUtil.getNodes(component.getNode("fieldsets")).iterator();
97              bindAndValidateFieldset(itFieldsets, step);
98          }
99          return step;
100     }
101 
102     private void bindAndValidateFieldset(Iterator<Node> itFieldsets, FormStepState step) throws RepositoryException {
103         while (itFieldsets.hasNext()) {
104             Node fieldset = itFieldsets.next();
105             if (fieldset.hasNode("fields")) {
106                 Iterator<Node> iterator = NodeUtil.getNodes(fieldset.getNode("fields")).iterator();
107                 bindAndValidateFields(iterator, step);
108             }
109         }
110     }
111 
112     /**
113      * Besides validating the fields for this step, it will also check for the existence of a <code>escapeHtml</code> property in a field configuration.
114      * If such property value is false, HTML won't be escaped. Default value is true.
115      */
116     protected void bindAndValidateFields(Iterator<Node> iterator, FormStepState step) throws RepositoryException {
117         while (iterator.hasNext()) {
118             final Node node = iterator.next();
119 
120             if (node.hasProperty("controlName")) {
121                 final String controlName = node.getProperty("controlName").getString();
122 
123                 Node config = getFieldConfiguration(node);
124                 boolean escapeHtml = true;
125 
126                 if (config != null) {
127                     escapeHtml = PropertyUtil.getBoolean(config, "escapeHtml", true);
128                 }
129 
130                 final String values = StringUtils.join(MgnlContext.getParameterValues(controlName), "__");
131                 final String value = escapeHtml ? EscapeUtil.escapeXss(values) : values;
132 
133                 FormField field = new FormField();
134                 field.setName(controlName);
135                 field.setValue(value);
136                 step.add(field);
137 
138                 if (StringUtils.isEmpty(value) && isMandatory(node)) {
139                     field.setErrorMessage(getErrorMessage("mandatory", node));
140                 } else if (value != null && node.hasProperty("validation")) {
141                     for (String validatorName : getValidatorNames(node)) {
142                         Validator validator = FormModule.getInstance().getValidatorByName(validatorName);
143                         if (validator != null) {
144                             ValidationResult validationResult = validator.validateWithResult(value);
145                             if (!validationResult.isSuccess()) {
146                                 field.setErrorMessage(getValidatorErrorMessage(validator, validationResult, node));
147                             }
148                         }
149                     }
150                 } else if (node.hasNode(CONTENT_NAME_TEXT_FIELD_GROUP)) {
151                     Iterator<Node> textFieldGroup = NodeUtil.getNodes(node.getNode(CONTENT_NAME_TEXT_FIELD_GROUP)).iterator();
152                     bindAndValidateFields(textFieldGroup, step);
153                 }
154             }
155         }
156     }
157 
158     /**
159      * Gets validator names from <code>validation</code> property.
160      */
161     private String[] getValidatorNames(Node node) throws RepositoryException {
162         Property validationProperty = node.getProperty("validation");
163 
164         Value[] values;
165         if (validationProperty.isMultiple()) {
166             values = validationProperty.getValues();
167         } else {
168             values = new Value[]{validationProperty.getValue()};
169         }
170 
171         return PropertyUtil.getValuesStringList(values).toArray(new String[values.length]);
172     }
173 
174     protected boolean isMandatory(Node node) {
175         return PropertyUtil.getBoolean(node, "mandatory", false);
176     }
177 
178     private String getValidatorErrorMessage(Validator validator, ValidationResult validationResult, Node node) {
179 
180         // If the validator returned an error message will use it, possible with a resource bundle configured on the validator itself
181         if (StringUtils.isNotEmpty(validationResult.getErrorMessage())) {
182             return getErrorMessage(validationResult.getErrorMessage(), "invalid input", node, validator.getI18nBasename());
183         }
184 
185         // Otherwise we'll default to a key of format: form.user.errorMessage.<validator name>
186         return getErrorMessage(validator.getName(), node);
187     }
188 
189     protected String getErrorMessage(String message, Node node) {
190         return getErrorMessage("form.user.errorMessage." + message, "invalid input", node, null);
191     }
192 
193     private String getErrorMessage(String key, String defaultMsg, Node node, String overridingResourceBundle) {
194 
195         Locale locale = I18nContentSupportFactory.getI18nSupport().getLocale();
196 
197         Messages messages = MessagesManager.getMessages(getDefaultPath());
198 
199         messages = MessagesUtil.chain(MessagesManager.getMessages(i18nBasename, locale), messages);
200 
201         if (overridingResourceBundle != null) {
202             messages = MessagesUtil.chain(MessagesManager.getMessages(overridingResourceBundle, locale), messages);
203         }
204 
205         String errorMessage = messages.getWithDefault(key, defaultMsg);
206         String title = PropertyUtil.getString(node, "title");
207         return title + ": " + errorMessage;
208     }
209 
210     /**
211      * @return The field configuration for the current node or null if such config doesn't exist or couldn't be retrieved.
212      */
213     protected Node getFieldConfiguration(Node node) {
214 
215         try {
216             String template = Renderable.getTemplate(node);
217             if (template == null) {
218                 log.warn("Could not find mgnl:template for node at {}", node.getPath());
219                 return null;
220             }
221             String[] idAndPath = template.split(":");
222 
223             final String path;
224 
225             if (idAndPath.length > 1) {
226                 path = String.format(PATH_TO_TEMPLATES, idAndPath[0], idAndPath[1]);
227             } else {
228                 path = String.format(PATH_TO_TEMPLATES, "form", idAndPath[0]);
229             }
230 
231             log.debug("Trying to get field configuration at {}", path);
232             return MgnlContext.doInSystemContext(new SilentSessionOp<Node>(RepositoryConstants.CONFIG) {
233 
234                 @Override
235                 public Node doExec(Session session) throws Throwable {
236                     return session.getNode(path);
237                 }
238             });
239 
240         } catch (RepositoryException e) {
241             e.printStackTrace();
242         }
243         return null;
244     }
245 }