Contact form

This page contains deprecated components, that are not contained in release 1.11. Thus, this page now is under development until this message is removed. If you have, any question, please create a new thread in our support forum.

Please refer to the German language copy&paste wiki tutorial to see how the contact form can be included into your web page.

1. Introduction

The present chapter gives another application of the form tag libs and the <core:importdesign /> tag. The contact form described in the following text gives the opportunity to prevent e-mail addresses from being grabbed by bots. Moreover, the recipient list is configurable and extensible. The module can be included in any page controller compliant application by using the <core:importdesign /> tag.
Within release 1.11, the HTML markup of the module was completely reworked. For this reason, the shipped CSS file (see /modules/kontakt4/pres/css/contact.css within the release package) must be added to the head of the corresponding page. This file contains a basic set of formatting for the module, that must be adapted as desired. A detailed description of the generated HTML code can be taken from the wiki chapter Kontakt-Formular-Modul (German).

2. Basics

This tutorial includes technics that were already used in the previous tutorials. The author assumes, that you have read chapter 2 of the Comment function carefully. This chapter describes the separation of a single software into three different layers, that contain different functionality. Beyond, the section depicts how domain objects are used as an agent of communication and which role is dedicated to the MVC pattern.


2.1. Configuration

To be able to integrate the contact form module in various projects the project specific parts must be outsourced to application configuration files. Configuration is necessary in two flavours: first of all the recipients must be configured, second the texts of the form tag libs (validators) must be defined per project.

At this point of the tutorial we're going to do a little excurse to functionality of the form validation included in the framework:
The tags
  • <form:validate />
  • <valgroup:validate />
need a language dependent configuration file for the validation messages to display. The configuration file required for the taglibs is expected to be storend in
Code
/config/tools/form/taglib/{CONTEXT}/{ENVIRONMENT}_formconfig.ini
{CONTEXT} is meant to be a place holder for the context of the current application and the value for {ENVIRONMENT} must be read from the registry. In case of the presend documentation page the file must be named like
Code
/config/tools/form/taglib/sites/demosite/DEFAULT_formconfig.ini
As described in the Standard taglibs article in section 2.3.16 and 2.3.17 these tags can be configured using the additional attributes
  • msginputreq=""
  • msginputwrg=""
These attributs define the configuration key that contains a language dependent text to be shown, if the desired field is not filled correctly. This makes it possible to define a single set of validation texts for an entire application. Moreover, it is easy to translate these texts, becaues they are stored in simple text files.

Example:
In order to display the message
APF template
Please fill the field in e-mail address!
if a form control was not filled and to print the text
APF template
Please provide a valid e-mail address!
if the field was not filled with a correct e-mail address, the validator must be configured as follows:
APF template
<form:validate button="send" field="email" type="EMail" msginputreq="Contact.EMail.InputRequired" msginputwrg="Contact.EMail.InputWrong" />
The attribute button defines the name of the button, that should initiate validation, field indicates the field, that should be checked and type explains the type of the validator. The keys contained in the two attributes msginputreq and msginputwrg were prefixed with "Contact.EMail." to indicate, that these configuration keys belong to the contact form application and are intended to declare the validation texts of the e-mail field. This convention is common to be able to differentiate between the various configuration keys provided within one configuration file. Besides, it is useful to place some comments in the configuration file.

A configuration file intended to be a basis for a multilanguage application may have the following content:
APF configuration
[de] Contact.EMail.InputRequired = "Bitte geben Sie eine E-Mail-Adresse sein!" Contact.EMail.InputWrong = "Bitte geben Sie eine gültige E-Mail-Adresse sein!" [en] Contact.EMail.InputRequired = "Please fill the field sender name!" Contact.EMail.InputWrong = "Please provide a valid email address!"

2.2. Multilanguage applications

The Adventure PHP Framework features several options to create multilangual applications or modules. At first, the language is stored within every single DOM node or service layer and hence can be used to display language dependent texts within a document controller. Further the framework provides the XML tags
  • <html:getstring />
  • <template:getstring />
  • <form:getstring />
