typo3-powermail

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TYPO3 Powermail Development

TYPO3 Powermail 开发

Compatibility: TYPO3 v13.x and v14.x with Powermail 13.x All code examples target PHP 8.2+ and TYPO3 v13/v14.
TYPO3 API First: Always use TYPO3's built-in APIs, core features, and established conventions before creating custom implementations. Do not reinvent what TYPO3 already provides. Always verify that the APIs and methods you use exist and are not deprecated in your target TYPO3 version (v13 or v14) by checking the official TYPO3 documentation.
Supplements:
  • SKILL-CONDITIONS.md - Conditional field/page visibility (powermail_cond)
  • SKILL-PHP84.md - PHP 8.4 patterns (property hooks, asymmetric visibility, new array functions)
  • SKILL-EXAMPLES.md - Multi-step shop form with Austrian legal types, DDEV SQL + DataHandler CLI
兼容性: TYPO3 v13.x 和 v14.x 搭配 Powermail 13.x 所有代码示例针对 PHP 8.2+ 和 TYPO3 v13/v14。
优先使用TYPO3 API: 在创建自定义实现前,务必优先使用TYPO3的内置API、核心功能及既定规范,不要重复造轮子。请务必通过官方TYPO3文档验证你使用的API和方法在目标TYPO3版本(v13或v14)中存在且未被弃用。
补充资源:
  • SKILL-CONDITIONS.md - 条件字段/页面可见性(powermail_cond)
  • SKILL-PHP84.md - PHP 8.4 模式(属性钩子、非对称可见性、新数组函数)
  • SKILL-EXAMPLES.md - 带奥地利法律类型的多步骤店铺表单、DDEV SQL + DataHandler CLI示例

1. Architecture Overview

1. 架构概述

Domain Model Hierarchy

领域模型层次结构

Form (tx_powermail_domain_model_form)
 └── Page (tx_powermail_domain_model_page)
      └── Field (tx_powermail_domain_model_field)

Mail (tx_powermail_domain_model_mail)
 └── Answer (tx_powermail_domain_model_answer)
      └── references Field
Form (tx_powermail_domain_model_form)
 └── Page (tx_powermail_domain_model_page)
      └── Field (tx_powermail_domain_model_field)

Mail (tx_powermail_domain_model_mail)
 └── Answer (tx_powermail_domain_model_answer)
      └── references Field

Plugin Registration

插件注册

  • Pi1 (cached/uncached):
    form
    ,
    create
    ,
    confirmation
    ,
    optinConfirm
    ,
    disclaimer
  • Pi5 (uncached):
    marketing
    (AJAX tracking)
  • Pi1(可缓存/不可缓存):
    form
    create
    confirmation
    optinConfirm
    disclaimer
  • Pi5(不可缓存):
    marketing
    (AJAX追踪)

Composer

Composer 安装

bash
composer require in2code/powermail
Requires: PHP ^8.2, TYPO3 ^13.4, ext-json, ext-gd, ext-fileinfo, ext-curl
bash
composer require in2code/powermail
依赖要求:PHP ^8.2, TYPO3 ^13.4, ext-json, ext-gd, ext-fileinfo, ext-curl

2. Field Types

2. 字段类型

TypeKeyValue TypeNotes
Text
input
TEXT (0)Standard input
Textarea
textarea
TEXT (0)Multi-line
Select
select
TEXT/ARRAY (0/1)Multiselect possible
Checkbox
check
ARRAY (1)Multiple values
Radio
radio
TEXT (0)Single selection
Submit
submit
Form submit button
Captcha
captcha
TEXT (0)Built-in CAPTCHA
Reset
reset
Form reset button
Static text
text
Display only
Content element
content
CE reference
HTML
html
TEXT (0)Raw HTML
Password
password
PASSWORD (4)Hashed storage
File upload
file
UPLOAD (3)File attachments
Hidden
hidden
TEXT (0)Hidden input
Date
date
DATE (2)Datepicker
Country
country
TEXT (0)Country selector
Location
location
TEXT (0)Geolocation
TypoScript
typoscript
TEXT (0)TS-generated content
类型标识值类型说明
文本
input
TEXT (0)标准输入框
文本域
textarea
TEXT (0)多行输入框
下拉选择框
select
TEXT/ARRAY (0/1)支持多选
复选框
check
ARRAY (1)支持多值选择
单选框
radio
TEXT (0)单选选择
提交按钮
submit
表单提交按钮
验证码
captcha
TEXT (0)内置CAPTCHA验证
重置按钮
reset
表单重置按钮
静态文本
text
仅用于展示
内容元素
content
关联内容元素(CE)
HTML代码
html
TEXT (0)原生HTML内容
密码框
password
PASSWORD (4)哈希加密存储
文件上传
file
UPLOAD (3)文件附件上传
隐藏字段
hidden
TEXT (0)隐藏输入字段
日期选择器
date
DATE (2)日期选择控件
国家选择器
country
TEXT (0)国家下拉选择
位置选择器
location
TEXT (0)地理定位选择
TypoScript生成
typoscript
TEXT (0)通过TS生成内容

Answer Value Types

回复值类型

php
Answer::VALUE_TYPE_TEXT     = 0;  // String values
Answer::VALUE_TYPE_ARRAY    = 1;  // JSON-encoded arrays (checkboxes, multiselect)
Answer::VALUE_TYPE_DATE     = 2;  // Timestamps
Answer::VALUE_TYPE_UPLOAD   = 3;  // File references
Answer::VALUE_TYPE_PASSWORD = 4;  // Hashed passwords
php
Answer::VALUE_TYPE_TEXT     = 0;  // 字符串值
Answer::VALUE_TYPE_ARRAY    = 1;  // JSON编码的数组(复选框、多选下拉)
Answer::VALUE_TYPE_DATE     = 2;  // 时间戳
Answer::VALUE_TYPE_UPLOAD   = 3;  // 文件引用
Answer::VALUE_TYPE_PASSWORD = 4;  // 哈希加密的密码

3. TypoScript Configuration

3. TypoScript 配置

Essential Settings

核心设置

typoscript
plugin.tx_powermail {
    settings {
        setup {
            # Form settings
            main {
                pid = {$plugin.tx_powermail.settings.main.pid}
                form = {$plugin.tx_powermail.settings.main.form}
                confirmation = 0
                optin = 0
                morestep = 0
            }

            # Receiver mail
            receiver {
                enable = 1
                subject = Mail from {firstname} {lastname}
                body = A new mail from your website
                senderNameField = firstname
                senderEmailField = email
                # Override receiver: receiver.overwrite.email = admin@example.com
                # Attach uploads: receiver.attachment = 1
                # Add CC: receiver.overwrite.cc = copy@example.com
            }

            # Sender confirmation mail
            sender {
                enable = 1
                subject = Thank you for your message
                body = We received your submission
                senderName = Website
                senderEmail = noreply@example.com
            }

            # Double Opt-In
            optin {
                subject = Please confirm your submission
                senderName = Website
                senderEmail = noreply@example.com
            }

            # Thank you page
            thx {
                redirect = # Page UID for redirect after submit
            }

            # Spam protection
            spamshield {
                _enable = 1
                indicator {
                    honeypod = 5
                    link = 3
                    name = 3
                    session = 1
                    unique = 2
                    blacklistString = 7
                    blacklistIp = 7
                    rateLimit = 10
                }
                # Factor threshold (0-100): reject if >=
                factor = 75
            }

            # Validation
            misc {
                htmlForLabels = 1
                showOnlyFilledValues = 1
                ajaxSubmit = 0
                file {
                    folder = uploads/tx_powermail/
                    size = 25000000
                    extension = jpg,jpeg,gif,png,tif,txt,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,csv,svg
                }
            }
        }
    }
}
typoscript
plugin.tx_powermail {
    settings {
        setup {
            # 表单设置
            main {
                pid = {$plugin.tx_powermail.settings.main.pid}
                form = {$plugin.tx_powermail.settings.main.form}
                confirmation = 0
                optin = 0
                morestep = 0
            }

            # 收件人邮件
            receiver {
                enable = 1
                subject = Mail from {firstname} {lastname}
                body = A new mail from your website
                senderNameField = firstname
                senderEmailField = email
                # 覆盖收件人:receiver.overwrite.email = admin@example.com
                # 附加上传文件:receiver.attachment = 1
                # 添加抄送:receiver.overwrite.cc = copy@example.com
            }

            # 发件人确认邮件
            sender {
                enable = 1
                subject = Thank you for your message
                body = We received your submission
                senderName = Website
                senderEmail = noreply@example.com
            }

            # 双重选择加入(Double Opt-In)
            optin {
                subject = Please confirm your submission
                senderName = Website
                senderEmail = noreply@example.com
            }

            # 感谢页设置
            thx {
                redirect = # 提交后跳转的页面UID
            }

            # 垃圾邮件防护
            spamshield {
                _enable = 1
                indicator {
                    honeypod = 5
                    link = 3
                    name = 3
                    session = 1
                    unique = 2
                    blacklistString = 7
                    blacklistIp = 7
                    rateLimit = 10
                }
                # 阈值(0-100):得分超过该值则拒绝
                factor = 75
            }

            # 验证设置
            misc {
                htmlForLabels = 1
                showOnlyFilledValues = 1
                ajaxSubmit = 0
                file {
                    folder = uploads/tx_powermail/
                    size = 25000000
                    extension = jpg,jpeg,gif,png,tif,txt,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,csv,svg
                }
            }
        }
    }
}

