Files
t3bootstrap-upgrade-skills/typo3-v11-to-v14-ddev-upgrade/SKILL.md
T
melnicenko db6cc87c90 v11->v14 upgrade: add Step 8c — non-admin editor permissions
After a v11->v14 upgrade, non-admin editors often can't edit content
(no edit icons) and/or the Media tab is missing, even though the group
has "edit content" and tables_modify includes tt_content. The upgrade
wizards do not convert the backend group access-lists. Document the fix
for all three facets on be_groups:

1. explicit_allowdeny: v<=11 ":ALLOW"-suffix format vs v12+ exact
   inList match (no suffix) — REPLACE strips it; blocks ALL editing.
2. CType allow-list missing renamed v14 names (wst3bootstrap_* -> t3bs_*,
   ws_slider, heroitem, news_*, address_*).
3. non_exclude_fields missing v14 fields -> hidden Media tab
   (e.g. hero tx_heroitem_bg*).

Includes the read-only recordEditAccessInternals/check/doesUserHaveAccess
CLI diagnosis and the db_mountpoints red-herring note. Bump to v1.1;
extend description + when-to-use for the symptom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:30:29 +02:00

179 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: typo3-v11-to-v14-ddev-upgrade
description: "Bring an existing TYPO3 v11 site up and booting on TYPO3 v14 LTS (v14.3) locally under DDEV, starting from a v11 SQL dump + the old sitepackage. Use when upgrading/migrating a TYPO3 v11 (or v12/13) project to v14 for local dev, when a v11 database dump must be made to boot on v14, when a t3bootstrap-based v11 site needs its composer/config/schema/upgrade-wizards/site-sets brought to v14, or when after such an upgrade a non-admin editor can't edit content (no edit icons / missing Media tab). Covers: composer.json rework to ^14 + private git repos & composer registry auth, config/system/settings.php, the v14 .htaccess, database:updateschema, backend admin, upgrade wizards (incl. confirmable + one-way ones), language packs, wiring t3bootstrap TypoScript via site sets, fixing non-admin editor permissions (be_groups explicit_allowdeny :ALLOW-format conversion, v14 CType allow-list, non_exclude_fields), and the recurring v11->v14 traps. NOT for fresh installs and NOT for server deployment (see the konsoleH staging-deploy skill for that)."
metadata:
author: Wappler
version: "1.1"
---
# TYPO3 v11 → v14 upgrade (local, DDEV)
Get an existing **TYPO3 v11 site** installable and **booting on v14 LTS (14.3.x)** under **DDEV**,
from a **v11 SQL dump** + the old sitepackage. This is the "make it run" phase — a later phase
matches the frontend design (see the *t3bootstrap live-design parity* skill) and another deploys to
staging (see the *konsoleH staging-deploy* skill).
Work **one step at a time and verify each** before moving on. **`ddev snapshot` before anything
destructive** (especially upgrade wizards — several are one-way). Never let a live secret appear in a
command. Many errors here look like config bugs but are data/schema/host issues — always confirm the
*actual* error before "fixing".
## Placeholders to resolve first
- `<PROJECT_DIR>` — the DDEV project root (docroot `public/`).
- `<SITE_ID>` — the site identifier folder under `config/sites/<SITE_ID>/`.
- `<SITE_BASE_HOST>` — the site's base host from `config/sites/<SITE_ID>/config.yaml` (e.g. the
`*.ddev.site` host). The FE only answers on this host, NOT `localhost`.
- `<REGISTRY_URL>` — the private Composer registry for the theme/vendor packages, if any (e.g. a
Gitea `.../api/packages/<vendor>/composer` URL). `<GIT_REPOS>` — any `gitea@`/GitHub SSH git repos
in `composer.json` `repositories`.
- `<TEMPLATE_PACKAGE>` — the sitepackage's composer `name` (⚠ often ≠ repo name — read the repo's
own `composer.json`). `<TEMPLATE_EXTKEY>` — its extension key (keep it stable across the rename).
---
## Step 0 — DDEV up + import the dump (read-only baseline)
`ddev start`; import the v11 dump into the `db` database (`ddev import-db --file=dump-*.sql` or
`ddev mysql < dump.sql`). Confirm PHP/DB versions match the target (v14.3 wants PHP 8.28.5). Snapshot:
`ddev snapshot --name pre-upgrade`.
## Step 1 — Rework `composer.json` for v14
- Bump every `typo3/cms-*` to `^14`. Bump third-party exts (news, address, t3bootstrap/*, …) to their
^14 releases. Drop anything with no v14 release (e.g. **flux/vhs are gone in v14 t3bootstrap**
`t3bootstrap/flux-*` do not exist; do not re-add them).
- **Custom-repo package name ≠ repo name.** A git repo `…/<name>.git` may publish itself as a
*different* composer package name — read the repo's `composer.json` `name` and require THAT.
- **Contradictory constraints fail:** `"^14 dev-release/v14"` (tag AND branch) → composer "cannot
possibly match". Use plain `"^14"` for a tagged package, or `"dev-<branch>"` for an untagged one.
- **Repository order controls the source.** If the same package name is served by both a git repo and
the registry, composer takes the **first** repo listed as canonical → list the project's git repo
**before** the composer registry in `repositories` to make it win.
## Step 2 — Composer auth
- SSH git repos: `ddev auth ssh` (loads your keys into the DDEV agent; covers `gitea@…`/GitHub).
- Private registry: `ddev composer config http-basic.<registry-host> <user> <token>` (writes the
project `auth.json`). Get the token from the user — never invent/guess it.
## Step 3 — Install
`ddev composer update -W` (resolve + install with dependencies). Fix constraint conflicts per Step 1.
## Step 4 — Create `config/system/settings.php`
v14 does **not** auto-write `settings.php`; `ConfigurationManager` reads it if present, else falls
back to the old `typo3conf/LocalConfiguration.php` in place — so a v11→v14 jump with **production DB
creds** in the old file fails to boot first. Create `settings.php` from the old
`LocalConfiguration.php`, but with **DDEV DB creds** (host `db`, name `db`, user `db`, pass `db`, port
3306, `utf8mb4`). Preserve `SYS/encryptionKey`, install-tool password, sitename, systemMaintainers,
mail sender, and per-extension `EXTENSIONS` config. Drop obsolete v11 feature flags.
## Step 5 — Replace `public/.htaccess`
Swap in the v14 template:
`cp vendor/typo3/cms-install/Resources/Private/FolderStructureTemplateFiles/root-htaccess public/.htaccess`.
Symptom of the stale v11 `.htaccess`: Apache **500 "Request exceeded the limit of 10 internal
redirects"** (looks like a config error; it's the htaccess).
## Step 6 — Update the DB schema
`ddev exec vendor/bin/typo3 database:updateschema "*.add,*.change"` — adds the ~20 tables v12v14 need
(e.g. `sys_csp_resolution`). Non-destructive (add/change only). Symptom if skipped:
`Table 'db.sys_csp_resolution' doesn't exist` on any backend request.
## Step 7 — Backend admin
`ddev exec vendor/bin/typo3 backend:createadmin <user> <pass>` (or `backend:resetpassword` if an admin
already exists in the dump). Do NOT create admin users the user didn't ask for on shared/live systems;
local DDEV is fine.
## Step 8 — Upgrade wizards (⚠ snapshot first)
`ddev snapshot --name pre-upgradewizards`. Then `vendor/bin/typo3 upgrade:list` and run wizards:
- **`upgrade:run` gates on a "Database Up-to-Date" prerequisite** → clear data blockers, re-run
`database:updateschema`, then `upgrade:run`. Blockers commonly seen:
- `tx_news_domain_model_news.related_links` NULL/non-int → `UPDATE … SET related_links=0 WHERE
related_links IS NULL OR NOT related_links REGEXP '^[0-9]+$'`.
- `sys_category_record_mm` duplicate PK tuples (table has no `uid`) → add a temp AUTO_INCREMENT PK,
`DELETE` via self-join keeping the lowest, drop the temp column.
- **Confirmable wizards** (e.g. `convertHeadersize`, `rewriteIconPaths`) error *"You have to
acknowledge this wizard"* under `--no-interaction`. There is **no `--confirm` flag** (`--confirm all`
throws). Run interactively with piped input: `printf 'yes\n' | ddev exec vendor/bin/typo3
upgrade:run <id>` (omit `--no-interaction`).
- **One-way wizards** (e.g. container migration, address→contacts) — the snapshot is your only undo.
- **news 14.0.x vs `FlexFormTools::cleanFlexFormXML()`**: older news calls it with 3 args, v14.3 wants
4 → blocks `txNewsPluginUpdater`. Fix: update news to a patched 14.x, or one-time-patch the vendor
`PluginUpdater` (the wizard marks itself done; the patch is then harmless if reverted).
## Step 8b — Language packs
`ddev exec vendor/bin/typo3 language:update`. Composer mode ships **no** language packs → labels fall
back to English (e.g. news "more-link"). Run this whenever the site language isn't English.
## Step 8c — Non-admin editor permissions (⚠ the migration does NOT fix these)
Very confusing symptom: a **non-admin editor** (e.g. a "Redakteur" group) opens the Page module, **sees
the content but there are no edit icons**, and/or the **Media tab is missing** from the content form —
while the Access (Berechtigungen) module shows the group *has* "edit content" and `tables_modify` includes
`tt_content`. Cause: the v11→v14 upgrade wizards do **not** convert the backend group access-lists. Fix
three separate `be_groups` fields, per editor group (find it: `SELECT uid,title FROM be_groups WHERE
deleted=0`). Editors must **log out/in** afterwards.
1. **`explicit_allowdeny` format — THE big one (blocks ALL content editing).** v≤11 stored allowed values
as `tt_content:CType:text:ALLOW`; **v12+ dropped the suffix** and `BackendUserAuthentication::
checkAuthMode()` does an exact `GeneralUtility::inList()` against `tt_content:CType:text` (no suffix).
The migrated `:ALLOW` never matches → every CType (even plain Text/Header) is denied → no edit icons on
anything. Strip the suffix (all groups):
`UPDATE be_groups SET explicit_allowdeny = REPLACE(explicit_allowdeny, ':ALLOW', '') WHERE deleted=0`
(`:DENY` entries may stay — `…:value:DENY` never equals `…:value`, so the value correctly stays
non-allowed.) Verify: `FIND_IN_SET('tt_content:CType:text', explicit_allowdeny) > 0`.
2. **Missing v14 CType names in the allow-list.** The container migration renamed CTypes `wst3bootstrap_*`
→ `t3bs_*`; the allow-list only has the old names. Append the new names actually used — compare
`SELECT DISTINCT CType FROM tt_content` vs the list and add `tt_content:CType:<name>` (NO `:ALLOW`
suffix) for each missing one; typically `t3bs_fluidrow/_column/_tabs/_tab_item/_accordion/
_accordion_item/_megamenu`, `ws_slider`, `heroitem`, `news_*`, `address_*`.
3. **`non_exclude_fields` missing v14 fields → hidden tabs (e.g. Media).** TCA `exclude` fields are hidden
from non-admins unless granted here (format `table:field`, unchanged in v12). The v11 list lacks fields
ADDED in v14, so any tab whose fields are ALL exclude+denied won't render — e.g. the hero **Media** tab
contains only `tx_heroitem_bg`/`_bg_tablet`/`_bg_smartphone` (all denied → no tab). Grant the missing
`tt_content` exclude fields (append `tt_content:<field>` for each). Common culprits: `imagecols_grid,
effects, aos_*, bg_container, tx_wsslider_{renderer,layout,source}, tx_heroitem_*, tx_teaser2_layout`.
NOTE `image`/`assets`/`media` are NOT exclude fields (always shown) — the missing tab is the *settings*
fields, not the FAL field. Other record types editors touch (news, address) can be stale the same way.
**Diagnose precisely — read-only CLI bootstrap** (also the way to *confirm* a fix without a BE login):
```php
SystemEnvironmentBuilder::run(0, SystemEnvironmentBuilder::REQUESTTYPE_CLI | SystemEnvironmentBuilder::REQUESTTYPE_BE);
Bootstrap::init($classLoader);
$be = GeneralUtility::makeInstance(BackendUserAuthentication::class);
$be->user = BackendUtility::getRecord('be_users', <uid>); $be->fetchGroupData(); $GLOBALS['BE_USER'] = $be;
$be->recordEditAccessInternals('tt_content', $row, false, false, true); // false → $be->errorMsg names the failing gate
$be->check('non_exclude_fields', 'tt_content:<field>'); // tab/field visibility
$be->doesUserHaveAccess($pageRow, 16); // page "edit content" perm
```
`recordEditAccessInternals` failing with `authMode "explicitAllow" failed for field "CType"` == facet 1/2.
(Writing `be_groups` *from* such a script may be blocked by agent tooling — apply fixes as plain SQL
`UPDATE`/`REPLACE`/`CONCAT`.) Red herring: an empty `db_mountpoints` on the user is fine if the group (or a
subgroup) supplies a mount — check the user's *effective* `getWebmounts()` before chasing it.
## Step 9 — Wire TypoScript via site sets
v14 t3bootstrap delivers TypoScript (incl. the `PAGE` object) through **site sets**, not `sys_template`.
The site config must declare them under `dependencies:` in `config/sites/<SITE_ID>/config.yaml`, or the
FE dies with **"No page configured for type=0"**. Add the set identifiers (the `name:` in each ext's
`Configuration/Sets/*/config.yaml` — independent of package name/extkey), e.g. the theme set +
container-bs5-templates + news/form/address/blog/slider as used. See the design skill for details.
## Step 10 — Flush + verify
`ddev exec vendor/bin/typo3 cache:flush`. Verify over HTTP with the **correct host**: `/typo3/` → 200,
FE with `curl -H "Host: <SITE_BASE_HOST>" …` (or the real DDEV URL) → 200. **"No site configuration
found" is usually a false alarm from curling `localhost`** instead of `<SITE_BASE_HOST>`.
---
## Other recurring traps
- **Leftover custom content element with no v14 provider → FE fatal** empty-`templateName`
`InvalidTemplateResourceException`. Don't assume it's flux — check the old sitepackage; a plain
custom FSC CType must be re-registered for v14 (see the design skill's iconmenu example).
- **Relocating a git-repo package's vendor path:** change the `name` in *that repo's own*
`composer.json` (composer installs by declared name, not repo URL). Keep the `extension-key`
unchanged so `EXT:<key>/…` refs + set names keep working. Untagged repo → require `dev-<branch>`.
- **v14 FlexForm relational fields:** `FlexFormFieldValues::get()` returns a `LazyRecordCollection` /
`RecordInterface` for relation fields (NOT a CSV of uids), which breaks `intExplode()`/`(int)` casts
in migrated/old ext code → `TypeError`. Normalize to `(string)`/`(string)$record->getUid()` before
parsing. Reproduce/verify from CLI with a small bootstrap script (SystemEnvironmentBuilder::run +
Bootstrap::init + the record API).
- **TYPO3 Console** (`helhum/typo3-console` ^8.3) works with v14 — `vendor/bin/typo3` is the console
binary.
Each step verified + a snapshot before wizards = a reversible, debuggable upgrade.