关于 typo3-workspaces
面向 TYPO3 v14 开发者的 Workspaces 技能,涵盖版本控制、覆盖机制、发布流程和文件处理限制。它强调使用 TYPO3 内置 API,并说明工作区风险及防护做法,尤其涉及文件与文件集合。
主要功能
- 01说明工作区版本控制与覆盖
- 02介绍发布流程及 API 用法
- 03记录 FAL 与文件集合限制
- 04识别文件机密性风险
- 05列出适用于工作区的文件实践
适用场景
- 01开发 TYPO3 v14 Workspaces 功能
- 02检查工作区版本行为
- 03避免未发布文件被访问
- 04选择更安全的文件集合模式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dirnbauer/ webconsulting-skills --skill typo3-workspaces安装后在 Claude Code 中输入/typo3-workspaces确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/04-datahandler-setup-script.md05-making-extensions-workspace-compatible.md06-migrating-queries-without-workspaces-to-with-workspaces.md07-translations-with-workspaces.md08-debugging-and-troubleshooting.md09-testing-workspace-support.md09a-workspace-diagnostics-and-repair-playbook.md10-best-practices.md11-psr-14-events-reference.mdcredits-and-attribution.mdfull-guide.mdrelated-skills.mdv14-only-changes.md详细介绍
TYPO3 工作区
**兼容性:**TYPO3 v14.x 此技能中的所有代码示例均适用于 TYPO3 v14。
**TYPO3 API 优先:**始终优先使用 TYPO3 内置 API、核心功能和既定约定,再创建自定义实现。不要重新实现 TYPO3 已提供的功能。使用前请查阅 TYPO3 官方文档,确认所用 API 和方法存在,且在 TYPO3 v14 中未被弃用。
来源
此技能基于 17 个权威来源:
- TYPO3 工作区扩展文档
- 版本控制(工作区扩展)
- 创建自定义工作区
- 配置选项(工作区)
- PSR-14 事件(工作区)
- 版本控制与工作区(TYPO3 详解 / 核心 API)
- TCA versioningWS 参考
- 限制构建器(TYPO3 详解 / Doctrine DBAL)
- b13 博客:TYPO3 覆盖机制的高效之道(Benni Mack)
- Scheduler 任务(工作区)
- 用户指南(工作区)
- TYPO3-CORE-SA-2025-022:工作区模块中的信息泄露
- 本地化内容指南
- 文件集合(TYPO3 详解)
- FAL 数据库架构
- Forge 错误 #60343:sys_file_metadata 无法识别工作区
- Forge 功能 #97923:sys_file_collection 的合并 folder_identifier 字段
1. 核心概念
什么是工作区?
工作区允许编辑人员准备内容更改,而不影响线上网站。更改会经过可配置的审核流程,然后才会发布。
工作区分为两种:
- LIVE 工作区(ID=0):默认状态。每项更改都会立即生效。必须明确授予后端用户/用户组访问权限。
- 自定义工作区(ID>0):安全的编辑环境。更改会进行版本控制、可供预览,并经过多个阶段后才会正式发布。
版本控制的工作原理(数据库层面)
离线(工作区)版本与实时记录存储在同一个数据库表中。它们通过以下字段识别:
| 字段 | 用途 |
|---|---|
t3ver_oid | 指向实时记录的 uid(实时记录和工作区新建记录为 0) |
t3ver_wsid | 此版本所属的工作区 ID(实时记录为 0) |
t3ver_state | 特殊状态标记(见下文) |
t3ver_stage | 工作流阶段(0=编辑中,-10=准备发布) |
pid | 实际页面 ID(与实时记录的 pid 相同) |
注意: 在 TYPO3 v10.4 之前,离线版本的
pid = -1。从 v10.4 起(Important #89555,“与工作区相关的数据库记录包含正确的页面 ID”),工作区记录会存储其实际 pid;v11 中的单独变更(Breaking #92497)移除了移动占位记录。如果你在旧代码或文档中遇到pid = -1,说明内容已过时。
t3ver_state 值(TYPO3 v14):
| 值 | 含义 |
|---|---|
0 | 默认:对现有实时记录进行工作区修改 |
1 | 仅在工作区中创建的新记录(没有对应的实时记录) |
2 | 删除占位记录(记录标记为在发布时删除) |
4 | 移动指针(记录将在发布时移动,并存储新的 pid/排序值) |
v11 中已移除:
t3ver_state = -1(旧的“新版本”对应项)、t3ver_state = 3(旧的“移动占位记录”)以及t3ver_move_id字段。如果你在旧代码中看到这些值,它们在 TYPO3 v14 中已不再使用。
覆盖机制
TYPO3 始终先获取实时记录,然后再叠加工作区版本。对于启用了工作区的翻译,处理链如下:
- 获取默认语言的实时记录(language=0,workspace=0)
- 叠加工作区版本(搜索
t3ver_oid=<uid>ANDt3ver_wsid=<current_ws>) - 叠加语言翻译(在目标语言中搜索
l10n_parent=<uid>) - 叠加翻译的工作区版本
覆盖过程中始终保留实时记录的 uid,从而确保所有引用和链接保持完整。
发布工作流
- 发布:草稿内容通过工作区发布流程替换实时内容。
- TYPO3 v14 仍接受 DataHandler
version命令,使用action => 'publish'(首选)或action => 'swap'(别名,在DataHandlerHook中按与 publish 相同的方式处理)。工作区版本 UID 的参数键仍为swapWith,而不是uid。 - 不要依赖
sys_workspace上历史遗留的双向工作区“交换模式”(在 v11 中移除,#92206)——这与 cmdmaps 中的swap关键字是两回事。
2. 关键:文件/FAL 限制
文件(FAL)不会进行版本控制。 这是 TYPO3 Workspaces 最重要的限制。 另请参阅下文第 2a 节,其中介绍了额外的文件集合限制(基于文件夹的集合)。
这意味着什么
fileadmin/中的文件只存在于实时工作区- 如果你覆盖一个文件,此更改会立即影响所有工作区
sys_file_reference记录支持工作区版本控制;物理文件和sys_file记录不会像内容覆盖那样进行版本控制- 替换现有引用中的文件,在工作区预览中仍可能遇到边缘情况;请针对你使用的确切 TYPO3 次版本进行测试,并尽可能使用新文件名,而不是原位替换。
可预测文件名带来的安全问题
场景: 编辑者创建某个页面的工作区版本,将 geschaeftsbericht2024.pdf 替换为 geschaeftsbericht2025.pdf。工作区尚未发布。但:
- 新 PDF 会立即上传到
fileadmin/(文件处于实时状态!) - 外部人员猜测 URL,只需将年份加 1
- 在引用该文件的内容元素发布之前,
geschaeftsbericht2025.pdf就已可访问
这会带来真实的安全/保密风险。
变通方案
1. 使用难以猜测的文件名:
# Instead of:
fileadmin/reports/geschaeftsbericht2025.pdf
# Use hashed/random names:
fileadmin/reports/gb-a8f3e2b1c9d4.pdf
2. 将机密文件存储在 Web 根目录之外:
// config/system/settings.php
// Use a private storage that is NOT publicly accessible
// Deliver files programmatically via a controller
3. 使用 EXT:secure_downloads (leuchtfeuer/secure-downloads):
通过 PHP 脚本交付文件,该脚本会检查访问权限。无法直接通过文件 URL 访问。
4. 在服务器级别保护敏感目录:
# Apache (.htaccess in a subdirectory)
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
# NGINX
location /fileadmin/confidential/ {
deny all;
return 403;
}
5. 为每个工作区版本使用单独的文件引用:
使用不同的名称上传新文件。切勿覆盖现有文件。内容元素的工作区版本引用新文件,线上版本仍保留对旧文件的引用。发布后,两个文件都存在,但只有新文件会被引用。
工作区安全的文件处理规则
- 切勿覆盖在多个工作区的内容元素中使用的文件
- 始终为工作区内容上传名称唯一的新文件
- 切勿依赖“页面尚未发布”来保护文件机密性
- 对任何机密文档,考虑使用 EXT:secure_downloads
- 审查
fileadmin/中是否存在命名模式容易预测的文件
2a. 文件集合与工作区
文件集合(
sys_file_collection)在记录级别进行工作区版本控制,但基于文件夹的集合会在运行时从线上文件系统解析文件——不存在可用于覆盖的数据库关联。
文件集合的工作方式
sys_file_collection 存储三种集合类型,由 type 字段区分:
type 值 | 解析机制 | 涉及的数据库关联 |
|---|---|---|
static (0) | sys_file_reference 行(tablenames='sys_file_collection',fieldname='files') | 每个文件对应一行 sys_file_reference |
folder (1) | folder_identifier 字符串(例如 1:/user_upload/gallery/) | 无——没有联结表,也没有引用行 |
category (2) | 通过 sys_category_record_mm 解析 category uid | MM 表(通过父记录覆盖处理) |
sys_file_collection 的 TCA 设置了 versioningWS => true。这意味着集合记录本身会创建带有 t3ver_* 字段的工作区版本。不过,记录级版本控制只会对存储在 sys_file_collection 中的字段进行版本控制,不会对集合解析出的文件进行版本控制。
各集合类型的工作区安全性
| 集合类型 | 是否对记录进行版本控制? | 是否对文件绑定进行版本控制? | 是否对物理文件进行版本控制? | 是否适用于工作区? |
|---|---|---|---|---|
| Static | 是 | 是(sys_file_reference 的 versioningWS = true) | 否 | 部分适用 |
| 基于文件夹 | 是(仅记录) | 否——不存在数据库绑定 | 否 | 不适用 |
| 基于分类 | 是 | 部分适用(通过父记录覆盖处理分类 MM) | 否 | 部分适用 |
基于文件夹的集合为何会破坏工作区隔离
对于基于文件夹的集合,集合与文件之间的“关联”不是数据库关联。集合记录只存储 folder_identifier 字符串。调用 FolderBasedFileCollection::loadContents() 时,TYPO3 会调用 $storage->getFilesInFolder(),直接从线上存储驱动列出文件。集合与文件之间不存在可支持覆盖的数据库行。
后果:
- 向文件夹添加文件后,它会立即在所有工作区中可见
- 从文件夹中移除文件后,它会立即从所有工作区中移除
- 更改工作区版本中的
folder_identifier字段会指向另一个文件夹,但文件夹内容始终来自线上 - 如果创建工作区版本后文件夹内容发生变化,使用基于文件夹的集合的“文件链接”内容元素(
CType=uploads)在工作区预览中显示的文件可能与预期不同
静态集合更安全
静态集合(type=static)通过 sys_file_reference 行绑定文件。sys_file_collection 和 sys_file_reference 的 versioningWS 均为 true。在工作区中向静态集合添加或移除文件时,TYPO3 会为 sys_file_reference 行创建工作区版本。集合与文件之间的绑定会进行版本控制。
剩余的限制是:物理文件(sys_file)仍不进行版本控制(参见第 2 节)。如果覆盖文件,所有位置都会受到影响。
基于文件夹的集合的变通方法
1. 在需要工作区隔离时优先使用静态集合:
使用 type=static,而不是 type=folder。文件与集合之间的绑定是一个会进行工作区版本控制的 sys_file_reference 行。
2. 为每个工作区版本使用单独的文件夹:
对于基于文件夹的集合,为工作区更改创建一个新文件夹(例如 gallery-v2/),并在集合记录的工作区版本中更改 folder_identifier。线上版本仍指向原始文件夹。
3. 工作区草稿引用共享文件夹时,切勿修改文件夹内容:
如果工作区内容使用了基于文件夹的集合,在工作区发布之前,避免向该文件夹添加或移除文件。
4. 发布后清理旧文件夹:
使用 AfterRecordPublishedEvent 在工作区版本发布后移除过时的文件夹:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Workspaces\Event\AfterRecordPublishedEvent;
#[AsEventListener]
final class CleanupOldCollectionFolderListener
{
public function __construct(
private readonly StorageRepository $storageRepository,
) {}
public function __invoke(AfterRecordPublishedEvent $event): void
{
if ($event->getTable() !== 'sys_file_collection') {
return;
}
// After publishing a folder-based collection, remove the old folder
// if it is no longer referenced by any collection.
// Implementation depends on your naming convention (e.g. gallery-v1/, gallery-v2/).
}
}
工作区安全的文件集合规则
- 优先使用静态集合(
type=static),而不是基于文件夹的集合,尤其是在使用工作区时 - 切勿在未发布的工作区中,当基于文件夹的集合引用某个文件夹时,向该文件夹添加或移除文件
- 如果必须使用基于文件夹的集合,为每个工作区版本使用单独的文件夹
- 向编辑人员说明:无论工作区状态如何,基于文件夹的集合都会显示线上文件夹中的内容
- 在依赖文件集合开展编辑工作流之前,先在工作区预览中测试其行为
3. 安装与设置清单
安装
# Composer (TYPO3 v14)
composer require typo3/cms-workspaces
# Non-Composer: activate in Admin Tools > Extensions
完整设置清单
使用此清单确认工作区设置已完成:
扩展与系统
- 已安装并启用
typo3/cms-workspaces - 数据库架构已更新(运行
bin/typo3 extension:setup,或使用项目中等效的架构更新封装命令) - 已安装
typo3/cms-scheduler(自动发布所必需) - 已在服务端配置调度器 cron 作业
后端用户组
- 已创建工作区专用的后端用户组(例如“工作区编辑人员”)
- 用户组拥有明确的 LIVE 工作区访问权限(“挂载与工作区”选项卡 > 勾选“Live”)
- 用户组有权访问自定义工作区(在工作区记录中指定为所有者或成员)
- DB 挂载设置正确(用户组可编辑的页面)
- 文件挂载设置正确(用户组可访问的目录)
- 用户组对所有相关表具有
tables_modify权限 - 用户组对工作区将包含的页面类型具有相应权限
后端用户
- 用户已分配到工作区用户组
- 用户可以在后端侧边栏中看到工作区选择器(v14.2+;更早的 v14 版本可能仍在顶部栏显示)
- 仅在非管理员用户需要 LIVE 工作区访问权限时,才为其授予该权限
工作区记录
- 已在根页面(pid=0)上将工作区记录创建为系统记录
- 已设置标题和描述
- 已指定所有者(使用用户组,而非单个用户)
- 已指定成员(使用用户组,而非单个用户)
- 如有需要,已创建自定义阶段(位于“编辑中”与“准备发布”之间)
- 已按阶段配置通知设置
- 如果工作区应限制为特定页面树,已设置挂载点
- 如果需要自动发布,已设置发布日期
- 已配置“发布权限”(如有需要,可将发布权限限制为所有者)
表(TCA)
- 所有自定义扩展表都在
$GLOBALS['TCA'][<table>]['ctrl']中设置了'versioningWS' => true - 不应进行版本控制的表设置了
'versioningWS' => false - 需要在工作区中实时编辑的表设置了
'versioningWS_alwaysAllowLiveEdit' => true - 数据库中存在
t3ver_*列(在versioningWS = true时自动创建)
Inline / IRRE 子表(弃用 #106821): 如果子表缺少
versioningWS,Core 会记录弃用警告,并可能在构建 TCA 时自动添加此标志——不会抛出硬错误。请在 TCA 中显式设置此标志;它将在 v15 中成为硬性要求。
Scheduler 任务
- 已创建并启用“工作区自动发布”任务
- 已创建并启用“工作区预览链接清理”任务
- Cron 频率适当(例如,每 15 分钟一次)
TSconfig
- 已设置
options.workspaces.previewLinkTTLHours(默认值:48) - 如需限制语言,已设置
options.workspaces.allowed_languages.<wsId> - 如需限制预览模式,已配置
workspaces.splitPreviewModes - 已为自定义记录预览页面设置
options.workspaces.previewPageId
详细参考
如果任务需要详细示例、较长的模板、故障排查矩阵、附录或上文未包含的部分,请阅读完整指南。对于范围较窄的任务,请勿加载此文件,以便该技能采用渐进式披露。