Prefill Fields via TypoScript

通过TypoScript预填充字段

typoscript
plugin.tx_powermail.settings.setup.prefill {
    # By field marker
    email = TEXT
    email.data = TSFE:fe_user|user|email

    firstname = TEXT
    firstname.data = TSFE:fe_user|user|first_name

    # Prefill from GET/POST
    subject = TEXT
    subject.data = GP:subject
}
typoscript
plugin.tx_powermail.settings.setup.prefill {
    # 通过字段标记预填充
    email = TEXT
    email.data = TSFE:fe_user|user|email

    firstname = TEXT
    firstname.data = TSFE:fe_user|user|first_name

    # 从GET/POST参数预填充
    subject = TEXT
    subject.data = GP:subject
}

Marketing Information

营销信息追踪

typoscript
plugin.tx_powermail.settings.setup.marketing {
    enable = 1
    # Tracked: refererDomain, referer, country, mobileDevice, frontendLanguage, browserLanguage, pageFunnel
}
typoscript
plugin.tx_powermail.settings.setup.marketing {
    enable = 1
    # 追踪项:refererDomain、referer、country、mobileDevice、frontendLanguage、browserLanguage、pageFunnel
}

4. Custom Finishers

4. 自定义完成器

Finishers run after successful form submission, sorted by TypoScript key.
完成器会在表单提交成功后执行,执行顺序由TypoScript的键值决定。

Registration

注册完成器

typoscript
plugin.tx_powermail.settings.setup.finishers {
    # Lower number = runs first
    0.class = In2code\Powermail\Finisher\RateLimitFinisher
    10.class = In2code\Powermail\Finisher\SaveToAnyTableFinisher
    20.class = In2code\Powermail\Finisher\SendParametersFinisher
    100.class = In2code\Powermail\Finisher\RedirectFinisher

    # Custom finisher
    50.class = Vendor\MyExt\Finisher\CrmFinisher
    50.config {
        apiUrl = https://crm.example.com/api
        apiKey = secret123
    }
}
typoscript
plugin.tx_powermail.settings.setup.finishers {
    # 数值越小,执行顺序越靠前
    0.class = In2code\Powermail\Finisher\RateLimitFinisher
    10.class = In2code\Powermail\Finisher\SaveToAnyTableFinisher
    20.class = In2code\Powermail\Finisher\SendParametersFinisher
    100.class = In2code\Powermail\Finisher\RedirectFinisher

    # 自定义完成器
    50.class = Vendor\MyExt\Finisher\CrmFinisher
    50.config {
        apiUrl = https://crm.example.com/api
        apiKey = secret123
    }
}

Creating a Custom Finisher

创建自定义完成器

php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\Finisher;

use In2code\Powermail\Finisher\AbstractFinisher;
use In2code\Powermail\Finisher\FinisherInterface;
use In2code\Powermail\Domain\Model\Mail;

final class CrmFinisher extends AbstractFinisher implements FinisherInterface
{
    /**
     * Method name MUST end with "Finisher"
     * Can have initialize*Finisher() called before
     */
    public function myCustomFinisher(): void
    {
        /** @var Mail $mail */
        $mail = $this->getMail();
        $settings = $this->getSettings();
        $configuration = $this->getConfiguration(); // TS config.*

        // Access form answers
        foreach ($mail->getAnswers() as $answer) {
            $fieldMarker = $answer->getField()->getMarker();
            $value = $answer->getValue();
            // Process...
        }

        // Access by marker
        $answers = $mail->getAnswersByFieldMarker();
        $email = $answers['email'] ?? null;

        // Check if form was actually submitted (not just displayed)
        if (!$this->isFormSubmitted()) {
            return;
        }
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\Finisher;

use In2code\Powermail\Finisher\AbstractFinisher;
use In2code\Powermail\Finisher\FinisherInterface;
use In2code\Powermail\Domain\Model\Mail;

final class CrmFinisher extends AbstractFinisher implements FinisherInterface
{
    /**
     * 方法名称必须以“Finisher”结尾
     * 执行前会调用initialize*Finisher()方法
     */
    public function myCustomFinisher(): void
    {
        /** @var Mail $mail */
        $mail = $this->getMail();
        $settings = $this->getSettings();
        $configuration = $this->getConfiguration(); // TS配置中的config.*

        // 遍历获取所有表单回复
        foreach ($mail->getAnswers() as $answer) {
            $fieldMarker = $answer->getField()->getMarker();
            $value = $answer->getValue();
            // 处理逻辑...
        }

        // 通过标记获取回复
        $answers = $mail->getAnswersByFieldMarker();
        $email = $answers['email'] ?? null;

        // 检查表单是否实际提交(而非仅展示)
        if (!$this->isFormSubmitted()) {
            return;
        }
    }
}

Built-in Finishers

内置完成器

ClassKeyPurpose
RateLimitFinisher
0Consumes rate limiter tokens
SaveToAnyTableFinisher
10Save answers to custom DB tables
SendParametersFinisher
20POST form data to external URL
RedirectFinisher
100Redirect after submission
类名标识用途
RateLimitFinisher
0消耗速率限制器的令牌
SaveToAnyTableFinisher
10将回复保存到自定义数据库表
SendParametersFinisher
20将表单数据POST到外部URL
RedirectFinisher
100提交后跳转页面

SaveToAnyTable Configuration

SaveToAnyTable 配置示例

typoscript
plugin.tx_powermail.settings.setup.dbEntry {
    1 {
        _enable = TEXT
        _enable.value = 1
        _table = fe_users
        _ifUnique.email = update  # update|skip|none
        username.value = {email}
        email.value = {email}
        first_name.value = {firstname}
        last_name.value = {lastname}
        pid.value = 123
    }
}
typoscript
plugin.tx_powermail.settings.setup.dbEntry {
    1 {
        _enable = TEXT
        _enable.value = 1
        _table = fe_users
        _ifUnique.email = update  # update|skip|none
        username.value = {email}
        email.value = {email}
        first_name.value = {firstname}
        last_name.value = {lastname}
        pid.value = 123
    }
}

5. Custom Validators

5. 自定义验证器

Creating a Custom Validator (PSR-14 Event)

创建自定义验证器(PSR-14事件)

php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\EventListener;

use In2code\Powermail\Events\CustomValidatorEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('vendor-myext/custom-validator')]
final class CustomValidatorListener
{
    public function __invoke(CustomValidatorEvent $event): void
    {
        $mail = $event->getMail();
        $field = $event->getField();

        // Validate specific field by marker
        if ($field->getMarker() === 'company_vat') {
            $answer = null;
            foreach ($mail->getAnswers() as $a) {
                if ($a->getField()?->getUid() === $field->getUid()) {
                    $answer = $a;
                    break;
                }
            }

            if ($answer !== null && !$this->isValidVat((string)$answer->getValue())) {
                $event->setIsValid(false);
                $event->setValidationMessage('Invalid VAT number');
            }
        }
    }