that display language dependent texts from configuration files. In case of the present module both possibilities are used. As a basis, the configuration file
Code
/config/modules/kontakt4/{CONTEXT}/{ENVIRONMENT}_language.ini
is used to store the texts to be displayed in the module. The file is filled with the following values:
APF configuration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Deutsche Texte ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [de] ; Header header.title = "Kontakt" ; Hinweise zum Formular formhint.text = "Wenn Sie mit mir in Kontakt treten möchten, dann benutzen Sie einfach dieses Formular. Geben Sie Ihre Nachricht ein und schon kann es los gehen. Ich werden mich dann umgehend mit Ihnen in Verbindung setzten. Bitte füllen Sie das Formular vollständig aus!" ; Formular form.person = "Person / Gruppe: " form.name = "Ihr Name:" form.email = "Ihre E-Mail-Adresse:" form.subject = "Ihr Betreff:" form.comment = "Ihre Nachricht:" form.button = "Senden" form.captcha = "Bestätigungscode:" ; confirmation text message.text = "Vielen Danke für Ihre Anfrage. Ich werde mich umgehend mit Ihnen in Verbindung setzen!" ; validation messages form.name.error = "Bitte füllen Sie das Feld Absender-Name!" form.email.error = "Bitte geben Sie eine gültige E-Mail-Adresse sein!" form.subject.error = "Bitte füllen Sie das Feld Betreff!" form.text.error = "Bitte füllen Sie das Feld Text!" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Englisch texts ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [en] ; header header.title = "Contact" ; hints on the form formhint.text = "If you want to contact me, please use the form provided below. Then I will immediately get in contact with you. Please fill all required fields!" ; form labels form.person = "Person / group: " form.name = "Your name:" form.email = "Your email address:" form.subject = "Your subject:" form.comment = "Your message:" form.button = "Send" form.captcha = "Security code:" ; confirmation text message.text = "Many thanks for your message. We will get in contact with you immediately!" ; validation messages form.name.error = "Please provide a sender name!" form.email.error = "Please provide a valid email address!" form.subject.error = "Please fill the subject field!" form.text.error = "The message may not be empty!"
The values stored there can be displayed by using the following XML tags:
  • <html:getstring namespace="modules::kontakt4" config="language.ini" entry="form.person" />,
  • <template:getstring namespace="modules::kontakt4" config="language.ini" entry="form.person" /> oder
  • <form:getstring namespace="modules::kontakt4" config="language.ini" entry="form.person" />
The second possibility of of displaying language dependent texts can be used to label form controls like buttons. In this case the labelling must be done within a document controller. The following code shows how to achieve this:
PHP code
// Load language dependent configuration $Config = $this->getConfiguration('modules::kontakt4','language'); // Get a reference on the desired form control $Button = &$Form->getFormElementByName('KontaktSenden'); // Set the value attribute of the control using the <em>$this->language</em> member $Button->setAttribute('value',$Config->getSection($this->language)->getValue('form.button'));

2.3. Structure of the module

According to the structure described in the comment module tutorial the structure of the kontakt4 module looks like this:
Code
/modules/ kontakt4/ biz/ data/ pres/ documentcontroller/ templates/
The biz folder serves the business components (two domain objects and one manager), data contains the mapper class, that reads the recipients, and the pres folder stores the controller and template files needed by the presentation layer. Considered as a whole, the contact form consists of the following files:

  • /modules/kontakt4/pres/templates/kontakt.html:
    The file kontakt.html contains the main design and coordinated the output of the form or the thank-you-page with a <core:importdesign /> tag.
  • /modules/kontakt4/pres/templates/formular.html:
    This file contains the definition of the form view.
  • /modules/kontakt4/pres/templates/meldung.html:
    In the file meldung.html the content of the thank-you-page is defined.
  • /modules/kontakt4/pres/documentcontroller/kontakt_v2_controller.php:
    To fill the form view with dynamically generated content a document controller is needed. The file kontakt_v2_controller.php contains the document controller class definition.
  • /modules/kontakt4/biz/ContactManager.php:
    ContactManager.php is the home of the business component class, that coordinates the workflow of the application.
  • /modules/kontakt4/biz/ContactFormRecipient.php:
    ContactFormRecipient.php contains the definition of the recipient domain object.
  • /modules/kontakt4/biz/ContactFormData.php:
    ContactFormData.php contains the definition of the form data domain object.
  • /modules/kontakt4/data/ContactMapper.php:
    The file ContactMapper.php is used to define the data layer component, that loads the recipients out of the configuration file and presents them as domain objects.
