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