    private function isValidVat(string $vat): bool
    {
        return (bool)preg_match('/^[A-Z]{2}\d{8,12}$/', $vat);
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\EventListener;

use In2code\Powermail\Events\CustomValidatorEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('vendor-myext/custom-validator')]
final class CustomValidatorListener
{
    public function __invoke(CustomValidatorEvent $event): void
    {
        $mail = $event->getMail();
        $field = $event->getField();

        // 针对特定标记的字段进行验证
        if ($field->getMarker() === 'company_vat') {
            $answer = null;
            foreach ($mail->getAnswers() as $a) {
                if ($a->getField()?->getUid() === $field->getUid()) {
                    $answer = $a;
                    break;
                }
            }

            if ($answer !== null && !$this->isValidVat((string)$answer->getValue())) {
                $event->setIsValid(false);
                $event->setValidationMessage('Invalid VAT number');
            }
        }
    }

    private function isValidVat(string $vat): bool
    {
        return (bool)preg_match('/^[A-Z]{2}\d{8,12}$/', $vat);
    }
}

Built-in Validators

内置验证器

ValidatorPurpose
InputValidator
Email, URL, phone, number, letters, min/max length, regex
UploadValidator
File size, extension whitelist
PasswordValidator
Password match and strength
CaptchaValidator
Built-in CAPTCHA
SpamShieldValidator
Multi-method spam detection
UniqueValidator
Unique field values
ForeignValidator
Validate against foreign table
CustomValidator
TypoScript-based custom rules
验证器用途
InputValidator
邮箱、URL、电话、数字、字符、长度、正则验证
UploadValidator
文件大小、扩展名白名单验证
PasswordValidator
密码匹配与强度验证
CaptchaValidator
内置CAPTCHA验证
SpamShieldValidator
多维度垃圾邮件检测
UniqueValidator
字段值唯一性验证
ForeignValidator
关联外部表验证
CustomValidator
基于TypoScript的自定义规则验证

Spam Shield Methods

垃圾邮件防护方法

MethodWeightDescription
HoneyPodMethod
5Hidden honeypot field
LinkMethod
3Excessive links detection
NameMethod
3Suspicious name patterns
SessionMethod
1Session token validation
UniqueMethod
2Duplicate submission check
ValueBlacklistMethod
7Blacklisted content
IpBlacklistMethod
7Blacklisted IP addresses
RateLimitMethod
10Request rate limiting
方法名称权重说明
HoneyPodMethod
5隐藏蜜罐字段检测
LinkMethod
3过多链接检测
NameMethod
3可疑名称模式检测
SessionMethod
1Session令牌验证
UniqueMethod
2重复提交检测
ValueBlacklistMethod
7黑名单内容检测
IpBlacklistMethod
7黑名单IP检测
RateLimitMethod
10请求速率限制检测

6. PSR-14 Events

6. PSR-14 事件

Form Lifecycle Events

表单生命周期事件

php
// Before form is rendered
FormControllerFormActionEvent

// Before confirmation page
FormControllerConfirmationActionEvent

// After mail is saved to database
FormControllerCreateActionAfterMailDbSavedEvent

// After submit view is built
FormControllerCreateActionAfterSubmitViewEvent

// Before final view is rendered
FormControllerCreateActionBeforeRenderViewEvent

// Controller initialization
FormControllerInitializeObjectEvent
php
// 表单渲染前触发
FormControllerFormActionEvent

// 确认页展示前触发
FormControllerConfirmationActionEvent

// 邮件保存到数据库后触发
FormControllerCreateActionAfterMailDbSavedEvent

// 提交视图构建完成后触发
FormControllerCreateActionAfterSubmitViewEvent

// 最终视图渲染前触发
FormControllerCreateActionBeforeRenderViewEvent

// 控制器初始化时触发
FormControllerInitializeObjectEvent

Mail Events

邮件相关事件

php
// Modify receiver email addresses
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent

// Modify receiver name
ReceiverMailReceiverPropertiesServiceGetReceiverNameEvent

// Modify sender email (receiver mail)
ReceiverMailSenderPropertiesGetSenderEmailEvent

// Modify sender name (receiver mail)
ReceiverMailSenderPropertiesGetSenderNameEvent

// Modify sender email (confirmation mail)
SenderMailPropertiesGetSenderEmailEvent

// Modify sender name (confirmation mail)
SenderMailPropertiesGetSenderNameEvent

// Modify email body before sending
SendMailServiceCreateEmailBodyEvent

// Before email is sent (last chance to modify)
SendMailServicePrepareAndSendEvent
php
// 修改收件人邮箱地址
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent

// 修改收件人名称
ReceiverMailReceiverPropertiesServiceGetReceiverNameEvent

// 修改发件人邮箱(收件人邮件)
ReceiverMailSenderPropertiesGetSenderEmailEvent

// 修改发件人名称(收件人邮件)
ReceiverMailSenderPropertiesGetSenderNameEvent

// 修改发件人邮箱(确认邮件)
SenderMailPropertiesGetSenderEmailEvent

// 修改发件人名称(确认邮件)
SenderMailPropertiesGetSenderNameEvent

// 发送前修改邮件内容
SendMailServiceCreateEmailBodyEvent

// 邮件发送前触发(最后修改机会)
SendMailServicePrepareAndSendEvent

Other Events

其他事件

php
// Control if mail should be saved to DB
CheckIfMailIsAllowedToSaveEvent

// Custom validation logic
CustomValidatorEvent

// Prefill field values
PrefillFieldViewHelperEvent
PrefillMultiFieldViewHelperEvent

// File upload processing
UploadServicePreflightEvent
UploadServiceGetFilesEvent
GetNewPathAndFilenameEvent

// Before password is hashed
MailFactoryBeforePasswordIsHashedEvent

// Modify mail variables/markers
MailRepositoryGetVariablesWithMarkersFromMailEvent

// Validation data attributes
ValidationDataAttributeViewHelperEvent

// Double opt-in confirmation
FormControllerOptinConfirmActionAfterPersistEvent
FormControllerOptinConfirmActionBeforeRenderViewEvent

// Disclaimer/unsubscribe
FormControllerDisclaimerActionBeforeRenderViewEvent
php
// 控制邮件是否允许保存到数据库
CheckIfMailIsAllowedToSaveEvent

// 自定义验证逻辑
CustomValidatorEvent

// 预填充字段值
PrefillFieldViewHelperEvent
PrefillMultiFieldViewHelperEvent

// 文件上传处理
UploadServicePreflightEvent
UploadServiceGetFilesEvent
GetNewPathAndFilenameEvent

// 密码哈希前触发
MailFactoryBeforePasswordIsHashedEvent

// 修改邮件变量/标记
MailRepositoryGetVariablesWithMarkersFromMailEvent

// 验证数据属性
ValidationDataAttributeViewHelperEvent

// 双重Opt-In确认
FormControllerOptinConfirmActionAfterPersistEvent
FormControllerOptinConfirmActionBeforeRenderViewEvent

// 免责声明/退订
FormControllerDisclaimerActionBeforeRenderViewEvent

Example: Modify Receiver Email

示例:动态修改收件人邮箱

php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\EventListener;

use In2code\Powermail\Events\ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('vendor-myext/dynamic-receiver')]
final class DynamicReceiverListener
{
    public function __invoke(
        ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent $event
    ): void {
        $mail = $event->getMail();
        $answers = $mail->getAnswersByFieldMarker();

        // Route to department based on form field
        $department = $answers['department'] ?? null;
        if ($department !== null) {
            $value = (string)$department->getValue();
            $emails = match ($value) {
                'sales' => ['sales@example.com'],
                'support' => ['support@example.com'],
                default => $event->getReceiverEmails(),
            };
            $event->setReceiverEmails($emails);
        }
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\EventListener;

use In2code\Powermail\Events\ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('vendor-myext/dynamic-receiver')]
final class DynamicReceiverListener
{
    public function __invoke(
        ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent $event
    ): void {
        $mail = $event->getMail();
        $answers = $mail->getAnswersByFieldMarker();

        // 根据表单字段值路由到对应部门邮箱
        $department = $answers['department'] ?? null;
        if ($department !== null) {
            $value = (string)$department->getValue();
            $emails = match ($value) {
                'sales' => ['sales@example.com'],
                'support' => ['support@example.com'],
                default => $event->getReceiverEmails(),
            };
            $event->setReceiverEmails($emails);
        }
    }
}

Example: Prevent DB Save

示例:阻止邮件保存到数据库

php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\EventListener;

use In2code\Powermail\Events\CheckIfMailIsAllowedToSaveEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('vendor-myext/skip-db-save')]
final class SkipDbSaveListener
{
    public function __invoke(CheckIfMailIsAllowedToSaveEvent $event): void
    {
        // Skip DB save for specific forms
        $form = $event->getMail()->getForm();
        if ($form !== null && $form->getTitle() === 'Contact (no storage)') {
            $event->setSavingOfMailAllowed(false);
        }
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\EventListener;

use In2code\Powermail\Events\CheckIfMailIsAllowedToSaveEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('vendor-myext/skip-db-save')]
final class SkipDbSaveListener
{
    public function __invoke(CheckIfMailIsAllowedToSaveEvent $event): void
    {
        // 针对特定表单跳过数据库保存
        $form = $event->getMail()->getForm();
        if ($form !== null && $form->getTitle() === 'Contact (no storage)') {
            $event->setSavingOfMailAllowed(false);
        }
    }
}

7. Email Templates

7. 邮件模板

Template Paths (TypoScript)

模板路径配置(TypoScript)

typoscript
plugin.tx_powermail {
    view {
        templateRootPaths {
            0 = EXT:powermail/Resources/Private/Templates/
            10 = EXT:my_ext/Resources/Private/Templates/Powermail/
        }
        partialRootPaths {
            0 = EXT:powermail/Resources/Private/Partials/
            10 = EXT:my_ext/Resources/Private/Partials/Powermail/
        }
        layoutRootPaths {
            0 = EXT:powermail/Resources/Private/Layouts/
            10 = EXT:my_ext/Resources/Private/Layouts/Powermail/
        }
    }
}
typoscript
plugin.tx_powermail {
    view {
        templateRootPaths {
            0 = EXT:powermail/Resources/Private/Templates/
            10 = EXT:my_ext/Resources/Private/Templates/Powermail/
        }
        partialRootPaths {
            0 = EXT:powermail/Resources/Private/Partials/
            10 = EXT:my_ext/Resources/Private/Partials/Powermail/
        }
        layoutRootPaths {
            0 = EXT:powermail/Resources/Private/Layouts/
            10 = EXT:my_ext/Resources/Private/Layouts/Powermail/
        }
    }
}

Key Templates

核心模板

TemplatePurpose
Form/Form.html
Main form rendering
Form/Confirmation.html
Confirmation page
Form/Create.html
Thank you page
Mail/ReceiverMail.html
Admin notification email
Mail/SenderMail.html
User confirmation email
Mail/OptinMail.html
Double opt-in email
Form/PowermailAll.html
All-fields summary
模板名称用途
Form/Form.html
主表单渲染模板
Form/Confirmation.html
确认页模板
Form/Create.html
感谢页模板
Mail/ReceiverMail.html
管理员通知邮件模板
Mail/SenderMail.html
用户确认邮件模板
Mail/OptinMail.html
双重Opt-In邮件模板
Form/PowermailAll.html
所有字段汇总模板

Field Partials

字段局部模板

Override individual field types by copying partials:
Partials/Form/Field/Input.html
Partials/Form/Field/Textarea.html
Partials/Form/Field/Select.html
Partials/Form/Field/Check.html
Partials/Form/Field/Radio.html
Partials/Form/Field/File.html
Partials/Form/Field/Date.html
Partials/Form/Field/Captcha.html
Partials/Form/Field/Hidden.html
Partials/Form/Field/Password.html
Partials/Form/Field/Country.html
Partials/Form/Field/Location.html
Partials/Form/Field/Html.html
Partials/Form/Field/Content.html
Partials/Form/Field/Typoscript.html
Partials/Form/Field/Submit.html
Partials/Form/Field/Reset.html
可通过复制以下局部模板来覆盖单个字段类型的渲染:
Partials/Form/Field/Input.html
Partials/Form/Field/Textarea.html
Partials/Form/Field/Select.html
Partials/Form/Field/Check.html
Partials/Form/Field/Radio.html
Partials/Form/Field/File.html
Partials/Form/Field/Date.html
Partials/Form/Field/Captcha.html
Partials/Form/Field/Hidden.html
Partials/Form/Field/Password.html
Partials/Form/Field/Country.html
Partials/Form/Field/Location.html
Partials/Form/Field/Html.html
Partials/Form/Field/Content.html
Partials/Form/Field/Typoscript.html
Partials/Form/Field/Submit.html
Partials/Form/Field/Reset.html

Available Variables in Mail Templates

邮件模板中的可用变量

html
<!-- In ReceiverMail.html / SenderMail.html -->
{mail}                          <!-- Mail domain object -->
{mail.senderName}               <!-- Sender name -->
{mail.senderMail}               <!-- Sender email -->
{mail.form.title}               <!-- Form title -->
{mail.answers}                  <!-- All answers (ObjectStorage) -->

<!-- Iterate answers -->
<f:for each="{mail.answers}" as="answer">
    {answer.field.title}: {answer.value}
</f:for>

<!-- PowermailAll marker (all fields formatted) -->
{powermail_all}
html
<!-- 在ReceiverMail.html / SenderMail.html中 -->
{mail}                          <!-- Mail领域模型对象 -->
{mail.senderName}               <!-- 发件人名称 -->
{mail.senderMail}               <!-- 发件人邮箱 -->
{mail.form.title}               <!-- 表单标题 -->
{mail.answers}                  <!-- 所有回复(ObjectStorage类型) -->

<!-- 遍历所有回复 -->
<f:for each="{mail.answers}" as="answer">
    {answer.field.title}: {answer.value}
</f:for>

<!-- PowermailAll标记(所有字段格式化展示) -->
{powermail_all}

8. Key ViewHelpers

8. 核心ViewHelper

Validation

验证相关

html
<!-- Enable JS validation and/or AJAX submit -->
<vh:validation.enableJavascriptValidationAndAjax
    form="{form}"
    additionalAttributes="{...}" />

<!-- Validation data attributes on fields -->
<vh:validation.validationDataAttribute field="{field}" />

<!-- Error CSS class -->
<vh:validation.errorClass field="{field}" class="error" />

<!-- Upload attributes (accept, multiple) -->
<vh:validation.uploadAttributes field="{field}" />
html
<!-- 启用JS验证和/或AJAX提交 -->
<vh:validation.enableJavascriptValidationAndAjax
    form="{form}"
    additionalAttributes="{...}" />

<!-- 为字段添加验证数据属性 -->
<vh:validation.validationDataAttribute field="{field}" />

<!-- 错误CSS类 -->
<vh:validation.errorClass field="{field}" class="error" />

<!-- 上传控件属性(accept、multiple) -->
<vh:validation.uploadAttributes field="{field}" />

Form Fields

表单字段相关

html
<!-- Country selector -->
<vh:form.countries
    settings="{settings}"
    field="{field}"
    mail="{mail}" />

<!-- Advanced select with optgroups -->
<vh:form.advancedSelect
    field="{field}"
    mail="{mail}" />

<!-- Multi-upload -->
<vh:form.multiUpload field="{field}" />
html
<!-- 国家选择器 -->
<vh:form.countries
    settings="{settings}"
    field="{field}"
    mail="{mail}" />

<!-- 带选项组的高级下拉选择框 -->
<vh:form.advancedSelect
    field="{field}"
    mail="{mail}" />

<!-- 多文件上传 -->
<vh:form.multiUpload field="{field}" />

Prefill

预填充相关

html
<!-- Prefill single-value field -->
<vh:misc.prefillField field="{field}" mail="{mail}" />

<!-- Prefill multi-value field (select, check, radio) -->
<vh:misc.prefillMultiField field="{field}" mail="{mail}" cycle="{cycle}" />
html
<!-- 单值字段预填充 -->
<vh:misc.prefillField field="{field}" mail="{mail}" />

<!-- 多值字段预填充(下拉、复选、单选) -->
<vh:misc.prefillMultiField field="{field}" mail="{mail}" cycle="{cycle}" />

Conditions

条件判断相关

html
<!-- Check if field is not empty -->
<vh:condition.isNotEmpty val="{value}">
    <f:then>Has value</f:then>
</vh:condition.isNotEmpty>

<!-- Check if array -->
<vh:condition.isArray val="{value}">
    <f:then>Is array</f:then>
</vh:condition.isArray>

<!-- Check file exists -->
<vh:condition.fileExists file="{path}">
    <f:then>File available</f:then>
</vh:condition.fileExists>
html
<!-- 检查字段是否非空 -->
<vh:condition.isNotEmpty val="{value}">
    <f:then>已填写内容</f:then>
</vh:condition.isNotEmpty>

<!-- 检查是否为数组 -->
<vh:condition.isArray val="{value}">
    <f:then>是数组类型</f:then>
</vh:condition.isArray>

<!-- 检查文件是否存在 -->
<vh:condition.fileExists file="{path}">
    <f:then>文件存在</f:then>
</vh:condition.fileExists>

Backend

后端相关

html
<!-- Edit link in backend module -->
<vh:be.editLink table="tx_powermail_domain_model_mail" uid="{mail.uid}">
    Edit
</vh:be.editLink>
html
<!-- 后端模块中的编辑链接 -->
<vh:be.editLink table="tx_powermail_domain_model_mail" uid="{mail.uid}">
    编辑
</vh:be.editLink>

9. AJAX Form Submission

9. AJAX表单提交

typoscript
plugin.tx_powermail.settings.setup.misc.ajaxSubmit = 1
When enabled, form submission is handled via AJAX without page reload. The response replaces the form container with the thank-you content.
typoscript
plugin.tx_powermail.settings.setup.misc.ajaxSubmit = 1
启用后,表单提交通过AJAX处理,无需页面刷新。响应内容会将表单容器替换为感谢页内容。

10. Double Opt-In

10. 双重选择加入(Double Opt-In)

typoscript
plugin.tx_powermail.settings.setup.main.optin = 1

plugin.tx_powermail.settings.setup.optin {
    subject = Please confirm your submission
    senderName = My Website
    senderEmail = noreply@example.com
}
Flow:
  1. User submits form
  2. Mail is saved with
    hidden=1
  3. Opt-in email sent with confirmation link (HMAC-secured)
  4. User clicks link ->
    optinConfirmAction
    unhides the mail
  5. Receiver email sent after confirmation
typoscript
plugin.tx_powermail.settings.setup.main.optin = 1

plugin.tx_powermail.settings.setup.optin {
    subject = Please confirm your submission
    senderName = My Website
    senderEmail = noreply@example.com
}
流程说明:
  1. 用户提交表单
  2. 邮件以
    hidden=1
    状态保存到数据库
  3. 发送包含HMAC加密确认链接的Opt-In邮件
  4. 用户点击链接 →
    optinConfirmAction
    将邮件设为可见状态
  5. 确认完成后发送收件人邮件

11. Backend Module

11. 后端模块

Powermail provides a backend module under Web > Powermail:
  • List: Browse/filter/search submitted mails
  • Export: CSV and Excel (PhpSpreadsheet) export
  • Reporting: Form analytics and marketing charts
  • System Check: Verify configuration (admin only)
Powermail在Web > Powermail路径下提供了后端模块:
  • 列表:浏览/筛选/搜索已提交的邮件
  • 导出:支持CSV和Excel(PhpSpreadsheet)格式导出
  • 报表:表单分析与营销数据图表
  • 系统检查:验证配置正确性(仅管理员可见)

Live Search

实时搜索

Search mails and forms directly from TYPO3 search bar:
  • #mail:searchterm
    - Search in mails
  • #form:searchterm
    - Search in forms
可直接在TYPO3搜索栏中搜索邮件和表单:
  • #mail:搜索关键词
    - 在邮件库中搜索
  • #form:搜索关键词
    - 在表单库中搜索

12. Extension Best Practices

12. 扩展开发最佳实践

Register Services (Services.yaml)

注册服务(Services.yaml)

yaml
services:
  Vendor\MyExt\EventListener\CrmSyncListener:
    tags:
      - name: event.listener
        identifier: 'vendor-myext/crm-sync'
Or use the
#[AsEventListener]
attribute (preferred in TYPO3 v13+).
yaml
services:
  Vendor\MyExt\EventListener\CrmSyncListener:
    tags:
      - name: event.listener
        identifier: 'vendor-myext/crm-sync'
或使用
#[AsEventListener]
属性(TYPO3 v13+推荐方式)。

Access Mail Answers Efficiently

高效获取邮件回复

php
// By field marker (most common)
$answers = $mail->getAnswersByFieldMarker();
$email = $answers['email']?->getValue();

// By field UID
$answers = $mail->getAnswersByFieldUid();

// Filter by value type
$uploads = $mail->getAnswersByValueType(Answer::VALUE_TYPE_UPLOAD);
php
// 通过字段标记获取(最常用)
$answers = $mail->getAnswersByFieldMarker();
$email = $answers['email']?->getValue();

// 通过字段UID获取
$answers = $mail->getAnswersByFieldUid();

// 按值类型筛选
$uploads = $mail->getAnswersByValueType(Answer::VALUE_TYPE_UPLOAD);

Custom Data on Mail Object

在Mail对象中添加自定义数据

php
// Add custom data (available in all finishers/events)
$mail->addAdditionalData('crm_id', $crmResponse['id']);

// Retrieve in another finisher/event
$crmId = $mail->getAdditionalData()['crm_id'] ?? null;
php
// 添加自定义数据(所有完成器/事件中均可访问)
$mail->addAdditionalData('crm_id', $crmResponse['id']);

// 在其他完成器/事件中获取
$crmId = $mail->getAdditionalData()['crm_id'] ?? null;

Rate Limiting

速率限制

Powermail uses Symfony RateLimiter. Configure in
ext_conf_template.txt
or extension settings.
Powermail使用Symfony RateLimiter组件,可在
ext_conf_template.txt
或扩展设置中配置。

Garbage Collection

垃圾回收

Powermail auto-registers garbage collection for mails and answers (default: 30 days). Configure via Scheduler task
TableGarbageCollectionTask
.
Powermail自动注册了邮件和回复的垃圾回收任务(默认保留30天),可通过调度器任务
TableGarbageCollectionTask
配置。

13. Common Recipes

13. 常见实现方案

Route Enhancer for SEO-Friendly URLs

SEO友好URL的路由增强器

yaml
routeEnhancers:
  PowermailOptIn:
    type: Plugin
    routePath: '/optin/{mail}/{hash}'
    namespace: 'tx_powermail_pi1'
    requirements:
      mail: '\d+'
      hash: '[a-zA-Z0-9]+'
yaml
routeEnhancers:
  PowermailOptIn:
    type: Plugin
    routePath: '/optin/{mail}/{hash}'
    namespace: 'tx_powermail_pi1'
    requirements:
      mail: '\d+'
      hash: '[a-zA-Z0-9]+'

Conditional Receiver Based on Form Field

基于表单字段的条件收件人

Use
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent
(see Section 6).
使用
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent
(见第6节示例)。

Custom Spam Shield Method

自定义垃圾邮件防护方法

php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\SpamShield;

use In2code\Powermail\Domain\Validator\SpamShield\AbstractMethod;

final class ApiCheckMethod extends AbstractMethod
{
    public function spamCheck(): bool
    {
        $mail = $this->getMail();
        // Return true if spam detected
        return $this->callExternalApi($mail);
    }
}
Register in TypoScript:
typoscript
plugin.tx_powermail.settings.setup.spamshield.methods {
    100 {
        class = Vendor\MyExt\SpamShield\ApiCheckMethod
        _enable = 1
        configuration {
            apiUrl = https://spam-api.example.com
        }
    }
}
php
<?php

declare(strict_types=1);

namespace Vendor\MyExt\SpamShield;

use In2code\Powermail\Domain\Validator\SpamShield\AbstractMethod;

final class ApiCheckMethod extends AbstractMethod
{
    public function spamCheck(): bool
    {
        $mail = $this->getMail();
        // 检测到垃圾邮件时返回true
        return $this->callExternalApi($mail);
    }
}
在TypoScript中注册:
typoscript
plugin.tx_powermail.settings.setup.spamshield.methods {
    100 {
        class = Vendor\MyExt\SpamShield\ApiCheckMethod
        _enable = 1
        configuration {
            apiUrl = https://spam-api.example.com
        }
    }
}

Extend Form with TypoScript-Generated Fields

通过TypoScript生成字段扩展表单

typoscript
plugin.tx_powermail.settings.setup.manipulateVariablesInPowermailAllMarker {
    timestamp = TEXT
    timestamp.data = date:U
    timestamp.strftime = %Y-%m-%d %H:%M:%S
}
typoscript
plugin.tx_powermail.settings.setup.manipulateVariablesInPowermailAllMarker {
    timestamp = TEXT
    timestamp.data = date:U
    timestamp.strftime = %Y-%m-%d %H:%M:%S
}

14. Database Structure

14. 数据库结构

Conditions tables: See SKILL-CONDITIONS.md Section 12 for
tx_powermailcond_*
tables.
条件相关表: 条件功能的表结构请查看SKILL-CONDITIONS.md第12节的
tx_powermailcond_*
表。

TYPO3 Standard Columns

TYPO3标准列

All powermail tables include these TYPO3-managed columns (not listed per table below):
ColumnTypePurpose
uid
int AUTO_INCREMENTPrimary key
pid
intStorage page UID
tstamp
intLast modification timestamp
crdate
intCreation timestamp
deleted
tinyintSoft-delete flag
hidden
tinyintVisibility flag
sys_language_uid
intLanguage UID (0 = default, -1 = all)
l10n_parent
intUID of the default language record
l10n_diffsource
mediumblobDiff source for translation
starttime
intPublish start (Unix timestamp)
endtime
intPublish end (Unix timestamp)
所有Powermail表都包含以下TYPO3管理的列(以下各表不再重复列出):
列名类型用途
uid
int AUTO_INCREMENT主键ID
pid
int存储页面UID
tstamp
int最后修改时间戳
crdate
int创建时间戳
deleted
tinyint软删除标记
hidden
tinyint可见性标记
sys_language_uid
int语言UID(0=默认,-1=所有语言)
l10n_parent
int关联默认语言记录的UID
l10n_diffsource
mediumblob翻译差异源数据
starttime
int发布开始时间戳
endtime
int发布结束时间戳

tx_powermail_domain_model_form

tx_powermail_domain_model_form

ColumnTypeDescription
title
varchar(255)Form title
note
tinyintBackend note renderer (internal)
css
varchar(255)CSS class for form wrapper
pages
varchar(255)IRRE children count or element browser list
autocomplete_token
varchar(3)Autocomplete on/off/empty
is_dummy_record
tinyintTest record flag
Indexes:
language (l10n_parent, sys_language_uid)
列名类型说明
title
varchar(255)表单标题
note
tinyint后端备注渲染标记(内部使用)
css
varchar(255)表单容器的CSS类
pages
varchar(255)IRRE子元素数量或元素浏览器列表
autocomplete_token
varchar(3)自动完成开关(on/off/empty)
is_dummy_record
tinyint测试记录标记
索引:
language (l10n_parent, sys_language_uid)

tx_powermail_domain_model_page

tx_powermail_domain_model_page

ColumnTypeDescription
form
intParent form UID
title
varchar(255)Page/step title
css
varchar(255)CSS class for fieldset
fields
intIRRE children count
sorting
intSort order within form
Indexes:
parent_form (form)
,
language (l10n_parent, sys_language_uid)
列名类型说明
form
int关联父表单UID
title
varchar(255)页面/步骤标题
css
varchar(255)字段集的CSS类
fields
intIRRE子元素数量
sorting
int在表单中的排序顺序
索引:
parent_form (form)
,
language (l10n_parent, sys_language_uid)

tx_powermail_domain_model_field

tx_powermail_domain_model_field

ColumnTypeDescription
page
intParent page UID
title
varchar(255)Field label
type
varchar(255)Field type key (input, select, check, ...)
settings
textOptions for select/radio/check (one per line)
path
varchar(255)File path reference
content_element
intCE reference for type=content
text
textStatic text for type=text
prefill_value
textDefault/prefill value
placeholder
textPlaceholder text
placeholder_repeat
textPlaceholder for repeat field (password)
create_from_typoscript
textTypoScript for type=typoscript
validation
intValidation type (0=none, 1=email, ...)
validation_configuration
varchar(255)Regex or config for validation
css
varchar(255)CSS class for field wrapper
description
varchar(255)Help text / description
multiselect
tinyintAllow multi-select
datepicker_settings
varchar(255)Datepicker format
feuser_value
varchar(255)Prefill from fe_user property
sender_email
tinyintThis field is the sender email
sender_name
tinyintThis field is the sender name
mandatory
tinyintRequired field
own_marker_select
tinyintCustom marker enabled
marker
varchar(255)Field marker (variable name)
mandatory_text
varchar(255)Custom mandatory error text
autocomplete_token
varchar(20)Autocomplete attribute
autocomplete_section
varchar(100)Autocomplete section
autocomplete_type
varchar(8)Autocomplete type
autocomplete_purpose
varchar(8)Autocomplete purpose
sorting
intSort order within page
Indexes:
parent_page (page)
,
language (l10n_parent, sys_language_uid)
列名类型说明
page
int关联父页面UID
title
varchar(255)字段标签
type
varchar(255)字段类型标识(input、select等)
settings
text下拉/单选/复选的选项配置(每行一个)
path
varchar(255)文件路径引用
content_element
int关联内容元素UID(type=content时使用)
text
text静态文本内容(type=text时使用)
prefill_value
text默认/预填充值
placeholder
text占位符文本
placeholder_repeat
text重复字段的占位符(密码框)
create_from_typoscript
textTypoScript配置(type=typoscript时使用)
validation
int验证类型(0=无,1=邮箱等)
validation_configuration
varchar(255)正则或验证配置参数
css
varchar(255)字段容器的CSS类
description
varchar(255)帮助文本/描述
multiselect
tinyint是否允许多选
datepicker_settings
varchar(255)日期选择器格式配置
feuser_value
varchar(255)从fe_user属性预填充的字段
sender_email
tinyint是否作为发件人邮箱字段
sender_name
tinyint是否作为发件人名称字段
mandatory
tinyint是否为必填字段
own_marker_select
tinyint是否启用自定义标记
marker
varchar(255)字段标记(变量名)
mandatory_text
varchar(255)自定义必填错误提示文本
autocomplete_token
varchar(20)自动完成属性值
autocomplete_section
varchar(100)自动完成分区
autocomplete_type
varchar(8)自动完成类型
autocomplete_purpose
varchar(8)自动完成用途
sorting
int在页面中的排序顺序
索引:
parent_page (page)
,
language (l10n_parent, sys_language_uid)

tx_powermail_domain_model_mail

tx_powermail_domain_model_mail

ColumnTypeDescription
sender_name
varchar(255)Submitter name
sender_mail
varchar(255)Submitter email
subject
varchar(255)Mail subject
receiver_mail
varchar(1024)Receiver email(s)
body
textMail body (RTE)
feuser
intFrontend user UID (if logged in)
sender_ip
tinytextSubmitter IP address
user_agent
textBrowser user agent
time
intSubmission timestamp
form
intSource form UID
answers
intIRRE children count
spam_factor
varchar(255)Spam score
marketing_referer_domain
textHTTP referer domain
marketing_referer
textFull HTTP referer
marketing_country
textVisitor country
marketing_mobile_device
tinyintMobile device flag
marketing_frontend_language
intFrontend language UID
marketing_browser_language
textBrowser Accept-Language
marketing_page_funnel
textPages visited before submit
Indexes:
form (form)
,
feuser (feuser)
列名类型说明
sender_name
varchar(255)提交人名称
sender_mail
varchar(255)提交人邮箱
subject
varchar(255)邮件主题
receiver_mail
varchar(1024)收件人邮箱(支持多个)
body
text邮件正文(富文本)
feuser
int关联前端用户UID(若登录)
sender_ip
tinytext提交人IP地址
user_agent
text浏览器User-Agent信息
time
int提交时间戳
form
int关联源表单UID
answers
intIRRE子元素数量
spam_factor
varchar(255)垃圾邮件得分
marketing_referer_domain
textHTTP来源域名
marketing_referer
text完整HTTP来源地址
marketing_country
text访问者国家
marketing_mobile_device
tinyint移动设备标记
marketing_frontend_language
int前端语言UID
marketing_browser_language
text浏览器Accept-Language信息
marketing_page_funnel
text提交前访问的页面路径
索引:
form (form)
,
feuser (feuser)

tx_powermail_domain_model_answer

tx_powermail_domain_model_answer

ColumnTypeDescription
mail
intParent mail UID
value
textAnswer value (JSON for arrays)
value_type
int0=text, 1=array, 2=date, 3=upload, 4=password
field
intSource field UID
Indexes:
mail (mail)
,
deleted (deleted)
,
hidden (hidden)
,
language (l10n_parent, sys_language_uid)
列名类型说明
mail
int关联父邮件UID
value
text回复值(数组以JSON存储)
value_type
int0=文本, 1=数组, 2=日期, 3=上传, 4=密码
field
int关联源字段UID
索引:
mail (mail)
,
deleted (deleted)
,
hidden (hidden)
,
language (l10n_parent, sys_language_uid)

ER Diagram (Relations)

ER关系图

tx_powermail_domain_model_form
  │ 1
  ├──── * tx_powermail_domain_model_page (IRRE via form)
  │       │ 1
  │       └──── * tx_powermail_domain_model_field (IRRE via page)
  │                    │
  │                    │ referenced by
  │                    ▼
  │             tx_powermail_domain_model_answer.field
  └──── * tx_powermail_domain_model_mail (via form)
           │ 1
           └──── * tx_powermail_domain_model_answer (IRRE via mail)
tx_powermail_domain_model_form
  │ 1
  ├──── * tx_powermail_domain_model_page(通过form关联IRRE)
  │       │ 1
  │       └──── * tx_powermail_domain_model_field(通过page关联IRRE)
  │                    │
  │                    │ 被引用
  │                    ▼
  │             tx_powermail_domain_model_answer.field
  └──── * tx_powermail_domain_model_mail(通过form关联)
           │ 1
           └──── * tx_powermail_domain_model_answer(通过mail关联IRRE)

15. Workspace Support

15. 工作区支持

Powermail records (forms, pages, fields) fully support TYPO3 workspaces. When EXT:workspaces is installed, editors can draft form changes in a workspace and publish them after review.
Key points:
  • All powermail tables gain
    t3ver_wsid
    ,
    t3ver_oid
    ,
    t3ver_state
    ,
    t3ver_stage
    columns
  • Records with
    t3ver_wsid > 0
    are drafts (not visible in live frontend)
  • Use DataHandler for workspace operations — it handles versioning automatically
  • Raw SQL requires manually setting all
    t3ver_*
    columns on every INSERT
  • Conditions (powermail_cond) must be in the same workspace as the form
Workspace lifecycle:
  1. Create records in workspace →
    t3ver_wsid = <ws_id>
    ,
    t3ver_state = 1
  2. Stage for review →
    t3ver_stage = 1
  3. Publish via backend module or CLI → records become live (
    t3ver_wsid = 0
    )
Detailed SQL and DataHandler examples: See SKILL-EXAMPLES.md for complete workspace-aware queries, publishing workflows, and CLI options.
Powermail记录(表单、页面、字段)完全支持TYPO3工作区。当安装EXT:workspaces扩展后,编辑者可在工作区中起草表单变更,审核通过后再发布到生产环境。
关键点:
  • 所有Powermail表会新增
    t3ver_wsid
    t3ver_oid
    t3ver_state
    t3ver_stage
  • t3ver_wsid > 0
    的记录为草稿(在前端生产环境不可见)
  • 工作区操作请使用DataHandler,它会自动处理版本控制逻辑
  • 原生SQL操作需要手动为每个INSERT语句设置所有
    t3ver_*
  • 条件规则(powermail_cond)必须与所属表单处于同一工作区
工作区生命周期:
  1. 创建:在工作区中创建记录 →
    t3ver_wsid = <工作区ID>
    ,
    t3ver_state = 1
  2. 提交审核
    t3ver_stage = 1
  3. 发布:通过后端模块或CLI工具发布 → 记录变为生产环境可见状态(
    t3ver_wsid = 0
详细SQL和DataHandler示例: 完整的工作区兼容查询、发布流程及CLI选项,请查看SKILL-EXAMPLES.md

16. Translations (Localization)

16. 翻译(本地化)

Powermail supports full TYPO3 localization. Form structure (form, pages, fields) can be translated so editors see localized labels, settings, and options. Submitted mails inherit the frontend language.
Powermail完全支持TYPO3本地化功能。表单结构(表单、页面、字段)可被翻译,编辑者能看到对应语言的标签、设置和选项。提交的邮件会继承提交时的前端语言。

How Translation Works

翻译工作原理

LevelWhat gets translatedKey columns
FormTitle
sys_language_uid
,
l10n_parent
PageTitle (step heading)
sys_language_uid
,
l10n_parent
FieldTitle, settings, placeholder, mandatory_text, description
sys_language_uid
,
l10n_parent
MailAutomatically stored with
sys_language_uid
from frontend
sys_language_uid
AnswerStored with language of submission
sys_language_uid
层级翻译内容核心列名
表单标题
sys_language_uid
,
l10n_parent
页面标题(步骤头)
sys_language_uid
,
l10n_parent
字段标题、设置、占位符、必填提示、描述
sys_language_uid
,
l10n_parent
邮件自动存储前端语言的
sys_language_uid
sys_language_uid
回复存储提交时的语言信息
sys_language_uid

Translation Rules

翻译规则

  • sys_language_uid = 0
    is the default language (e.g., English)
  • sys_language_uid = 1
    (or higher) is a translation (e.g., German)
  • l10n_parent
    points to the default language record UID
  • The
    marker
    field is not translated -- markers stay identical across languages
  • Field
    type
    is not translated -- structure is shared
  • Field
    settings
    (select/radio options) is translated -- option labels change per language
  • sys_language_uid = 0
    默认语言(如英语)
  • sys_language_uid = 1
    (或更高)为翻译语言(如德语)
  • l10n_parent
    指向默认语言记录的UID
  • marker
    字段不翻译 -- 标记在所有语言中保持一致
  • 字段
    type
    不翻译 -- 字段结构在多语言中共享
  • 字段
    settings
    (下拉/单选/复选选项)需要翻译 -- 选项标签可随语言变化,但选项值需保持一致(用于条件判断)

Example: Create Form in English, Translate to German

示例:创建英文表单并翻译为德语

Default Language (English, sys_language_uid=0)

默认语言(英语,sys_language_uid=0)

sql
-- Form
INSERT INTO tx_powermail_domain_model_form (pid, title, sys_language_uid, l10n_parent)
VALUES (1, 'Contact Form', 0, 0);
-- Assume UID = 10

-- Page
INSERT INTO tx_powermail_domain_model_page (pid, form, title, sorting, sys_language_uid, l10n_parent)
VALUES (1, 10, 'Your Details', 1, 0, 0);
-- Assume UID = 20

-- Fields
INSERT INTO tx_powermail_domain_model_field
  (pid, page, title, type, marker, mandatory, sender_name, sorting, sys_language_uid, l10n_parent)
VALUES
  (1, 20, 'First Name', 'input', 'firstname', 1, 1, 1, 0, 0),   -- UID 30
  (1, 20, 'Last Name', 'input', 'lastname', 1, 0, 2, 0, 0),     -- UID 31
  (1, 20, 'Email', 'input', 'email', 1, 0, 3, 0, 0),            -- UID 32
  (1, 20, 'Message', 'textarea', 'message', 0, 0, 4, 0, 0),     -- UID 33
  (1, 20, 'Subject', 'select', 'subject', 1, 0, 5, 0, 0),       -- UID 34
  (1, 20, 'Send', 'submit', 'submit', 0, 0, 6, 0, 0);           -- UID 35

-- Select options for subject (English)
UPDATE tx_powermail_domain_model_field
SET settings = 'General Inquiry\nSupport Request\nPartnership\nOther'
WHERE uid = 34;

-- Mark email field as sender_email
UPDATE tx_powermail_domain_model_field SET sender_email = 1 WHERE uid = 32;
sql
-- 表单
INSERT INTO tx_powermail_domain_model_form (pid, title, sys_language_uid, l10n_parent)
VALUES (1, 'Contact Form', 0, 0);
-- 假设生成的UID = 10

-- 页面
INSERT INTO tx_powermail_domain_model_page (pid, form, title, sorting, sys_language_uid, l10n_parent)
VALUES (1, 10, 'Your Details', 1, 0, 0);
-- 假设生成的UID = 20

-- 字段
INSERT INTO tx_powermail_domain_model_field
  (pid, page, title, type, marker, mandatory, sender_name, sorting, sys_language_uid, l10n_parent)
VALUES
  (1, 20, 'First Name', 'input', 'firstname', 1, 1, 1, 0, 0),   -- UID 30
  (1, 20, 'Last Name', 'input', 'lastname', 1, 0, 2, 0, 0),     -- UID 31
  (1, 20, 'Email', 'input', 'email', 1, 0, 3, 0, 0),            -- UID 32
  (1, 20, 'Message', 'textarea', 'message', 0, 0, 4, 0, 0),     -- UID 33
  (1, 20, 'Subject', 'select', 'subject', 1, 0, 5, 0, 0),       -- UID 34
  (1, 20, 'Send', 'submit', 'submit', 0, 0, 6, 0, 0);           -- UID 35

-- 主题字段的英文下拉选项
UPDATE tx_powermail_domain_model_field
SET settings = 'General Inquiry\nSupport Request\nPartnership\nOther'
WHERE uid = 34;

-- 将邮箱字段标记为发件人邮箱
UPDATE tx_powermail_domain_model_field SET sender_email = 1 WHERE uid = 32;

German Translation (sys_language_uid=1)

德语翻译(sys_language_uid=1)

sql
-- Form translation (l10n_parent = 10, the English form)
INSERT INTO tx_powermail_domain_model_form (pid, title, sys_language_uid, l10n_parent)
VALUES (1, 'Kontaktformular', 1, 10);

-- Page translation (l10n_parent = 20)
INSERT INTO tx_powermail_domain_model_page
  (pid, form, title, sorting, sys_language_uid, l10n_parent)
VALUES (1, 10, 'Ihre Daten', 1, 1, 20);

-- Field translations (l10n_parent points to English field UID)
INSERT INTO tx_powermail_domain_model_field
  (pid, page, title, type, marker, mandatory, sender_name, sorting, sys_language_uid, l10n_parent)
VALUES
  (1, 20, 'Vorname', 'input', 'firstname', 1, 1, 1, 1, 30),
  (1, 20, 'Nachname', 'input', 'lastname', 1, 0, 2, 1, 31),
  (1, 20, 'E-Mail-Adresse', 'input', 'email', 1, 0, 3, 1, 32),
  (1, 20, 'Nachricht', 'textarea', 'message', 0, 0, 4, 1, 33),
  (1, 20, 'Betreff', 'select', 'subject', 1, 0, 5, 1, 34),
  (1, 20, 'Absenden', 'submit', 'submit', 0, 0, 6, 1, 35);

-- German select options for subject
UPDATE tx_powermail_domain_model_field
SET settings = 'Allgemeine Anfrage\nSupportanfrage\nPartnerschaft\nSonstiges'
WHERE sys_language_uid = 1 AND l10n_parent = 34;

-- Mark email field as sender_email (must be set on translation too)
UPDATE tx_powermail_domain_model_field
SET sender_email = 1
WHERE sys_language_uid = 1 AND l10n_parent = 32;
sql
-- 表单翻译(l10n_parent = 10,即英文表单的UID)
INSERT INTO tx_powermail_domain_model_form (pid, title, sys_language_uid, l10n_parent)
VALUES (1, 'Kontaktformular', 1, 10);

-- 页面翻译(l10n_parent = 20)
INSERT INTO tx_powermail_domain_model_page
  (pid, form, title, sorting, sys_language_uid, l10n_parent)
VALUES (1, 10, 'Ihre Daten', 1, 1, 20);

-- 字段翻译(l10n_parent指向对应英文字段的UID)
INSERT INTO tx_powermail_domain_model_field
  (pid, page, title, type, marker, mandatory, sender_name, sorting, sys_language_uid, l10n_parent)
VALUES
  (1, 20, 'Vorname', 'input', 'firstname', 1, 1, 1, 1, 30),
  (1, 20, 'Nachname', 'input', 'lastname', 1, 0, 2, 1, 31),
  (1, 20, 'E-Mail-Adresse', 'input', 'email', 1, 0, 3, 1, 32),
  (1, 20, 'Nachricht', 'textarea', 'message', 0, 0, 4, 1, 33),
  (1, 20, 'Betreff', 'select', 'subject', 1, 0, 5, 1, 34),
  (1, 20, 'Absenden', 'submit', 'submit', 0, 0, 6, 1, 35);

-- 主题字段的德语下拉选项
UPDATE tx_powermail_domain_model_field
SET settings = 'Allgemeine Anfrage\nSupportanfrage\nPartnerschaft\nSonstiges'
WHERE sys_language_uid = 1 AND l10n_parent = 34;

-- 将翻译后的邮箱字段标记为发件人邮箱
UPDATE tx_powermail_domain_model_field
SET sender_email = 1
WHERE sys_language_uid = 1 AND l10n_parent = 32;

Translation via DataHandler

通过DataHandler实现翻译

php
<?php

declare(strict_types=1);

use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start([], []);

// Localize form (UID 10) to German (sys_language_uid=1)
$cmdMap = [
    'tx_powermail_domain_model_form' => [
        10 => [
            'localize' => 1, // target language UID
        ],
    ],
];
$dataHandler->start([], $cmdMap);
$dataHandler->process_cmdmap();

// DataHandler auto-creates translations of all IRRE children (pages + fields)
// Then update the translated titles:
$translatedFormUid = $dataHandler->copyMappingArray_merged['tx_powermail_domain_model_form'][10] ?? null;
if ($translatedFormUid) {
    $data = [
        'tx_powermail_domain_model_form' => [
            $translatedFormUid => [
                'title' => 'Kontaktformular',
            ],
        ],
    ];
    $dataHandler->start($data, []);
    $dataHandler->process_datamap();
}
php
<?php

declare(strict_types=1);

use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start([], []);

// 将UID为10的表单翻译为德语(sys_language_uid=1)
$cmdMap = [
    'tx_powermail_domain_model_form' => [
        10 => [
            'localize' => 1, // 目标语言UID
        ],
    ],
];
$dataHandler->start([], $cmdMap);
$dataHandler->process_cmdmap();

// DataHandler会自动创建所有IRRE子元素(页面+字段)的翻译
// 然后更新翻译后的标题:
$translatedFormUid = $dataHandler->copyMappingArray_merged['tx_powermail_domain_model_form'][10] ?? null;
if ($translatedFormUid) {
    $data = [
        'tx_powermail_domain_model_form' => [
            $translatedFormUid => [
                'title' => 'Kontaktformular',
            ],
        ],
    ];
    $dataHandler->start($data, []);
    $dataHandler->process_datamap();
}

Important Translation Notes

重要翻译注意事项

  • Markers are language-independent. The marker
    email
    stays
    email
    in all languages.
  • IRRE localization: When you localize a form via DataHandler (
    localize
    command), TYPO3 automatically creates translations for all child pages and fields.
  • Select options: The
    settings
    field (select/radio/check options) must be translated separately -- option values should match (for condition evaluation) but labels can differ.
  • Submitted mails: Mails store
    sys_language_uid
    from the frontend context. Answers reference the default-language field UID regardless of submission language.
  • Backend module: The mail list shows mails from all languages. Filter by language if needed.
  • 标记与语言无关:标记
    email
    在所有语言中都保持为
    email
    ,无需翻译。
  • IRRE本地化:当通过DataHandler的
    localize
    命令本地化表单时,TYPO3会自动为所有子页面和字段创建翻译记录。
  • 下拉选项
    settings
    字段(下拉/单选/复选选项)必须单独翻译 -- 选项值需保持一致(用于条件判断逻辑),但显示标签可随语言调整。
  • 已提交邮件:邮件会存储提交时的前端语言
    sys_language_uid
    ,回复会引用默认语言的字段UID,与提交语言无关。
  • 后端模块:邮件列表会展示所有语言的邮件,可按需通过语言筛选。

17. Full Example: Multi-Step Shop Form with Conditions

17. 完整示例:带条件的多步骤店铺表单

For a comprehensive multi-step mini-shop example with Austrian legal types (Gesellschaftsformen), conditional fields per legal type, GDPR compliance, and two implementation approaches (DDEV SQL + DataHandler CLI command), see SKILL-EXAMPLES.md.
如需了解包含奥地利法律类型(Gesellschaftsformen)、按法律类型显示条件字段GDPR合规的多步骤迷你店铺示例,以及两种实现方式(DDEV SQL + DataHandler CLI命令),请查看SKILL-EXAMPLES.md