As already mentioned the following configuration files must be provided:
  • /config/modules/kontakt4/{CONTEXT}/{ENVIRONMENT}_empfaenger.ini:
    Configuration of the recipients.
  • /config/tools/form/taglib/{CONTEXT}/{ENVIRONMENT}_formconfig.ini:
    Texts for the validator form tags.
  • /config/modules/kontakt4/{CONTEXT}/{ENVIRONMENT}_language.ini:
    Language dependent labels.
The syntax of the configuration files was already described above but the content of these files is discussed later on.


3. Implementation of the module

This tutorial uses the TOP-DOWN approach once again. The current chapter therefore describes the files mentioned above starting with the presentation layer and in the given order:


3.1. File kontakt.html

The template file kontakt.html defines the structure of the module. It contains the headline, that is displayed by use of the <html:getstring /> tag and a <core:importdesign /> tag, that defines the view that either displays the form (default behaviour) of the thank-you-page:
APF template
<font style="..."><html:getstring namespace="modules::kontakt4" config="language" entry="header.title" /></font> <br /> <br /> <core:importdesign namespace="modules::kontakt4::pres::templates" template="[pagepart=formular]" />
Note: Within this template file, the <core:importdesign /> tag is used with the pagepart option. This means, that the name of the template can be influenced by the URL parameter pagepart. If the parameter is not present in the request string, the name of the template is taken from the XML tag definition. In this case the form is displayed.


3.2. File formular.html

This template file contains four essential blocks:
  • Definition of the document controller (Line 1)
    Here the document controller (MVC controller of the current DOM node) that is taken for transformation is defined.
  • Inclusion of external tag libs: (Lines 2-3)
    To be able to use the <html:form /> or <html:getstring /> tag libraries they must be introduced by using the <core:addtaglib /> tag.
  • Definition of the HTML content: (Lines 5-9)
    The lines 5 to 9 define the HTML frame of the form view. This view features notices, that are included using the <html:getstring /> tag and a <html:placeholder /> tag that is filled with the output of the form within the document controller.
  • Form: (Lines 11-43)
    The lines 11 to 43 define the form. It contains a validator group, that is used to display validation messages. The labeling of the form controls is done by the <form:getstring /> tag.
