Appearance
Contact Forms
Flow ID: CF-29 | Module(s): forms, Cms domain | Complexity: Medium
Business Overview
Config-driven dynamic contact forms with field-level customization, file attachments, CAPTCHA protection, and email delivery. Submissions are archived in the database and viewable in the admin panel. The system supports multiple form definitions from a single config file, allowing each site to define any number of contact forms with different field sets.
What customers experience:
- Visit a language-specific contact URL (e.g.,
/epikoinoniain Greek,/contactin English) - Fill in a dynamically rendered form (text, email, textarea, select, radio, checkbox, checkbox-group, file upload fields)
- CAPTCHA verification (Google reCAPTCHA v2 or CodeIgniter image captcha)
- On success, see a confirmation page with the generated email content
- Store owner receives an email with all submitted data and file attachments
Key business behaviors:
- Forms are entirely config-driven: fields, validation rules, labels, and templates defined in
contact_forms.php - Two rendering modes: hand-crafted HTML (
contactFormview) and auto-generated from config (contactFormGeneratedview) - File uploads stored in
../storage/forms/with type/size validation per field - Submissions archived in
contact_emailstable with JSON metadata and rendered HTML - Email subject resolved from registry (
EMAIL_SUBJECTSgroup, keyed by form name and language) - Sender email validation: submissions without a valid sender email are silently skipped (no email sent, no DB record)
- If a logged-in customer submits, their
customer_idis linked to the submission - Meta Conversions API integration:
Contactevent type available for Facebook pixel tracking
API Reference
REST Endpoints (Admin)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /rest/cms/contact-email | Backend (ADMIN, CMS) | List submissions with filtering/sorting |
| GET | /rest/cms/contact-email/{id} | Backend (ADMIN, CMS) | Get single submission |
| GET | /rest/cms/contact-email/item | Backend (ADMIN, CMS) | Get single submission by filters |
| POST | /rest/cms/contact-email | Backend (ADMIN, CMS) | Create submission record |
| POST | /rest/cms/contact-email/{id} | Backend (ADMIN, CMS) | Update submission record |
| DELETE | /rest/cms/contact-email/{id} | Backend (ADMIN, CMS) | Delete submission record |
Filters: id (exact), email (partial), customerId (exact), emailId (exact) Sorts: id, emailDatetime, email, emailIdRelations: customer (belongs_to)
Legacy Storefront
| URL | Controller Method | Description |
|---|---|---|
/epikoinonia (Greek) | Adv_forms::contactForm() | Default contact form |
/en/contact (English) | Adv_forms::contactForm() | Default contact form |
/de/kontakt (German) | Adv_forms::contactForm() | Default contact form |
Custom forms are accessed by passing a contactFormKey parameter: contactForm('fullSampleForm').
Admin Panel
| URL | Controller | Description |
|---|---|---|
/contacts/list | Admin_contact_email_viewer::index() | Paginated list with filters |
/contacts/view/{id} | Admin_contact_email_viewer::view() | View rendered email in popup |
Code Flow
Storefront Submission
File: ecommercen/forms/controllers/Adv_forms.php
- Load config:
FormsConfigloadscontact_forms.phpand resolves the form definition by key (default:contactForm) - Set validation:
setContactValidation()iterates config fields, setting CIform_validationrules per field (skipsuploadtype fields) - Set CAPTCHA:
setCaptchaValidationRule()adds CAPTCHA validation (trait-based: Google reCAPTCHA, CI image captcha, or no-op) - Check required files:
requiredFilesInPost()validates that requireduploadfields have files in$_FILES - Upload files:
uploadFiles()processes file uploads viaAdvUploaderto../storage/forms/with per-field type/size constraints - Collect post data:
getContactPostData()builds an associative array from POST values, handlingtext,email,textarea,select,radio,checkbox, andcheckbox-grouptypes - Render email: Loads the email template view with form data, external language helper, config, and attachment paths
- Allow check:
allowContactEmail()verifies the sender email field is present and non-empty (if configured) - Send email:
Adv_mailer::sendContactFormEmail()sends via SMTP usingEMAIL_CONTACT_FORMregistry credentials, with file attachments - Archive: Insert into
contact_emailstable: email, customer_id (if logged in), form key asemail_id, timestamp, JSON metadata, and rendered HTML - Success view: Swap the form view for
contactSuccesstemplate showing the generated email content
Email Delivery
File: application/models/Adv_mailer.php method sendContactFormEmail()
- Retrieves SMTP credentials from registry group
EMAIL_CONTACT_FORMviagetDefaultSmtpCredentials() - Sends to the admin email configured in that registry group
- Attaches uploaded files from
../storage/forms/directory
Admin Viewing
File: ecommercen/forms/controllers/AdvAdminContactEmailViewer.php
- Paginated list (15 per page) with filters: email (LIKE), type/email_id (exact), date (LIKE)
- Inline JSON metadata expansion per row
- Popup window to view the original rendered HTML email
- Customer name resolved from
customer_modelfor linked submissions
Domain Layer
| Component | Path |
|---|---|
| Entity | src/Domains/Cms/ContactEmail/Repository/Entity.php |
| Repository | src/Domains/Cms/ContactEmail/Repository/Repository.php |
| RepositoryConfigurator | src/Domains/Cms/ContactEmail/Repository/RepositoryConfigurator.php |
| WriteRepository | src/Domains/Cms/ContactEmail/Repository/WriteRepository.php |
| Service (Read) | src/Domains/Cms/ContactEmail/Service.php |
| WriteService | src/Domains/Cms/ContactEmail/WriteService.php |
| WriteData | src/Domains/Cms/ContactEmail/WriteData.php |
| Validator | src/Domains/Cms/ContactEmail/Validator.php |
| ListRequest | src/Domains/Cms/ContactEmail/ListRequest.php |
| REST Controller | src/Rest/Cms/Controllers/ContactEmail.php |
| Resource | src/Rest/Cms/Resources/ContactEmail/Resource.php |
| Collection | src/Rest/Cms/Resources/ContactEmail/Collection.php |
| Domain DI | src/Domains/Cms/container.php |
| REST DI | src/Rest/Cms/container.php |
Architecture
Forms Config System
File: ecommercen/forms/libraries/AdvFormsConfig.php (extended by application/modules/forms/libraries/FormsConfig.php)
The FormsConfig class wraps application/config/contact_forms.php and provides accessor methods:
| Method | Purpose |
|---|---|
getContactFormKeys() | All defined form keys |
getContactFormKeysDropDown() | Translated dropdown for admin filters |
getContactFormConfig($key) | Full config array for a form |
getContactFormSubmitName($key) | POST field name for submit detection |
getFrontTemplateView($key) | Template key for storefront layout |
getEmailTemplateView($key) | Template key for email body |
getTranslatedSelectOptions($key, $field) | Translated options for select/radio/checkbox-group |
getRequiredFileKeys($key) | Upload fields marked as required |
getClientEmailFieldKey($key) | Which field holds the sender's email |
getPageTitleKey($key) | Translation key for the page title |
CAPTCHA Strategy
Three interchangeable traits (used on the front controller hierarchy):
| Trait | File | Mechanism |
|---|---|---|
CaptchaTrait | ecommercen/core/CaptchaTrait.php | No-op (disabled) |
GoogleRecaptchaTrait | ecommercen/core/GoogleRecaptchaTrait.php | Google reCAPTCHA v2 via registry keys GOOGLE.RECAPTCHA_KEY / GOOGLE.RECAPTCHA_SECRET |
CodeigniterCaptchaTrait | ecommercen/core/CodeigniterCaptchaTrait.php | Server-generated image captcha using SimpleImage |
The active trait is applied on the front controller base class. Only one is active at a time.
Template System
Two rendering strategies mapped in template.json / mainTemplate.json:
| Template Key | View | Description |
|---|---|---|
contactForm | layouts/contact_form/contact_form.php | Hand-crafted HTML for the default form |
contactFormGenerated | layouts/contact_form/contact_form_generated.php | Auto-generated from config (iterates fields by type) |
contactSuccess | layouts/contact_form/contact_form_success.php | Success confirmation page |
Email templates in emailViews.json:
| Template Key | View |
|---|---|
contactEmail | mail/contact_email.php |
contactEmailGenerated | mail/contact_email_generated.php |
The generated templates (contactFormGenerated / contactEmailGenerated) dynamically render all field types from config, while the standard templates (contactForm / contactEmail) have hardcoded field HTML for the default contact form.
Data Model
contact_emails
| Column | Type | Description |
|---|---|---|
id | int(11) unsigned PK AI | Submission ID |
email | varchar(255) nullable | Sender email address |
customer_id | int(11) nullable | FK to customer if logged in |
email_id | varchar(255) NOT NULL | Form key identifier (e.g., contactForm) |
email_datetime | datetime NOT NULL | Submission timestamp |
meta_data | longtext nullable | JSON-encoded form field values |
generated_email | longtext nullable | Rendered HTML email content |
Indexes: customer_id, email, email_id, email_datetime, composite (email, email_id), composite (email, email_datetime), composite (email_id, email_datetime)
Configuration
Form Definition (application/config/contact_forms.php)
Each form is a keyed entry in $config['contactForms'] with this structure:
php
'formKey' => [
'pageTitleKey' => 'translation.key', // Page <title> translation key
'fields' => [
'fieldName' => [
'type' => 'text|email|textarea|select|radio|checkbox|checkbox-group|upload',
'labelKey' => 'translation.key', // Field label translation key
'validation' => 'trim|required|...', // CI form_validation rules
'options' => [...], // For select/radio/checkbox-group
'upload' => [ // For upload type
'allowed_types' => 'pdf|png|jpg',
'size' => 2048 // Max KB
],
],
],
'submit' => [
'name' => 'postFieldName', // POST key for submit detection
'labelKey' => 'translation.key',
],
'senderEmailField' => 'fieldName', // Which field holds sender email
'frontTemplateView' => 'contactForm', // Template key for storefront
'emailTemplateView' => 'contactEmail', // Template key for notification email
]Default form (contactForm): email, name, surname, phone, mobile, message Sample form (fullSampleForm): all field types including uploads, select, radio, checkbox-group
Registry Keys
| Group | Key | Description |
|---|---|---|
EMAIL_CONTACT_FORM | (SMTP config) | SMTP credentials for sending contact emails |
EMAIL_SUBJECTS | {formKey} | Email subject line per form per language |
GOOGLE | RECAPTCHA_KEY | reCAPTCHA v2 site key (if Google trait active) |
GOOGLE | RECAPTCHA_SECRET | reCAPTCHA v2 secret key |
TITLE | SITE | Fallback email subject (site name) |
REST Policies
php
ContactEmail::class => [
'defaults' => ['auth' => 'backend', 'roles' => [AUTH_ROLE_ADMIN, AUTH_ROLE_CMS]],
],Admin Menu
Listed under the CUSTOMERS group at route contacts/list.
Constants
php
const FORMS_ATTACHMENTS_PATH = '../storage/forms/'; // application/config/constants.phpClient Extension Points
- Custom forms: Add entries to
application/config/contact_forms.phpwith unique keys and views - FormsConfig override:
application/modules/forms/libraries/FormsConfig.phpextendsAdvFormsConfig-- add custom methods or override existing ones - Controller override:
Adv_formscan be extended inapplication/controllers/to add pre/post-submission hooks viacontactFormExtras() - CAPTCHA strategy: Switch the active captcha trait on the front controller (no-op, Google reCAPTCHA, or CI image captcha)
- Email templates: Override
contact_email.php/contact_email_generated.phpinapplication/views/main/mail/ - Form views: Override
contact_form.php/contact_form_generated.php/contact_form_success.phpinapplication/views/main/layouts/contact_form/ - Template mapping: Override
contactForm,contactFormGenerated,contactSuccesskeys inmainTemplate.json - Custom routes: Add language-specific routes in
application/config/my_routes.phppointing toforms/contactForm/{formKey} - Block sender: Override
allowContactEmail()to implement email blocklists - Meta Conversions API:
EventType::CONTACTavailable for Facebook pixel tracking of form submissions
Business Rules
| Rule | Implementation |
|---|---|
| Sender email required for archival | allowContactEmail() returns false if the configured senderEmailField is empty |
| One email per submission | Email sent to admin only (single EMAIL_CONTACT_FORM SMTP config) |
| File uploads validated per-field | allowed_types and size from config; AdvUploader enforces constraints |
| Required file uploads checked before processing | requiredFilesInPost() verifies required upload fields have files in $_FILES before upload attempt |
| CAPTCHA required | Enforced at validation level; trait determines which implementation |
| Customer linking | If session has customer_id, it is stored with the submission |
| Language-scoped URLs | Routes per language (Greek /epikoinonia, English /contact, etc.) with contactFormLinks() generating hreflang alternates |
| Email subject from registry | EMAIL_SUBJECTS.{formKey} per language, falls back to site name |
Related Flows
- CF-10 Customer Auth -- customer_id linking for logged-in submissions
- CF-30 Cookie Consent -- CAPTCHA strategy shared via front controller trait hierarchy
- SY-24 Email Dispatch System --
Adv_mailer::sendContactFormEmail()SMTP routing and file attachment handling - SY-25 File Upload & Storage --
AdvUploaderprocesses per-field upload constraints and stores files understorage/forms/
See also the Contact Forms from Config guide for details on creating custom form definitions.