001/* 002 * Copyright 2010 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.processing; 017 018import java.io.BufferedInputStream; 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.io.Reader; 025import java.nio.charset.StandardCharsets; 026import java.sql.Connection; 027import java.sql.PreparedStatement; 028import java.sql.ResultSet; 029import java.sql.SQLException; 030import java.sql.Timestamp; 031import java.sql.Types; 032import java.text.DateFormat; 033import java.text.ParseException; 034import java.text.SimpleDateFormat; 035import java.util.ArrayList; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.Date; 039import java.util.HashMap; 040import java.util.Iterator; 041import java.util.LinkedHashMap; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045import java.util.regex.Pattern; 046import java.util.regex.PatternSyntaxException; 047 048import javax.mail.MessagingException; 049 050import org.apache.avalon.framework.context.Context; 051import org.apache.avalon.framework.context.ContextException; 052import org.apache.avalon.framework.context.Contextualizable; 053import org.apache.avalon.framework.parameters.Parameters; 054import org.apache.avalon.framework.service.ServiceException; 055import org.apache.avalon.framework.service.ServiceManager; 056import org.apache.cocoon.acting.ServiceableAction; 057import org.apache.cocoon.environment.ObjectModelHelper; 058import org.apache.cocoon.environment.Redirector; 059import org.apache.cocoon.environment.Request; 060import org.apache.cocoon.servlet.multipart.Part; 061import org.apache.cocoon.servlet.multipart.PartOnDisk; 062import org.apache.cocoon.servlet.multipart.RejectedPart; 063import org.apache.commons.io.IOUtils; 064import org.apache.commons.lang.StringUtils; 065import org.apache.excalibur.source.Source; 066import org.apache.excalibur.source.SourceResolver; 067 068import org.ametys.cms.repository.Content; 069import org.ametys.cms.transformation.xslt.URIDecoder; 070import org.ametys.core.captcha.CaptchaHelper; 071import org.ametys.core.datasource.ConnectionHelper; 072import org.ametys.core.datasource.dbtype.SQLDatabaseTypeExtensionPoint; 073import org.ametys.core.group.InvalidModificationException; 074import org.ametys.core.util.mail.SendMailHelper; 075import org.ametys.plugins.forms.Field; 076import org.ametys.plugins.forms.Field.FieldType; 077import org.ametys.plugins.forms.Form; 078import org.ametys.plugins.forms.FormsException; 079import org.ametys.plugins.forms.data.FieldValue; 080import org.ametys.plugins.forms.jcr.FormPropertiesManager; 081import org.ametys.plugins.forms.table.DbTypeHelper; 082import org.ametys.plugins.forms.table.FormTableManager; 083import org.ametys.plugins.repository.AmetysObjectResolver; 084import org.ametys.plugins.repository.UnknownAmetysObjectException; 085import org.ametys.plugins.workflow.store.JdbcWorkflowStore; 086import org.ametys.plugins.workflow.support.WorkflowHelper; 087import org.ametys.plugins.workflow.support.WorkflowProvider; 088import org.ametys.runtime.config.Config; 089import org.ametys.runtime.i18n.I18nizableText; 090import org.ametys.web.URIPrefixHandler; 091import org.ametys.web.repository.content.SharedContent; 092import org.ametys.web.repository.content.WebContent; 093import org.ametys.web.repository.page.Page; 094import org.ametys.web.site.SiteConfigurationExtensionPoint; 095 096import com.opensymphony.workflow.InvalidActionException; 097import com.opensymphony.workflow.InvalidEntryStateException; 098import com.opensymphony.workflow.InvalidInputException; 099import com.opensymphony.workflow.InvalidRoleException; 100import com.opensymphony.workflow.Workflow; 101import com.opensymphony.workflow.WorkflowException; 102 103/** 104 * Action that processes the user submitted data on a form. 105 */ 106public class ProcessFormAction extends ServiceableAction implements Contextualizable 107{ 108 /** The form ID parameter. */ 109 protected static final String PARAM_FORM_ID = "ametys-form-id"; 110 111 /** The content ID parameter. */ 112 protected static final String PARAM_CONTENT_ID = "ametys-content-id"; 113 114 /** The integer validation pattern. */ 115 protected static final Pattern _INT_PATTERN = Pattern.compile(FormValidators.getIntegerPattern()); 116 117 /** The integer validation pattern. */ 118 protected static final Pattern _FLOAT_PATTERN = Pattern.compile(FormValidators.getFloatPattern()); 119 120 /** The integer validation pattern. */ 121 protected static final Pattern _DATE_PATTERN = Pattern.compile(FormValidators.getDatePattern()); 122 123 /** The integer validation pattern. */ 124 protected static final Pattern _TIME_PATTERN = Pattern.compile(FormValidators.getTimePattern()); 125 126 /** The integer validation pattern. */ 127 protected static final Pattern _DATETIME_PATTERN = Pattern.compile(FormValidators.getDateTimePattern()); 128 129 /** The email validation pattern. */ 130 protected static final Pattern _EMAIL_PATTERN = Pattern.compile(FormValidators.getEmailPattern()); 131 132 /** The phone validation pattern. */ 133 protected static final Pattern _PHONE_PATTERN = Pattern.compile(FormValidators.getPhonePattern()); 134 135 /** The date format pattern. */ 136 protected static final DateFormat _DATE_FORMAT = new SimpleDateFormat(FormValidators.getDateFormat()); 137 138 /** The time format pattern. */ 139 protected static final DateFormat _TIME_FORMAT = new SimpleDateFormat(FormValidators.getTimeFormat()); 140 141 /** The date and time format pattern. */ 142 protected static final DateFormat _DATETIME_FORMAT = new SimpleDateFormat(FormValidators.getDateTimeFormat()); 143 144 private static final String __FORM_ENTRY_PATTERN = "${form}"; 145 146 private static final String ANTIVIRUS_RESULT_OK = "OK"; 147 148 /** Form properties manager. */ 149 protected FormPropertiesManager _formPropertiesManager; 150 151 /** Form table manager. */ 152 protected FormTableManager _formTableManager; 153 154 /** The source resolver. */ 155 protected SourceResolver _sourceResolver; 156 157 /** The ametys object resolver */ 158 protected AmetysObjectResolver _ametysObjectResolver; 159 160 /** The site configuration. */ 161 protected SiteConfigurationExtensionPoint _siteConfiguration; 162 163 /** The plugin name. */ 164 protected String _pluginName; 165 166 /** The URI prefix handler */ 167 protected URIPrefixHandler _prefixHandler; 168 169 /** The workflow provider */ 170 protected WorkflowProvider _workflowProvider; 171 172 /** The workflow helper component */ 173 protected WorkflowHelper _workflowHelper; 174 175 /** The SQLDatabaseTypeExtensionPoint instance */ 176 protected SQLDatabaseTypeExtensionPoint _sqlDatabaseTypeExtensionPoint; 177 178 /** The context */ 179 protected Context _context; 180 181 @Override 182 public void contextualize(Context context) throws ContextException 183 { 184 _context = context; 185 } 186 187 @Override 188 public void service(ServiceManager serviceManager) throws ServiceException 189 { 190 super.service(serviceManager); 191 _formPropertiesManager = (FormPropertiesManager) serviceManager.lookup(FormPropertiesManager.ROLE); 192 _formTableManager = (FormTableManager) serviceManager.lookup(FormTableManager.ROLE); 193 _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 194 _ametysObjectResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 195 _siteConfiguration = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE); 196 _sqlDatabaseTypeExtensionPoint = (SQLDatabaseTypeExtensionPoint) manager.lookup(SQLDatabaseTypeExtensionPoint.ROLE); 197 _prefixHandler = (URIPrefixHandler) serviceManager.lookup(URIPrefixHandler.ROLE); 198 _workflowProvider = (WorkflowProvider) serviceManager.lookup(WorkflowProvider.ROLE); 199 _workflowHelper = (WorkflowHelper) serviceManager.lookup(WorkflowHelper.ROLE); 200 } 201 202 @Override 203 public Map act(Redirector redirector, org.apache.cocoon.environment.SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 204 { 205 Request request = ObjectModelHelper.getRequest(objectModel); 206 207 _pluginName = (String) request.getAttribute("pluginName"); 208 String siteName = (String) request.getAttribute("site"); 209 210 Content content = (Content) request.getAttribute(Content.class.getName()); 211 if (content != null && content instanceof SharedContent) 212 { 213 siteName = _getSiteNameFromSharedContent ((SharedContent) content); 214 } 215 216 String id = request.getParameter(PARAM_FORM_ID); 217 if (StringUtils.isEmpty(id)) 218 { 219 throw new IllegalArgumentException("A form ID must be provided."); 220 } 221 222 Form form = _formPropertiesManager.getForm(siteName, id); 223 if (form == null) 224 { 225 throw new IllegalArgumentException("No form definition exists for ID " + id + " and site " + siteName); 226 } 227 228 FormErrors errors = new FormErrors(form, new LinkedHashMap<String, List<I18nizableText>>()); 229 230 Map<String, FieldValue> input = _getInput(form, request, errors); 231 232 _validateInput(form, input, errors, request); 233 234 if (errors.hasErrors()) 235 { 236 request.setAttribute("form-errors", errors); 237 return null; 238 } 239 240 _insertInput(form, input, objectModel); 241 242 _sendEmails(form, input, siteName); 243 244 if (StringUtils.isNotEmpty(form.getRedirectTo())) 245 { 246 String pageId = form.getRedirectTo(); 247 try 248 { 249 Page page = _ametysObjectResolver.resolveById(pageId); 250 redirector.globalRedirect(false, _prefixHandler.getAbsoluteUriPrefix(page.getSiteName()) + "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + ".html"); 251 } 252 catch (UnknownAmetysObjectException e) 253 { 254 getLogger().warn("The form '" + form.getId() + "' wants to redirect to the unexisting page '" + pageId + "'. Redirecting to default page.", e); 255 } 256 } 257 258 return EMPTY_MAP; 259 } 260 261 private String _getSiteNameFromSharedContent (SharedContent sharedContent) 262 { 263 SharedContent defaultContentContent = _ametysObjectResolver.resolveById(sharedContent.getId()); 264 265 Content initialContent = defaultContentContent.getInitialContent(); 266 if (initialContent != null && initialContent instanceof WebContent) 267 { 268 return ((WebContent) initialContent).getSiteName(); 269 } 270 271 return null; 272 } 273 274 /** 275 * Antivirus analysis. Based on clamscan results. 276 * 277 * @param fileToAnalyse the file to analyse 278 * @return true if the file is correct, false if a malware was discovered in 279 * the file 280 */ 281 private boolean _analyseFile(File fileToAnalyse) 282 { 283 InputStream in = null; 284 boolean toReturn = false; 285 try 286 { 287 String command = Config.getInstance().getValueAsString("plugins.forms.antivirus.command"); 288 String absolutePath = fileToAnalyse.getAbsolutePath(); 289 String[] commandExcecuted = new String[] {command, absolutePath}; 290 if (getLogger().isDebugEnabled()) 291 { 292 getLogger().debug("Executing antivirus analysis : " + commandExcecuted); 293 } 294 // Execute command 295 Process child = Runtime.getRuntime().exec(commandExcecuted); 296 297 // Get the input stream and read from it 298 in = new BufferedInputStream(child.getInputStream()); 299 List<String> lines = IOUtils.readLines(in, StandardCharsets.UTF_8); 300 if (lines != null && lines.size() > 0) 301 { 302 if (getLogger().isDebugEnabled()) 303 { 304 getLogger().debug("Result of the command : "); 305 StringBuilder builder = new StringBuilder(); 306 for (String line : lines) 307 { 308 builder.append(line); 309 } 310 getLogger().debug(builder.toString()); 311 } 312 String firstLine = lines.get(0); 313 if (firstLine.startsWith(absolutePath)) 314 { 315 return ANTIVIRUS_RESULT_OK.equals(firstLine.substring(absolutePath.length() + 2)); 316 } 317 } 318 } 319 catch (IOException e) 320 { 321 getLogger().error("Unable to get to output from the command", e); 322 } 323 finally 324 { 325 IOUtils.closeQuietly(in); 326 } 327 328 return toReturn; 329 } 330 331 /** 332 * Get the user input. 333 * 334 * @param form the Form object. 335 * @param request the user request. 336 * @param errors the input errors. 337 * @return the user data as a Map of column name -> column entry. 338 */ 339 protected Map<String, FieldValue> _getInput(Form form, Request request, FormErrors errors) 340 { 341 Map<String, FieldValue> entries = new LinkedHashMap<>(); 342 343 // For each field declared in the form, 344 for (Field field : form.getFields()) 345 { 346 final String id = field.getId(); 347 final String name = field.getName(); 348 349 FieldValue entry = null; 350 351 switch (field.getType()) 352 { 353 case TEXT: 354 case HIDDEN: 355 case PASSWORD: 356 String sValue = (String) request.get(name); 357 entry = new FieldValue(id, Types.VARCHAR, sValue, field); 358 break; 359 case SELECT: 360 String[] values = request.getParameterValues(name); 361 sValue = values == null ? "" : StringUtils.join(values, "\n"); 362 entry = new FieldValue(id, Types.VARCHAR, sValue, field); 363 break; 364 case TEXTAREA: 365 sValue = (String) request.get(name); 366 entry = new FieldValue(id, Types.LONGVARCHAR, sValue, field); 367 break; 368 case RADIO: 369 if (!entries.containsKey(name)) 370 { 371 sValue = (String) request.get(name); 372 entry = new FieldValue(name, Types.VARCHAR, sValue, field); 373 } 374 else 375 { 376 // The value exists, clone it, concatenating the label. 377 if (StringUtils.isNotEmpty(field.getLabel())) 378 { 379 Field radioField = entries.get(name).getField(); 380 Field dummyField = new Field(radioField.getId(), radioField.getType(), radioField.getName(), radioField.getLabel() + "/" + field.getLabel(), 381 radioField.getProperties()); 382 entries.get(name).setField(dummyField); 383 } 384 } 385 break; 386 case CHECKBOX: 387 boolean bValue = request.get(name) != null; 388 entry = new FieldValue(id, Types.BOOLEAN, bValue, field); 389 break; 390 case FILE: 391 entry = _getFileEntry(request, field, id, name, errors); 392 break; 393 case CAPTCHA: 394 final String formId = request.getParameter(PARAM_FORM_ID); 395 final String contentId = request.getParameter(PARAM_CONTENT_ID); 396 397 final String encodedName = contentId + "%20" + formId + "%20" + field.getId(); 398 399 final String captchaValue = request.getParameter(encodedName); 400 final String captchaKey = request.getParameter(encodedName + "-key"); 401 402 entry = new FieldValue(id, Types.OTHER, new String[] {captchaValue, captchaKey}, field); 403 break; 404 default: 405 break; 406 } 407 408 if (entry != null) 409 { 410 entries.put(entry.getColumnName(), entry); 411 } 412 } 413 414 return entries; 415 } 416 417 /** 418 * Get a file entry from the request. 419 * 420 * @param request the user request. 421 * @param field the field. 422 * @param id the entry ID. 423 * @param name the field name. 424 * @param errors the form errors. 425 * @return the file entry. 426 */ 427 protected FieldValue _getFileEntry(Request request, Field field, String id, String name, FormErrors errors) 428 { 429 FieldValue entry = null; 430 431 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_FILE_"; 432 433 Part part = (Part) request.get(name); 434 if (part instanceof RejectedPart) 435 { 436 errors.addError(field.getId(), new I18nizableText("plugin." + _pluginName, keyPrefix + "REJECTED")); 437 } 438 else 439 { 440 PartOnDisk uploadedFilePart = (PartOnDisk) part; 441 if (uploadedFilePart != null) 442 { 443 entry = new FieldValue(id, Types.BLOB, uploadedFilePart.getFile(), field); 444 } 445 else 446 { 447 entry = new FieldValue(id, Types.BLOB, null, field); 448 } 449 } 450 451 return entry; 452 } 453 454 /** 455 * Insert the user submission in the database. 456 * 457 * @param form the Form object. 458 * @param input the user input. 459 * @param objectModel The object model 460 * @throws FormsException if an error occurs. 461 * @throws WorkflowException if an exception occurs while initializing a workflow instance for a form entry 462 * @throws InvalidModificationException If an error occurs 463 */ 464 protected void _insertInput(Form form, Map<String, FieldValue> input, Map objectModel) throws FormsException, WorkflowException, InvalidModificationException 465 { 466 final String tableName = FormTableManager.TABLE_PREFIX + form.getId(); 467 468 Connection connection = null; 469 PreparedStatement stmt = null; 470 471 try 472 { 473 String dataSourceId = Config.getInstance().getValueAsString(FormTableManager.FORMS_POOL_CONFIG_PARAM); 474 connection = ConnectionHelper.getConnection(dataSourceId); 475 476 String dbType = ConnectionHelper.getDatabaseType(connection); 477 478 StringBuilder sql = new StringBuilder(); 479 StringBuilder values = new StringBuilder(); 480 481 sql.append("INSERT INTO ").append(_sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, tableName)).append(" ("); 482 if (DbTypeHelper.insertIdentity(dbType)) 483 { 484 sql.append("id, "); 485 } 486 sql.append(FormTableManager.CREATION_DATE_FIELD).append(", "); 487 488 if (ConnectionHelper.DATABASE_ORACLE.equals(dbType)) 489 { 490 values.append("seq_" + form.getId() + ".nextval, "); 491 } 492 else if (DbTypeHelper.insertIdentity(dbType)) 493 { 494 values.append("?, "); 495 } 496 497 // creation date 498 values.append("?, "); 499 500 Iterator<FieldValue> entries = _getEntriesToInsert(input.values()).iterator(); 501 while (entries.hasNext()) 502 { 503 FieldValue entry = entries.next(); 504 String colName = entry.getColumnName(); 505 sql.append(_sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, colName)); 506 values.append("?"); 507 508 if (entry.getType() == Types.BLOB) 509 { 510 String fileNameColumn = colName + FormTableManager.FILE_NAME_COLUMN_SUFFIX; 511 String normalizedName = DbTypeHelper.normalizeName(dbType, fileNameColumn); 512 513 sql.append(", ").append(_sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, normalizedName)); 514 values.append(", ?"); 515 } 516 517 if (entries.hasNext()) 518 { 519 sql.append(", "); 520 values.append(", "); 521 } 522 } 523 524 if (_formTableManager.hasWorkflowIdColumn(form.getId())) 525 { 526 // Ensure compatibility with form entries that remained without workflow 527 sql.append(", ").append(_sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, FormTableManager.WORKFLOW_ID_FIELD)); 528 values.append(", ?"); 529 } 530 531 sql.append(") VALUES (").append(values).append(")"); 532 533 if (getLogger().isDebugEnabled()) 534 { 535 getLogger().debug("Inserting a user submission in the database :\n" + sql.toString()); 536 } 537 538 stmt = connection.prepareStatement(sql.toString()); 539 540 _setParameters(form, input, stmt, dbType); 541 542 stmt.executeUpdate(); 543 544 ConnectionHelper.cleanup(stmt); 545 546 if (_formTableManager.hasWorkflowIdColumn(form.getId())) 547 { 548 _createWorkflow(form, objectModel, tableName, connection, dbType); 549 } 550 } 551 catch (SQLException e) 552 { 553 getLogger().error("Error inserting submission data.", e); 554 throw new FormsException("Error inserting submission data.", e); 555 } 556 finally 557 { 558 ConnectionHelper.cleanup(stmt); 559 ConnectionHelper.cleanup(connection); 560 } 561 } 562 563 private void _createWorkflow(Form form, Map objectModel, final String tableName, Connection connection, String dbType) throws SQLException, InvalidModificationException, InvalidActionException, InvalidRoleException, InvalidInputException, InvalidEntryStateException, WorkflowException, FormsException 564 { 565 String id = null; 566 if (ConnectionHelper.DATABASE_MYSQL.equals(dbType)) 567 { 568 try (PreparedStatement stmt = connection.prepareStatement("SELECT id FROM " + _sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, tableName) + " WHERE id = last_insert_id()"); 569 ResultSet rs = stmt.executeQuery()) 570 { 571 if (rs.next()) 572 { 573 id = rs.getString("id"); 574 } 575 else 576 { 577 if (connection.getAutoCommit()) 578 { 579 throw new InvalidModificationException("Cannot retrieve inserted group. Group was created but listeners not called : base may be inconsistant"); 580 } 581 else 582 { 583 connection.rollback(); 584 throw new InvalidModificationException("Cannot retrieve inserted group. Rolling back"); 585 } 586 } 587 } 588 } 589 else if (ConnectionHelper.DATABASE_DERBY.equals(dbType)) 590 { 591 try (PreparedStatement stmt = connection.prepareStatement("VALUES IDENTITY_VAL_LOCAL ()"); 592 ResultSet rs = stmt.executeQuery()) 593 { 594 if (rs.next()) 595 { 596 id = rs.getString(1); 597 } 598 } 599 } 600 else if (ConnectionHelper.DATABASE_HSQLDB.equals(dbType)) 601 { 602 603 try (PreparedStatement stmt = connection.prepareStatement("CALL IDENTITY ()"); 604 ResultSet rs = stmt.executeQuery()) 605 { 606 if (rs.next()) 607 { 608 id = rs.getString(1); 609 } 610 } 611 } 612 else if (ConnectionHelper.DATABASE_POSTGRES.equals(dbType)) 613 { 614 try (PreparedStatement stmt = connection.prepareStatement("SELECT currval('groups_id_seq')"); 615 ResultSet rs = stmt.executeQuery()) 616 { 617 if (rs.next()) 618 { 619 id = rs.getString(1); 620 } 621 } 622 } 623 624 if (id != null) 625 { 626 // create workflow with entry id 627 Workflow workflow = _workflowProvider.getExternalWorkflow(JdbcWorkflowStore.ROLE); 628 629 String workflowName = form.getWorkflowName(); 630 int initialActionId = _workflowHelper.getInitialAction(workflowName); 631 632 Map<String, Object> inputs = new HashMap<>(); 633 inputs.put("formId", form.getId()); 634 inputs.put("entryId", id); 635 inputs.put(ObjectModelHelper.PARENT_CONTEXT, ObjectModelHelper.getContext(objectModel)); 636 inputs.put(ObjectModelHelper.REQUEST_OBJECT, ObjectModelHelper.getRequest(objectModel)); 637 long workflowInstanceId = workflow.initialize(form.getWorkflowName(), initialActionId, inputs); 638 // insert workflow id in db 639 _updateWorkflowId(form.getId(), id, workflowInstanceId); 640 } 641 } 642 643 private void _updateWorkflowId(String formId, String entryId, long workflowId) throws FormsException 644 { 645 final String tableName = FormTableManager.TABLE_PREFIX + formId; 646 647 Connection connection = null; 648 PreparedStatement stmt = null; 649 650 try 651 { 652 String dataSourceId = Config.getInstance().getValueAsString(FormTableManager.FORMS_POOL_CONFIG_PARAM); 653 connection = ConnectionHelper.getConnection(dataSourceId); 654 655 String dbType = ConnectionHelper.getDatabaseType(connection); 656 657 StringBuilder sql = new StringBuilder(); 658 659 sql.append("UPDATE ").append(_sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, tableName)) 660 .append(" SET ").append(_sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, FormTableManager.WORKFLOW_ID_FIELD)).append(" = ?") 661 .append(" WHERE id = ?"); 662 663 stmt = connection.prepareStatement(sql.toString()); 664 stmt.setLong(1, workflowId); 665 stmt.setString(2, entryId); 666 667 stmt.executeUpdate(); 668 } 669 catch (SQLException e) 670 { 671 getLogger().error("Error inserting submission data.", e); 672 throw new FormsException("Error inserting submission data.", e); 673 } 674 finally 675 { 676 ConnectionHelper.cleanup(stmt); 677 ConnectionHelper.cleanup(connection); 678 } 679 } 680 681 /** 682 * Set the parameters into the prepared statement. 683 * 684 * @param form the form. 685 * @param input the user input. 686 * @param stmt the prepared statement. 687 * @param dbType the database type. 688 * @throws SQLException if a SQL error occurs. 689 * @throws WorkflowException if an exception occurs while initializing a workflow instance for a form entry 690 */ 691 protected void _setParameters(Form form, Map<String, FieldValue> input, PreparedStatement stmt, String dbType) throws SQLException, WorkflowException 692 { 693 // First two are id (auto-increment) and creation date 694 int index = 1; 695 if (DbTypeHelper.insertIdentity(dbType) && !ConnectionHelper.DATABASE_ORACLE.equals(dbType)) 696 { 697 DbTypeHelper.setIdentity(stmt, index, dbType); 698 index++; 699 } 700 701 stmt.setTimestamp(index, new Timestamp(System.currentTimeMillis())); 702 index++; 703 704 // Then set all the entries to be stored into the prepared statement. 705 Collection<FieldValue> entries = _getEntriesToInsert(input.values()); 706 for (FieldValue entry : entries) 707 { 708 if (entry.getValue() == null) 709 { 710 stmt.setNull(index, entry.getType()); 711 if (entry.getType() == Types.BLOB) 712 { 713 index++; 714 stmt.setNull(index, Types.VARCHAR); 715 } 716 } 717 else if (entry.getType() == Types.BLOB && entry.getValue() instanceof File) 718 { 719 File file = (File) entry.getValue(); 720 721 try 722 { 723 if (ConnectionHelper.DATABASE_POSTGRES.equals(dbType)) 724 { 725 stmt.setBinaryStream(index, new FileInputStream(file), (int) file.length()); 726 } 727 else 728 { 729 stmt.setBlob(index, new FileInputStream(file)); 730 } 731 index++; 732 733 stmt.setString(index, file.getName()); 734 } 735 catch (IOException e) 736 { 737 // Should never happen, as it was checked before. 738 getLogger().error("Can't read uploaded file.", e); 739 } 740 } 741 else if (entry.getType() == Types.BOOLEAN && entry.getValue() instanceof Boolean) 742 { 743 stmt.setInt(index, Boolean.TRUE.equals(entry.getValue()) ? 1 : 0); 744 } 745 else 746 { 747 stmt.setObject(index, entry.getValue(), entry.getType()); 748 } 749 index++; 750 } 751 752 // Ensure compatibility with form entries that remained without workflow 753 if (_formTableManager.hasWorkflowIdColumn(form.getId())) 754 { 755 stmt.setLong(index, -1); 756 } 757 } 758 759 /** 760 * Validate the user input. 761 * 762 * @param form the Form object. 763 * @param input the user input. 764 * @param errors the FormErrors object to fill. 765 * @param request the user request. 766 */ 767 protected void _validateInput(Form form, Map<String, FieldValue> input, FormErrors errors, Request request) 768 { 769 for (FieldValue entry : input.values()) 770 { 771 Field field = entry.getField(); 772 switch (field.getType()) 773 { 774 case TEXT: 775 errors.addErrors(field.getId(), _validateTextField(entry, request)); 776 break; 777 case PASSWORD: 778 errors.addErrors(field.getId(), _validatePassword(entry, request)); 779 break; 780 case SELECT: 781 errors.addErrors(field.getId(), _validateSelect(entry, request)); 782 break; 783 case TEXTAREA: 784 errors.addErrors(field.getId(), _validateTextarea(entry, request)); 785 break; 786 case RADIO: 787 errors.addErrors(field.getId(), _validateRadio(entry, request)); 788 break; 789 case CHECKBOX: 790 errors.addErrors(field.getId(), _validateCheckbox(entry, request)); 791 break; 792 case FILE: 793 errors.addErrors(field.getId(), _validateFile(entry, request)); 794 break; 795 case CAPTCHA: 796 errors.addErrors(field.getId(), _validateCaptcha(entry, request)); 797 break; 798 case HIDDEN: 799 default: 800 break; 801 } 802 } 803 } 804 805 /** 806 * Validate a text field. 807 * 808 * @param entry the text field entry. 809 * @param request the user request. 810 * @return the list of error messages. 811 */ 812 protected List<I18nizableText> _validateTextField(FieldValue entry, Request request) 813 { 814 List<I18nizableText> errors = new ArrayList<>(); 815 816 Field field = entry.getField(); 817 Map<String, String> properties = field.getProperties(); 818 String value = StringUtils.defaultString((String) entry.getValue()); 819 820 final String textPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_TEXT_"; 821 822 errors.addAll(_validateMandatory(entry, textPrefix)); 823 824 errors.addAll(_validateConfirmation(entry, request, textPrefix)); 825 826 String regexpType = properties.get("regexptype"); 827 if (StringUtils.isEmpty(regexpType) || "text".equals(regexpType)) 828 { 829 errors.addAll(_validateTextLength(entry, textPrefix)); 830 } 831 else if (!StringUtils.isBlank(value)) 832 { 833 _validateNonblankRegexp(entry, errors, value, textPrefix, regexpType); 834 } 835 836 return errors; 837 } 838 839 private void _validateNonblankRegexp(FieldValue entry, List<I18nizableText> errors, String value, final String textPrefix, String regexpType) 840 { 841 if ("int".equals(regexpType) && StringUtils.isBlank(value)) 842 { 843 errors.addAll(_validateInteger(entry, textPrefix)); 844 } 845 else if ("float".equals(regexpType)) 846 { 847 errors.addAll(_validateFloat(entry, textPrefix)); 848 } 849 else if ("email".equals(regexpType)) 850 { 851 if (StringUtils.isNotEmpty(value) && !_EMAIL_PATTERN.matcher(value).matches()) 852 { 853 errors.add(new I18nizableText("plugin." + _pluginName, "PLUGINS_FORMS_FORMS_RENDER_ERROR_TEXT_EMAIL")); 854 } 855 } 856 else if ("phone".equals(regexpType)) 857 { 858 if (StringUtils.isNotEmpty(value) && !_PHONE_PATTERN.matcher(value).matches()) 859 { 860 errors.add(new I18nizableText("plugin." + _pluginName, "PLUGINS_FORMS_FORMS_RENDER_ERROR_TEXT_PHONE")); 861 } 862 } 863 else if ("date".equals(regexpType)) 864 { 865 errors.addAll(_validateDate(entry, textPrefix)); 866 } 867 else if ("time".equals(regexpType)) 868 { 869 errors.addAll(_validateTime(entry, textPrefix)); 870 } 871 else if ("datetime".equals(regexpType)) 872 { 873 errors.addAll(_validateDateTime(entry, textPrefix)); 874 } 875 else if ("custom".equals(regexpType)) 876 { 877 errors.addAll(_validateCustomRegexp(entry, textPrefix)); 878 } 879 } 880 881 /** 882 * Validate a password field. 883 * 884 * @param entry the password field entry. 885 * @param request the user request. 886 * @return the list of error messages. 887 */ 888 protected List<I18nizableText> _validatePassword(FieldValue entry, Request request) 889 { 890 List<I18nizableText> errors = new ArrayList<>(); 891 892 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_PASSWORD_"; 893 894 errors.addAll(_validateMandatory(entry, keyPrefix)); 895 896 errors.addAll(_validateConfirmation(entry, request, keyPrefix)); 897 898 errors.addAll(_validateCustomRegexp(entry, keyPrefix)); 899 900 return errors; 901 } 902 903 /** 904 * Validate a select input. 905 * 906 * @param entry the select input entry. 907 * @param request the user request. 908 * @return the list of error messages. 909 */ 910 protected List<I18nizableText> _validateSelect(FieldValue entry, Request request) 911 { 912 List<I18nizableText> errors = new ArrayList<>(); 913 914 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_SELECT_"; 915 916 errors.addAll(_validateMandatory(entry, keyPrefix)); 917 918 return errors; 919 } 920 921 /** 922 * Validate a textarea. 923 * 924 * @param entry the textarea entry. 925 * @param request the user request. 926 * @return the list of error messages. 927 */ 928 protected List<I18nizableText> _validateTextarea(FieldValue entry, Request request) 929 { 930 List<I18nizableText> errors = new ArrayList<>(); 931 932 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_TEXTAREA_"; 933 934 errors.addAll(_validateMandatory(entry, keyPrefix)); 935 936 return errors; 937 } 938 939 /** 940 * Validate a radio input. 941 * 942 * @param entry the radio input entry. 943 * @param request the user request. 944 * @return the list of error messages. 945 */ 946 protected List<I18nizableText> _validateRadio(FieldValue entry, Request request) 947 { 948 List<I18nizableText> errors = new ArrayList<>(); 949 950 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_RADIO_"; 951 952 errors.addAll(_validateMandatory(entry, keyPrefix)); 953 954 return errors; 955 956 } 957 958 /** 959 * Validate a checkbox input. 960 * 961 * @param entry the checkbox entry. 962 * @param request the user request. 963 * @return the list of error messages. 964 */ 965 protected List<I18nizableText> _validateCheckbox(FieldValue entry, Request request) 966 { 967 List<I18nizableText> errors = new ArrayList<>(); 968 Field field = entry.getField(); 969 Map<String, String> properties = field.getProperties(); 970 Boolean value = (Boolean) entry.getValue(); 971 972 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_CHECKBOX_"; 973 974 if (Boolean.parseBoolean(properties.get("mandatory")) && !value) 975 { 976 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "MANDATORY")); 977 } 978 979 return errors; 980 } 981 982 /** 983 * Validate a file input. 984 * 985 * @param entry the file input entry. 986 * @param request the user request. 987 * @return the list of error messages. 988 */ 989 protected List<I18nizableText> _validateFile(FieldValue entry, Request request) 990 { 991 List<I18nizableText> errors = new ArrayList<>(); 992 993 Field field = entry.getField(); 994 Map<String, String> properties = field.getProperties(); 995 File file = (File) entry.getValue(); 996 997 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_FILE_"; 998 999 if (Boolean.parseBoolean(properties.get("mandatory")) && file == null) 1000 { 1001 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "MANDATORY")); 1002 } 1003 1004 if (file != null) 1005 { 1006 // Validate file readability. 1007 if (!file.isFile() || !file.canRead()) 1008 { 1009 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "INVALID")); 1010 } 1011 1012 // Validate file extensions. 1013 String fileExtensions = StringUtils.defaultString(properties.get("fileextension")); 1014 String[] fileExtArray = fileExtensions.split(","); 1015 1016 boolean extensionOk = false; 1017 for (int i = 0; i < fileExtArray.length && !extensionOk; i++) 1018 { 1019 String ext = fileExtArray[i].trim().toLowerCase(); 1020 extensionOk = file.getName().toLowerCase().endsWith(ext); 1021 } 1022 1023 if (!extensionOk) 1024 { 1025 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "EXTENSION")); 1026 } 1027 1028 float maxLength = _getFloat(properties.get("maxsize"), Float.MAX_VALUE); 1029 if (maxLength < Float.MAX_VALUE) 1030 { 1031 maxLength = maxLength * 1024 * 1024; 1032 } 1033 1034 if (file.length() > maxLength) 1035 { 1036 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "TOOLARGE", Collections.singletonList(properties.get("maxsize")))); 1037 } 1038 1039 if (Config.getInstance().getValueAsBoolean("plugins.forms.antivirus.activated")) 1040 { 1041 if (!_analyseFile(file)) 1042 { 1043 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "INFECTED")); 1044 } 1045 } 1046 } 1047 1048 return errors; 1049 } 1050 1051 /** 1052 * Validate a captcha. 1053 * 1054 * @param entry the captcha entry. 1055 * @param request the user request. 1056 * @return the list of error messages. 1057 */ 1058 protected List<I18nizableText> _validateCaptcha(FieldValue entry, Request request) 1059 { 1060 List<I18nizableText> errors = new ArrayList<>(); 1061 1062 final String keyPrefix = "PLUGINS_FORMS_FORMS_RENDER_ERROR_CAPTCHA_"; 1063 1064 String[] values = (String[]) entry.getValue(); 1065 String value = values[0]; 1066 String key = values[1]; 1067 1068 if (!CaptchaHelper.checkAndInvalidate(key, value)) 1069 { 1070 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "INVALID")); 1071 } 1072 return errors; 1073 } 1074 1075 /** 1076 * Validate a mandatory field. 1077 * @param entry the field value 1078 * @param keyPrefix the key profix 1079 * @return the list of error messages. 1080 */ 1081 protected List<I18nizableText> _validateMandatory(FieldValue entry, String keyPrefix) 1082 { 1083 List<I18nizableText> errors = new ArrayList<>(); 1084 1085 Field field = entry.getField(); 1086 Map<String, String> properties = field.getProperties(); 1087 String value = StringUtils.defaultString((String) entry.getValue()); 1088 1089 if (Boolean.parseBoolean(properties.get("mandatory")) && StringUtils.isBlank(value)) 1090 { 1091 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "MANDATORY")); 1092 } 1093 1094 return errors; 1095 } 1096 1097 /** 1098 * Validate a confirmation. 1099 * @param entry the field value 1100 * @param request the request 1101 * @param keyPrefix the key prefix 1102 * @return the list of error messages. 1103 */ 1104 protected List<I18nizableText> _validateConfirmation(FieldValue entry, Request request, String keyPrefix) 1105 { 1106 List<I18nizableText> errors = new ArrayList<>(); 1107 1108 Field field = entry.getField(); 1109 Map<String, String> properties = field.getProperties(); 1110 String value = StringUtils.defaultString((String) entry.getValue()); 1111 1112 if (Boolean.parseBoolean(properties.get("confirmation"))) 1113 { 1114 String confName = field.getName() + "_confirmation"; 1115 String confValue = request.getParameter(confName); 1116 1117 if (!value.equals(confValue)) 1118 { 1119 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "CONFIRMATION")); 1120 } 1121 } 1122 1123 return errors; 1124 } 1125 1126 private List<I18nizableText> _validateTextLength(FieldValue entry, String keyPrefix) 1127 { 1128 List<I18nizableText> errors = new ArrayList<>(); 1129 1130 Field field = entry.getField(); 1131 Map<String, String> properties = field.getProperties(); 1132 String value = StringUtils.defaultString((String) entry.getValue()); 1133 1134 Integer minValue = _getInteger(properties.get("minvalue"), null); 1135 1136 if (minValue != null) 1137 { 1138 if (StringUtils.isNotBlank(value) && value.length() < minValue) 1139 { 1140 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "MINLENGTH", Collections.singletonList(minValue.toString()))); 1141 } 1142 } 1143 1144 return errors; 1145 } 1146 1147 private List<I18nizableText> _validateInteger(FieldValue entry, String keyPrefix) 1148 { 1149 List<I18nizableText> errors = new ArrayList<>(); 1150 1151 Field field = entry.getField(); 1152 Map<String, String> properties = field.getProperties(); 1153 String value = StringUtils.defaultString((String) entry.getValue()); 1154 1155 if (!_INT_PATTERN.matcher(value).matches()) 1156 { 1157 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "INTEGER")); 1158 } 1159 else 1160 { 1161 Integer intValue = _getInteger(value, null); 1162 Integer minValue = _getInteger(properties.get("minvalue"), null); 1163 Integer maxValue = _getInteger(properties.get("maxvalue"), null); 1164 1165 if (minValue != null && intValue != null && intValue < minValue) 1166 { 1167 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "INTEGER_MINVALUE", Collections.singletonList(minValue.toString()))); 1168 } 1169 if (maxValue != null && intValue != null && intValue > maxValue) 1170 { 1171 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "INTEGER_MAXVALUE", Collections.singletonList(maxValue.toString()))); 1172 } 1173 } 1174 1175 return errors; 1176 } 1177 1178 private List<I18nizableText> _validateFloat(FieldValue entry, String keyPrefix) 1179 { 1180 List<I18nizableText> errors = new ArrayList<>(); 1181 1182 Field field = entry.getField(); 1183 Map<String, String> properties = field.getProperties(); 1184 String value = StringUtils.defaultString((String) entry.getValue()); 1185 1186 if (!_FLOAT_PATTERN.matcher(value).matches()) 1187 { 1188 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "FLOAT")); 1189 } 1190 else 1191 { 1192 Float floatValue = _getFloat(value, null); 1193 if (floatValue != null) 1194 { 1195 Integer minValue = _getInteger(properties.get("minvalue"), null); 1196 Integer maxValue = _getInteger(properties.get("maxvalue"), null); 1197 1198 if (minValue != null && floatValue < minValue) 1199 { 1200 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "FLOAT_MINVALUE", Collections.singletonList(minValue.toString()))); 1201 } 1202 if (maxValue != null && floatValue > maxValue) 1203 { 1204 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "FLOAT_MAXVALUE", Collections.singletonList(maxValue.toString()))); 1205 } 1206 } 1207 } 1208 1209 return errors; 1210 } 1211 1212 private List<I18nizableText> _validateDate(FieldValue entry, String keyPrefix) 1213 { 1214 List<I18nizableText> errors = new ArrayList<>(); 1215 1216 Field field = entry.getField(); 1217 Map<String, String> properties = field.getProperties(); 1218 String value = StringUtils.defaultString((String) entry.getValue()); 1219 1220 if (!_DATE_PATTERN.matcher(value).matches()) 1221 { 1222 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "DATE")); 1223 } 1224 1225 Date date = _getDate(value, null, _DATE_FORMAT); 1226 if (date != null) 1227 { 1228 Date minDate = _getDate(properties.get("minvalue"), null, _DATE_FORMAT); 1229 Date maxDate = _getDate(properties.get("maxvalue"), null, _DATE_FORMAT); 1230 1231 if (minDate != null && date.before(minDate)) 1232 { 1233 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "DATE_MINVALUE", Collections.singletonList(properties.get("minvalue")))); 1234 } 1235 if (maxDate != null && date.after(maxDate)) 1236 { 1237 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "DATE_MAXVALUE", Collections.singletonList(properties.get("maxvalue")))); 1238 } 1239 } 1240 1241 return errors; 1242 } 1243 1244 private List<I18nizableText> _validateTime(FieldValue entry, String keyPrefix) 1245 { 1246 List<I18nizableText> errors = new ArrayList<>(); 1247 1248 Field field = entry.getField(); 1249 Map<String, String> properties = field.getProperties(); 1250 String value = StringUtils.defaultString((String) entry.getValue()); 1251 1252 if (!_TIME_PATTERN.matcher(value).matches()) 1253 { 1254 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "TIME")); 1255 } 1256 1257 Date time = _getDate(value, null, _TIME_FORMAT); 1258 if (time != null) 1259 { 1260 Date minTime = _getDate(properties.get("minvalue"), null, _TIME_FORMAT); 1261 Date maxTime = _getDate(properties.get("maxvalue"), null, _TIME_FORMAT); 1262 1263 if (minTime != null && time.before(minTime)) 1264 { 1265 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "TIME_MINVALUE", Collections.singletonList(properties.get("minvalue")))); 1266 } 1267 if (maxTime != null && time.after(maxTime)) 1268 { 1269 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "TIME_MAXVALUE", Collections.singletonList(properties.get("maxvalue")))); 1270 } 1271 } 1272 1273 return errors; 1274 } 1275 1276 private List<I18nizableText> _validateDateTime(FieldValue entry, String keyPrefix) 1277 { 1278 List<I18nizableText> errors = new ArrayList<>(); 1279 1280 Field field = entry.getField(); 1281 Map<String, String> properties = field.getProperties(); 1282 String value = StringUtils.defaultString((String) entry.getValue()); 1283 1284 if (!_DATETIME_PATTERN.matcher(value).matches()) 1285 { 1286 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "DATETIME")); 1287 } 1288 1289 Date date = _getDate(value, null, _DATETIME_FORMAT); 1290 if (date != null) 1291 { 1292 Date minDate = _getDate(properties.get("minvalue"), null, _DATETIME_FORMAT); 1293 Date maxDate = _getDate(properties.get("maxvalue"), null, _DATETIME_FORMAT); 1294 1295 if (minDate != null && date.before(minDate)) 1296 { 1297 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "DATETIME_MINVALUE", Collections.singletonList(properties.get("minvalue")))); 1298 } 1299 if (maxDate != null && date.after(maxDate)) 1300 { 1301 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "DATETIME_MAXVALUE", Collections.singletonList(properties.get("maxvalue")))); 1302 } 1303 } 1304 1305 return errors; 1306 } 1307 1308 private List<I18nizableText> _validateCustomRegexp(FieldValue entry, String keyPrefix) 1309 { 1310 List<I18nizableText> errors = new ArrayList<>(); 1311 1312 Field field = entry.getField(); 1313 Map<String, String> properties = field.getProperties(); 1314 String value = StringUtils.defaultString((String) entry.getValue()); 1315 1316 if (StringUtils.isNotBlank(value)) 1317 { 1318 Integer minValue = _getInteger(properties.get("minvalue"), null); 1319 1320 if (minValue != null && value.length() < minValue) 1321 { 1322 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "MINLENGTH", Collections.singletonList(minValue.toString()))); 1323 } 1324 1325 String jsPattern = properties.get("regexp"); 1326 if (StringUtils.isNotBlank(jsPattern)) 1327 { 1328 try 1329 { 1330 String decodedJsPattern = URIDecoder.decode(StringUtils.defaultString(jsPattern)); 1331 Pattern pattern = _getPattern(decodedJsPattern); 1332 1333 if (!pattern.matcher(value).matches()) 1334 { 1335 errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "REGEXP")); 1336 } 1337 } 1338 catch (PatternSyntaxException e) 1339 { 1340 // Ignore, just don't validate. 1341 } 1342 } 1343 } 1344 1345 return errors; 1346 } 1347 1348 /** 1349 * Parses a JS pattern (i.e. "/^[a-z]+$/i") 1350 * @param jsRegexp the JS regexp string 1351 * @return the compiled Pattern object. 1352 */ 1353 private Pattern _getPattern(String jsRegexp) 1354 { 1355 Pattern pattern = null; 1356 String regex = ""; 1357 int flags = 0; 1358 1359 int firstSlash = jsRegexp.indexOf('/'); 1360 int lastSlash = jsRegexp.lastIndexOf('/'); 1361 1362 if (firstSlash > -1 && lastSlash > firstSlash) 1363 { 1364 regex = jsRegexp.substring(firstSlash + 1, lastSlash); 1365 if ("i".equals(jsRegexp.substring(lastSlash + 1))) 1366 { 1367 flags = Pattern.CASE_INSENSITIVE; 1368 } 1369 pattern = Pattern.compile(regex, flags); 1370 } 1371 1372 return pattern; 1373 } 1374 1375 private Integer _getInteger(String stringValue, Integer defaultValue) 1376 { 1377 Integer value = defaultValue; 1378 if (StringUtils.isNotEmpty(stringValue)) 1379 { 1380 try 1381 { 1382 value = Integer.parseInt(stringValue); 1383 } 1384 catch (NumberFormatException e) 1385 { 1386 // Ignore. 1387 } 1388 } 1389 return value; 1390 } 1391 1392 private Float _getFloat(String stringValue, Float defaultValue) 1393 { 1394 Float value = defaultValue; 1395 if (StringUtils.isNotEmpty(stringValue)) 1396 { 1397 try 1398 { 1399 value = Float.parseFloat(stringValue); 1400 } 1401 catch (NumberFormatException e) 1402 { 1403 // Ignore. 1404 } 1405 } 1406 return value; 1407 } 1408 1409 private Date _getDate(String stringValue, Date defaultValue, DateFormat format) 1410 { 1411 Date value = defaultValue; 1412 if (StringUtils.isNotEmpty(stringValue)) 1413 { 1414 try 1415 { 1416 value = format.parse(stringValue); 1417 } 1418 catch (ParseException e) 1419 { 1420 // Ignore. 1421 } 1422 } 1423 return value; 1424 } 1425 1426 /** 1427 * Retain only the entries that are to be inserted in the database. 1428 * 1429 * @param entries the entries to filter. 1430 * @return the entries to insert in the database. 1431 */ 1432 protected Collection<FieldValue> _getEntriesToInsert(Collection<FieldValue> entries) 1433 { 1434 List<FieldValue> filteredEntries = new ArrayList<>(entries.size()); 1435 1436 for (FieldValue entry : entries) 1437 { 1438 if (!FieldType.CAPTCHA.equals(entry.getField().getType())) 1439 { 1440 filteredEntries.add(entry); 1441 } 1442 } 1443 1444 return filteredEntries; 1445 } 1446 1447 /** 1448 * Send the receipt and notification emails. 1449 * 1450 * @param form the Form object. 1451 * @param input the user input. 1452 * @param siteName the site name. 1453 */ 1454 protected void _sendEmails(Form form, Map<String, FieldValue> input, String siteName) 1455 { 1456 Config config = Config.getInstance(); 1457 String sender = config.getValueAsString("smtp.mail.from"); 1458 1459 if (StringUtils.isNotEmpty(siteName)) 1460 { 1461 sender = _siteConfiguration.getValueAsString(siteName, "site-mail-from"); 1462 } 1463 1464 _sendNotificationEmails(form, input, sender); 1465 1466 _sendReceiptEmail(form, input, sender); 1467 } 1468 1469 /** 1470 * Send the notification emails. 1471 * 1472 * @param form the form. 1473 * @param input the user input. 1474 * @param sender the sender e-mail. 1475 */ 1476 protected void _sendNotificationEmails(Form form, Map<String, FieldValue> input, String sender) 1477 { 1478 Set<String> emails = form.getNotificationEmails(); 1479 try 1480 { 1481 String params = "?type=results&form-name=" + form.getLabel(); 1482 String subject = _getMail("subject.txt" + params, form, input); 1483 String html = _getMail("results.html" + params, form, input); 1484 String text = _getMail("results.txt" + params, form, input); 1485 Collection<File> files = _getFiles(input); 1486 1487 for (String email : emails) 1488 { 1489 if (StringUtils.isNotEmpty(email)) 1490 { 1491 try 1492 { 1493 SendMailHelper.sendMail(subject, html, text, files, email, sender); 1494 } 1495 catch (MessagingException e) 1496 { 1497 getLogger().error("Error sending the notification mail to " + email, e); 1498 } 1499 } 1500 } 1501 } 1502 catch (IOException e) 1503 { 1504 getLogger().error("Error creating the notification message.", e); 1505 } 1506 } 1507 1508 /** 1509 * Send the receipt email. 1510 * 1511 * @param form the form. 1512 * @param input the user input. 1513 * @param sender the sender e-mail. 1514 */ 1515 protected void _sendReceiptEmail(Form form, Map<String, FieldValue> input, String sender) 1516 { 1517 String email = ""; 1518 try 1519 { 1520 String receiptFieldId = form.getReceiptFieldId(); 1521 if (StringUtils.isNotEmpty(receiptFieldId)) 1522 { 1523 FieldValue receiptEntry = input.get(receiptFieldId); 1524 1525 if (receiptEntry.getValue() != null) 1526 { 1527 email = receiptEntry.getValue().toString(); 1528 1529 if (_EMAIL_PATTERN.matcher(email).matches()) 1530 { 1531 String subject = URIDecoder.decode(form.getReceiptFieldSubject()); 1532 String bodyTxt = URIDecoder.decode(form.getReceiptFieldBody()); 1533 String bodyHTML = bodyTxt.replaceAll("\r?\n", "<br/>"); 1534 1535 if (bodyTxt.contains(__FORM_ENTRY_PATTERN)) 1536 { 1537 String entry2html = _getMail("entry.html", form, input); 1538 String entry2text = _getMail("entry.txt", form, input); 1539 1540 bodyTxt = StringUtils.replace(bodyTxt, __FORM_ENTRY_PATTERN, entry2text); 1541 bodyHTML = StringUtils.replace(bodyHTML, __FORM_ENTRY_PATTERN, entry2html); 1542 } 1543 1544 String overrideSender = form.getReceiptFieldFromAddress(); 1545 1546 SendMailHelper.sendMail(subject, bodyHTML, bodyTxt, email, StringUtils.isEmpty(overrideSender) ? sender : overrideSender); 1547 } 1548 } 1549 } 1550 } 1551 catch (MessagingException e) 1552 { 1553 getLogger().error("Error sending the receipt mail to " + email, e); 1554 } 1555 catch (IOException e) 1556 { 1557 getLogger().error("Error creating the receipt mail to " + email, e); 1558 } 1559 } 1560 1561 /** 1562 * Get a mail pipeline's content. 1563 * 1564 * @param resource the mail resource pipeline (i.e. "results.html" or 1565 * "receipt.txt"). 1566 * @param form the Form. 1567 * @param input the user input. 1568 * @return the mail content. 1569 * @throws IOException if an error occurs. 1570 */ 1571 protected String _getMail(String resource, Form form, Map<String, FieldValue> input) throws IOException 1572 { 1573 Source src = null; 1574 1575 try 1576 { 1577 String uri = "cocoon:/mail/" + resource; 1578 Map<String, Object> parameters = new HashMap<>(); 1579 parameters.put("form", form); 1580 parameters.put("input", input); 1581 1582 src = _sourceResolver.resolveURI(uri, null, parameters); 1583 Reader reader = new InputStreamReader(src.getInputStream(), "UTF-8"); 1584 return IOUtils.toString(reader); 1585 } 1586 finally 1587 { 1588 _sourceResolver.release(src); 1589 } 1590 } 1591 1592 /** 1593 * Get the files of a user input. 1594 * 1595 * @param input the user input. 1596 * @return the files submitted by the user. 1597 */ 1598 protected Collection<File> _getFiles(Map<String, FieldValue> input) 1599 { 1600 List<File> files = new ArrayList<>(); 1601 1602 for (FieldValue entry : input.values()) 1603 { 1604 if (FieldType.FILE.equals(entry.getField().getType())) 1605 { 1606 File file = (File) entry.getValue(); 1607 if (file != null) 1608 { 1609 files.add(file); 1610 } 1611 } 1612 } 1613 1614 return files; 1615 } 1616 1617}