APF template
<@controller namespace="modules::kontakt4::pres::documentcontroller" class="contact_form_controller" @> <core:addtaglib namespace="tools::form::taglib" class="HtmlFormTag" prefix="html" name="form" /> <p> <html:getstring namespace="modules::kontakt4" config="language" entry="formhint.text" /> </p> <div class="contact-form"> <html:form name="contact" method="post"> <div> <form:error id="toperror"> <div class="error-container"> <ul> </form:error> <form:listener control="AbsenderName" id="sender-error"> <li><listener:placeholder name="content" /></li> </form:listener> <form:listener control="AbsenderAdresse" id="addr-error"> <li><listener:placeholder name="content" /></li> </form:listener> <form:listener control="Betreff" id="subject-error"> <li><listener:placeholder name="content" /></li> </form:listener> <form:listener control="Text" id="text-error"> <li><listener:placeholder name="content" /></li> </form:listener> <form:listener control="captcha" id="captcha-error"> <li><listener:placeholder name="content" /></li> </form:listener> <form:error id="bottomerror"> </ul> </div> </form:error> <form:addvalidator class="TextLengthValidator" button="sendFormContact" control="AbsenderName|Betreff|Text" /> <form:addvalidator class="EMailValidator" button="sendFormContact" control="AbsenderAdresse" /> <form:addfilter class="EMailFilter" button="sendFormContact" control="AbsenderAdresse" /> <label for="contact-form-recipient"> <form:getstring namespace="modules::kontakt4" config="language" entry="form.person" /> </label> <form:select id="contact-form-recipient" name="Empfaenger" /> <label for="contact-form-sendername"> <form:getstring namespace="modules::kontakt4" config="language" entry="form.name" /> </label> <form:text id="contact-form-sendername" name="AbsenderName" /> <label for="contact-form-recipient-email"> <form:getstring namespace="modules::kontakt4" config="language" entry="form.email" /> </label> <form:text id="contact-form-recipient-email" name="AbsenderAdresse" /> <label for="contact-form-subject"> <form:getstring namespace="modules::kontakt4" config="language" entry="form.subject" /> </label> <form:text id="contact-form-subject" name="Betreff" /> <label for="contact-form-textarea"> <form:getstring namespace="modules::kontakt4" config="language" entry="form.comment" /> </label> <form:area id="contact-form-textarea" name="Text" cols="50" rows="6"/> <div class="fullsizebox captchabox"> <label for="contact-form-captcha"> <form:getstring namespace="modules::kontakt4" config="language" entry="form.captcha" /> </label> <form:addtaglib namespace="modules::captcha::pres::taglib" class="SimpleCaptchaTag" prefix="form" name="captcha" /> <form:captcha name="captcha-control" clearonerror="true" text_id="contact-form-captcha" disable_inline="true" /> <form:addvalidator namespace="modules::captcha::pres::validator" class="CaptchaValidator" control="captcha-control" button="sendFormContact" /> </div> <div class="fullsizebox buttonbox"> <form:button name="sendFormContact" class="button"/> </div> </div> </html:form> </div>

3.3. File meldung.html

Within the file meldung.html the thank-you message is defined, that is displayed on submission of the form. Merely this message contains a sentence that describes, that the recipient will send an answer soon. In this case, the message is taken from a configuration file, that contains the multilanguage texts of the module. Due to the fact, that these texts are statical no more document controller is necessary.
APF template
<br /> <html:getstring namespace="modules::kontakt4" config="language.ini" entry="message.text" /> <br /> <br />

3.4. Document controller contact_form_controller

