typo3-powermail
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTYPO3 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 FieldForm (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 FieldPlugin Registration
插件注册
- Pi1 (cached/uncached): ,
form,create,confirmation,optinConfirmdisclaimer - Pi5 (uncached): (AJAX tracking)
marketing
- Pi1(可缓存/不可缓存):、
form、create、confirmation、optinConfirmdisclaimer - Pi5(不可缓存):(AJAX追踪)
marketing
Composer
Composer 安装
bash
composer require in2code/powermailRequires: 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. 字段类型
| Type | Key | Value Type | Notes |
|---|---|---|---|
| Text | | TEXT (0) | Standard input |
| Textarea | | TEXT (0) | Multi-line |
| Select | | TEXT/ARRAY (0/1) | Multiselect possible |
| Checkbox | | ARRAY (1) | Multiple values |
| Radio | | TEXT (0) | Single selection |
| Submit | | — | Form submit button |
| Captcha | | TEXT (0) | Built-in CAPTCHA |
| Reset | | — | Form reset button |
| Static text | | — | Display only |
| Content element | | — | CE reference |
| HTML | | TEXT (0) | Raw HTML |
| Password | | PASSWORD (4) | Hashed storage |
| File upload | | UPLOAD (3) | File attachments |
| Hidden | | TEXT (0) | Hidden input |
| Date | | DATE (2) | Datepicker |
| Country | | TEXT (0) | Country selector |
| Location | | TEXT (0) | Geolocation |
| TypoScript | | TEXT (0) | TS-generated content |
| 类型 | 标识 | 值类型 | 说明 |
|---|---|---|---|
| 文本 | | TEXT (0) | 标准输入框 |
| 文本域 | | TEXT (0) | 多行输入框 |
| 下拉选择框 | | TEXT/ARRAY (0/1) | 支持多选 |
| 复选框 | | ARRAY (1) | 支持多值选择 |
| 单选框 | | TEXT (0) | 单选选择 |
| 提交按钮 | | — | 表单提交按钮 |
| 验证码 | | TEXT (0) | 内置CAPTCHA验证 |
| 重置按钮 | | — | 表单重置按钮 |
| 静态文本 | | — | 仅用于展示 |
| 内容元素 | | — | 关联内容元素(CE) |
| HTML代码 | | TEXT (0) | 原生HTML内容 |
| 密码框 | | PASSWORD (4) | 哈希加密存储 |
| 文件上传 | | UPLOAD (3) | 文件附件上传 |
| 隐藏字段 | | TEXT (0) | 隐藏输入字段 |
| 日期选择器 | | DATE (2) | 日期选择控件 |
| 国家选择器 | | TEXT (0) | 国家下拉选择 |
| 位置选择器 | | TEXT (0) | 地理定位选择 |
| 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 passwordsphp
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
内置完成器
| Class | Key | Purpose |
|---|---|---|
| 0 | Consumes rate limiter tokens |
| 10 | Save answers to custom DB tables |
| 20 | POST form data to external URL |
| 100 | Redirect after submission |
| 类名 | 标识 | 用途 |
|---|---|---|
| 0 | 消耗速率限制器的令牌 |
| 10 | 将回复保存到自定义数据库表 |
| 20 | 将表单数据POST到外部URL |
| 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
内置验证器
| Validator | Purpose |
|---|---|
| Email, URL, phone, number, letters, min/max length, regex |
| File size, extension whitelist |
| Password match and strength |
| Built-in CAPTCHA |
| Multi-method spam detection |
| Unique field values |
| Validate against foreign table |
| TypoScript-based custom rules |
| 验证器 | 用途 |
|---|---|
| 邮箱、URL、电话、数字、字符、长度、正则验证 |
| 文件大小、扩展名白名单验证 |
| 密码匹配与强度验证 |
| 内置CAPTCHA验证 |
| 多维度垃圾邮件检测 |
| 字段值唯一性验证 |
| 关联外部表验证 |
| 基于TypoScript的自定义规则验证 |
Spam Shield Methods
垃圾邮件防护方法
| Method | Weight | Description |
|---|---|---|
| 5 | Hidden honeypot field |
| 3 | Excessive links detection |
| 3 | Suspicious name patterns |
| 1 | Session token validation |
| 2 | Duplicate submission check |
| 7 | Blacklisted content |
| 7 | Blacklisted IP addresses |
| 10 | Request rate limiting |
| 方法名称 | 权重 | 说明 |
|---|---|---|
| 5 | 隐藏蜜罐字段检测 |
| 3 | 过多链接检测 |
| 3 | 可疑名称模式检测 |
| 1 | Session令牌验证 |
| 2 | 重复提交检测 |
| 7 | 黑名单内容检测 |
| 7 | 黑名单IP检测 |
| 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
FormControllerInitializeObjectEventphp
// 表单渲染前触发
FormControllerFormActionEvent
// 确认页展示前触发
FormControllerConfirmationActionEvent
// 邮件保存到数据库后触发
FormControllerCreateActionAfterMailDbSavedEvent
// 提交视图构建完成后触发
FormControllerCreateActionAfterSubmitViewEvent
// 最终视图渲染前触发
FormControllerCreateActionBeforeRenderViewEvent
// 控制器初始化时触发
FormControllerInitializeObjectEventMail 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)
SendMailServicePrepareAndSendEventphp
// 修改收件人邮箱地址
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent
// 修改收件人名称
ReceiverMailReceiverPropertiesServiceGetReceiverNameEvent
// 修改发件人邮箱(收件人邮件)
ReceiverMailSenderPropertiesGetSenderEmailEvent
// 修改发件人名称(收件人邮件)
ReceiverMailSenderPropertiesGetSenderNameEvent
// 修改发件人邮箱(确认邮件)
SenderMailPropertiesGetSenderEmailEvent
// 修改发件人名称(确认邮件)
SenderMailPropertiesGetSenderNameEvent
// 发送前修改邮件内容
SendMailServiceCreateEmailBodyEvent
// 邮件发送前触发(最后修改机会)
SendMailServicePrepareAndSendEventOther 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
FormControllerDisclaimerActionBeforeRenderViewEventphp
// 控制邮件是否允许保存到数据库
CheckIfMailIsAllowedToSaveEvent
// 自定义验证逻辑
CustomValidatorEvent
// 预填充字段值
PrefillFieldViewHelperEvent
PrefillMultiFieldViewHelperEvent
// 文件上传处理
UploadServicePreflightEvent
UploadServiceGetFilesEvent
GetNewPathAndFilenameEvent
// 密码哈希前触发
MailFactoryBeforePasswordIsHashedEvent
// 修改邮件变量/标记
MailRepositoryGetVariablesWithMarkersFromMailEvent
// 验证数据属性
ValidationDataAttributeViewHelperEvent
// 双重Opt-In确认
FormControllerOptinConfirmActionAfterPersistEvent
FormControllerOptinConfirmActionBeforeRenderViewEvent
// 免责声明/退订
FormControllerDisclaimerActionBeforeRenderViewEventExample: 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
核心模板
| Template | Purpose |
|---|---|
| Main form rendering |
| Confirmation page |
| Thank you page |
| Admin notification email |
| User confirmation email |
| Double opt-in email |
| All-fields summary |
| 模板名称 | 用途 |
|---|---|
| 主表单渲染模板 |
| 确认页模板 |
| 感谢页模板 |
| 管理员通知邮件模板 |
| 用户确认邮件模板 |
| 双重Opt-In邮件模板 |
| 所有字段汇总模板 |
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.htmlAvailable 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 = 1When 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:
- User submits form
- Mail is saved with
hidden=1 - Opt-in email sent with confirmation link (HMAC-secured)
- User clicks link -> unhides the mail
optinConfirmAction - 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
}流程说明:
- 用户提交表单
- 邮件以状态保存到数据库
hidden=1 - 发送包含HMAC加密确认链接的Opt-In邮件
- 用户点击链接 → 将邮件设为可见状态
optinConfirmAction - 确认完成后发送收件人邮件
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:
- - Search in mails
#mail:searchterm - - Search in forms
#form:searchterm
可直接在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 attribute (preferred in TYPO3 v13+).
#[AsEventListener]yaml
services:
Vendor\MyExt\EventListener\CrmSyncListener:
tags:
- name: event.listener
identifier: 'vendor-myext/crm-sync'或使用属性(TYPO3 v13+推荐方式)。
#[AsEventListener]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 or extension settings.
ext_conf_template.txtPowermail使用Symfony RateLimiter组件,可在或扩展设置中配置。
ext_conf_template.txtGarbage Collection
垃圾回收
Powermail auto-registers garbage collection for mails and answers (default: 30 days). Configure via Scheduler task .
TableGarbageCollectionTaskPowermail自动注册了邮件和回复的垃圾回收任务(默认保留30天),可通过调度器任务配置。
TableGarbageCollectionTask13. 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 (see Section 6).
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEvent使用(见第6节示例)。
ReceiverMailReceiverPropertiesServiceSetReceiverEmailsEventCustom 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 fortables.tx_powermailcond_*
条件相关表: 条件功能的表结构请查看SKILL-CONDITIONS.md第12节的表。tx_powermailcond_*
TYPO3 Standard Columns
TYPO3标准列
All powermail tables include these TYPO3-managed columns (not listed per table below):
| Column | Type | Purpose |
|---|---|---|
| int AUTO_INCREMENT | Primary key |
| int | Storage page UID |
| int | Last modification timestamp |
| int | Creation timestamp |
| tinyint | Soft-delete flag |
| tinyint | Visibility flag |
| int | Language UID (0 = default, -1 = all) |
| int | UID of the default language record |
| mediumblob | Diff source for translation |
| int | Publish start (Unix timestamp) |
| int | Publish end (Unix timestamp) |
所有Powermail表都包含以下TYPO3管理的列(以下各表不再重复列出):
| 列名 | 类型 | 用途 |
|---|---|---|
| int AUTO_INCREMENT | 主键ID |
| int | 存储页面UID |
| int | 最后修改时间戳 |
| int | 创建时间戳 |
| tinyint | 软删除标记 |
| tinyint | 可见性标记 |
| int | 语言UID(0=默认,-1=所有语言) |
| int | 关联默认语言记录的UID |
| mediumblob | 翻译差异源数据 |
| int | 发布开始时间戳 |
| int | 发布结束时间戳 |
tx_powermail_domain_model_form
tx_powermail_domain_model_form
| Column | Type | Description |
|---|---|---|
| varchar(255) | Form title |
| tinyint | Backend note renderer (internal) |
| varchar(255) | CSS class for form wrapper |
| varchar(255) | IRRE children count or element browser list |
| varchar(3) | Autocomplete on/off/empty |
| tinyint | Test record flag |
Indexes:
language (l10n_parent, sys_language_uid)| 列名 | 类型 | 说明 |
|---|---|---|
| varchar(255) | 表单标题 |
| tinyint | 后端备注渲染标记(内部使用) |
| varchar(255) | 表单容器的CSS类 |
| varchar(255) | IRRE子元素数量或元素浏览器列表 |
| varchar(3) | 自动完成开关(on/off/empty) |
| tinyint | 测试记录标记 |
索引:
language (l10n_parent, sys_language_uid)tx_powermail_domain_model_page
tx_powermail_domain_model_page
| Column | Type | Description |
|---|---|---|
| int | Parent form UID |
| varchar(255) | Page/step title |
| varchar(255) | CSS class for fieldset |
| int | IRRE children count |
| int | Sort order within form |
Indexes: ,
parent_form (form)language (l10n_parent, sys_language_uid)| 列名 | 类型 | 说明 |
|---|---|---|
| int | 关联父表单UID |
| varchar(255) | 页面/步骤标题 |
| varchar(255) | 字段集的CSS类 |
| int | IRRE子元素数量 |
| int | 在表单中的排序顺序 |
索引: ,
parent_form (form)language (l10n_parent, sys_language_uid)tx_powermail_domain_model_field
tx_powermail_domain_model_field
| Column | Type | Description |
|---|---|---|
| int | Parent page UID |
| varchar(255) | Field label |
| varchar(255) | Field type key (input, select, check, ...) |
| text | Options for select/radio/check (one per line) |
| varchar(255) | File path reference |
| int | CE reference for type=content |
| text | Static text for type=text |
| text | Default/prefill value |
| text | Placeholder text |
| text | Placeholder for repeat field (password) |
| text | TypoScript for type=typoscript |
| int | Validation type (0=none, 1=email, ...) |
| varchar(255) | Regex or config for validation |
| varchar(255) | CSS class for field wrapper |
| varchar(255) | Help text / description |
| tinyint | Allow multi-select |
| varchar(255) | Datepicker format |
| varchar(255) | Prefill from fe_user property |
| tinyint | This field is the sender email |
| tinyint | This field is the sender name |
| tinyint | Required field |
| tinyint | Custom marker enabled |
| varchar(255) | Field marker (variable name) |
| varchar(255) | Custom mandatory error text |
| varchar(20) | Autocomplete attribute |
| varchar(100) | Autocomplete section |
| varchar(8) | Autocomplete type |
| varchar(8) | Autocomplete purpose |
| int | Sort order within page |
Indexes: ,
parent_page (page)language (l10n_parent, sys_language_uid)| 列名 | 类型 | 说明 |
|---|---|---|
| int | 关联父页面UID |
| varchar(255) | 字段标签 |
| varchar(255) | 字段类型标识(input、select等) |
| text | 下拉/单选/复选的选项配置(每行一个) |
| varchar(255) | 文件路径引用 |
| int | 关联内容元素UID(type=content时使用) |
| text | 静态文本内容(type=text时使用) |
| text | 默认/预填充值 |
| text | 占位符文本 |
| text | 重复字段的占位符(密码框) |
| text | TypoScript配置(type=typoscript时使用) |
| int | 验证类型(0=无,1=邮箱等) |
| varchar(255) | 正则或验证配置参数 |
| varchar(255) | 字段容器的CSS类 |
| varchar(255) | 帮助文本/描述 |
| tinyint | 是否允许多选 |
| varchar(255) | 日期选择器格式配置 |
| varchar(255) | 从fe_user属性预填充的字段 |
| tinyint | 是否作为发件人邮箱字段 |
| tinyint | 是否作为发件人名称字段 |
| tinyint | 是否为必填字段 |
| tinyint | 是否启用自定义标记 |
| varchar(255) | 字段标记(变量名) |
| varchar(255) | 自定义必填错误提示文本 |
| varchar(20) | 自动完成属性值 |
| varchar(100) | 自动完成分区 |
| varchar(8) | 自动完成类型 |
| varchar(8) | 自动完成用途 |
| int | 在页面中的排序顺序 |
索引: ,
parent_page (page)language (l10n_parent, sys_language_uid)tx_powermail_domain_model_mail
tx_powermail_domain_model_mail
| Column | Type | Description |
|---|---|---|
| varchar(255) | Submitter name |
| varchar(255) | Submitter email |
| varchar(255) | Mail subject |
| varchar(1024) | Receiver email(s) |
| text | Mail body (RTE) |
| int | Frontend user UID (if logged in) |
| tinytext | Submitter IP address |
| text | Browser user agent |
| int | Submission timestamp |
| int | Source form UID |
| int | IRRE children count |
| varchar(255) | Spam score |
| text | HTTP referer domain |
| text | Full HTTP referer |
| text | Visitor country |
| tinyint | Mobile device flag |
| int | Frontend language UID |
| text | Browser Accept-Language |
| text | Pages visited before submit |
Indexes: ,
form (form)feuser (feuser)| 列名 | 类型 | 说明 |
|---|---|---|
| varchar(255) | 提交人名称 |
| varchar(255) | 提交人邮箱 |
| varchar(255) | 邮件主题 |
| varchar(1024) | 收件人邮箱(支持多个) |
| text | 邮件正文(富文本) |
| int | 关联前端用户UID(若登录) |
| tinytext | 提交人IP地址 |
| text | 浏览器User-Agent信息 |
| int | 提交时间戳 |
| int | 关联源表单UID |
| int | IRRE子元素数量 |
| varchar(255) | 垃圾邮件得分 |
| text | HTTP来源域名 |
| text | 完整HTTP来源地址 |
| text | 访问者国家 |
| tinyint | 移动设备标记 |
| int | 前端语言UID |
| text | 浏览器Accept-Language信息 |
| text | 提交前访问的页面路径 |
索引: ,
form (form)feuser (feuser)tx_powermail_domain_model_answer
tx_powermail_domain_model_answer
| Column | Type | Description |
|---|---|---|
| int | Parent mail UID |
| text | Answer value (JSON for arrays) |
| int | 0=text, 1=array, 2=date, 3=upload, 4=password |
| int | Source field UID |
Indexes: , , ,
mail (mail)deleted (deleted)hidden (hidden)language (l10n_parent, sys_language_uid)| 列名 | 类型 | 说明 |
|---|---|---|
| int | 关联父邮件UID |
| text | 回复值(数组以JSON存储) |
| int | 0=文本, 1=数组, 2=日期, 3=上传, 4=密码 |
| 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_statecolumnst3ver_stage - Records with are drafts (not visible in live frontend)
t3ver_wsid > 0 - Use DataHandler for workspace operations — it handles versioning automatically
- Raw SQL requires manually setting all columns on every INSERT
t3ver_* - Conditions (powermail_cond) must be in the same workspace as the form
Workspace lifecycle:
- Create records in workspace → ,
t3ver_wsid = <ws_id>t3ver_state = 1 - Stage for review →
t3ver_stage = 1 - 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)必须与所属表单处于同一工作区
工作区生命周期:
- 创建:在工作区中创建记录 → ,
t3ver_wsid = <工作区ID>t3ver_state = 1 - 提交审核:
t3ver_stage = 1 - 发布:通过后端模块或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
翻译工作原理
| Level | What gets translated | Key columns |
|---|---|---|
| Form | Title | |
| Page | Title (step heading) | |
| Field | Title, settings, placeholder, mandatory_text, description | |
Automatically stored with | | |
| Answer | Stored with language of submission | |
| 层级 | 翻译内容 | 核心列名 |
|---|---|---|
| 表单 | 标题 | |
| 页面 | 标题(步骤头) | |
| 字段 | 标题、设置、占位符、必填提示、描述 | |
| 邮件 | 自动存储前端语言的 | |
| 回复 | 存储提交时的语言信息 | |
Translation Rules
翻译规则
- is the default language (e.g., English)
sys_language_uid = 0 - (or higher) is a translation (e.g., German)
sys_language_uid = 1 - points to the default language record UID
l10n_parent - The field is not translated -- markers stay identical across languages
marker - Field is not translated -- structure is shared
type - Field (select/radio options) is translated -- option labels change per language
settings
- 为默认语言(如英语)
sys_language_uid = 0 - (或更高)为翻译语言(如德语)
sys_language_uid = 1 - 指向默认语言记录的UID
l10n_parent - 字段不翻译 -- 标记在所有语言中保持一致
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 stays
emailin all languages.email - IRRE localization: When you localize a form via DataHandler (command), TYPO3 automatically creates translations for all child pages and fields.
localize - Select options: The field (select/radio/check options) must be translated separately -- option values should match (for condition evaluation) but labels can differ.
settings - Submitted mails: Mails store from the frontend context. Answers reference the default-language field UID regardless of submission language.
sys_language_uid - Backend module: The mail list shows mails from all languages. Filter by language if needed.
- 标记与语言无关:标记在所有语言中都保持为
email,无需翻译。email - IRRE本地化:当通过DataHandler的命令本地化表单时,TYPO3会自动为所有子页面和字段创建翻译记录。
localize - 下拉选项:字段(下拉/单选/复选选项)必须单独翻译 -- 选项值需保持一致(用于条件判断逻辑),但显示标签可随语言调整。
settings - 已提交邮件:邮件会存储提交时的前端语言,回复会引用默认语言的字段UID,与提交语言无关。
sys_language_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。