/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.web.portal.services;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.util.HtmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.enonic.esl.containers.ExtendedMap;
import com.enonic.esl.io.FileUtil;
import com.enonic.esl.xml.XMLTool;
import com.enonic.vertical.engine.VerticalCreateException;
import com.enonic.vertical.engine.VerticalSecurityException;
import com.enonic.cms.core.content.ContentAndVersion;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentKey;
import com.enonic.cms.core.content.ContentVersionEntity;
import com.enonic.cms.core.content.binary.BinaryData;
import com.enonic.cms.core.content.binary.BinaryDataAndBinary;
import com.enonic.cms.core.content.command.CreateContentCommand;
import com.enonic.cms.core.mail.MailRecipientType;
import com.enonic.cms.core.mail.SendMailService;
import com.enonic.cms.core.mail.SimpleMailTemplate;
import com.enonic.cms.core.portal.PrettyPathNameCreator;
import com.enonic.cms.core.portal.VerticalSession;
import com.enonic.cms.core.security.user.User;
import com.enonic.cms.core.security.user.UserEntity;
import com.enonic.cms.core.security.user.UserType;
import com.enonic.cms.core.service.UserServicesService;
import com.enonic.cms.core.servlet.ServletRequestAccessor;
import com.enonic.cms.core.structure.SiteKey;
@Component
public final class FormServicesProcessor
extends ContentServicesBase
{
private final static int ERR_MISSING_REQUIRED_INPUT = 1; // http 400 Bad Request
private final static String ERR_MSG_MISSING_REQ = "Mandatory field is missing.";
private final static int ERR_VALIDATION_FAILED = 2; // http 400 Bad Request
private final static String ERR_MSG_VALIDATION_FAILED = "Validation failed.";
private String adminEmail;
private SendMailService sendMailService;
class FormException
extends VerticalUserServicesException
{
private static final long serialVersionUID = 6224618853303943458L;
Document doc = null;
Integer[] errorCodes = null;
FormException( Document doc, Integer[] errorCodes )
{
super( "" );
this.doc = doc;
this.errorCodes = errorCodes;
}
}
private final static int contentTypeKey = 50;
public FormServicesProcessor()
{
super( "form" );
}
@Override
protected void buildContentTypeXML( UserServicesService userServices, Element contentdataElem, ExtendedMap formItems,
boolean skipElements )
throws VerticalUserServicesException
{
int menuItemKey = formItems.getInt( "_form_id" );
// Elements in the old form XML are prefixed with an underscore
Element _formElement = (Element) formItems.get( "__form" );
Document doc = contentdataElem.getOwnerDocument();
Element formElement = XMLTool.createElement( doc, contentdataElem, "form" );
formElement.setAttribute( "categorykey", _formElement.getAttribute( "categorykey" ) );
Element formTitleElement = XMLTool.getElement( _formElement, "title" );
formElement.setAttribute( "title", XMLTool.getElementText( formTitleElement ) );
// Set title element:
final String formTitleEl = formItems.getString( menuItemKey + "_form_title" );
String titleElNum = StringUtils.substringAfter( formTitleEl, "form_" + menuItemKey + "_elm_" );
if ( StringUtils.isBlank( titleElNum ) )
{
titleElNum = StringUtils.substringAfter( formTitleEl, "label_" + menuItemKey + "_form_" );
}
final String titleElementId = menuItemKey + "_form_" + titleElNum;
String title = formItems.getString( titleElementId, "" );
if ( StringUtils.isBlank( title ) )
{
Element _titleElement = XMLTool.getElement( _formElement, titleElementId );
title = XMLTool.getElementText( _titleElement );
}
XMLTool.createElement( doc, formElement, "title", title );
// There may be multiple error states/codes, so we have to keep track of them.
// When errors occur, XML is inserted into the resulting document, and sent
// back to the user client.
// TIntArrayList errorCodes = new TIntArrayList(5);
List<Integer> errorCodes = new ArrayList<Integer>( 5 );
// The people that will receive the form mail:
Element recipientsElem = XMLTool.getElement( _formElement, "recipients" );
if ( recipientsElem != null )
{
formElement.appendChild( doc.importNode( recipientsElem, true ) );
}
// Loop all form items and insert the data from the form:
int fileattachmentCount = 0;
Element[] _formItems = XMLTool.getElements( _formElement, "item" );
for ( int i = 0; i < _formItems.length; i++ )
{
String formName = menuItemKey + "_form_" + ( i + 1 );
Element itemElement = (Element) doc.importNode( _formItems[i], true );
formElement.appendChild( itemElement );
String type = itemElement.getAttribute( "type" );
if ( "text".equals( type ) )
{
// Remove default data:
Element tmpElement = XMLTool.getElement( itemElement, "data" );
if ( tmpElement != null )
{
itemElement.removeChild( tmpElement );
}
}
if ( "text".equals( type ) || "textarea".equals( type ) || "checkbox".equals( type ) )
{
String value = formItems.getString( formName, "" );
// If a regular expression is specified, it should be verified that
// the data entered in the form conforms to this:
String regexp = itemElement.getAttribute( "validation" );
if ( "text".equals( type ) && regexp != null && regexp.length() > 0 )
{
final boolean valueIsNonEmpty = value.length() > 0;
if ( !value.matches( regexp ) && valueIsNonEmpty )
{
XMLTool.createElement( doc, itemElement, "error", ERR_MSG_VALIDATION_FAILED ).setAttribute( "id", String.valueOf(
ERR_VALIDATION_FAILED ) );
if ( !errorCodes.contains( ERR_VALIDATION_FAILED ) )
{
errorCodes.add( ERR_VALIDATION_FAILED );
}
}
}
// If the form element is required, we must test that the user actually
// entered data:
if ( itemElement.getAttribute( "required" ).equals( "true" ) && value.length() == 0 )
{
XMLTool.createElement( doc, itemElement, "error", ERR_MSG_MISSING_REQ ).setAttribute( "id", String.valueOf(
ERR_MISSING_REQUIRED_INPUT ) );
if ( !errorCodes.contains( ERR_MISSING_REQUIRED_INPUT ) )
{
errorCodes.add( ERR_MISSING_REQUIRED_INPUT );
}
}
XMLTool.createElement( doc, itemElement, "data", value );
}
else if ( "checkbox".equals( type ) )
{
String value;
if ( formItems.getString( formName, "" ).equals( "on" ) )
{
value = "1";
}
else
{
value = "0";
}
XMLTool.createElement( doc, itemElement, "data", value );
}
else if ( "radiobuttons".equals( type ) || "dropdown".equals( type ) )
{
String value = formItems.getString( formName, null );
boolean selected = false;
Element tmpElement = XMLTool.getElement( itemElement, "data" );
Element[] options = XMLTool.getElements( tmpElement, "option" );
for ( Element option : options )
{
tmpElement = option;
if ( tmpElement.getAttribute( "value" ).equals( value ) )
{
tmpElement.setAttribute( "selected", "true" );
selected = true;
break;
}
}
// If the form element is required, we must test that the user actually
// checked on of the radiobuttons:
if ( itemElement.getAttribute( "required" ).equals( "true" ) && !selected )
{
XMLTool.createElement( doc, itemElement, "error", ERR_MSG_MISSING_REQ ).setAttribute( "id", String.valueOf(
ERR_MISSING_REQUIRED_INPUT ) );
if ( !errorCodes.contains( ERR_MISSING_REQUIRED_INPUT ) )
{
errorCodes.add( ERR_MISSING_REQUIRED_INPUT );
}
}
}
else if ( "checkboxes".equals( type ) )
{
String[] values = formItems.getStringArray( formName );
Element tmpElement = XMLTool.getElement( itemElement, "data" );
Element[] options = XMLTool.getElements( tmpElement, "option" );
for ( Element option : options )
{
tmpElement = option;
for ( String currentValue : values )
{
if ( tmpElement.getAttribute( "value" ).equals( currentValue ) )
{
tmpElement.setAttribute( "selected", "true" );
break;
}
}
}
}
else if ( "fileattachment".equals( type ) )
{
FileItem fileItem = formItems.getFileItem( formName, null );
// If the form element is required, we must test that the user actually
// entered data:
if ( "true".equals( itemElement.getAttribute( "required" ) ) && fileItem == null )
{
XMLTool.createElement( doc, itemElement, "error", ERR_MSG_MISSING_REQ ).setAttribute( "id", String.valueOf(
ERR_MISSING_REQUIRED_INPUT ) );
if ( !errorCodes.contains( ERR_MISSING_REQUIRED_INPUT ) )
{
errorCodes.add( ERR_MISSING_REQUIRED_INPUT );
}
}
else if ( fileItem != null )
{
String fileName = FileUtil.getFileName( fileItem );
Element binaryDataElem = XMLTool.createElement( doc, itemElement, "binarydata", fileName );
binaryDataElem.setAttribute( "key", "%" + fileattachmentCount++ );
}
}
}
HttpServletRequest request = ServletRequestAccessor.getRequest();
Boolean captchaOk = captchaService.validateCaptcha( formItems, request, "form", "create" );
if ( ( captchaOk != null ) && ( !captchaOk ) )
{
errorCodes.add( ERR_INVALID_CAPTCHA );
}
// If one or more errors occurred, an exception is thrown, containing the errorcodes
// and the resulting document (that now should include error XML):
if ( errorCodes.size() > 0 )
{
throw new FormException( doc, errorCodes.toArray( new Integer[errorCodes.size()] ) );
}
}
protected void handlerCreate( HttpServletRequest request, HttpServletResponse response, HttpSession session, ExtendedMap formItems,
UserServicesService userServices, SiteKey siteKey )
throws VerticalUserServicesException, VerticalCreateException, VerticalSecurityException, IOException, MessagingException
{
User user = securityService.getLoggedInPortalUser();
VerticalSession vsession = (VerticalSession) session.getAttribute( VerticalSession.VERTICAL_SESSION_OBJECT );
if ( vsession == null )
{
vsession = new VerticalSession();
session.setAttribute( VerticalSession.VERTICAL_SESSION_OBJECT, vsession );
}
try
{
// get anonymous user if user is not logged in:
if ( user == null )
{
user = userServices.getAnonymousUser();
}
// Get the form config XML
int menuItemKey = formItems.getInt( "_form_id" );
Document doc = userServices.getMenuItem( user, menuItemKey ).getAsDOMDocument();
Element rootElement = doc.getDocumentElement();
Element formElement = (Element) XMLTool.selectNode( rootElement, "/menuitems/menuitem/data/form" );
formItems.put( "__form", formElement );
// Find the title element
String contentTitle = XMLTool.getElementText( XMLTool.getElement( formElement, "title" ) );
Element[] itemElems = XMLTool.getElements( formElement, "item" );
for ( int i = 0; i < itemElems.length; i++ )
{
if ( "true".equals( itemElems[i].getAttribute( "title" ) ) )
{
String titleFormName = menuItemKey + "_form_" + ( i + 1 );
String subTitle = formItems.getString( titleFormName, null );
if ( subTitle != null && subTitle.length() > 0 )
{
contentTitle += ": " + subTitle;
}
break;
}
}
// Find the category key
int categoryKey = -1;
String categoryKeyStr = formElement.getAttribute( "categorykey" );
if ( categoryKeyStr != null && categoryKeyStr.length() > 0 )
{
categoryKey = Integer.parseInt( categoryKeyStr );
}
// Find email recipients
Element recipientsElem = XMLTool.getElement( formElement, "recipients" );
Element[] emailElems = XMLTool.getElements( recipientsElem, "e-mail" );
ArrayList<String> emailAddresses = new ArrayList<String>();
for ( Element emailElem : emailElems )
{
String email = XMLTool.getElementText( emailElem );
if ( email != null && email.length() > 0 )
{
emailAddresses.add( email );
}
}
// Add the category key to formItems so buildXML method works (even if it is -1)
formItems.put( "categorykey", String.valueOf( categoryKey ) );
// Build the content XML and check for errors:
String xmlData = buildXML( userServices, user, formItems, siteKey, contentTypeKey, contentTitle, false );
// int contentKey = -1;
int contentReference = -1;
if ( categoryKey != -1 )
{
UserEntity runningUser = securityService.getUser( user );
BinaryData[] binaries = null;
if ( formItems.hasFileItems() )
{
FileItem[] fileItems = getFileItems( formItems, menuItemKey );
binaries = new BinaryData[fileItems.length];
for ( int i = 0; i < fileItems.length; i++ )
{
binaries[i] = createBinaryData( fileItems[i] );
}
}
List<BinaryDataAndBinary> binaryDataAndBinaries = BinaryDataAndBinary.createNewFrom( binaries );
ContentAndVersion parsedContentAndVersion = contentParserService.parseContentAndVersion( xmlData, null, true );
ContentEntity parsedContent = parsedContentAndVersion.getContent();
ContentVersionEntity parsedVersion = parsedContentAndVersion.getVersion();
CreateContentCommand createCommand = new CreateContentCommand();
createCommand.setAccessRightsStrategy( CreateContentCommand.AccessRightsStrategy.INHERIT_FROM_CATEGORY );
createCommand.populateCommandWithContentValues( parsedContent );
createCommand.populateCommandWithContentVersionValues( parsedVersion );
createCommand.setCreator( runningUser );
createCommand.setBinaryDatas( binaryDataAndBinaries );
createCommand.setUseCommandsBinaryDataToAdd( true );
createCommand.setContentName(
new PrettyPathNameCreator( transliterate ).generatePrettyPathName( parsedVersion.getTitle() ) );
ContentKey key = contentService.createContent( createCommand );
contentReference = key.toInt();
}
// Mail the form posting:
if ( emailAddresses.size() > 0 )
{
String[] emailAddr = emailAddresses.toArray( new String[emailAddresses.size()] );
String subject = contentTitle;
if ( contentReference > 0 )
{
subject += " #" + contentReference;
}
mailForm( subject, user, menuItemKey, emailAddr, formElement, formItems );
}
Element sendReceiptElem = XMLTool.selectElement( formElement, "receipt/sendreceipt" );
String sendReceipt = XMLTool.getElementText( sendReceiptElem );
if ( sendReceipt != null && sendReceipt.equalsIgnoreCase( "yes" ) )
{
// mail reciept to person who submitted form
mailReciept( menuItemKey, formElement, formItems, contentReference );
}
// Remove the error XML if content creation and/or e-mail dispatch was successfull
vsession.removeAttribute( "error_form_create" );
redirectToPage( request, response, formItems );
}
catch ( FormException e )
{
vsession.setAttribute( "error_form_create", e.doc );
Integer[] tmp = new Integer[e.errorCodes.length];
System.arraycopy( e.errorCodes, 0, tmp, 0, e.errorCodes.length );
redirectToErrorPage( request, response, tmp, this );
}
}
/**
* Get fileItems in the same order as in buildContentTypeXML()
*
* @param formItems Input from the user form.
* @param menuItemKey The current menu item.
* @return The selected file items.
*/
private FileItem[] getFileItems( ExtendedMap formItems, int menuItemKey )
{
ArrayList<FileItem> fileItems = new ArrayList<FileItem>();
// Elements in the old form XML are prefixed with an underscore
Element formElement = (Element) formItems.get( "__form" );
Element[] formItemsFromForm = XMLTool.getElements( formElement, "item" );
for ( int i = 0; i < formItemsFromForm.length; i++ )
{
String formName = menuItemKey + "_form_" + ( i + 1 );
String type = formItemsFromForm[i].getAttribute( "type" );
if ( "fileattachment".equals( type ) )
{
FileItem fileItem = formItems.getFileItem( formName, null );
if ( fileItem != null )
{
fileItems.add( fileItem );
}
}
}
return fileItems.toArray( new FileItem[fileItems.size()] );
}
protected void mailForm( String subject, User user, int menuItemKey, String[] recipients, Element formElement, ExtendedMap formItems )
throws VerticalUserServicesException, IOException, MessagingException
{
// don't waste time here if there are no recipients
if ( recipients == null || recipients.length == 0 )
{
return;
}
StringBuffer body = createMailBody( menuItemKey, formElement, formItems );
String fromEmail = null;
Element fromEmailElem = XMLTool.selectElement( formElement, "item[@fromemail = 'true']" );
if ( fromEmailElem != null )
{
int itemIndex = XMLTool.getElementIndex( fromEmailElem ) + 1;
String formName = menuItemKey + "_form_" + itemIndex;
fromEmail = formItems.getString( formName, null );
}
String fromName = null;
Element fromNameElem = XMLTool.selectElement( formElement, "item[@fromname = 'true']" );
if ( fromNameElem != null )
{
int itemIndex = XMLTool.getElementIndex( fromNameElem ) + 1;
String formName = menuItemKey + "_form_" + itemIndex;
fromName = formItems.getString( formName, null );
}
createAndSendMail( subject, user, recipients, formItems, body, fromEmail, fromName, true );
}
private void mailReciept( int menuItemKey, Element formElement, ExtendedMap formItems, int contentReference )
throws VerticalUserServicesException, IOException, MessagingException
{
String toEmail = null;
Element toEmailElem = XMLTool.selectElement( formElement, "item[@fromemail = 'true']" );
if ( toEmailElem != null )
{
int itemIndex = XMLTool.getElementIndex( toEmailElem ) + 1;
String formName = menuItemKey + "_form_" + itemIndex;
toEmail = formItems.getString( formName, null );
}
if ( toEmail == null || toEmail.trim().equals( "" ) )
{
// If there are no e-mail recipient specified, check if there is a logged in user who can recieve a reciept
UserEntity loggedInUser = securityService.getLoggedInPortalUserAsEntity();
if ( loggedInUser != null )
{
toEmail = loggedInUser.getEmail();
}
}
if ( toEmail == null || toEmail.trim().equals( "" ) )
{
// don't waste time here if there are no recipients
return;
}
String[] recipient = new String[]{toEmail};
Element receipt = XMLTool.getElement( formElement, "receipt" );
String fromName = XMLTool.getElementText( XMLTool.getElement( receipt, "name" ) );
String fromEmail = XMLTool.getElementText( XMLTool.getElement( receipt, "email" ) );
String message = XMLTool.getElementText( XMLTool.getElement( receipt, "message" ) );
String subject = XMLTool.getElementText( XMLTool.getElement( receipt, "subject" ) );
if ( contentReference > 0 )
{
subject += " #" + contentReference;
}
String formData = XMLTool.getElementText( XMLTool.getElement( receipt, "includeform" ) );
boolean includeFormData = true;
if ( "no".equalsIgnoreCase( formData ) )
{
includeFormData = false;
}
StringBuffer body = new StringBuffer();
if ( message != null )
{
body.append( HtmlUtils.htmlUnescape( message ) );
}
if ( includeFormData )
{
body.append( "\n\n" ).append( createMailBody( menuItemKey, formElement, formItems ) );
}
createAndSendMail( HtmlUtils.htmlUnescape( subject ), null, recipient, formItems, body, fromEmail, fromName, false );
}
private void createAndSendMail( String subject, User user, String[] recipients, ExtendedMap formItems, StringBuffer body,
String fromEmail, String fromName, boolean addAttachment )
throws IOException, MessagingException
{
final SimpleMailTemplate formMail = new SimpleMailTemplate();
if ( fromEmail != null )
{
formMail.setFrom( fromName, fromEmail );
}
else
{
if ( user.getType() != UserType.ANONYMOUS )
{
formMail.setFrom( user.getDisplayName(), user.getEmail() );
}
else
{
formMail.setFrom( "Anonymous", adminEmail );
}
}
formMail.setSubject( subject );
formMail.setMessage( body.toString() );
for ( String recipient : recipients )
{
formMail.addRecipient( null, recipient, MailRecipientType.TO_RECIPIENT );
}
if ( addAttachment )
{
if ( formItems.hasFileItems() )
{
FileItem[] fileItems = formItems.getFileItems();
for ( FileItem fileItem : fileItems )
{
try
{
formMail.addAttachment( fileItem.getName(), fileItem.getInputStream() );
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
}
}
sendMailService.sendMail( formMail );
}
private StringBuffer createMailBody( int menuItemKey, Element formElement, ExtendedMap formItems )
{
// build mail body
StringBuffer body = new StringBuffer();
Element[] items = XMLTool.getElements( formElement, "item" );
for ( int i = 0; i < items.length; i++ )
{
String formName = menuItemKey + "_form_" + ( i + 1 );
Element itemElement = items[i];
String type = itemElement.getAttribute( "type" );
if ( type.equals( "separator" ) )
{
body.append( "\n" );
}
else
{
body.append( itemElement.getAttribute( "label" ) ).append( ": " );
if ( type.equals( "text" ) )
{
body.append( formItems.getString( formName ) );
body.append( "\n" );
}
else if ( type.equals( "textarea" ) )
{
body.append( "\n" );
body.append( formItems.getString( formName ) );
body.append( "\n" );
}
else if ( type.equals( "checkbox" ) )
{
if ( formItems.getString( formName, "" ).equals( "on" ) )
{
body.append( "true" );
}
else
{
body.append( "false" );
}
body.append( "\n" );
}
else if ( type.equals( "radiobuttons" ) )
{
body.append( formItems.getString( formName, "" ) );
body.append( "\n" );
}
else if ( type.equals( "dropdown" ) )
{
body.append( formItems.getString( formName, "" ) );
body.append( "\n" );
}
else if ( type.equals( "checkboxes" ) )
{
String[] values = formItems.getStringArray( formName );
for ( int j = 0; j < values.length; j++ )
{
String string = values[j];
if ( j > 0 )
{
body.append( ", " );
}
body.append( string );
}
body.append( "\n" );
}
else if ( type.equals( "fileattachment" ) )
{
FileItem fileItem = formItems.getFileItem( formName, null );
if ( fileItem != null )
{
String fileName = FileUtil.getFileName( fileItem );
if ( fileName != null )
{
body.append( fileName );
}
}
body.append( "\n" );
}
}
}
return body;
}
@Value("${cms.admin.email}")
public void setAdminEmail( final String adminEmail )
{
this.adminEmail = adminEmail;
}
@Autowired
public void setSendMailService( final SendMailService sendMailService )
{
this.sendMailService = sendMailService;
}
@Override
public Integer httpResponseCodeTranslator( final Integer[] errorCodes )
{
return HTTP_STATUS_BAD_REQUEST;
}
}