The document controller contact_form_controller inherits from the abstract document controller BaseDocumentController (interface). Because of the DOM structure of the framework each document controller must inherit from the base document controller. The rest of th class can be defined freely. The developer can define own class methods or load classes via import() each time he wants to. To control the output of the form the abstract method transformContent() is implemented. This function describes, that the form is diplayed as long as the form is not filled as desired by the programmer (see line 60). In case of correct values the business layer is created to send the form and to display the thank-you-page (template file meldung.html).
PHP code
import('tools::link', 'LinkGenerator'); import('modules::kontakt4::biz', 'ContactFormData'); class contact_form_controller extends BaseDocumentController { public function transformContent() { $form = & $this->getForm('contact'); // generate a generic action url, to be included in various pages $action = LinkGenerator::generateUrl(Url::fromCurrent()); $form->setAction($action); // fill recipient list /* @var $recipients SelectBoxTag */ $recipients = & $form->getFormElementByName('Empfaenger'); /* @var $cM ContactManager */ $cM = & $this->getServiceObject('modules::kontakt4::biz', 'ContactManager'); /* @var $recipientList ContactFormRecipient[] */ $recipientList = $cM->loadRecipients(); for ($i = 0; $i < count($recipientList); $i++) { $recipients->addOption($recipientList[$i]->getName(), $recipientList[$i]->getId()); } if ($form->isSent() && $form->isValid()) { $formData = new ContactFormData(); /* @var $recipient SelectBoxTag */ $recipient = & $form->getFormElementByName('Empfaenger'); $option = & $recipient->getSelectedOption(); $recipientId = $option->getAttribute('value'); $formData->setRecipientId($recipientId); $name = & $form->getFormElementByName('AbsenderName'); $formData->setSenderName($name->getAttribute('value')); $email = & $form->getFormElementByName('AbsenderAdresse'); $formData->setSenderEmail($email->getAttribute('value')); $subject = & $form->getFormElementByName('Betreff'); $formData->setSubject($subject->getAttribute('value')); $text = & $form->getFormElementByName('Text'); $formData->setMessage($text->getContent()); /* @var $cM ContactManager */ $cM = & $this->getServiceObject('modules::kontakt4::biz', 'ContactManager'); $cM->sendContactForm($formData); } else { // label the button $config = $this->getConfiguration('modules::kontakt4', 'language'); $value = $config->getSection($this->language)->getValue('form.button'); $button = & $form->getFormElementByName('sendFormContact'); $button->setAttribute('value', $value); // fill listeners with the language dependent values $senderError = & $form->getFormElementByID('sender-error'); $senderError->setPlaceHolder('content', $config->getSection($this->language)->getValue('form.name.error')); $addrError = & $form->getFormElementByID('addr-error'); $addrError->setPlaceHolder('content', $config->getSection($this->language)->getValue('form.email.error')); $subjectError = & $form->getFormElementByID('subject-error'); $subjectError->setPlaceHolder('content', $config->getSection($this->language)->getValue('form.subject.error')); $textError = & $form->getFormElementByID('text-error'); $textError->setPlaceHolder('content', $config->getSection($this->language)->getValue('form.text.error')); $captchaError = & $form->getFormElementByID('captcha-error'); $captchaError->setPlaceHolder('content', $config->getSection($this->language)->getValue('form.captcha.error')); $form->transformOnPlace(); } } }

Taking a closer look to the source code, you can see, that the controller class uses two more components: the RequestHandler and the ContactManager. The first class is a class integrated in the framework, that registers request variables in a locally used array and fills this offsets with default values if not present in the request. Details on this class can be seen on the RequestHandler page. The second component is the business layer class of the contact form. This class provides interfaces to load recipients or to submit the form.

The method transformContent() takes over the generation of the view. For this reason a reference on the form object is fetched to check, wheather the form is sent and filled correctly. If not, the form is displayed in the current view. Is the user input considered ok, a form data domain object is created and filled. After that the business component is created by use of the getServiceObject() method. The function sendContactForm() called with the domain object ContactFormData as an argument then sends the data of the form via mail. The submission and the redirection to the thank-you-message is completely done by the business layer.

3.5. Class ContactManager

The contactManager class is an implementation of the business layer. It encapsulates the business logic of the application and communicates with lower tiers. In this case the business layer is in charge of sending the form or loading recipients. Therefore the current layer defines two API methods: sendContactForm() to send the form and loadRecipients() to provide a list of recipients. The interface between the presentation and business layer is formed by the two domain objects ContactFormData and ContactFormRecipient.
PHP code
import('modules::kontakt4::biz', 'ContactFormData'); import('modules::kontakt4::biz', 'ContactFormRecipient'); import('tools::link', 'LinkGenerator'); import('tools::http', 'HeaderManager'); class ContactManager extends APFObject { public function sendContactForm(ContactFormData $formData) { $cM = &$this->getServiceObject('modules::kontakt4::data', 'ContactMapper'); // set up the mail sender $MAIL = &$this->getAndInitServiceObject('tools::mail', 'mailSender', 'ContactForm'); $recipient = $cM->loadRecipientPerId($formData->getRecipientId()); $MAIL->setRecipient($recipient->getEmailAddress(), $recipient->getName()); $Text = 'Sehr geehrter Empfänger, sehr geehrte Empfängerin,'; $Text .= "\n\n"; $Text .= $formData->getSenderName() . ' (E-Mail: ' . $formData->getSenderEmail() . ') hat Ihnen folgende Nachricht über das Kontaktformular zukommen lassen:'; $Text .= "\n\n\n"; $Text .= $formData->getMessage(); $MAIL->setContent($Text); $MAIL->setSubject($formData->getSubject()); // send mail to notify the recipient $MAIL->sendMail(); $MAIL->clearRecipients(); $MAIL->clearCCRecipients(); $MAIL->clearContent(); $MAIL->setRecipient($formData->getSenderEmail(), $formData->getSenderName()); $Text = 'Sehr geehrter Empfänger, sehr geehrte Empfängerin,'; $Text .= "\n\n"; $Text .= 'Ihre Anfrage wurde an die Kontaktperson "' . $recipient->getName() . '" weitergeleitet. Wir setzen uns baldmöglich mit Ihnen in Verbindung!'; $Text .= "\n\n"; $Text .= 'Hier nochmals Ihr Anfragetext:'; $Text .= "\n"; $Text .= $formData->getMessage(); $MAIL->setContent($Text); $MAIL->setSubject($formData->getSubject()); // send mail to notify the sender $MAIL->sendMail(); // redirect to the thanks page to avoid F5 bugs! $link = LinkGenerator::generateUrl(Url::fromCurrent()->mergeQuery(array('contactview' => 'thanks'))); $urlRewriting = Registry::retrieve('apf::core', 'URLRewriting'); if ($urlRewriting != true) { $link = str_replace('&', '&', $link); } HeaderManager::forward($link); } public function loadRecipients() { $cM = & $this->getServiceObject('modules::kontakt4::data', 'ContactMapper'); return $cM->loadRecipients(); } }
In the first two lines twoe more framework components are included to assist mail delivery (mailSender) and link generation (LinkGenerator).
Within the method sendContactForm() two service layers are instanciated, that are necessary for the delivery of the form data: the data layer to load the choosen recipient object and the mailSender to send confirmation and notification mails.
The function loadRecipients() is a service function for the presentation layer, that loads recipient domain objects.


4. Class ContactMapper

The ContactMapper is a data mapper pattern implementation. It is used as a data provider by the business layer. For this reason the mapper loads the configuraton file, that contains the recipients and maps this data into recipient domain object (ContactFormRecipient).
PHP code
import('modules::kontakt4::biz','ContactFormData'); import('modules::kontakt4::biz','ContactFormRecipient'); class ContactMapper extends APFObject { public function loadRecipients(){ // read config $Config = $this->getConfiguration('modules::kontakt4','empfaenger.ini'); // initialize return array $Recipients = array(); // parse config and generate recipients foreach($Config->getSectionNames() as $name){ $Count = count($Recipients); $Recipients[$Count] = new ContactFormRecipient(); // id preg_match("/Kontakt ([0-9]+)/i",$name,$Matches); $Recipients[$Count]->set('oID',$Matches[1]); // name $Recipients[$Count]->set('Name',$Config->getSection($name)->getValue('EmpfaengerName')); // email $Recipients[$Count]->set('Adresse',$Config->getSection($name)->getValue('EmpfaengerAdresse')); } // return recipient list return $Recipients; } public function loadRecipientPerId($Id){ $Rec = $this->loadRecipients(); if(!is_array($Rec)){ return array(); } else{ for($i = 0; $i < count($Rec); $i++){ if($Rec[$i]->get('oID') == $Id){ return $Rec[$i]; } } } } }
The configuration file itself is separated into several sections:
APF configuration
[Kontakt ([0-9]+)] EmpfaengerName = "([A-Za-z0-9,.-_ ]+)" EmpfaengerAdresse = "([A-Za-z0-9.-_@]+)"
Each section is mapped into on domain object (ContactFormRecipient). loadRecipients() returns a list if ContactFormRecipient objects. Every recipient can be identified by an unique ID (see section name) and can thus the loaded by the method loadRecipientById().
If you intend to read the recipients from a database instead of a configuration file, only the implementation of the data layer methods must be changed to read the domain objects from a database.


Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.