This extension extends the Academic Persons extension by
connecting person profiles to frontend users. It provides plugins to edit the
assigned profiles from the TYPO3 frontend.
Learn about what has changed and which actions are required to
upgrade.
What does it do?
This extension provides the option to connect person profils from EXT_academic_persons to TYPO3 frontend users.
In addition this extension provides plugins to choose and edit an assigned profile. Therefore a frontend user with
an assigned person profile has to be logged in.
Installation
The extension has to be installed like any other TYPO3 CMS extension. You can
download and install it using one of the following methods.
Requirements
Version 3 requires TYPO3 13.4 or TYPO3 14.3, and PHP 8.2 or newer.
Updating an existing installation from 2.4 is a sequence of schema update,
upgrade wizards, settings migration and template work, and the order of it
matters - two steps destroy data when they are taken too early. It is described
once, for both extensions, in Upgrading from 2.4 to 3.0.0
of academic_persons.
Two dependencies are new in this version:
typo3/cms-rte-ckeditor, a system extension. The profile editing frontend
loads CKEditor 5 from it for its rich text fields: the rich text module
imports six bundles, and Configuration/JavaScriptModules.php maps
nineteen - those six plus the thirteen they import from each other. It has
to be installed and active for them to resolve. The extension does not use
the backend rich text configuration in any way - only the shipped
JavaScript. Composer resolves it; in a classic installation activate it in
the Extension Manager.
typo3/html-sanitizer, a Composer library the TYPO3 core already ships
with - not an extension, so there is nothing to activate. Every rich text
value is sanitized against an allow list on the server before it is stored,
and the extension extends the builder of that package to declare it.
We recommend to pin academic extensions on minor level to mitigate
possible issues in projects in case composer update is used based
on the fact that projects commonly tends to override fluid templates
and changes for otherwise non-breaking changes are possible promoted
to be breaking in case template changes are not adopted why it has
been considered to mark template changes as breaking changes on
minor version updates. That means, we suggest to use for example
following command to ensure that we stay in the minor version range
but have the hightest patchlevel enforced for it and keep possible
bugfix releases for that minor version possible to install:
Search for the extension key academic_persons_edit.
Import the extension from the repository.
Get the current version from TER by downloading the ZIP version.
Alternatively, get the ZIP from the GitHub Releases page.
Switch to the module Admin Tools > Extensions.
Enable Upload Extension.
Select or drag the extension ZIP archive and upload the file.
Configuration
This extension ships its frontend TypoScript and its backend page TSconfig in
two forms: as TYPO3 site sets, and as classic static templates plus
page TSconfig files that are selected on a page. Both forms read the very
same files, so they configure an installation identically.
Pick one of them per site and stay with it — see
Do not combine both for what happens otherwise.
What the sets contain
The extension ships the ProfileEditing component and one aggregate set that
keeps the stable extension-level set name.
Set
Delivers
fgtclb/academic-persons-edit-profile-editing
The assigned-profile list, profile editor, AJAX page type and the
page TSconfig that offers ProfileEditing in the backend.
fgtclb/academic-persons-edit
ProfileEditing under the stable aggregate name. This is the normal
set to use.
Both depend on fgtclb/academic-base-ctype-group, the set of
EXT:academic_base that labels the content element group all academic
extensions sort their elements into.
The plugin renders partials of EXT:academic_persons, but it reads
them from Resources/, not from that extension's TypoScript. There is
therefore no set dependency on any fgtclb/academic-persons-… set, and none is
needed.
The content element is hidden by default
EXT:academic_persons_edit hides the editing content type for the
whole installation and brings ProfileEditing back per component. Whichever of
the two mechanisms below you use, ProfileEditing is the only profile-editing
content element offered in the backend.
Include the site set
Add the set to the config.yaml of the site that should offer the content
element:
The View action in the assigned-profile list uses the same public
detail page as the Academic Persons list plugins. Configure
plugin.tx_academicpersons.detailPid in the Academic Persons site settings
or TypoScript constants. ProfileEditing copies that value into its own Extbase
settings and targets the academicpersons_detail content element; it does
not require a second page setting in Academic Persons Edit.
Include static templates
For an installation that still configures its frontend through
sys_template
records, the same files are registered as static templates
and as selectable page TSconfig files.
Tip
On TYPO3 v13 and v14 we recommend the site set — and if you use it, do not
press the backend button Create a root TypoScript record on that
site. The
sys_template
record it creates carries the flag
Clear for constants and setup, and that flag discards everything
the site sets contributed. An installation that is already in that state
gets its configuration back by selecting the static templates below in that
very record.
Include static TypoScript
Edit the
sys_template
record of the site root and add the entry to
Include static (from extensions):
Makes ProfileEditing selectable and configures its wizard entry.
Academic Persons Edit: All components (academic_persons_edit)
Every component this extension ships, in one entry.
The setting is inherited by every page below the one it is set on.
Do not combine both
A site that uses the site set and the static template reads the shipped
files twice. The site set is applied before the
sys_template
record, so
the second read happens after the site settings and after
config/sites/<site>/constants.typoscript — and it resets every constant
the extension ships a default for back to that default.
Nothing else is damaged: the Constants and Setup fields
of the
sys_template
record, the page TSconfig of a page and the page
TSconfig files selected on a page are all applied afterwards and still win. Use
one mechanism per site and the question does not arise.
Extension configuration
There are some options for global extension configuration:
profile.allowedLanguages
profile.allowedLanguages
type
string
Default
A comma-separated list of language IDs. These IDs configure in which languages a
persons profile can be translated by a frontend user.
The synchronisation into these languages runs after a profile is auto-created
— on frontend user login or through the
academic:createprofiles
command of EXT:academic_persons — and after every change
persisted through ProfileEditing. Left empty, frontend edits do not touch
translated profile records at all.
Which fields can be edited
Which profile fields belong to each visual section, how they are rendered,
which are mandatory and which are locked is configured by
EXT:academic_persons in
Configuration/AcademicPersons/Settings.yaml. The
single
profile
map contains both the public layout and the editable
field definitions. Structured records use the
documentSections
map
from the same file.
Consequences worth knowing before reporting a problem:
A field configured
disabled
or
readonly
is rendered locked.
The ProfileEditing JSON endpoint rejects attempts to submit it.
First name, Middle name and Last name are
locked by default, because profile names are usually owned by the
connected frontend user record and synchronised from elsewhere. They are
therefore not editable in the frontend form - and not in the TYPO3 record
editor either: the same validation set is merged into the TCA of the
profile table through
TcaValidationMerger
, where
disabled
becomes
readOnly
. Unlocking a field for the frontend form unlocks it
in the backend as well.
Document validators are selected by the section's stored record type;
validators from sibling sections are never merged as a fallback.
The normalized rules are applied to the frontend controls, server-side
Extbase validation and the corresponding backend TCA field state.
See Profile editor and validation for the schema, supported validator
flags, document aliases, shipped defaults and override rules. The same
profile
map also controls the public detail layout.
Image processing: WebP
The ProfileEditing image editor offers the profile image as WebP through the
<picture>
candidates, with the
<img>
fallback in the source
format. TYPO3 has to be allowed to produce WebP, otherwise rendering a profile
that has an image fails with:
Unable to render image uri in "tt_content:1": The extension webp is not
specified in $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] as a valid
image file extension and can not be processed.
Copied!
On TYPO3 v13 and v14 webp is part of the default value of
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
, so nothing has to be
done. An installation that removes it from that list - some restrict the
allowed formats deliberately - has to put it back, either in
Admin Tools > Settings > Configure Installation-Wide Options >
[GFX][imagefile_ext] or in config/system/settings.php.
Permitting the format is not the same as being able to produce it: the
configured image processor, GraphicsMagick or ImageMagick, has to be built with
WebP support.
Profile editor and validation
academic-persons/Configuration/AcademicPersons/Settings.yaml is the
canonical settings source for academic_persons and
academic_persons_edit. It contains one ordered profile schema and
the related inline maps:
profile
The public detail layout together with direct Profile fields grouped into
visual sections. The structure and details keys configure public
rendering; the remaining entries define editable fields and their shared
validation metadata.
special
Composed or dedicated components such as the title, image and sync switch.
contracts
The reusable Contract document type. Its ordered fields map drives the
Contract editor; contactSections contains the physical-address, email
and phone editors and their respective ordered fields.
documentSections
Ordered structured collections. The Contract entry only references
type: contracts; ordinary profile-information entries remain inline.
All consumers receive the same normalized settings graph. This keeps public
rendering, profile editing, frontend validation and the corresponding backend
TCA state aligned. Public layout stays below
profile
; the other maps
remain technical sections in the same file.
Loading and overrides
The central factory reads the same relative path from every active package and
merges maps at the top level. A site package can therefore override the shared
configuration by providing
Configuration/AcademicPersons/Settings.yaml. Replacing one of the four
maps replaces that complete map; repeat every entry which must remain.
Flush TYPO3 caches after a change so the unified typed settings graph and its
cache entry are rebuilt.
Adds NotEmptyValidator plus the frontend HTML, marker and JSON
metadata.
readonly
Prevents editing in the frontend editor.
disabled
Disables editing and implies readonly. A locked field can never
remain required.
email
Adds EmailAddressValidator and email input metadata.
url
Adds UrlValidator and URL input metadata.
number, tel and date
Select the matching frontend input type. date is the exception
to that: it selects the date field type, which the profile editor
renders as a plain text control showing d.m.Y. A date picker is
deliberately not shipped yet, and the native
<input type="date"> is not used - see
the contract dates.
textarea and html
Select text-area input; html also activates sanitized rich text.
Rules remain attached to the configured profile, contact or document section.
They are not collected from a sibling section. Only submitted fields are
validated during a partial AJAX update.
Profile CKEditor character limit
A profile field configured as CKEditor may declare its character limit directly
on the field:
A positive integer characterLimit adds a live current / limit counter
and prevents the editor from accepting additional visible characters beyond
the limit. The partial AJAX update is checked independently by the same
server-side Extbase validation. HTML tags do not count. The property is ignored
when renderType is not ckeditor or its value is invalid or non-positive.
The key is case-sensitive; characterlimit is not normalized.
This deliberately differs from document descriptions, which keep their
existing nested editor.limit schema. Both forms normalize to the same typed
validation metadata. Character limits do not alter backend TCA or the database
schema.
Document validators and aliases
Document aliases map presentation names to DTO and database properties:
Consequently year receives a required attribute and marker while from
and to remain optional. Removing or adding required in a site override
changes the JSON metadata, the rendered controls, the Extbase validation and the corresponding
backend TCA field state together; no field name is hard-coded as mandatory.
The richer description form remains supported:
description:editor:limit:500type:ckeditor
Copied!
editor.type: ckeditor is normalized to the html validation/input
metadata so the editor and the server side sanitizer agree. A positive integer
editor.limit additionally defines the maximum number of visible
characters. HTML tags do not consume that allowance; entities, non-breaking
spaces and repeated whitespace are normalized before counting. The editor
returns the limit in its JSON field metadata, displays a live current / limit
counter and prevents CKEditor from accepting further characters beyond it.
Both the document JSON actions and the Extbase form-data validator enforce the
same limit on the server.
editor.limit is frontend validation metadata, not TCA configuration. It is
ignored for non-CKEditor controls, and removing it or setting it to a non-
positive value disables both the counter and the additional validation without
changing backend FormEngine.
Contract form and contact sections
The order of
contracts.fields
is the form order returned by the JSON
endpoint. Every entry declares its frontend field and render type, validation,
option source and helptext together. The shipped organisationalUnits,
functionTypes and locations option sources resolve their records
through the corresponding repositories. Removing or reordering an entry
therefore removes or reorders that control without changing the template.
Physical addresses, email addresses and phone numbers are ordered structural
children below
contracts.contactSections
. Each child owns an ordered
fields map and an isolated validation set:
The shipped physical-address country control obtains its localized labels and
ISO alpha-2 values from TYPO3's CountryProvider. Keeping fieldType as
input preserves the domain extension's existing backend TCA while
renderType: select selects the frontend control and its option validation.
Every Contract and contact field helptext is rendered as a Bootstrap popover
in add/edit mode.
The shipped autocomplete tokens are street-address, postal-code,
address-level2, country, email and tel. They describe the
purpose of the corresponding control without changing validation or storage.
Backend TCA integration
The domain extension still owns the base TCA and the column configuration. It applies the normalized validation metadata from the unified
settings graph to the relevant Profile, contact and Profile-information TCA
fields. Consequently required, readonly and field-type metadata remain
consistent between frontend validation, profile editing and FormEngine in
TYPO3 13 and TYPO3 14. Character limits remain frontend/server-side metadata
and do not change the database schema.
Year row
year, yearStart and yearEnd each receive col-12 col-md-3. They
therefore stack on small screens and share one row from the medium breakpoint.
Each is an <input type="number"> carrying the bounds of its TCA range
as min, max and step.
Profile editing
The Profile editing content element first renders all
profiles assigned to the authenticated frontend user. Its Edit
action opens the selected profile in Profile editing; View opens
the public academic_persons Detail plugin on the page configured through
plugin.tx_academicpersons.detailPid. This is the same target setting used
by the Academic Persons list views. The shipped
Resources/Private/Templates/Profile/Index.html editor template
contains four independently persisted areas:
profile fields using the generic JSON update endpoint,
the synchronization checkbox using its own JSON endpoint,
the expanding profile-image editor using dedicated upload and delete
endpoints, and
the structured document sections — contracts, their contacts and the
profile-information collections — each with their own form, create, update,
delete and sort endpoints.
Fluid renders the markup of that view, and five custom elements own its
behavior. The entry point is maintained as TypeScript in
Resources/Private/TypeScript/frontend/profile.ts, from which the
frontend build generates
Resources/Public/JavaScript/frontend/profile.js; it defines those
elements and does nothing else. Every editor on the page is an
<academic-persons-edit-profile-editing> element that starts itself when the
browser upgrades it, so a second plugin on the page — or one that is loaded
into the page later — needs no start-up scan and cannot be started twice.
Typed feature modules below
Resources/Private/TypeScript/frontend/profile/ separately own common
requests and status output, field editing, documents, rich text,
synchronization, image editing and sticky positioning; the five elements below
…/profile/elements/ drive them. All of them are plain custom elements
that depend on no user interface library, and none of them renders markup: each
controls what Fluid rendered. No framework is bundled with the extension, and
none is loaded from anywhere else either.
All changes are saved through AJAX without reloading the page. Editable fields
are discovered across the complete component root, even when the responsive
page layout places them in separate data-pe-fields-form elements. Every
element renders into the light DOM — none of them has a shadow root — so a
project's stylesheet reaches every control they render, exactly as it reaches
the Fluid-rendered ones. The inline collapse targets, the two status regions
and the client-side templates live in the same component scope.
Assigned profile overview
ProfileController::listAction() is the default action. It uses the
authenticated frontend-user relation and passes {profileListItems} to
Resources/Private/Templates/Profile/List.html. Every item contains
the Profile object and the title of its sys_language_uid from the current
site configuration. The profile column contains a square thumbnail or the
shipped placeholder followed by the complete name. Edit passes the
explicit profileUid to indexAction(); the action resolves that UID again
through the authenticated user's assigned profiles before rendering anything.
An unassigned or manipulated UID receives the 403 access denied response of
the site, the same one an unauthenticated visitor gets — raised as a propagated
response rather than returned by the action, because on TYPO3 v13 the status
code of an Extbase plugin response never reaches the frontend response.
The View URI uses extension academicpersons, controller
Profile, action detail and plugin Detail. Its target page comes
from plugin.tx_academicpersons.detailPid and must contain the
academicpersons_detail content element. The ProfileEditing TypoScript copies
the Academic Persons constant into plugin.tx_academicpersonsedit.settings;
there is no separate editor-specific detail PID. Importing
EXT:academic_persons/Configuration/Routes/Detail.yaml is optional and
turns the query-string link into a speaking URI.
View data
The controller assigns the following variables to the Fluid template:
Variable
Description
{profile}
Explicitly selected profile after its assignment to the authenticated
frontend user has been verified.
{profileListItems}
Assigned Profiles and their readable site-language labels. This
variable belongs to the default list action and list template.
{profileSections}
Ordered Fluid view models generated from
profile
. Every
section contains regular fields and inserted composite special items.
{specialFields}
Typed
special
components, including composed title, image and
synchronization metadata.
{profileFieldOptions}
Options for every configured
renderType: select
field. The
option source remains the matching Profile TCA field.
{documentSections}
Ordered structured-section view models derived from
documentSections
, including mapping, read-only and validation
metadata plus the typed records.
{imageAllowedMimeTypes}
Comma-separated MIME types accepted by the image input. The server
validates the configured values independently.
{editorLanguage}
The language code of the current site language, lower-cased and
without its region. CKEditor is initialized with it; an empty string
when the request carries no site language.
{data} and {record}
Current content element data and its record object.
View structure
The template is intentionally a composition root. The main partial groups are:
Partial group
Responsibility
Image/Card.html and Image/Editor.html
Profile image heading above the sticky page preview,
animated full-width editor, file selection, crop preview and image
actions.
Profile/Personal.html, Profile/About.html and
Profile/Fields.html
Personal-data and about-section boundaries plus ordered iteration of
settings-driven profile fields.
Field/Editable.html, Field/Control.html,
Field/Select.html, Field/Checkbox.html and
Field/Group.html
renderType-driven controls, previews, grouped values and
persistence actions.
Documents/Sections.html, Documents/Contract.html,
Documents/ProfileInformation.html and their *Row.html
Structured document sections, their rows, the row actions and the
hidden row prototype that is cloned after a create.
Complete profile-name heading with synchronization/edit-all controls,
and scoped status output. The status output is two live regions, one
assertive and one polite, because a region's politeness cannot be
changed reliably once it is in the accessibility tree. The personal
form renders its own Personal data heading.
ButtonTemplates.html remains a compatibility fallback for
existing template overrides; the shipped read view does not use its
button-shaped value controls.
Layout and responsive behavior
The view uses Bootstrap 5 grid, spacing, typography, background, positioning
and form utilities; the Fluid templates contain no inline style declarations.
Resources/Public/Css/frontend/profile-editing.css is generated from
Resources/Private/Scss/frontend/profile-editing.scss by the repository
build and holds only what Bootstrap cannot express: the display: block the
custom elements need, a [hidden] rule that outranks Bootstrap's display
utilities, three corrections to a surrounding theme (a .section overflow,
one frame spacing variable, the stacking of the sticky card), the focus ring of
the controls and buttons, the drag states of a sortable list and the enter/leave
classes of the two editor transitions. All Bootstrap button controls of the
shipped editor carry rounded-0 so their corners remain square.
The focus ring is the one appearance the view takes away from the surrounding
theme rather than correcting. Bootstrap draws it with box-shadow, several
themes layer further opaque rings behind that one, and a translucent accent ring
over an opaque black one reads as a hard dark rectangle around the focused
control. A shadow is also the wrong mechanism twice over: it is painted outside
the border box, so the overflow: hidden of the two collapse panels cut the
ring off the fields and buttons inside them, and forced-colours mode drops
shadows altogether while the rules that draw them also set outline: 0, which
leaves the appearance in that mode to the browser rather than to the site.
The view therefore draws its own ring as a real outline on
:focus-visible — the pseudo-class the theme's own rules use, so the rule
sits exactly where the defect is — sunk inside the border box with a negative outline-offset so that no
ancestor can clip it, coloured with currentcolor so that it contrasts with
whatever the control it sits on is filled with, and it rewrites box-shadow
in the same rule so no layered ring and no inset shadow survives underneath. It
applies to every input, select, textarea and button the view
renders — the five controls of Partials/Profile/Field/Control.html, the
image upload of Partials/Profile/Image/Editor.html, the synchronisation
switch of Partials/Profile/Header.html and the buttons of every action
group — because two focus appearances in one form would be worse than the one
being replaced.
:focus-visible is not a synonym for keyboard focus. Measured in Chrome, a
pointer click on a checkbox or on the synchronisation switch does not match it
and those two keep Bootstrap's soft glow, while a pointer click on a text
input or a select does match it and those take the inset ring. Most of what
the view renders is in the second group, so the change is visible to a visitor
using a mouse and not only to one using the keyboard. The colour is
currentcolor, which is the colour a control draws its own text in — but
not the colour of a checkbox tick or a switch knob, which Bootstrap paints as
a background image with a hardcoded #ffffff. On those two the ring takes
the inherited body colour, #212121 on the #577760 of a checked control
with the shipped theme, and measures 3.23:1: enough for WCAG 2.1 SC 1.4.11 and
no more, so a site with a darker $primary has to check that pair. Plain links are not covered, no theme rule takes their focus
ring away. CKEditor 5 is covered by halves: its editable region is a div,
so it is outside the rule and keeps the library's own focus styling, while the
buttons of its toolbar are button elements below the plugin root and take
this ring rather than the library's. That is the same answer as everywhere
else - one focus appearance for everything the visitor can focus.
Above the grid, the complete profile name and the synchronization/edit-all
controls share one responsive header row. The controls wrap below the name on
narrow viewports. On lg and larger viewports the first content row uses a
4 / 8 column split.
The profile image block has sticky-top so the image and its edit action
stay visible while the profile data scrolls. Below lg both columns stack in
document order. The about section follows the complete first row and therefore
never overlaps the sticky column.
At runtime initializeStickyImageOffset() reads the visible outer height of
#page-header.navbar-fixed-top through getBoundingClientRect().height,
adds a 10-pixel visual gap and assigns the result to the top property of
data-pe-sticky-image. The measurement itself is
observePageHeaderOffset() of academic_persons, which the public detail
view uses for its own sticky navigation — one implementation, two callers. A
ResizeObserver watches the header's border-box and keeps the offset
synchronized whenever the navbar changes height, including height or padding
changes caused by a scroll-dependent header state. Environments without it use
the window resize event as a fallback. If the matching fixed page header
is absent, Bootstrap's regular sticky-top value remains in control.
The two columns live in their own align-items-stretch row. The image column
inherits the stretched cross-axis size, giving the sticky image a containing
block as tall as the adjacent profile data. The full-width about section keeps
its own col-12 in a separate sibling row below it.
The complete profile name is the page's h1 above both columns. Both Fluid and
JavaScript use the ordered fields list from
special.title
. Fluid
renders the initial name; data-pe-profile-name-field-ids lets JavaScript
recompose the same name after a successful update without reloading the page.
Profile values are rendered as readable text rows with alternating
bg-body-tertiary surfaces. The only read-mode action is a borderless pencil
button with an accessible label. Name components and the URL/title pair of
each link share one preview row and open as one editing group. Only one field
or group is ever open: a second pencil, and Edit all, discard the
one that is open before they open theirs
(Two editing modes).
The special name editor retains the established responsive grid (academic
title / first name at 4 / 8 and middle / last name at 6 / 6) without
putting layout metadata into YAML.
Settings-driven controls
ProfileSectionProvider converts the typed
profile
and
special
settings into an ordered Fluid view model. Section placement
itself remains explicit in Templates/Profile/Index.html: the
template decides where profileSections.information and
profileSections.aboutme appear. It does not enumerate their individual
fields.
Profile/Fields.html chooses the field control solely from renderType.
Option values are not duplicated in YAML: for a select field,
ProfileFieldOptionsService reads the corresponding Profile TCA items.
Preview behavior likewise follows the rendered control type.
Removing a special component removes its controls; marking the image or
synchronization special readonly or disabled also blocks the matching
write endpoint, not just its Fluid control.
A translated profile initially uses TYPO3's parent localization state for
the image and follows changes to the default-language image. The first upload
or deletion in that frontend language changes the image field to custom;
the translated profile then owns its FAL reference and can select, replace or
delete its image independently. The image controls therefore remain available
while editing a translated profile; the endpoint still verifies that the
requested profile is assigned to the authenticated frontend user.
The shared
Resources/Private/Partials/Profile/Field/Editable.html partial
composes three focused, reusable partials:
Field/Preview.html renders the text preview and pencil trigger —
the trigger that opens this field and closes whichever other one is open,
Field/Control.html renders either f:form.textfield or
f:form.textarea, including the CKEditor hook, and
Field/Actions.html renders delete, cancel and save.
Field/Group.html composes related textfields below one preview. Its
data-pe-field-ids value defines which fields open, cancel and save together;
data-pe-display-field-ids and data-pe-display-mode control whether the
preview joins values (the name) or uses the first non-empty value (link title
falling back to its URL).
Helptext buttons are edit controls: field and group previews do not render
them. They appear after the corresponding field editor is opened. Document
helptexts follow the same rule and are present in add/edit forms, but not in a
document editor opened in view mode.
validation.inputType supplies the concrete HTML input type. Checkbox
controls save immediately on change. Select controls use the same clear, undo
and save actions as text fields. The synchronization switch in
Header.html is persisted through its own endpoint.
Requirement
Implementation
Telephone number
Textfield with inputType: 'tel' or a validation input type of
tel.
Website address
Textfield with inputType: 'url' or a validation input type of
url.
Free text input
Default textfield.
Select
Field/Select.html; options come from the configured
field's TCA items and changes save immediately.
Checkbox
Field/Checkbox.html for direct Profile flags. The
synchronization special uses its own form and endpoint.
Multiline text
Field partial with textarea: true. Passing richText: true
additionally turns the textarea into the TYPO3 CKEditor 5 when the
field is opened.
Two editing modes
The profile fields have two mutually exclusive editing modes, and the controls
say which one is active.
Single field. The pencil beside a value opens that field — or, for a group,
that group — and the three buttons of Field/Actions.html act on it:
clear empties the control and keeps it open, undo restores the saved value and
closes it, save posts what changed. This is the mode described everywhere else
on this page, and the editors of the document and contract panels behave the
same way.
Only one editor is open at a time — one field or group, the whole form, one
document row or one contact of a contract. Opening another one, and entering
full form editing, closes the one that is open first. An editor that still
holds the values it was opened with closes silently, exactly as its own
Undo or Cancel would close it. An editor with changes
does not: the view asks, in a dialog of its own rather than a browser prompt,
whether to Save and continue, to Discard changes or to
Keep editing. Saving stores exactly what the open editor's own
save would store and then opens the other editor; a save the server refuses
keeps the visitor in the refused editor with its messages, and opens nothing.
Discarding puts the editor back to the stored values and announces it in the
polite live region: Unsaved changes were discarded. Keeping — and
Escape in the dialog — opens nothing at all. Pressing the pencil of the
field or group that is already open changes nothing and keeps what has been
typed.
The dialog is the unsaved-changes template of
Partials/Profile/UnsavedChanges.html, a <dialog> element the view
clones and shows modally; its labels are the extension's own and are
overridable with the partial. A site that drops the template leaves the visitor
in the editor that is open: nothing is thrown away because a template is
missing.
Full form.Edit all opens every editable field of the profile
at once. While it is open, every per-field and per-group button group is hidden
— including the undo beside an autosaving checkbox — and one bar governs the
whole form. Field/FormActions.html renders it at the end of every
data-pe-fields-form, and the shipped template has two of those (personal
data and about me); each bar acts on all of them.
Control
Behavior
Apply
Posts every changed field in one request to the generic field update
endpoint below. On success the stored values are written back into
the controls, the previews and the name heading, the form closes and
the focus returns to Edit all.
Undo
Restores every field to the value that is stored and keeps the form
open. Announced in the polite live region.
Discard
Restores every field and closes the form. Pressing
Edit all again does the same.
updateAction() validates the complete field map before it persists anything,
so an apply either stores all of it or none of it. A refusal reverts nothing:
the entered values stay where they were entered, each refused field is marked
and described by its own message, the caret goes to the first of them, and the
refusal is announced once rather than once per field.
While an apply is on its way to the server, Undo,
Discard, Edit all and Escape do nothing. The
request cannot be taken back, and reverting under it would leave the stored
profile and the editor's own baseline disagreeing without anything on screen
saying so.
The same holds for a save beside a single field: because opening an editor
now discards one, a pencil, Edit all and the
Delete content and Undo beside a field or group do nothing at all while such a save is
on its way — not even open the editor that was asked for. All of them work
again as soon as the answer has been written back, and while they do not, they
say so in the polite live region: Please wait until the change has been
saved.
A request that the server accepts and never answers therefore leaves the
editors of that profile refusing until the page is reloaded. The frontend
deliberately sets no timeout on its requests, so nothing here decides on the
visitor's behalf that a slow save has failed.
A pencil pressed while Edit all is open does nothing at all, and
says nothing: every field is already open, and the form's own bar is the way
out of the mode.
A checkbox that saves on change does not save while the form is open — it is
applied with everything else. Without that it would reach the database while the
visitor is still deciding and Discard could not take it back. The
synchronization switch of Header.html is outside the field forms and
keeps saving immediately.
Keyboard and assistive technology
Opening the form puts the caret in the first editable field.
The bar stands after the fields in document order, so tabbing on from the
last field reaches Apply, Undo,
Discard.
Escape discards the form, unless the caret is inside a CKEditor
instance, where the key closes the editor's own balloon first.
Ctrl + Enter applies it, and so does Enter in a text
field: the form is submitted and the submission is turned into an apply
rather than into a page load.
Both keys are handled by the field form itself, so a document, contract or
image editor that is open at the same time keeps its own handling of them.
The bar is a role="group" with its own accessible name.
Edit all carries aria-pressed and names the forms it
controls in aria-controls. Results are announced in the two live
regions the editor already has. The restored notice and a success are
polite; a failure and a refusal of the form are assertive, because the caret
has just been moved to the first refused field and a polite message queued
behind that is routinely dropped. A refusal beside a single field stays
polite — it stands next to the control the visitor is already in.
What an override has to keep
Profile/Fields.html renders Profile/Field/FormActions at its
end; an override of it that drops the line leaves the profile with no way to
apply the form. An override of the bar itself keeps the
data-pe-form-actions element, its data-pe-form-reverted-message and the
three buttons marked data-pe-form-apply, data-pe-form-undo and
data-pe-form-discard. Everything else about it — the tags, the classes, the
labels, the order — is Fluid, and none of it is spelled in JavaScript. The
optional sectionLabel argument names the button group after the section it
stands in, so a page with two field forms does not offer two groups of the same
name.
Generic field update
The URL is generated by f:uri.action for updateAction() with page type
1733735
(see The page type of the JSON endpoints). Requests must use
POST, Content-Type: application/json and the request header
X-Requested-With: XMLHttpRequest (see The request header the endpoints require).
Only values changed since the last successful save are sent. An empty string
clears a property when its section-local validation permits an empty value;
omitted properties remain unchanged.
Unknown profile properties, configured read-only/disabled fields and select
values not configured in the matching TCA field are rejected. The direct academic/honorific
profile.title field is an ordinary configured Profile property; the
composed display name is
special.title
. skipSync is a direct special
property. A configured combinedLink additionally enables its matching
*Title companion. All other writable Profile properties come from
profile
.
Extbase validation errors are returned in an errors object keyed by
property path and are mapped back to the corresponding form controls by the
frontend module.
ProfileFieldOptionsService supplies both the presentation options and the
strict allow-list validation for every configured select. Thus another select
does not require a new Fluid partial, validator service or JavaScript branch.
Rich-text content fields
The shipped configuration marks five profile properties with
renderType: ckeditor
. Four are displayed with the information
section, while miscellaneous belongs to
aboutme
:
coreCompetences,
teachingArea,
supervisedDoctoralThesis,
supervisedThesis, and
miscellaneous.
The editor is TYPO3's shipped CKEditor 5 from the
typo3/cms-rte-ckeditor package. The profile/rich-text.js module imports
its CKEditor modules through TYPO3's JavaScript import map; it does not load an
editor from a CDN. An editor instance is created lazily when a rich-text field is opened.
If initialization fails, the original textarea remains available and the
component reports an error.
The toolbar intentionally exposes only undo, redo, bold, italic, bulleted
lists, numbered lists and links. Editor changes are mirrored into the
underlying textarea, so required-field validation, changed-value detection and
the existing JSON request remain the single persistence path. CKEditor's
initial HTML normalization becomes the local comparison baseline; merely
opening an editor therefore does not submit or rewrite legacy content.
Profile fields may configure a positive characterLimit next to
renderType: ckeditor. Fluid then adds the limit metadata and an accessible
live counter to that field. The shared rich-text module counts normalized
visible text, keeps the last accepted value when an addition would exceed the
limit and still allows older over-limit content to be shortened. The Extbase
validator checks the submitted sanitized value again before the partial update
is persisted. The shipped miscellaneous field uses a limit of 1000.
Outside edit mode, each rich-text field renders its formatted content directly
and provides a borderless pencil action on the right. Empty values show a
localized placeholder. The preview is initially rendered through TYPO3's
HTML formatting pipeline and is replaced after a successful save with the
sanitized markup returned by the server. The frontend applies the same strict
tag, attribute and URI-scheme allowlist without assigning markup through
innerHTML.
Each open text or rich-text field has three explicit actions. For
renderType: ckeditor only, the action group sits in the label row with
Bootstrap's ms-auto utility, leaving the editor itself full width. Other
field types retain the action group beside their control. Selects and checkboxes
save immediately when their value changes.
Delete (data-pe-dismiss) clears the current browser-side
draft. The editor stays open and no request is sent.
Cancel (data-pe-cancel) restores the last successfully
persisted value and closes only that field. No request is sent.
Save (data-pe-save) sends that field through the JSON AJAX
endpoint. It closes the field only after a successful response or when
there is no changed value to persist.
The action group uses Bootstrap utility classes to remain content-sized and
align itself to the end of the editor row instead of stretching to the
CKEditor height. No additional stylesheet or inline style is required. The
Edit all toggle beside the page heading opens both regular
fields and grouped rows, receives Bootstrap's active state and changes its
label to Close all. Activating it again collapses every editor
without saving or discarding browser-side drafts. There is no global footer
action area; save and undo remain explicit per-field actions.
The pencil is rendered through TYPO3's core:icon ViewHelper. Template
overrides may replace the icon but must retain the button's edit hook,
data-pe-for target and accessible label. The profile value itself must not
be placed back inside the button.
Server-side sanitization
Client-side editor configuration is not treated as a security boundary.
ProfileUpdateValidationService derives writable properties from the
configured profile sections. It passes every configured
renderType: ckeditor
property through ProfileRichTextSanitizer
before validation and persistence. The sanitizer uses TYPO3's allow-list based
HTML sanitizer and permits only:
the tags p, br, strong, em, ul, ol, li and
a;
the href attribute on a; and
local links and the URI schemes http, https, mailto and
tel.
Scripts, event-handler attributes, style attributes, images, unknown tags and
unsafe URI schemes are removed. The successful JSON response contains the
normalized, sanitized values. The frontend replaces its local editor and
preview state with exactly those returned values rather than trusting the
submitted markup.
Each AJAX request is validated as a partial profile submission. The validator
selects only submitted or explicitly overridden DTO properties from their
configured profile sections. A required rule on an omitted sibling field
therefore does not block a field update or the dedicated skipSync request.
Submitted inline overrides are validated after normalization and sanitization.
The extension requires TYPO3 13.4 or TYPO3 14.3. These constraints
include the HTML-sanitizer fixes published with TYPO3-CORE-SA-2026-006. Projects
must still keep TYPO3 security updates current.
Editable structured profile sections
The profile editing view renders the structured records directly below the
About me field. ProfileDocumentSectionProvider supplies one
ordered view model instead of duplicating relation mapping in Fluid. It reads
all sections, including contracts, from the shared settings graph. That
graph is built from the active packages'
EXT:academic_persons/Configuration/AcademicPersons/Settings.yaml files,
so configured order
is also presentation order. The same graph supplies the public profile,
frontend validation and backend TCA metadata.
For every document section the provider reuses the configured identifier,
fieldName, record type, LLL label, readonly state,
rowFields, actions and the section-local validations. The heading is
translated directly from that label.
A newly configured type consequently does not require another section registry
in academic_persons_edit. The generic localized empty state is used until
an identifier-specific message is added.
contracts contains FGTCLB\AcademicPersons\Domain\Model\Contract
objects. Every other collection contains
FGTCLB\AcademicPersons\Domain\Model\ProfileInformation objects. Contract
rows show the configured values, which are start date and position in the
shipped settings. Profile-information rows render only the configured values in
their declared order. The aliases from, to and description map to
yearStart, yearEnd and bodytext. All sections remain visible when
empty and display a localized empty state.
The three year properties year, yearStart and yearEnd are four
digit integers stored in nullable
int
columns. Their add/edit control is
an <input type="number"> carrying min="0", max="9999" and
step="1" - the bounds of the TCA range of the same columns, which the
endpoint enforces again on every submission, so a client that ignores them is
refused rather than clamped. Nothing about a year is formatted and nothing
follows a locale.
The three controls each use col-12 col-md-3 and share one responsive row on
medium and larger viewports. Their HTML and server-side required states come
from the same validation set: only a field with the additional required
flag must be filled. In the shipped settings this applies to year but not
to from or to.
The two Contract dates validFrom and validTo are the only date fields
of the editor, and their control is a plain <input type="text"> showing
d.m.Y behind the hint dd.mm.yyyy - the control the previous editor
had. It is not an <input type="date">: the native control follows the
locale of the browser rather than the one of the site, it cannot be styled
with the rest of the editor, and its calendar is not the date picker this
editor is meant to get. A date picker is deliberately not shipped yet. It
is a feature of its own, it will not be the browser's, and until it exists the
date is typed.
The endpoint accepts two formats for such a field. d.m.Y is what the
control shows and submits. Y-m-d is what the endpoint answered and
accepted while the control was a native one and stays accepted, so a client
written against that shape keeps working. Both are read strictly - the parsed
date is formatted back and has to match what came in, so 32.01.2026 is
refused rather than read as the first of February - and anything that is
neither format is refused with The value must be a valid date.
The read view is unaffected: a stored date is rendered as the MEDIUMDATE
of the site language, the way the public profile renders it.
How the document editor is rendered
The editor of a document row is rendered in the browser, and cannot be
rendered anywhere else: its fields, their labels, their select options and
their display values all come from the documentForm response, which the
permission-checked endpoint decides per section, per record and per mode.
Partials/Profile/Documents/Editor.html therefore renders the shape of
that editor, as a <template data-pe-proto="document-panel"> block, and the
custom element <academic-persons-edit-document-editor> clones it and fills
its slots. The element is created by
TypeScript/frontend/profile/documents.tsinside the collapse target
below the selected section heading for add actions, and inside the selected
record row for view, edit and delete actions. The element is handed the
response as properties and is removed again when its close transition reports
back. This avoids pre-rendering one hidden form per section and record while
keeping the static structure in Fluid and mapping validation errors directly
back to the returned field names.
Exactly one document collapse is open at a time, while the complete profile
view remains visible — and it counts as the open editor of the profile, so a
pencil of another row, of another section, of a profile field or
Edit all closes it first and asks about unsaved changes exactly
as described for the profile fields above. Activating the same add or view
trigger a second time closes its collapse with the same cleanup as
Cancel. Saving an edit keeps the collapse open with what it
stored, so the next change starts from the stored record; creating and
deleting close it. The contact editor inside a contract behaves the same way:
switching to another contact of the contract asks about a changed one, and a
saved edit stays open. The element is
created where it is shown and is never moved: moving it would disconnect it,
and a disconnect destroys the CKEditor instances below it. The collapse target
keeps the unique ID the controller assigns it when it is first opened, now
for aria-controls alone.
A hidden ordinary DOM container supplies the Fluid-rendered row prototype used
after creation (data-pe-document-item-template). It is a plain container
rather than an HTML template element, and the successful create response is
rendered by cloning the row inside it.
The icons of a browser-rendered editor cannot be resolved in the browser:
core:icon asks the icon registry, which knows the set this extension
registers and whatever a site overrode. Under the prototype design that needs no
mechanism of its own: an icon is rendered by Fluid inside the prototype that
draws it — the help button of a field, the five row controls of a contact and
the add control of a section — so it is part of the markup an override reaches
and no module ever looks one up.
contract is retained as a separate document kind. It uses the same editor
for its configured fields and appends three contract-specific contact sections
in the read view: physical addresses, email addresses and phone numbers. Their
field schemas and validation flags come from
contracts.contactSections.<section>.fields in the shared settings graph.
The Contract form itself follows the order and metadata in
contracts.fields.
Those sections, their rows and the editor of one contact are rendered by a
second custom element, <academic-persons-edit-contract-contacts>, which the
document editor creates in its own template. The contact editor stands below
the contact-section heading for add actions and directly inside the selected
contact row for view, edit and delete actions — the two placements
Documents/ContractContactEditor.html renders one prototype for.
Every contact row of a contract also carries a visibility toggle, the first
control of its action group: it flips the record's own hidden column - the
TCA enablecolumns.disabled field of the address, email and phone number
tables, exactly the switch the previous editor offered for these three record
kinds - through the toggleContractContactVisibility endpoint, which is sent
the target state rather than a "flip" so that a double press stores what the
visitor saw. The button's label names the press - Hide in frontend
or Show in frontend - and its glyph (academic-persons-edit-visible
respectively academic-persons-edit-hidden) shows the state; it carries no
aria-pressed, because a label that changes with the state would announce
the opposite of what a pressed button does. A hidden row is drawn in the
secondary text colour, its controls at full contrast, and carries a
Hidden tag in front of its action group. The toggle follows the
edit entry of the contracts allow-list.
Contracts and profile information rows have the same toggle as the hide
entry of their section's actions list - configurable like every other
action, listed first in the shipped settings, and withdrawn by readonly
with the others. It flips the record's own hidden column through the
toggleDocumentVisibility endpoint, which is sent the target state and
answers the serialised record; the row is written from that answer
(data-item-hidden, the Hidden tag, the label and the glyph of
the toggle) without being rebuilt, so the caret stays on the button. A hidden
record stays in the editor - it is the one place it can be shown again - while
the public views keep reading the relations, which respect the enable fields.
Every writable section heading has an Add action. Record controls
are rendered in the exact order of the configured actions list. The first
row's move-up action and the final row's move-down action are disabled. A list
with both directions also has a drag handle; dropping a row submits the complete
UID order and the server accepts it only when it contains every current section
record exactly once. After a successful mutation JavaScript updates the row
collection, alternating background, sort controls and empty placeholder without
reloading the page. The drag handle is hidden below Bootstrap's md
breakpoint; the explicit up/down controls remain available on mobile.
The action group is drawn in one line and is never pushed below the values of
its row. From the md breakpoint up it stands at the right edge of the row
(col-md-auto flex-shrink-0 ms-md-auto) and the value columns give way to
it: the date columns are col-md-2, the title or position column takes what
is left, and a long word breaks (text-break) instead of claiming the width
of the buttons. Below md the group drops onto a full-width line of its own
below the values and is centred there. The list header carries the same column
widths, so it stays above the values it names.
The add, view, edit and delete workflows share one inline collapse. Its field
schema and current values are loaded through documentFormAction(). Contract
fields include the current organisational-unit, function-type and location
options. Profile-information fields use the section's validation metadata. In
every mode the view heading uses the non-empty title field of the current
record. New records, contracts and records without a title fall back to the
translated section heading; the mode label remains as its prefix.
A field carrying the html flag is rendered as a full-width CKEditor 5
control; ordinary textareas are full width as well. Such a field is an
<academic-persons-edit-rich-text> element of its own, which creates the
editor when it is connected and destroys it when it is disconnected, so a
re-render of the editor around it — which every validation error causes — never
reaches into the subtree CKEditor owns. Rich-text values are sanitized before
persistence and parsed through the frontend sanitizer before the row or the
read-only view receives the HTML.
When that field's editor.limit is a positive integer, the textarea carries
the normalized limit and the view renders an accessible live character
counter below CKEditor. The count uses normalized visible text rather than the
stored HTML. CKEditor rejects additions past the limit while still allowing an
older over-limit value to be shortened. The JSON endpoint and the Extbase
form-data validator independently reject over-limit submissions, so client-side
code is not the security boundary. Character limits do not alter backend TCA or
the database schema; the shared required, readonly and field-type metadata does.
The document pending state is released before CKEditor is initialized, because
CKEditor deliberately skips disabled controls.
Every JSON request increments one shared busy counter. While at least one
request is active the document shows the wait cursor, and the final request
restores the previous cursor in a finally path. aria-busy is set on the
region that is actually waiting — the plugin root while a profile field is
saved, the section while its rows are sorted, the open document or contact
editor, the image editor, the synchronization form — and never on <body>,
which would make a screen reader stop reporting the rest of the page as well. This
keeps failures and concurrent requests from leaving a stale loading state.
Opening a different document replaces only the in-memory editor schema and
values after the new form has loaded. It never calls the submit action: document
forms have no blur, change, teardown or focus-loss save hook. Only their
explicit save/delete submit control persists a mutation. Focus moves into the
first writable control (or the read/delete heading), returns to the action that
opened a closed editor and remains on native buttons throughout keyboard
sorting. Drag sorting is an optional pointer enhancement; the up/down actions
remain the complete keyboard path.
All generated controls keep an explicit label. Validation errors have stable
IDs referenced through aria-describedby and update aria-invalid;
dynamic editor headings and expanded controls expose their relationships via
aria-controls and aria-expanded. The field editors are not modal
dialogs and deliberately do not trap focus.
When the view enters delete mode, its submit control is rendered with
btn-danger. Every other mode renders it with btn-primary.
An open editor carries its controls in one action bar below the form:
Cancel, and the submit control in every mode but the read view. The
panel header carries the heading alone. A read view has no form and therefore
offers no control of its own - the row action that opened it closes it again
and reports which state it is in through aria-expanded.
That row action also looks like the way out while the panel stands: the eye
is swapped for a crossed-out eye and its label changes from View to
Close details. Both icons and both strings are rendered by Fluid
into the button - the second icon carries the hidden attribute - so an
override reaches them the way it reaches every other label of the editor. The
browser only flips hidden and rewrites aria-label and title.
With a keyboard that action is reached by tabbing backwards out of the panel.
The panel is rendered directly after the action group of the row it belongs to,
so Shift + Tab passes the actions of that row and arrives at the
control that opened it. No key closes a panel from the inside: the editor of a
single profile field is the only place Escape is bound, and a document
or contact panel is not a modal dialog, does not trap focus and leaves the page
below it reachable.
An override of Documents/Editor.html or
Documents/ContractContactEditor.html that still renders the removed
header control keeps working. Its condition data-pe-when="showClose" is no
longer filled, an unfilled condition counts as not met, and the control is
removed with it - such an override renders the panel without the second
Cancel rather than failing to render it at all.
createDocumentAction(), updateDocumentAction(),
deleteDocumentAction() and sortDocumentAction() complete the document
JSON API.
Up, down and drag-and-drop intentionally share the sort endpoint. All endpoints
resolve the profile from the authenticated frontend user and then resolve a
record only inside the requested section. A UID from another profile, model kind
or profile-information type is therefore rejected. They additionally enforce
readonly and the configured action list. The shipped contracts section is
writable and exposes the complete configured Contract field set.
The dedicated contractContactForm, createContractContact,
updateContractContact, deleteContractContact and
sortContractContact actions operate below a Contract resolved through the
authenticated Profile. A contact UID from another Contract is rejected. New
contacts are appended with a normalized sorting value; the up/down controls
persist their order independently in each contact section. The first and last
controls are disabled at their respective boundaries. Read-only Contract
configuration blocks every contact mutation at the endpoint as well.
Contract and contact helptexts use the same edit-only Bootstrap popovers as
other inline fields. Physical-address countries are selected from localized
TYPO3 CountryProvider options and persisted as ISO alpha-2 codes; submitted
values outside that list are rejected by the endpoint.
The ProfileEditing plugin registers only ProfileController. All profile,
contract, profile-information and contact mutations are handled by its normal
or non-cacheable action map.
Section order is centralized and every section emits data-section-key and
data-section-position together with the configured
data-section-field-name, data-section-record-type,
data-section-kind and data-section-readonly. Records additionally emit data-item-uid,
data-item-sorting and data-item-position. data-section-sortable
exposes whether both sorting directions are available. The explicit up/down
controls and drag handle persist the same record order.
The presentation uses Bootstrap rows with one shared desktop column heading,
compact flat records, separating borders and alternating tertiary backgrounds
within each document section, which are a :nth-child(odd) rule of the
extension's stylesheet rather than a class on the row. The year columns
remain narrow while title and position columns consume the available width.
On small viewports every record repeats its field labels instead of rendering
the desktop heading. An empty section keeps its heading and add action,
followed by one unobtrusive translated status line. During drag sorting the
browser uses the complete record row as the drag image.
Extension-specific state classes outline both the source row and active list,
while a prominent line above or below the hovered row marks the exact insertion
edge. Dropping into free list space shows the same line at the end of the list.
Profile-editing development boundary
academicpersonsedit_profileediting is the only profile-editing content
element offered in the backend CType selector and new-content-element wizard.
All profile-editing behavior is implemented through the Profile template
and partial tree, ProfileController AJAX actions and the inline JavaScript
component. The former dedicated controllers and their Fluid trees are removed;
they are not compatibility entry points.
The ProfileEditing TypoScript, AJAX page type, site set and page TSconfig live in
their own Configuration/*/ProfileEditing components. ProfileEditing is the
only component enabled through either its component configuration or the
aggregate.
The ProfileEditing functional test setup reflects the same boundary. It uses a
dedicated academicpersonsedit_profileediting fixture and the neutral
AbstractFrontendProfilePluginTestCase base.
Synchronization checkbox
The synchronization checkbox appears as the compact
Private switch
immediately left of the Edit all/Close all toggle in
the page header and is persisted immediately through
updateSkipSyncAction(). Its form is a sibling of the profile form, not a
nested form. Its presence and writable metadata follow the
special.skipSync configuration; the underlying data and endpoint semantics
remain skipSync. It does not submit or mutate any other field. The endpoint
accepts exactly one boolean property:
Synchronization update
{
"profile": 123,
"data": {
"skipSync": true
}
}
Copied!
Any additional property or a non-boolean value returns invalid_payload.
On failure the JavaScript restores the last successfully persisted checkbox
state.
Expanding profile image editor
Clicking the compact edit button below the current profile image or its
placeholder keeps the profile view active.
Partials/Profile/Image/Editor.html is rendered by Fluid into a
dedicated full-width target above the profile header,
data-pe-image-editor-target, which is where it is shown: it is hidden until
the editor is opened rather than inserted then, so the container contributes
its final width and height from the first paint and the cropper can initialize
with the complete available width.
The header, profile fields and structured sections remain in the same profile
flow below the cropper; no separate view or overlay is involved. While the
editor is open, the complete image-preview column is hidden and the
profile-fields column changes from col-lg-8 to col-lg-12. The editor
itself uses a bordered, padded surface. Closing it scrolls the restored
image-preview column into view and focuses its edit action without causing a
second browser scroll. The focus is restored only after two animation frames
have applied the collapsed layout. A 1.5rem scroll margin keeps the
restored preview clear of the viewport edge.
<academic-persons-edit-image-editor> is the element that drives that
partial. It renders nothing of its own — the <f:form> carries the
__trustedProperties signature the property mapper validates the upload
against, and only the server can produce that — so it is a controller over
Fluid's markup: it binds the events and writes everything the shipped view
derives from the editing state, including the two column widths of
Templates/Profile/Index.html.
Opening and closing is animated with a short vertical move and fade, driven by
the …-image-editor-enter-active / -enter-from / -leave-active /
-leave-to classes of the extension's stylesheet. An explicit CSS grid row
expands and collapses the editor height, padding, margin and border instead of
removing the complete block in one layout step. The open scroll leaves 2rem
above the editor. The return scroll starts together with the collapse and uses
the preview's calculated final position; native scroll anchoring is disabled
only during that phase, so it cannot introduce a competing correction.
Environments requesting reduced motion skip the transition, and the close then
completes in the same frame rather than waiting for an animation that never
runs.
The full-width btn-sm edit action sits directly below the preview. Its
visible label and image upload icon are complemented by localized title and
aria-label attributes.
The image editor deliberately has no state-dependent Add or
Replace action. Selecting a file immediately replaces only the
inline crop preview with a local object URL. The page preview and persisted profile
remain unchanged until Save succeeds. A successful upload replaces
both previews, collapses the editor and shows the saved image directly. During
the leave transition, the preview remains hidden and the profile fields retain
their full width. The regular 4 / 8 grid is restored only after the editor
has finished collapsing, preventing competing layout changes while the page
returns to the image preview.
Cancel discards the selected preview and restores the persisted
image. Delete is shown only while an image is persisted.
If special.image.renderType is cropper and
special.image.settings.ratio contains a positive ratio such as 1x1 or
16:9, the editor instantiates the CropperJS module the TYPO3 core maps as
cropperjs - version 1.6.1 on both supported core versions. The selection
remains constrained to that ratio and only the generated cropped file is added
to the multipart request. No CDN or other runtime request is used. The ratio
remains fixed while the selection can be dragged and resized to choose the
visible image area, and the cropped file is written at the resolution of the
selected file rather than at the size the editor displays it, up to 2400 pixels
wide. The cropper is instantiated only after a new local file is selected. A
persisted profile image and the placeholder remain normal previews and cannot
themselves become crop input. The original upload behavior remains
active for every other render type.
The save button remains disabled until a new local file is selected. This also
prevents an already persisted image from being fetched, cropped and uploaded
again. While an upload or deletion is pending, all image controls are disabled
and the active action displays a Bootstrap spinner. This prevents duplicate
requests and leaving the image editor during a running operation.
Upload
The image form is intercepted by the frontend module and sends
multipart/form-data to uploadImageAction() through fetch().
The FormData object is built before the file control is disabled for the
pending state. Disabled controls are omitted by the browser and would otherwise
produce an apparently valid request without an uploaded image.
Extbase's file handling service validates the configured maximum file size and
allowed MIME types, stores the file in the configured target folder and updates
the FAL relation. Authorization is checked before Extbase maps or stores the
uploaded file. A replaced physical file is removed only when it has no other
references.
After persistence, the ordered non-empty values title, firstName,
middleName and lastName are joined with spaces. That composed profile
name is written to alternative and title of the profile's file-reference
overlay, which is the language-correct place for it: a translation carries its
own name there, and the file may be shared between the languages of a profile.
The metadata record of the uploaded file itself is written once, by the upload,
and only where it is empty — the record
sys_file_metadata
carries is the
backend editor's from then on, and no later change of the profile name touches
it. It is written at all because nothing else fills it: alternative and
title are the fields an installation running
typo3/cms-filemetadata
or
fgtclb/file-required-attributes
reports as missing required
attributes for a file uploaded in the frontend.
The JSON response returns the composed name so the in-page preview immediately
uses the persisted metadata.
The image editor does not render f:form.validationResults. Upload validation
failures are returned as JSON and displayed in an alert inside the active view.
The controller additionally compares the submitted image with the
persisted FAL reference. It returns image_upload_missing with status
422 instead of reporting success when no new file arrived.
The cropper preserves JPEG, PNG and WebP as output formats and falls back to
PNG for unsupported source types. Every upload remains a separate FAL file.
When a profile already has exactly one independent image relation, replacing
the image keeps the sys_file_reference uid but assigns the newly uploaded
file to it through DataHandler and persists image=custom in the profile's
l10n_state. The old physical file is removed only after no active reference
uses it anymore. Localized or duplicate legacy references are rebuilt as one
independent relation, so changing one language cannot overwrite another
language's custom image.
The upgrade wizard
academicPersonsEdit_repairLocalizedProfileImages repairs legacy shared,
localized or duplicate image relations and inconsistent relation counters. It
rebuilds every relation through the DataHandler and deletes no file: a file may
be reached through a soft reference that
sys_file_reference
knows nothing
about, and an unattended bulk run is the wrong place to act on that question.
All JSON actions propagate non-successful responses out of TYPO3's
Extbase USER content rendering with PropagateResponseException. A
JsonResponse returned by the action alone would contribute its body to the
surrounding PAGE object while the outer frontend response retained status
200. Propagation therefore preserves the documented non-200 status
codes for the AJAX client.
The delete button calls deleteImageAction() exclusively through AJAX. The
endpoint accepts a POST JSON request without profile field changes:
Image deletion
{
"profile": 123,
"data": {}
}
Copied!
The profile relation is cleared first. The physical file is deleted only if no
other record references it. The response includes deleted and
hasImage so clients can synchronize their local state.
Authentication and responses
Every endpoint above requires an authenticated frontend user and accepts only the
profile assigned to that user. The generic update, synchronization and delete
endpoints propagate machine-readable JSON errors. Image upload validation is
also converted to and propagated as JSON by the controller's error action.
Status
Error identifier
Meaning
200
—
The request was persisted successfully.
400
invalid_request, invalid_json or invalid_payload
The X-Requested-With header is missing, or the JSON or the request
structure is invalid.
401
authentication_required
No frontend user is authenticated.
403
profile_not_editable
The profile is not assigned to the frontend user.
405
method_not_allowed
A JSON endpoint was called with a method other than POST.
415
unsupported_media_type
A JSON endpoint was called without `Content-Type:
application/json`.
422
invalid_profile_data, validation_failed or
image_upload_missing
A field value or uploaded file is invalid.
500
internal_server_error
An unexpected error occurred. Details are logged but not exposed in
the JSON response.
Customizing the view
Override Resources/Private/Templates/Profile/Index.html and the partials below
Resources/Private/Partials/Profile/ through the regular template
and partial root paths. The index keeps URL/data setup, the responsive main
grid, the prototypes and the composition. The Profile, Documents,
Image and Field directories group the corresponding UI
responsibilities; the status regions and client-side button templates remain
shared at the root.
Every file in that tree is an override point, including the two regions a
browser builds at runtime. The editor of one document or contract and the
contacts of one contract cannot be rendered as finished markup - their fields,
labels, options and display values come from the documentForm and
contractContactForm responses - so Fluid renders their shapes instead, as
<template data-pe-proto="…"> blocks, and the elements clone one and fill it.
File
What it renders
Partials/Profile/Prototypes.html
Every shape the browser-rendered editors draw, once per page: the five
controls, the three field rows, an option, a help button and a
display row. It renders the three partials below.
Partials/Profile/Documents/Editor.html
The panel of one document or contract, as the document-panel
prototype.
Partials/Profile/Documents/ContractContacts.html
The section, the row and one summary cell of a contact list.
The editor of one contact, for both places it is shown.
Partials/Profile/Field/Control.html
The only place a form control is spelled - inline for the permanent
profile fields and, with its prototype flag, into the prototypes.
Partials/Profile/Image/Editor.html
The image editor. Its <f:form> and hidden fields must stay - only
the server can sign __trustedProperties - and so must its
data-pe-* hooks; what the template used to derive is written by
the element.
A prototype is filled through exactly four attributes, and an override keeps
them and their keys:
Attribute
Meaning
data-pe-slot="key"
The text of the node becomes the value.
data-pe-attr="attribute:key …"
Those attributes take the value; an absent or false value removes
them.
data-pe-when="key"
The node is removed when the value is falsy.
data-pe-list="key"
Where repeated clones go.
What an override may change is every tag, every class and every label. What it
may not change is the vocabulary - the data-pe-* hooks and the slot,
condition and list keys - and what it cannot change is the order the elements
insert things in and which slot carries which value; both are TypeScript. See
Important: The profile editor is Fluid, driven by five custom elements in the
changelog.
The custom elements and their events
Five element names are part of the public contract of this extension from
version 3.0 on. The prefix is the extension key with its underscores replaced,
because a custom element name is global and has no scoping mechanism.
Element
What it owns
<academic-persons-edit-profile-editing>
One editor. It wraps the plugin root, reads its data-* contract
once and starts everything below it. It renders nothing.
<academic-persons-edit-image-editor>
The profile image editor, as a controller over the server-rendered
upload form. It renders nothing.
<academic-persons-edit-document-editor>
One open document or contract editor, in add, view, edit
or delete mode. It clones the document-panel prototype and
fills it from the documentForm response. It renders nothing.
<academic-persons-edit-contract-contacts>
The contacts of one contract and the editor of one contact, from the
section, row and editor prototypes. It renders nothing.
<academic-persons-edit-rich-text>
One rich text field of a document editor and the CKEditor 5 instance
on it. It wraps the textarea its prototype carries and renders
nothing. The rich text fields of the profile itself stay
Fluid-rendered textareas and are not wrapped in it.
Four events report what an open document editor did; the root element listens
for a fifth, so that a descendant which does not hold the editing context can
still have a status shown. All of them bubble, and none of them crosses a
shadow boundary because there is none:
Event
Meaning
pe:status
Asks the root element to write one of the two live regions; detail
{ type, message? }. Listened for on the root, dispatched by
nothing the extension ships.
pe:document-close
The cancel button of an open editor was pressed.
pe:document-submit
Its form was submitted; the browser's own submission is prevented.
pe:document-input
A control changed; detail { name, value }.
pe:document-closed
The close transition is over and the element may be removed.
Two vocabularies carry the rest, and both are unchanged in meaning by the move
to custom elements. The plugin root of Templates/Profile/Index.html
carries the configuration of this profile — fifteen endpoint URLs, the
profile uid and the editor language, five image settings, twenty-six messages
and six labels. It is read once, when the element above it starts the editor,
and an attribute changed afterwards is not seen. Every control below the root
carries a data-pe-* hook, including the controls an element clones out of a
prototype: those carry the same hooks the removed partials did.
Keep the following contracts when reusing the shipped JavaScript. A hook that
sits in a prototype is authored in Fluid like any other, so an override may
retag, restyle and relabel it; what an override may not do is rename the hook,
and what it cannot do is change the order the element inserts things in or
which slot carries which value.
Selector or attribute
Purpose
data-academic-persons-profile-editing
Root component and scope for all queries. Everything below is read
from it, and the <academic-persons-edit-profile-editing> element
that wraps it is what starts the editor.
data-pe-missing-ajax-page-type
The role="alert" message Templates/Profile/Index.html
renders above the plugin root when the site delivers no
PAGE
object with
typeNum = 1733735
, which
is the one configuration error the editor cannot survive: every save
would be answered with the site's error page instead of JSON. The
same request logs the cause and names the site set to include. It is
absent on a correctly configured site.
data-profile-uid and data-editor-language
Positive profile identifier, and the language code CKEditor is
initialized with.
data-update-url
Generic field update endpoint.
data-skip-sync-url
Synchronization endpoint.
data-delete-image-url
Image deletion endpoint.
data-document-form-url, data-create-document-url,
data-update-document-url, data-delete-document-url,
data-sort-document-url and data-toggle-document-visibility-url
The six document endpoints.
data-contract-contact-form-url,
data-create-contract-contact-url,
data-update-contract-contact-url,
data-delete-contract-contact-url,
data-sort-contract-contact-url and
data-toggle-contract-contact-visibility-url
The six contract-contact endpoints.
data-message-* and data-label-*
The localized texts a browser-rendered control needs. They are on the
root because a template cannot be reached from the browser.
data-pe-proto
One <template> per shape a browser-rendered editor draws. The
elements clone one and fill its data-pe-slot, data-pe-attr,
data-pe-when and data-pe-list nodes.
data-pe-fields-form and
academic-persons-profile-editing__field
Generic field forms and controls. Separate forms preserve valid markup
across the personal-data and about-section grid areas.
data-pe-rich-text and data-pe-editor-container
Marks a textarea for lazy CKEditor initialization and its wrapper for
show/hide handling.
data-pe-rich-text-preview and
data-pe-rich-text-preview-content
Direct formatted read preview and its safely replaceable content
container.
data-pe-field-preview and data-pe-field-editor
Plain read row and the inline control region for one field.
data-pe-profile-name and
data-pe-profile-name-field-ids
Main heading and the name controls used to refresh it after saving.
data-pe-sticky-image
Sticky image container receiving the measured
#page-header.navbar-fixed-top height plus a 10-pixel visual gap
as its runtime top offset.
data-pe-document-sections and data-pe-document-section
Editable structured-section list and stable boundary for its AJAX
controls.
data-section-key and data-section-position
Stable section identity and current zero-based presentation position.
data-section-field-name, data-section-record-type,
data-section-kind and data-section-readonly
Field, relation type, record kind and write state, taken from the
shared settings graph for section-specific persistence.
data-pe-document-items and data-pe-document-item
Mutable item collection and record boundaries inside a section.
data-pe-document-item-template
Hidden Fluid-rendered row prototype retained in the mounted DOM and
cloned after a successful create response.
data-pe-document-add-collapse-target and
data-pe-document-item-collapse-target
Where the <academic-persons-edit-document-editor> element is
created: below a section heading for an addition, inside an individual
record row for everything else. The document controller assigns a
unique ID when a target is first opened, for aria-controls.
data-item-uid, data-item-sorting, data-item-position and
data-item-hidden
Persisted record identity, domain sorting value, current zero-based
presentation position, and - present only on a hidden record - the
styling anchor of the dimmed row.
data-pe-document-empty-state
Localized placeholder rendered when a structured collection is empty.
data-pe-document-add, data-pe-document-view,
data-pe-document-edit and data-pe-document-delete
Section creation and in-place row actions.
data-pe-document-sort
Up/down row action persisted through the shared sort endpoint.
data-pe-document-hide and data-pe-document-hidden-badge
The visibility toggle of a row (its label names the press, its
glyph shows the state) and the Hidden tag in front of
its action group, both written from the answer of the visibility
endpoint.
data-pe-document-view-container, data-pe-document-form,
data-pe-document-heading, data-pe-document-fields and
data-pe-document-field
The collapse, the form, the heading, the field region used for add,
view, edit and delete, and one control inside it. Fluid renders all
of them: the first four in the document-panel prototype of
Partials/Profile/Documents/Editor.html, the last through
Partials/Profile/Field/Control.html, which carries the field
name as its value. <academic-persons-edit-document-editor> clones
the prototype and fills its slots.
The contact sections of a contract, their rows, the editor and its
controls. Fluid renders them too: the section, the row and one
summary cell in the contact-section, contact-row and
contact-summary-cell prototypes of
Partials/Profile/Documents/ContractContacts.html, the editor
in the contact-editor-panel prototype of
Partials/Profile/Documents/ContractContactEditor.html, and
-field again through Partials/Profile/Field/Control.html.
<academic-persons-edit-contract-contacts> clones and fills them;
the presses are delegated on the plugin root like the document ones.
-hidden marks a hidden contact row: it is the styling anchor of
the dimmed row, and the hidden key that carries it also renders
the Hidden badge. -hide is the visibility toggle of
the row, see below.
data-pe-field-group, data-pe-field-ids and
data-pe-display-field-ids
Grouped preview/editor and the controls participating in it.
data-pe-group-edit, data-pe-group-dismiss,
data-pe-group-cancel and data-pe-group-save
Open, clear the draft, restore and persist a grouped field row.
data-pe-field-actions
Content-sized Bootstrap group for the three per-field actions.
data-pe-autosave-on-change
Saves configured checkbox controls immediately after a change.
data-pe-autosave-undo and data-pe-cancel
Marks the undo action beside an editable checkbox. It restores the
last successfully persisted value and closes the editor without
sending another request. Select fields use the regular clear, undo
and save action group.
Enters and leaves full form editing: it discards the single field or
group that is open, opens every editable field and grouped row at
once, hides the per-field action groups and shows the form bars.
Pressed again it discards the form, exactly as Discard
does. It carries aria-pressed and names the field forms it
controls in aria-controls.
data-pe-edit-all-label, data-pe-close-all-label and
data-pe-edit-all-button-label
Localized labels and replaceable label container for the edit-all
toggle.
data-pe-dismiss
Deletes the current draft value without closing or saving it.
data-pe-cancel
Restores the last persisted value and closes one field without a
request.
data-pe-save
Persists one field through the generic JSON endpoint.
data-pe-sync-form and
academic-persons-profile-editing__sync-checkbox
Synchronization control.
academic-persons-profile-editing__image-form and
data-pe-image-view-container
AJAX-only multipart upload form and the editor panel around it. The
form is server rendered and stays that way: it carries the
__trustedProperties signature the property mapper validates the
upload against, which only the server can produce.
data-pe-image-editor-target
Profile-specific full-width container Fluid renders the image editor
into, above the profile header.
data-pe-image-preview and
data-pe-image-view-preview
Image locations updated after upload or deletion.
data-image-render-type and data-image-cropper-ratio
Render type and ratio consumed by the cropper of the image editor.
data-pe-status-toast="status" and
data-pe-status-toast="alert"
The two scoped status regions of the component: the polite one for
saving, success and information, the assertive one for a failure. Both
must exist — a region's politeness cannot be changed reliably once it
is in the accessibility tree.
Not every hook in the table has a reader in the shipped JavaScript. Fifteen of
them are override and styling anchors: they name a region so a stylesheet or
a project's own script can address it, and no module of this extension looks
them up. They are part of the contract for that reason and not by accident - an
override may move them, and should keep them:
Of these, data-pe-compact is the only one a stylesheet of this extension
uses; the rest exist for overrides.
One consequence of the prototype mechanism is worth stating: a clone keeps the
data-pe-slot, data-pe-attr, data-pe-when and data-pe-list
attributes of the block it came from. They are inert in the live DOM - the
filler has already written the values they name - and they are not state. Do not
read them, and do not style on them.
Every editable field needs one invalid-feedback element in its closest
data-pe-field-wrapper, data-pe-group-control or .form-check wrapper.
Inline collapse targets, views, status regions, icon templates and
compatibility-template elements must remain inside the component root. All DOM
lookups are scoped to that root, so multiple components remain independent.
State classes and selectors JavaScript uses
No element and no prototype filler writes markup: every tag, every class
attribute and every label of the editor is authored in Fluid. What the modules
below them do is toggle state classes on markup that already exists, and
select a few nodes by class. Both are part of the override contract - renaming
one of them breaks the editor silently, because nothing throws when a
classList.toggle() writes a class no stylesheet defines.
Class or selector
Written or read for
d-none
Everything that is shown and hidden by the field editing: a preview,
an editor, a per-field action group, an empty section, the toast.
d-md-flex
The header row of a structured document list, which is shown only
while the list has rows.
is-invalid
A control the server refused, on a profile field and on the
synchronisation switch.
text-danger
A rich text character counter that is over its limit.
text-body-secondary
A preview that shows the empty label instead of a value.
active
The Edit all toggle while it is pressed.
bg-danger, bg-success, bg-info, bg-warning
The severity of the status toast; the four are exchanged, never
combined.
.status-title and .status-message
The two nodes of Partials/Profile/StatusToast.html a status is
written into.
The drag sorting of a structured document list and its insertion
indicator.
is-image-closing
The plugin root while the image editor collapses.
col-lg-4, col-lg-8, col-lg-12 and
.academic-persons-profile-editing__profile-fields-column
The two column widths the image editor exchanges while it is open.
.alert[role="alert"] and .spinner-border
The message and the busy indicator of an open document or contact
panel, written onto the panel that is already there rather than by
rebuilding it.
.invalid-feedback, .form-check and .mb-3
The message element of a refused field, and the two wrappers it is
looked up from when the field carries no data-pe-field-wrapper
or data-pe-group-control.
.academic-persons-profile-editing__field
Every editable control of the profile fields. This is the class the
field editing enumerates by, so an override that drops it leaves the
field unreachable.
.academic-persons-profile-editing__sync-checkbox
The synchronisation switch of Partials/Profile/Header.html.
.ck
CKEditor's own root, read to decide whether Escape belongs to
the editor or to the form. It is the library's class, not this
extension's, and is the one entry here an override cannot change.
The collapse transitions, derived from the prefix of the editor that
runs them. The declarations are in
Resources/Private/Scss/frontend/profile-editing.scss.
Frontend build
The package has no separate development toolchain below
Resources/Public/. TypeScript sources, SCSS sources and the committed
JavaScript and CSS output use the repository-wide frontend suites from the
repository root:
Nothing is bundled: the build emits one module per source file and leaves every
import as it was written. No JavaScript library is committed with the
extension - both libraries the editor uses, CKEditor 5 and CropperJS, are
resolved through the import map from rte_ckeditor and core.
Tests
Controller unit tests cover method, payload and authentication errors for the
JSON actions. Sanitizer unit tests cover the supported profile properties,
allowed editor markup and rejection of scripts, event attributes, styles,
unknown tags and unsafe link schemes. Validation-service unit tests verify that
sanitization happens before a value is registered for persistence.
A JavaScript suite runs the shipped modules and elements against a DOM
(Build/Scripts/runTests.sh -s testJs). It drives the rendered markup of the
Fluid partials through the modules that read it, which is the only place the
event handling, the optimistic list updates, the transitions and the focus
management are executed at all.
Functional plugin tests render the inline-collapse targets, the prototypes and
the image view, verify the decomposed Fluid contracts, AJAX-only controls,
direct rich-text previews and the separate delete, cancel and save actions.
They also assert the prototype inventory, every slot key of every prototype,
and that a prototype control and the live control of the same type carry the
same tag, the same classes and the same attributes. The section-provider unit
test
verifies that order, identifiers, field names, relation types and labels come
from the shared settings graph while row fields, action capabilities,
presentation modes and typed records are preserved. Functional fixtures cover
contracts and every configured profile-information relation; the rendered page
test derives the expected
order and metadata from the same settings service, then checks placement below
About me, alternating records, empty states, writable add controls
and exactly the configured row actions. AJAX lifecycle tests cover schema
loading, CKEditor inline fields, creation, partial updates, arrow and drag
sorting, cross-section rejection, deletion and read-only endpoint denial.
Contract tests additionally cover every configured field plus address,
email-address and phone-number creation, display, update, independent sorting,
deletion and cross-Contract rejection. They also guard the ProfileEditing
plugin against accidentally exposing removed mutation controllers.
Registration tests ensure that ProfileEditing is the only editing content
element offered to editors. The architecture unit test scans the
ProfileController, Fluid tree and JavaScript for forbidden legacy
controller and template references.
The AJAX tests persist malicious rich-text input through the real update
endpoint and assert that only the sanitized response is stored. The inline
image tests verify that a missing file can never return success and that a real
multipart upload returns hasImage: true and creates the FAL relation. They
also exercise the dedicated image deletion endpoint through the generated
action URL. Form submissions reuse the complete rendered action URL, including
the JSON page type, so the tests exercise the same routing contract as the
browser. Upload tests are assigned to the not-core-13 PHPUnit group because
TYPO3 v13's CLI upload permission check requires a real HTTP upload.
The page type of the JSON endpoints
Every writing endpoint of the editor is an Extbase action of the same plugin,
reached through a dedicated
PAGE
object with
typeNum = 1733735
. The object is delivered by the site set and by
the static template of this extension, it is not cached, and it sets
Content-Type: application/json on the page - TYPO3 v13 drops the headers a
plugin response carries, so the page has to declare it.
Three things in a project's infrastructure have to know about that number:
The TypoScript of the site itself. The object exists where the
TypoScript of this extension is included - through the site set
fgtclb/academic-persons-edit-profile-editing or the static template
Academic Persons Edit: Profile editing. There was no such page
type before version 3.0, because the previous editor was a server-rendered
form flow, so a site package that copied the extension's TypoScript
into its own instead of including it has no
academicPersonsProfileEditingAjax
object. The editor then
renders, and every save is answered with the HTML of the page instead of
JSON - a failure that happens in the browser, with nothing in the TYPO3 log
to look at. The editor therefore checks the TypoScript setup of the request
for a
PAGE
object with that
typeNum
, and where
there is none it renders a role="alert" message
(data-pe-missing-ajax-page-type) instead of the silent failure, and
logs the cause at error level. Include the delivered TypoScript, or copy
the object into the site package:
Route enhancers. A PageTypeDecorator that maps page types onto URL
suffixes has to list 1733735, or the editor's request URLs are not
resolvable. Without a decorator the type travels as &type=1733735 and
nothing has to be configured.
Web application firewalls and reverse proxies. The endpoints are
POST requests carrying JSON, and one of them carries a
multipart/form-data image upload. A rule set that blocks unknown JSON
bodies, strips the X-Requested-With header or limits the upload size
below the configured maxFileSize makes the editor fail with a generic
error message and no server side log entry.
The request header the endpoints require
Every writing endpoint - the fifteen JSON actions and the image upload -
requires the request header X-Requested-With: XMLHttpRequest. A request
without it is answered with 400 and the error code invalid_request.
The shipped JavaScript sends the header on every request, so this is invisible
in normal use. It exists for the image upload, which is a
multipart/form-data request and therefore one that any foreign page could
submit with a plain form, carrying the visitor's session along. A custom header
cannot be set on a cross-origin request without a CORS preflight, which the
browser refuses for a cross-site page - so requiring one closes that door
without relying on the SameSite attribute of the session cookie, which an
installation may change.
A project that talks to the endpoints from its own code has to send the header.
Icon identifiers
The action icons of the editor are registered in
Configuration/Icons.php and rendered through core:icon with
alternativeMarkupIdentifier="inline", so the SVG is inlined and follows the
text colour of the button it sits in. The files are Bootstrap Icons (MIT, see
Resources/Public/Icons/LICENSE-bootstrap-icons.txt) drawn in
currentColor.
Identifier and file name name the action, not the glyph: a project that
replaces the icon set changes the drawing, not the identifiers its template
overrides address.
Identifier
File
Used for
academic-persons-edit-add
add.svg
Add a document, contract or contact row
academic-persons-edit-back
back.svg
Back to the profile overview
academic-persons-edit-clear
clear.svg
Clear the value of a field
academic-persons-edit-delete
delete.svg
Delete a row or the profile image
academic-persons-edit-edit
edit.svg
Open a field or a row for editing
academic-persons-edit-help
help.svg
Help text popover
academic-persons-edit-move-down
move-down.svg
Move a row down
academic-persons-edit-move-up
move-up.svg
Move a row up
academic-persons-edit-save
save.svg
Save a field or a row
academic-persons-edit-sort-handle
sort-handle.svg
Drag handle of a sortable list
academic-persons-edit-undo
undo.svg
Restore the last saved value
academic-persons-edit-upload-image
upload-image.svg
Open the profile image editor
academic-persons-edit-view
view.svg
Open a row read-only, or the public profile
academic-persons-edit-view-close
view-close.svg
Close the read view a row action opened
Templates
This chapter is all about templating EXT:academic_persons_edit
EXT:academic_persons_edit is using Fluid as template engine.
This documentation won't bring you all information about Fluid but only the
most important things you need for using it. You can get
more information in the section Fluid templates of the Sitepackage tutorial. A complete reference of Fluid ViewHelpers
provided by TYPO3 can be found in the ViewHelper Reference
Change the templates using TypoScript constants
As any Extbase based extension, you can find the templates in the directory
Resources/Private/.
If you want to change a template, copy the desired files to the directory
where you store the templates.
Profile editing renders from Resources/Private/Templates/Profile/Index.html
and thirty-one partials below Resources/Private/Partials/Profile/. Four
of them sit directly in that directory - the header, the status toast, the
button templates and the prototypes - and the rest in four subdirectories:
Field/ for the controls and the two action bars, Documents/ for
the structured sections and their two editors, Image/ for the image card
and the image editor, and Profile/ for the field sections. Any of them
can be overridden on its own.
Two of them carry more than markup. Profile/Field/Control.html is the one
place a form control is spelled - every field, of every section and of both
editors, is that partial - and Profile/Prototypes.html holds the
<template data-pe-proto> blocks the custom elements clone for everything they
draw in the browser. When overriding while keeping the shipped JavaScript,
preserve the form data attributes, the field class and the slot names of those
blocks. The complete contract is documented in Profile editing.
Replacing an icon
The action icons of the profile editing frontend are addressed by identifier,
not by file. They are registered in Configuration/Icons.php of
EXT:academic_persons_edit as academic-persons-edit-add,
-back, -clear, -delete, -edit, -help, -move-down,
-move-up, -save, -sort-handle, -undo, -upload-image and
-view.
To use different artwork, register the identifier again in the
Configuration/Icons.php of the sitepackage with the own file - a later
registration wins, and no template has to be overridden:
That provider inlines the file rather than rendering an
<img>
, so the
glyph takes the colour of the button it sits in. A file registered with it
carries a viewBox, draws its shapes in currentColor and has no id
attribute - the markup is part of the document, possibly more than once.
The shipped files are Bootstrap Icons;
their MIT licence ships beside them in
Resources/Public/Icons/LICENSE-bootstrap-icons.txt.
Known problems
Please note that this extension is still in development.
Changes to existing code may appear in upcoming versions.
If you run into a bug or a feature that would be helpful, please use the issue tracker.
ChangeLog v3
Every change to the Academic Persons Edit extension is documented here.
A contract and the address, email address and phone number records it owns are
edited as one document in the new profile editing view, and they are configured
that way too. The flat validations keys the editor used to read are gone
(see the Breaking: Section-based AcademicPersons settings
entry of EXT:academic_persons); what the editing frontend reads instead is:
contracts.fields, the ordered list of the contract's own fields, each
with its render type, its option source and its help text;
contracts.contactSections.<section>.fields, the same per contact kind -
physicalAddresses, emailAddresses and phoneNumbers;
documentSections.<section>, which declares the rows a compact list
shows (rowFields), the actions it offers (actions) and whether it
is readonly.
The order of the fields in the file is the order of the controls in the form,
and a field that is not declared is not rendered and not written. That is the
breaking half: a project that relied on the editor rendering every column of a
record now has to declare the fields it wants.
readonly and the actions list are enforced on the server, not only in
the user interface. A request that creates, updates, deletes or sorts a record
of a read-only section is answered with 403 and the error code
document_action_not_allowed or contract_contact_action_not_allowed,
whether or not the button that would trigger it was rendered.
The rendered form itself changed with it: every control carries a label, its
aria-describedby help text and its validation state; the compact lists put
a column heading row above the rows on wide viewports and repeat each column's
label beside its value on narrow ones; and the sortable lists offer keyboard
controls next to the drag handle. The lists are Bootstrap grid rows rather than
tables - a row is one record and its cells reflow into a block on a phone.
Profile editing describes the result.
Impact
A site package overriding the settings file has to move its contract and
contact configuration into contracts.fields and
contracts.contactSections. The runtime overlay described in
the Feature: Legacy settings overlay and migration
command entry of EXT:academic_persons reads the old shape for one more release and logs a
warning per package and key; it does not read the new keys above, which
have no old equivalent.
A field the project's settings file does not declare disappears from the
editing form.
Code or tests posting to a read-only section now receive 403 instead
of writing.
Affected Installations
All installations of EXT:academic_persons_edit that ship their own
Configuration/AcademicPersons/Settings.yaml, and all installations that
relied on the previous, non configurable contract form.
Migration
Declare the contract fields and the contact sections in the settings file of
the site package, in the order the form should render them. The shipped
EXT:academic_persons/Configuration/AcademicPersons/Settings.yaml is the
reference; Profile editor and validation documents every key.
Breaking: Extbase plugins require CType on TYPO3 v14
TYPO3 v14 removed the tt_content sub-type feature (the list_type column) and
changed ExtensionManagementUtility::addPlugin() accordingly. The academic
plugins have been registered as first-class content elements (CType) since the
2.1 version line (see the 2.1 breaking note about migrating from list_type
to CType); for TYPO3 v14 support the internal registration was adapted to the
new addPlugin() signature and the vestigial list_type handling was dropped.
Impact
On TYPO3 v14 the tt_content.list_type column no longer exists. Any content
records still stored as CType=list with a list_type of one of the plugins
below will no longer resolve, and custom TypoScript, TSconfig, page TSconfig or
SQL that references list_type for these plugins stops working.
The change relates to the following plugins:
academicpersonsedit_profileediting
academicpersonsedit_profileswitcher
Affected Installations
Installations that upgrade to TYPO3 v14 and still hold content elements stored
as CType=list + list_type=<plugin>, or that reference list_type for these
plugins in their own configuration.
Migration
Run the provided upgrade wizard academicPersonsEdit_pluginContentbefore
upgrading to TYPO3 v14 (it requires the list_type column, which v14 removes) to
migrate the tt_content records to the dedicated CType values. Update any
custom configuration referencing list_type to match on CType instead.
plugin.tx_academicpersonsedit.settings.editForm.profileImage.validation.allowedMimeTypes
and
...validation.maxFileSize
no longer switch their validator off
when they are set to an empty value.
Until 2.x an empty
allowedMimeTypes
meant "no mime type
restriction": the setting was passed through as configured, and an empty list
added no validator. The same held for an empty
maxFileSize
.
From 3.0.0 both settings fall back to the shipped default when they are missing,
blank or not a string, and both validators are always added:
Setting
Default used for a blank value
validation.allowedMimeTypes
image/jpeg,image/png,image/webp
validation.maxFileSize
2M
The defaults are the same list the accept attribute of the file input
advertises, so what the browser offers and what the server accepts cannot drift
apart.
Impact
An installation that deliberately blanked one of the two settings to accept any
image, or any size, now has uploads rejected that were accepted before: a TIFF,
an SVG or a file above 2 MB reaches the editor as a validation error instead of
being stored.
Silently accepting whatever a browser sends is not a defensible default for an
upload a frontend user performs, which is why the semantics were changed rather
than kept.
Affected Installations
Installations using the profile editing plugin
(academicpersonsedit_profileediting) that set either setting to an empty
value. An installation that never touched the two settings is unaffected -
the previous shipped values were the same defaults.
Migration
Set the mime types you want to accept explicitly, for example:
Support for TYPO3 v12 has been removed for the 3.x version line, based on
the dual TYPO3 core version support per major version of the academic
extensions support matrix.
This includes removing build, test and configuration parts only required for
TYPO3 v12. Version specific code paths are dropped in a dedicated step.
Impact
TYPO3 v12 or older instances can no longer install or update to the 3.x
version of the academic extensions and are required to upgrade TYPO3 first.
The extension cannot be installed on TYPO3 v12 anymore but does not break
otherwise.
Affected installations
All installations using an academic extension on TYPO3 v12 that want to
upgrade to the 3.x version line.
Migration
Upgrade the TYPO3 installation to a supported version - TYPO3 v13 or v14 -
beforehand or within the same upgrade step.
The Profile editing content element used to be a set of Extbase
forms: one page per record, a form submission per change, a redirect after
every save. It is replaced by a single view that edits the profile in place
and writes through JSON endpoints of the same plugin - see
Profile editing for what it does and
Important: Profile editing is replaced in place for why nothing has to be
migrated in the page tree.
The plugin identity is unchanged: the content type stays
academicpersonsedit_profileediting, the plugin stays
AcademicPersonsEdit / ProfileEditing, the request namespace stays
tx_academicpersonsedit_profileediting, the site sets keep their names and
the default action stays list. Everything below the plugin is new.
Removed controllers and actions
Five of the six controllers are removed. ProfileController is the only one
the plugin registers, and its action list is new:
Removed
Replacement
ContractController
,
ProfileInformationController
,
PhysicalAddressController
,
EmailAddressController
,
PhoneNumberController
, each with list, show, new,
create, edit, update, confirmDelete, delete,
sort and partly toggleVisibility
The documentForm/createDocument/updateDocument/
deleteDocument/sortDocument/toggleDocumentVisibility and
contractContactForm/createContractContact/
updateContractContact/deleteContractContact/
sortContractContact/toggleContractContactVisibility
JSON actions of
ProfileController
An installation that links to one of the removed actions - a
f:link.action in an override, a hand written URL, a bookmark - gets a
500, not a fallback view. Extbase throws
InvalidControllerNameException
(1313855173) for a removed controller and
InvalidActionNameException
(1313855175) for a removed action; the
fallback to the default action only happens where
config.tx_extbase.mvc.callDefaultActionIfActionCantBeResolved
is
set, and this extension does not set it.
Removed templates, layouts and partials
Every Fluid file of the form flow is removed. An override of one of them is
dead: the file is no longer rendered, and the project keeps a copy of a view
that no longer exists.
Resources/Private/Templates/Contract/{Edit,New,Show}.html and the
same three files for EmailAddress/, PhoneNumber/,
PhysicalAddress/ and ProfileInformation/
The plugin renders without a Fluid layout since, so the TypoScript constant
plugin.tx_academicpersonsedit.view.layoutRootPath
and the
view.layoutRootPaths
block it filled are removed as well. A site
package that still sets the constant sets something nothing reads.
Resources/Private/Templates/Profile/List.html is kept and rewritten,
and Resources/Private/Templates/Profile/Index.html with the
thirty-one partials below Partials/Profile/ is the new tree: four of
them - Header.html, StatusToast.html,
ButtonTemplates.html and Prototypes.html - directly in that
directory and the rest in {Documents,Field,Image,Profile}/.
Overriding templates describes what may be overridden.
The JavaScript module of the removed form flow goes with it:
Resources/Public/JavaScript/frontend/rich-text.js, addressed by the bare
specifier @fgtclb/academic-persons-edit/frontend/rich-text.js, configured
CKEditor 4 for Templates/Profile/Edit.html and is deleted. A site package
that still loads that specifier loads nothing. Rich text is CKEditor 5 from
EXT:rte_ckeditor now, created by the editing view itself - see
Profile editing.
Removed icons
The ten icon files 2.4 shipped for the form flow are deleted:
Five of the ten identifiers they were registered under go with them:
academic-persons-edit-add-image, -add-item, -cancel, -sort and
-to-top. The other five - academic-persons-edit-edit, -view,
-delete, -save and -back - stay and now resolve to the new artwork.
They also resolve through a different icon provider:
\FGTCLB\AcademicBase\Imaging\IconProvider\CurrentColorSvgIconProvider
inlines the <svg> where TYPO3's own
SvgIconProvider
emitted an
<img>. A site package styling the editor's icons through a rule such as
.t3js-icon img therefore stops matching, and styles the <svg> instead;
the Feature: Icon provider for icons that follow the text colour entry of
EXT:academic_base describes the provider.
Thirteen action icons are registered, under the identifiers listed in
Icon identifiers; the extension icon persons_edit_icon is the
fourteenth entry of Configuration/Icons.php and is unchanged. They are
Bootstrap Icons (MIT) drawn in currentColor and rendered inline, so they
take the colour of the control they sit in - see
Feature: The profile editing icon set.
Removed labels
Resources/Private/Language/locallang.xlf goes from 208 to 145
trans-units: 144 are removed and 81 are new. 64 survive, all of them
byte-identical. The
German de.locallang.xlf follows one to one. That is not a list worth
printing - the authoritative one is the diff of the file for this release -
but the shape of it is:
Everything the removed Extbase form flow needed goes with it: every
*.create.success / *.update.success / *.delete.success /
*.sort.success message, every *.placeholder, every
list.no*Found entry except list.noProfilesFound, which the profile
overview still renders, the actions.hide / show / translate
/ saveAndExit
/ setToTop / setToBottom / replace actions, back,
list.hidden.badge, list.contract.position, the profile.*
section headings and every *FormData.*.error.* unit.
contract.published.label was a stale duplicate of
contract.publish.label, which stays, and is removed;
emailAddress.emailAddress.label is replaced by the shorter
emailAddress.email.label, which is new in this release.
The year fields of a timeline entry keep their labels
profileInformation.year.label, profileInformation.yearStart.label
and profileInformation.yearEnd.label; their *.placeholder
companions are removed with the form flow that rendered them.
The new view brings its own vocabulary under the profileEditing.*
prefix - status messages, empty states, the image editor, the document
section labels and the controls of full form editing
(Feature: Full form editing applies as one form).
All of them are overridable through
locallangXMLOverride
, so an
installation that translated or reworded one of the 149 removed units loses
that override silently: the key is simply not read any more. Compare the
overrides against the shipped file after the update.
Removed extension configuration
profile.autoCreateProfiles and profile.createProfileForUserGroups are
removed from ext_conf_template.txt. Neither is read by any code path of
this extension. Their stored values are not lost by the removal - the extension
configuration merges the current values over the template and prunes nothing -
and they are still the source the
academicPersons_MigrateProfileAutoCreateExtensionsConfiguration upgrade
wizard of EXT:academic_persons reads to carry both settings over.
profile.allowedLanguages is unchanged.
New page type
The JSON endpoints are reached through a
PAGE
object named
academicPersonsProfileEditingAjax with
typeNum = 1733735
,
delivered by the site set fgtclb/academic-persons-edit-profile-editing and
by the static template. A project with a PageType route enhancer, a web
application firewall or a reverse proxy in front of TYPO3 has to let that page
type and the X-Requested-With header through - see
The page type of the JSON endpoints.
There was no such page type in 2.4, where the editor was an Extbase form
flow, so a site package that copied the TypoScript of this extension into its
own instead of including the site set or the static template has the editor and
not the page type. Every save is then answered with the site's error page where
the browser expects JSON, and the editor can do nothing about it. The editor
therefore looks the page type up while it renders: when no PAGE object of
this site carries
typeNum = 1733735
, it writes an error naming
the cause to the TYPO3 log and tells the visitor that the profile cannot be
saved, instead of letting them find out on their first change. The object is
matched by its
typeNum
, not by its name, so a project that
declares it under a name of its own is recognised.
Impact
Overrides of any removed Fluid file stop having an effect. The editing
view renders from the shipped templates.
Links to the removed actions are a 500 rather than a fallback view.
Templates and PHP code referring to the five removed icon identifiers,
labels or controller classes fail: an unknown icon identifier renders
TYPO3's default-not-found placeholder, an unknown label renders its
own key, and an unknown class is a fatal error.
A form posting profileInformationFormData[year],
contractFormData[...], addressFormData[...],
emailFormData[...] or phoneNumberFormData[...] to one of the
removed actions is not processed. The editor posts JSON, under the
property names year, yearStart and yearEnd.
The two removed extension configuration options disappear from the
Settings module. A stored value is ignored.
Affected Installations
All installations using the Profile editing content element of
EXT:academic_persons_edit, and in particular every installation that
overrides one of its Fluid files or links to one of its actions.
Migration
Remove the overrides of the deleted Fluid files, and re-apply the project
specific changes on top of the new template tree where they are still
wanted. Overriding templates names the files that carry the hooks the
JavaScript binds to; changing those breaks the editor rather than the
layout.
Replace links to the removed actions with a link to the index action
and the profileUid argument, or with the profile overview.
Replace the five removed icon identifiers with the ones of
Icon identifiers, and re-point CSS that selected the icons as
an <img>.
Confirm that the site actually delivers page type 1733735. Include the
site set fgtclb/academic-persons-edit-profile-editing or the static
template of this extension; a site package that maintains a copy of the
extension's TypoScript adds the academicPersonsProfileEditingAjax
PAGE
object to that copy. The editor reports a missing page
type in the TYPO3 log and on the page itself, but it reports it, it cannot
repair it.
Let page type 1733735 and the X-Requested-With header pass through
route enhancers and firewalls.
Drop profile.autoCreateProfiles and
profile.createProfileForUserGroups from the deployment configuration -
but only after the
academicPersons_MigrateProfileAutoCreateExtensionsConfiguration upgrade
wizard has run, because that wizard reads them.
The frontend editing flow of EXT:academic_persons_edit dispatches
\FGTCLB\AcademicPersons\Event\AfterProfileUpdateEvent
again. The
dispatch had been lost in the 2.x profile editing restructuring: since then the
event fired only when a profile was auto-created on frontend user login, so a
profile edited through the frontend plugins was never synchronised into its
translations, and the profile slug was not regenerated either.
These actions dispatch the event once after the change is persisted: the
generic field update of the profile form, the skip_sync toggle, the profile
image upload — which is also a replacement — the removal of the profile image,
and the create, update, delete and sort actions of a document section and of
the contacts of a contract. The event always carries the persisted default
language profile; child records resolve their owning profile through the
contract, and an edit of a profile fetched as translation overlay does not
dispatch (synchronisation runs from the default language record only). A
removal that found no image to remove changed nothing and announces nothing.
Impact
With profile.allowedLanguages configured, a frontend edit keeps the
translated profile records in sync again: missing translations are created and
existing ones updated through the record synchronisation of
EXT:academic_persons, which routes every write through the TYPO3
DataHandler
. The profile slug is regenerated on every frontend edit as
well, since the slug listener reacts to the same event.
Installations that left profile.allowedLanguages empty only regain the slug
regeneration — the synchronisation itself stays a no-op for them.
Affected Installations
Every installation using the frontend editing plugins of
EXT:academic_persons_edit. Behaviour beyond the editing flow is unchanged:
profile auto-creation dispatched the event before and still does, and project
side dispatches from own DataHandler hooks are not affected.
Feature: Full form editing applies as one form
Description
Edit all opens every editable profile field at once. It now also
gives the form controls of its own: Apply, Undo and
Discard, rendered once at the end of every field form by
Partials/Profile/Field/FormActions.html. The per-field clear, undo and
save groups are hidden for as long as the form is open, and the undo beside an
autosaving checkbox with them.
Apply sends every changed field of the profile in one
request. updateAction() validates the whole field map before it writes
anything, so either all of it is stored or none of it is - a refused
property leaves the other submitted properties unwritten as well. On
success the stored values are written back into the fields, the previews
and the name heading, the form closes and the focus returns to
Edit all.
Undo restores every field to the value that is stored and keeps
the form open.
Discard restores every field and closes the form. Closing the
form with Edit all does the same thing.
Entering the form discards the single field or group that was open. Its
control goes back to the stored value and any message beside it is cleared,
exactly as its own Undo would leave it, so the form starts from the
stored profile rather than from a value the visitor typed in a state they have
just left.
While an apply is on its way to the server none of the three, and neither
Edit all nor Escape, does anything: the request cannot be
taken back, and reverting under it would leave the stored profile and the
editor's baseline disagreeing silently. Because entering the form discards, the
same applies while a single field is being saved: Edit all and
every pencil do nothing until the answer has been written back, and say so in
the polite live region rather than merely not reacting - see
Important: Opening an editor asks about the unsaved one.
A refusal reverts nothing. Every entered value stays where it was entered, the
refused fields are marked and described by their own message, the caret goes to
the first of them and the refusal is announced once rather than once per field.
Reverting thirty typed fields because one of them was rejected is data loss;
the per-field paths revert only because a checkbox has one bit and no other way
to report a failure.
A checkbox that saves on change stops doing so while the form is open and is
applied with everything else instead. Without that, the value would reach the
database while the visitor is still deciding, and Discard could not
take it back. The synchronisation switch of the page header sits outside the
field forms and keeps saving immediately.
Keyboard and screen reader
Opening the form puts the caret in the first editable field.
The controls stand after the fields in document order and are tabbed
through as Apply, Undo, Discard.
Escape discards the form, unless the caret is inside a rich text
editor - that key belongs to the editor's own balloons first.
Ctrl + Enter (Cmd + Enter) applies it, and so
does Enter in a text field, which submits the form. Both keys belong
to the field form, so a document, contract or image editor open at the same
time keeps its own handling of them.
The bar is a role="group" with its own accessible name, the
Edit all button carries aria-pressed and names the forms it
controls through aria-controls, and the two live regions of the editor
announce the result: the polite one carries the restored notice and the
success, the assertive one a failure and a refusal of the form. A refusal
beside a single field stays polite, as it always was.
Impact
This is a feature rather than a breaking change: the whole profile editing view
is new in 3.0.0 (Breaking: Replaced the profile editing view) and no
release ever shipped Edit all with per-field controls.
Single-field editing keeps its controls - the pencil, the three buttons beside
a field and the field groups act exactly as they did, and the editors of every
document and contract panel are not touched at all. What changed is that only
one field or group is open at a time: opening another field or group, with a
pencil or with Edit all, discards the editor that was open before
it. Pressing the pencil of the field that is already open is not that - its own
editor is left alone and what has been typed into it survives. An unsaved value
left behind in another row is lost, rather than being posted later from a
control the visitor can no longer see. A pencil pressed while the form is open
does nothing at all - every field is already open, and the bar is the way out.
An override of Partials/Profile/Profile/Fields.html has to render
Profile/Field/FormActions at its end, or the profile offers no way to
apply the form. An override of the bar itself keeps the data-pe-form-actions
element with its data-pe-form-reverted-message, and the three
data-pe-form-apply, data-pe-form-undo and data-pe-form-discard
buttons; every label of it is Fluid, none is spelled in JavaScript. See
Two editing modes.
Feature: Contracts and profile information rows can be hidden
Description
Every row of a document section of the Profile editing view - a
contract, a publication, a lecture, a cooperation and the other timeline
entries - carries a visibility switch as the hide entry of its section's
actions list. Pressing it hides the record in the public profile, pressing
it again shows it. While the record is hidden the row is drawn in the
secondary text colour, its controls at full contrast, and carries a
Hidden tag in front of its action group; the label of the switch
names the press (Hide in frontend respectively Show in
frontend) and its glyph shows the state.
The switch is what the contact rows below a contract already have, extended to
the records they belong to. It flips the record's own hidden column, the
TCA enablecolumns.disabled field, through the new
toggleDocumentVisibility JSON action of
ProfileController
, which is
sent the target state rather than a "flip" so that a double press stores what
the visitor saw, answers the serialised record, and follows the hide entry
of the section's allow-list like every other action follows its own.
A hidden record stays in the editor: it is the one place it can be shown
again. The editor therefore lists the records of a section through the
repositories' IncludingHidden queries, while the public views keep reading
the relations of the profile, which respect the enable fields.
Impact
The switch is listed first in the shipped
actions
of every section. A
site that overrides a section's
actions
keeps its list and adds
hide where the switch is wanted; a section marked
readonly
never
offers it. A site overriding Templates/Profile/Index.html has to carry
data-toggle-document-visibility-url, data-message-document-hidden and
data-message-document-shown; one overriding
Partials/Profile/Documents/Actions.html renders the hide case and
the tag itself.
Affected Installations
Installations of the Profile editing content element of
EXT:academic_persons_edit.
Feature: The profile editing view rewritten
Description
The Profile editing content element renders the whole profile on
one page and saves each change where it is made. The previous form flow -
one page per record, a submission per change, a redirect after every save - is
gone (Breaking: Replaced the profile editing view).
Profile editing is the reference; this entry is the overview.
Overview
list renders the profiles assigned to the authenticated frontend user,
with the language each of them belongs to and a link to the public detail page.
index opens one of them for editing. Every other action of the plugin
answers JSON.
Editing in place
Each field carries an edit control. Activating it turns the rendered value into
an input, saving posts only what changed, and the answer is written back into
the page - no reload, no lost scroll position. A group of fields that belong
together (the name parts, a link and its title) is edited and saved as one.
An unsaved field can be restored to the value that is stored, and a checkbox
saves on change and reverts itself when the request fails, so what is on screen
is what is in the database. Only one field or group is open at a time: opening
another one discards the one that is open, so no value is ever left behind in a
control the visitor cannot see.
Contracts and the seven kinds of timeline entry - cooperation, lectures,
memberships, press and media, publications, scientific research and vita - are
compact lists with an editor that folds out below the row it belongs to.
Creating, viewing, editing, deleting and reordering happen without leaving the
page; deleting asks first. Which columns a list shows, which actions it offers
and whether it is read-only is configured per section
(Breaking: Contract fields and contacts are configured as a unit), and
the server enforces it.
Reordering works with the up and down buttons, with the keyboard, and by
dragging a row onto its new place. A failed request puts the previous order
back.
Contract contacts
The addresses, email addresses and phone numbers of a contract are edited
inside the contract's own editor, each kind as its own list with its own
configured fields.
Rich text and character limits
A field configured as ckeditor opens a CKEditor 5 instance with bold,
italic, lists and links, in the language of the site. A configured character
limit is shown while typing, enforced in the editor and validated again on the
server. Every stored value is sanitized against an allow list - paragraphs,
line breaks, bold, italic, lists and links with an http, https,
mailto or tel target - so what an editor pastes cannot reach the public
profile as markup the template did not expect.
Profile image
The image is edited in a panel that folds out over the profile. A newly
selected file is cropped to the configured aspect ratio in the browser before
it is uploaded, so the stored file is the one that is shown. Uploading replaces
the existing relation rather than adding a second one, and deleting asks for a
confirmation first. The upload is validated on the server against the
configured maximum file size and MIME type list - a blanked setting falls back
to the same defaults the file input advertises, it does not disable the check.
Years
The three year fields of a timeline entry are <input type="number">
controls carrying min="0", max="9999" and step="1" - the bounds the
TCA of
fgtclb/academic-persons
declares for the same columns, which
the endpoint enforces again on every submission. Nothing about a year is
formatted, so nothing about it follows a locale.
Contract dates
The two contract dates validFrom and validTo are typed into a plain
text control showing d.m.Y behind the hint dd.mm.yyyy, exactly as the
previous editor showed them. They are deliberately not an
<input type="date">: the native control follows the locale of the browser
rather than the one of the site, it cannot be styled with the rest of the
editor, and its calendar is not the date picker this editor is meant to get.
A date picker is deliberately not shipped in 3.0. It is a feature of its
own and it will not be the browser's.
The endpoint reads d.m.Y and Y-m-d, both strictly, and refuses
anything else. A stored date is displayed as the MEDIUMDATE of the site
language, the way the public profile renders it.
Synchronisation switch
The switch that excludes a profile from the translation synchronisation is
saved through its own endpoint, and reverts itself when the request fails.
Accessibility
Every control has an accessible name, the fold-out regions carry
aria-controls/aria-expanded, validation errors are announced through
aria-describedby/aria-invalid, a failed request is announced through an
assertive live region and a successful one through a polite one, and closing an
editor returns the focus to the control that opened it.
That last one has one deliberate exception. An editor that is closed because
another one is being opened does not hand the focus back to its own pencil -
the caret belongs in the editor the visitor has just opened, and moving it
twice would take it out of there again. The rule is therefore that a close the
visitor asked for returns the focus, and a close that happens on the way to
somewhere else leaves it where it is going.
Bundled libraries
One third-party set is shipped with the extension, with its licence file next
to it: Bootstrap Icons (MIT), the thirteen control icons of this view, as SVG
files under Resources/Public/Icons/ with
LICENSE-bootstrap-icons.txt beside them.
The editing view is replaced for every installation of the content element.
What has to be looked at when updating is listed in
Breaking: Replaced the profile editing view.
Feature: The profile editing icon set
Description
The profile editing frontend addresses its action icons through fourteen
identifiers registered in Configuration/Icons.php:
Identifier
Action
academic-persons-edit-add
Add an entry to a list
academic-persons-edit-back
Leave the editor
academic-persons-edit-clear
Clear the value of a field
academic-persons-edit-delete
Delete an entry
academic-persons-edit-edit
Start editing a field or an entry
academic-persons-edit-help
Show the help text of a field
academic-persons-edit-move-down
Move an entry down
academic-persons-edit-move-up
Move an entry up
academic-persons-edit-save
Apply an edit
academic-persons-edit-sort-handle
Drag handle of a sortable entry
academic-persons-edit-undo
Undo an edit
academic-persons-edit-upload-image
Upload or replace the profile image
academic-persons-edit-view
Open the public view of a record
academic-persons-edit-view-close
Close the read view a row action opened
academic-persons-edit-visible
The visibility toggle of a contact that is shown in the frontend
academic-persons-edit-hidden
The visibility toggle of a contact that is hidden in the frontend
Identifier and file name are the action, never the drawing: a later icon set
changes the glyph, not the API the templates address.
The files are Bootstrap Icons and carry
their MIT licence in
Resources/Public/Icons/LICENSE-bootstrap-icons.txt. They are drawn in
currentColor and registered with
\FGTCLB\AcademicBase\Imaging\IconProvider\CurrentColorSvgIconProvider
,
which inlines the file instead of rendering an
<img>
- so a glyph takes
the colour of the button it sits in, in the frontend as much as in a dark
backend colour scheme.
Impact
The identifiers are public API. A site package that wants different artwork
re-registers one of them in its own Configuration/Icons.php with its
own file and needs no template override; a file registered that way is drawn
in currentColor as well, or it will not follow the surrounding text.
Five of the identifiers - academic-persons-edit-edit,
academic-persons-edit-view, academic-persons-edit-delete,
academic-persons-edit-save and academic-persons-edit-back - existed
before and now resolve to the new artwork and the new provider. An
installation that renders them through the shipped templates sees a different
glyph that follows the text colour instead of a fixed dark grey.
Affected Installations
All installations using the EXT:academic_persons_edit extension starting
with version 3.0.
Migration
No migration is required. An installation that re-registered one of the five
existing identifiers in a site package keeps its own artwork, since a later
registration wins.
The profile image upload of the academicpersonsedit_profileediting plugin was
handled by the custom type converter
\FGTCLB\AcademicBase\Extbase\Property\TypeConverter\FileUploadConverter
(EXT:academic_base). It has been replaced with the native Extbase file upload
handling introduced in TYPO3 v13.3 (
FileUploadConfiguration
, see TYPO3
feature forge#103511), and the replacement survives the editor rewrite of
the same release: the new editing view uploads the image through the same
configuration.
The TypoScript setting names are unchanged.
settings.editForm.profileImage.targetFolder
,
settings.editForm.profileImage.validation.maxFileSize
and
settings.editForm.profileImage.validation.allowedMimeTypes
keep
their names and are mapped onto the core
FileSizeValidator
and
MimeTypeValidator
. What a blank value means changed - see
Breaking: Profile image upload validation always applies.
Impact
The upload behaves differently in four ways:
Stored file names change. The custom converter built the file name from
the profile data as <firstname>-<lastname>-<uid>.<extension> and replaced
an existing file of that name. The native handling keeps the name supplied by
the client, appends a random suffix and renames on conflict instead. Stored
names therefore no longer contain the name of the person, which is an
improvement for a folder that is usually reachable over the web.
The previous image is deleted on re-upload. Because the generated name
changed on every upload, replacing an image would leave the previous file
behind. Uploading a new profile image now deletes the file the profile
referenced before, unless it is still referenced by another record.
The mime type is detected from the file content. The custom converter
trusted the media type sent by the browser, which can be spoofed. The core
MimeTypeValidator
inspects the uploaded file itself and additionally
cross-checks the file extension. An upload whose real content does not match
an allowed mime type is now rejected, even if the browser announced an
allowed one. Uploads that only passed because of a faked header stop working
- this is intended.
The file is only stored once the upload validates. Previously the file
was imported into FAL while mapping the request, so an upload that failed
validation afterwards left an unreferenced file behind in the upload folder.
The file is imported after successful validation now, which avoids those
orphaned files.
Affected Installations
Installations using the profile editing plugin
(academicpersonsedit_profileediting) with profile image uploads. Installations
that rely on the stored file name - for example when referencing those files by
a fixed path outside of FAL, or when addressing them by the person's name - need
to review that assumption.
Migration
No configuration change is required for the upload itself; check the blank-value
semantics of the two validation settings in the breaking entry named above.
Files uploaded before this change keep their existing names and references, and
are deleted as soon as the corresponding profile image is replaced.
Important: Repair wizard for profile image relations
With academic_persons 3.0 a translated profile can carry an image of its own
(changelog entry Breaking: The profile image translates of that
extension). The relation rows this
extension's pre-3.0 image upload and translation synchronisation wrote by hand
do not always fit that model, and the new upgrade wizard
academicPersonsEdit_repairLocalizedProfileImages repairs the three shapes
that do not:
Duplicate references on one profile. The profile keeps the file of
the reference the frontend renders — the first by
sorting_foreign
,
then by
uid
. The relation itself is rewritten: one new reference
for that file, every old row of the column deleted. A crop or a caption
that sat on one of those rows is not carried over, because the new row is
what carries the localization state the column now needs. No file is
deleted.
A relation counter in
tx_academicpersons_domain_model_profile.image
that disagrees with the number of references — on a default-language
profile, on a translation with an image of its own, or on a translation
that follows the default-language image. The last case is the shape the
2.x synchronisation left behind, a copied counter without the reference;
it is repaired by letting the core localize the default-language reference
into the translation, which is what every later synchronisation does.
A translation carrying a reference of its own — one that is not a
localization of the default-language reference — without the custom
localization state. Without the state the next synchronisation would
replace that reference with a localization of the default one. The state
is set; the reference and its file stay.
A translation whose image follows the default language, with a localized
reference or none at all, is the regular shape of the translatable column and
is not touched. A translation whose
l10n_parent
is missing, deleted or
itself a translation cannot be propagated into; it is repaired as the
independent record it de facto is, and the orphan is written to the log.
Every write of the wizard goes through the TYPO3 DataHandler, so the reference
index, the record history and the localization state are maintained by the
core. The wizard is idempotent and reports nothing to do once the relations
are consistent. It is registered as repeatable, so it is never marked as done
and stays available in the upgrade module and on the command line, however
often it ran and whatever it found.
The wizard reads and writes live records only. Workspace versions and
records that exist only inside a workspace are skipped, and the run itself acts
as a backend user in the live workspace. It therefore repairs the same records
whether it is started from the command line or from the upgrade module, and it
does so also when the person who starts it has a workspace selected in the
backend. Repairing a workspace version is not possible and is not attempted:
publish the workspace and run the wizard again.
No file is deleted, and none is copied. The wizard rewrites relations
only. Whether a file is still used cannot be answered from
sys_file_reference
, which records FAL relations and knows nothing about
an RTE
t3://file
link, a typolink or a file collection — and an
unattended bulk delete on that basis is unrecoverable. A file left without a
relation stays in its storage; the "unused files" report of the install tool
lists such files for a human to judge.
Impact
Installations upgrading from 2.x see the wizard in the upgrade module after
the database has been updated. Until it ran, a profile with duplicate
references renders whichever reference sorts first — as before — and a
translation with a stale counter renders no image — as before. Running it
leaves no file behind that was not there before, so it is safe to run on an
installation whose files are also linked from content elements.
Run it with
vendor/bin/typo3 upgrade:run academicPersonsEdit_repairLocalizedProfileImages
or from Admin Tools > Upgrade > Upgrade Wizard. Because the wizard
is repeatable, it can be started before the database update as well: it reports
that there is nothing to do and stays available for the run that follows the
schema update.
Affected Installations
Installations that uploaded profile images or synchronised translated
profiles with a 2.x version of this extension.
Important: New dependencies cms-rte-ckeditor and html-sanitizer
Description
EXT:academic_persons_edit gained two hard requirements with the replaced
profile editing view:
The rich text fields of the editing view load six CKEditor 5
bundles shipped by that system extension. Only its JavaScript is
used - none of its backend rich text configuration.
Every rich text value is sanitized against an allow list on the
server before it is stored. The extension registers its own builder
on that package.
ext_emconf.php declares rte_ckeditor accordingly.
typo3/html-sanitizer
is a Composer library, not an extension, so it
has no ext_emconf.php counterpart.
Impact
Composer-managed installations resolve both automatically on update; nothing has
to be done.
typo3/html-sanitizer
is a dependency of the TYPO3 core
already, so only
typo3/cms-rte-ckeditor
is actually added to most
installations.
Classic, non-composer installations have to activate the system extension
rte_ckeditor in the Extension Manager. Without it the rich text fields of
the editing view stay plain textareas and their JavaScript fails to load.
Affected Installations
Every installation of EXT:academic_persons_edit 3.0.0. Classic installations
that had rte_ckeditor deactivated need to activate it.
Migration
Nothing beyond installing the extension - see
Installation.
Important: Plugins assign a record view variable
Description
TYPO3 v14 rewrote the header partial of EXT:fluid_styled_content. Where v13
Header/All.html reads {data.header}, the v14 Header/All.fluid.html renders
header and subheader with {record -> f:render.text(...)}, and that ViewHelper
requires a record object.
Content elements based on lib.contentElement receive that record from the
record-transformation data processor, but an Extbase plugin view assigns only
the data array. Templates of this extension render the shared header partial,
so on TYPO3 v14 they aborted with
The record argument must be an instance of ... Given: null
Copied!
The plugin controllers now assign an additional record view variable, built
from the tt_content row of the current content element. TYPO3 v13 ignores it,
its header partial keeps reading data, so one implementation serves both core
versions.
Impact
The affected plugins render again on TYPO3 v14. Nothing was removed or renamed,
so no configuration or template override needs to be adapted.
Custom templates and template overrides may use the new {record} variable, for
example with <f:render.text record="{record}" field="header" />.
Affected Installations
Installations running the plugins of this extension on TYPO3 v14. TYPO3 v13
installations are unaffected.
Migration
None required.
Important: Opening an editor asks about the unsaved one
Description
Only one editor of the Profile editing view is open at a time — one
field or group, the whole form, one document row or one contact of a contract.
Opening another one, and pressing Edit all, closes the editor that
is open first. One that still holds the values it was opened with closes
silently, exactly as its own Undo or Cancel would close
it. One with changes is not thrown away: the view asks, in a dialog of its own,
whether to Save and continue, to Discard changes or to
Keep editing. Saving stores what the open editor's own save would
store and then opens the other one; a save the server refuses keeps the visitor
in the refused editor, with its messages, and opens nothing. Keeping — and
Escape — opens nothing at all.
The dialog is a <dialog> element the view clones from the
unsaved-changes template of
Partials/Profile/UnsavedChanges.html, never a browser prompt: its
question and its three answers are labels of the extension, translated in both
languages, and overridable with the partial. A site that drops the template
leaves the visitor in the editor that is open.
The view also says what it did. A discard the visitor chose is announced in the
polite live region — Unsaved changes were discarded. — because the row
collapsing is the only other sign of it, and that is no sign at all for a
visitor who is not looking at it. An editor closed with the value it was opened
with announces nothing, and the Undo a visitor pressed themselves
keeps its silence.
Two related rules. While a save is on its way to the server, the pencil,
Edit all and the Delete content and Undo
beside a field or a group do nothing: the answer is about to write the stored
value back into the field it saved, so a discard under it would be taken back a
moment later with nothing saying why. They say Please wait until the change has
been saved. rather than simply not reacting. The requests carry no timeout, so
a save the server accepts and never answers leaves those controls refusing
until the page is reloaded. And a pencil pressed while Edit all is
open does nothing at all, silently: every editable field is already open, and
the form's own bar is the way out of that mode.
A saved edit of a document row or of a contact stays open with what it stored,
so the next change starts from the record; creating and deleting close the
editor as before.
Impact
The two status messages travel from Templates/Profile/Index.html to
the frontend as data-message-discarded and
data-message-save-in-progress, translated from
profileEditing.status.discarded and profileEditing.status.saveInProgress.
A site that overrides that template has to carry the two attributes, and has to
render Partials/Profile/UnsavedChanges.html below the plugin root;
without the attributes the view announces its generic information text in their
place, without the partial it never asks and never opens a second editor over
a changed one.
Affected Installations
Installations of the Profile editing content element of
EXT:academic_persons_edit, and in particular those overriding
Templates/Profile/Index.html or the language files of the extension.
Important: The profile editor is Fluid, driven by five custom elements
Description
The Profile editing view of EXT:academic_persons_edit is rendered by
Fluid and driven by five custom elements. The elements control that markup;
none of them produces any. An integrator therefore changes what the editor looks
like by overriding a Fluid partial, never by replacing a JavaScript module, and
that holds for the two regions whose content only exists at runtime as well.
Those two regions are the editor of one document or contract and the contacts of
one contract: their fields, labels, options and display values come from the
documentForm and contractContactForm responses, which answer field
descriptors and never HTML. Fluid renders their shapes as
<template data-pe-proto="…"> blocks - eleven of them in
Resources/Private/Partials/Profile/Prototypes.html and five more in the
three Documents/ partials it renders - and the elements clone one and
fill it.
The four verbs
A prototype is filled through exactly four attributes, and there is no fifth:
Attribute
Meaning
data-pe-slot="key"
The text of the node becomes the value. Never markup: a value that
contains <script> is shown, not run.
data-pe-attr="attribute:key …"
Those attributes take the value. A value that is absent or false takes
the attribute off the clone.
data-pe-when="key"
The node is removed when the value is falsy.
data-pe-list="key"
Where repeated clones go: into the marked element, or in place of it
when the marker is a <template>.
An override may change every tag, every class and every label of a prototype.
What it may not change is the vocabulary: the data-pe-* hooks with their
profile-editing-{uid}-{field} id shape, and the slot, condition and list
keys. What it cannot change is the order the elements insert things in and which
slot carries which value - both are TypeScript.
One place spells a control
Resources/Private/Partials/Profile/Field/Control.html is the only place
a field control of this editor is spelled - a text input, a textarea, a rich
text field, a select and a checkbox. It is rendered inline for the permanent
profile fields, once per type into the prototypes, and through those for every
field of a document or contact editor. Overriding that one file changes every
field control of the editor at once. Two controls stand outside it because they
are not profile fields: the synchronisation switch of Header.html and
the
f:form.upload
of Image/Editor.html, which belongs to the
Extbase form that carries the upload signature.
The five elements
Their names are public API from this release on. The prefix is the extension key
with its underscores replaced, because a custom element name is global.
Element
What it owns
<academic-persons-edit-profile-editing>
One editor. It wraps the plugin root, reads its data-* contract
once and starts everything below it.
<academic-persons-edit-image-editor>
The image editor over the server rendered upload form. That form stays
server rendered because only the server can sign its
__trustedProperties.
<academic-persons-edit-document-editor>
One open document or contract editor, in add, view, edit
or delete mode.
<academic-persons-edit-contract-contacts>
The contacts of one contract and the editor of one contact.
<academic-persons-edit-rich-text>
One rich text field and the CKEditor 5 instance on it.
Four events report what an open editor did, and one is dispatched upwards by any
descendant that wants a status shown: pe:document-close,
pe:document-submit, pe:document-input with { name, value },
pe:document-closed and pe:status with { type, message? }.
No element opens a shadow root. A project's stylesheet, the theme's
Bootstrap classes and any CSS written against the rendered markup reach every
control - there is no style encapsulation to work around and no ::part() to
declare.
Impact
Every part of the editor is overridable in Fluid, including the document
editor and the contact list of a contract.
An override of a partial that carries a <template data-pe-proto> block
has to keep the block, its name and its slot keys. An element that cannot
find its prototype fails loudly: the editor that was being opened is taken
back out, the error message of the plugin is announced, and the missing
name is in the browser console. A slot that is not emitted leaves the value
unwritten.
The class names and the data-pe-* hooks of the rendered controls are as
much part of the contract as the endpoints are.
The element names must not be defined a second time by a project.
Affected Installations
Installations of the Profile editing content element of
EXT:academic_persons_edit that override one of its Fluid files, or that style
or script the editor against its markup.
Important: The profile editor draws its own focus ring
Description
The Profile editing view now styles the focus of the controls and the
buttons it renders, and no longer leaves that to the surrounding theme.
Bootstrap draws a focused control's ring with box-shadow, and a theme is
free to layer more rings behind Bootstrap's. bk2k/bootstrap-package does: it
adds an opaque white ring and an opaque black one behind the translucent accent
ring, on :focus-visible of .form-control, .form-select,
.form-check-input and .btn. Shadows paint in the order they are
written, so the black ring covers the full width and the white one only its
inner half, and a translucent accent ring over an opaque black one is dark: the
outer half of the ring reads as a hard black rectangle tight around the
control. Rendered and read back pixel by pixel, that band is #161e18 on the
theme's light body background.
Two more things were wrong with drawing a focus ring as a shadow, and the view
now avoids all three at once by drawing a real outline instead.
A shadow is painted outside the border box, so an ancestor with
overflow: hidden cuts it away. The document editor's collapse panel and the
image editor are exactly that, the grid row inside them pulls itself out to the
clipping edge, and every field and every button of those panels therefore sat
with its left and its right border edge on that edge. Measured before this
change, a focused field of a document panel and the file input of the image
editor - which the image editor focuses on every open - showed no ring pixel at
all on either side, only Bootstrap's #abbbb0 border at about 2:1 against
white.
And forced-colours mode drops box-shadow altogether, while the rules that
draw a ring that way also set outline: 0. What a user of a high contrast
theme then saw was not the site's focus indicator but whatever the browser put
in its place, which differs between browsers. An outline is kept and
recoloured in every one of them.
The ring is therefore drawn as an outline on :focus-visible, inside the
border box so that no ancestor can clip it, and box-shadow: none in the
same rule takes the layered rings and the theme's inset shadow off with it.
:focus-visible rather than :focus is what the theme's own rules use and
what Bootstrap uses for buttons, so it is exactly where the defect is.
:focus-visible is not the same thing as keyboard focus, and the difference
decides what a visitor using a mouse sees. Measured in Chrome, a pointer click
on a checkbox or on the synchronisation switch does not match it, so those
two keep the soft glow Bootstrap has always drawn for them. A pointer click on
a text input or a select does match it, so those lose the glow and take the
inset ring instead. Four of the five controls of
Partials/Profile/Field/Control.html - the text field, the two
textareas and the select - therefore look different on a plain mouse click
than they did before, and so do the file input of the image editor and the
controls of the document and contract editors. The majority of the editor's
controls change their appearance on a mouse click, not only under the
keyboard. Buttons are the exception in the other direction: the theme's rule
for .btn was already :focus-visible, so what changes for a button is
the shape of the ring and not when it appears.
Its colour is currentcolor, the colour the control draws its own text in.
That contrasts with the control by construction and in every colour mode,
which no fixed colour and no Bootstrap custom property does -
--bs-primary-text-emphasis is dark by design and measures 1.6:1 against the
green of a focused .btn-success.
.form-check-input is the one control that reasoning does not cover, and it
is worth stating rather than glossing over. Bootstrap draws the tick of a
checkbox and the knob of a switch as a background image with a hardcoded
#ffffff fill, not in currentcolor, so the ring on those two takes the
inherited body colour instead of the colour of the mark. With the shipped
theme that is #212121 on the #577760 of a checked control, which
measures 3.23:1 - above the 3:1 WCAG 2.1 SC 1.4.11 asks of a focus indicator,
but with nothing to spare. A site whose $primary is darker than the
theme's falls below it, and should either lighten the control or override the
outline-color of .form-check-input in the view.
The rule reaches every input, select, textarea and button of
the view: the five controls of Partials/Profile/Field/Control.html, the
image upload of Partials/Profile/Image/Editor.html, the synchronisation
switch of Partials/Profile/Header.html and the buttons of every action
group. Plain links are deliberately not covered - no theme rule takes their
focus ring away. CKEditor 5 is covered by halves: its editable region is a
div and keeps the library's own focus styling, while the buttons of its
toolbar are button elements below the plugin root and take this ring
instead.
Impact
A site that did nothing about the focus appearance gets one clearly visible
ring on every control and every button of the view, in both colour modes, and
gets it in the panels where the previous appearance was cut away.
Check it with the mouse, not only with the keyboard. Most of the controls the
view renders match :focus-visible on a plain pointer click, so the new ring
replaces Bootstrap's glow for a visitor who never touches the tab key; only the
checkbox and the synchronisation switch keep the appearance they had.
A site that styled the focus of the editor's controls itself has to check that
its rule still wins. The shipped rule carries the plugin root three times
against the theme's compat layer, which puts it at four class selectors and a
tag name; a project rule that matched two class selectors now loses to it.
Adding the plugin root to the project's own selector, or an !important,
restores the project's appearance.
Affected Installations
Installations of the Profile editing content element of
EXT:academic_persons_edit whose site styles the focus of its form controls or
buttons, or whose theme relied on styling them.
Important: Profile editing is replaced in place
Description
The profile editing frontend is rewritten from the ground up
(Breaking: Replaced the profile editing view), but it keeps the identity
of the content element it replaces. Nothing in the page tree, in a site
configuration or in a deployment has to be renamed:
What
Value
Content type (tt_content.CType)
academicpersonsedit_profileediting
Extbase plugin
AcademicPersonsEdit / ProfileEditing
Request namespace
tx_academicpersonsedit_profileediting
Site sets
fgtclb/academic-persons-edit and
fgtclb/academic-persons-edit-profile-editing
Static template / page TSconfig
EXT:academic_persons_edit/Configuration/TypoScript/Full and
EXT:academic_persons_edit/Configuration/TSconfig/ProfileEditing/page.tsconfig
TypoScript object
plugin.tx_academicpersonsedit
Default action
list
Existing content records keep working after the update. There is no upgrade
wizard for the content element and none is needed.
Impact
An update replaces the rendered view and the markup below the plugin, not the
record that renders it. What does change is described in
Breaking: Replaced the profile editing view; what an integrator has to look
at is the list of removed Fluid files, the removed action links and the new
page type of the JSON endpoints.
Affected Installations
All installations using the Profile editing content element of
EXT:academic_persons_edit.
Important: An uploaded profile image carries its metadata
Description
A profile image uploaded in the frontend editor now arrives with its metadata
filled instead of empty. Two records are involved, and they are written for
different reasons:
The
sys_file_reference
row of the profile carries the composed name
of the profile record it belongs to in
title
and
alternative
,
and follows every later change of that name. This is the language-correct
place for the text — a translation carries its own name, and the file may
be shared between the languages of a profile. It is written by
fgtclb/academic-persons
, for a backend save as well as for a
frontend one (changelog entry Breaking: The profile image translates of
that extension).
The
sys_file_metadata
record of the uploaded file is filled
once, by the upload that created the file, and only where it is empty:
title
,
alternative
and — where
typo3/cms-filemetadata
adds the column —
copyright
.
These are what an installation running that extension or
fgtclb/file-required-attributes
reports as missing required
attributes, and nothing else fills them for a file that was never touched
in the backend. A value a backend editor maintained on the record is never
overwritten, and a later change of the profile name does not reach it.
The composed name is the ordered non-empty values of
title
,
first_name
,
middle_name
and
last_name
, joined with single
spaces.
Impact
An image uploaded through the profile editor no longer shows up in
File > Filelist as a file with missing required attributes, and the
name of the person is rendered as alt and title text wherever the file
is used — not only where the profile's own reference is rendered.
copyright
is a column of
sys_file_metadata
only. The relation row
sys_file_reference
has no such column, in the core or through any system
extension, so it carries the composed name in
title
and
alternative
and nothing else.
Nothing has to be configured for it. An installation that maintains file
metadata editorially keeps what it maintained: the upload fills empty fields
only.
What is written can be changed, and columns an installation adds itself can be
filled, through the event
ModifyProfileImageMetadataEvent
of
fgtclb/academic-persons
— changelog entry Feature: Decide what a
profile image's metadata will be of that extension.
Important: Validator exceptions moved to EXT:academic_base
Description
The two exceptions the form data validators of this extension raise moved to
EXT:academic_base, next to the validation settings classes they belong to:
No class aliases are registered for the old names. Both are raised by
@internal
classes only, and nothing in this set caught them from
outside EXT:academic_persons_edit.
The validationEnsure ViewHelper moved to EXT:academic_base in the same
step. No Fluid file of this extension declares the p namespace any more -
the form partials that used it were removed with the editing rewrite
(Breaking: Replaced the profile editing view), and the new editing view
resolves the validation of a field from the settings graph in the controller
instead of in the template.
Impact
Code catching one of the two exceptions by its old name no longer matches and
has to import the EXT:academic_base name. A Fluid override still declaring
xmlns:p="http://typo3.org/ns/FGTCLB/AcademicPersons/ViewHelpers" fails to
render, because Fluid resolves the ViewHelper class under the declared
namespace and the old class no longer exists. In this extension that can only
be an override of a file that no longer exists either - see
Breaking: Replaced the profile editing view.
3.x Changes by type
This lists all changes to the Academic Persons Edit extension of minor versions
grouped by their type.
The list partials no longer iterate the contract relation
({contract.physicalAddresses}, {contract.emailAddresses},
{contract.phoneNumbers}). They now iterate dedicated variables
({physicalAddresses}, {emailAddresses}, {phoneNumbers})
assigned by the controller, which also contain hidden records.
Contract/Show.html passes these new variables to the partials.
Each row gained a show/hide toggle and a "Hidden" badge, and the
remaining row actions are only rendered for visible records.
Impact
Installations that override or extend any of these templates or partials
for project specific styling will not show the new show/hide
functionality and may not list hidden records, until the overrides are
adapted.
Affected Installations
All installations using the EXT:academic_persons_edit extension that
override the contract show template or the contact record list partials.
Migration
Re-apply the project specific overrides on top of the updated templates
and partials: switch the list iterations to the new {physicalAddresses},
{emailAddresses} and {phoneNumbers} variables and integrate the new
show/hide toggle column.
Breaking: Removed the profile switcher plugin
Description
The content element academicpersonsedit_profileswitcher is no longer
registered. It cannot be selected any more, and the content elements that
still use it are removed by an upgrade wizard.
This is a cleanup that was missed in 2.1, not a change of behaviour.
Background
The profile switcher used to be a real Extbase plugin. It let a frontend
user pick one of their profiles, stored that choice in the session, and
the profile editing plugin then worked on the chosen profile.
Version 2.1 restructured the frontend editing and replaced that concept:
the profile editing plugin now lists every profile of the logged in user
and links each one directly, so there is nothing left to switch between.
The restructure removed the plugin registration, both controller actions
(showProfileSwitch and executeProfileSwitch), the templates and the
whole "active profile" infrastructure around it — the request middleware,
the context aspect and the session handling.
What it did not remove was the content type registration. So since 2.1 the
element has been offered to editors while nothing rendered it: there is no
plugin configuration, no controller action and no TypoScript behind it.
Selecting it produces an empty content element.
The upgrade wizard "Remove content elements of the removed
academic_persons_edit profile switcher plugin"
(academicPersonsEdit_removeProfileSwitcherContent) sets the affected
content elements to deleted. It covers both shapes:
CType = academicpersonsedit_profileswitcher — records of an
installation that ran the list_type to CType migration while it still
carried this type along.
CType = list with list_type = academicpersonsedit_profileswitcher
— records of an installation that never ran that migration. TYPO3 v14
removed the list_type column, so this shape is only looked for when the
column exists.
The records are set to deleted, not removed from the table, so they stay
visible in the recycler and can be restored if an installation wants to
look at them before they are finally discarded.
The list_type to CType migration no longer migrates this plugin. A
record that has not been migrated yet therefore stays in its legacy shape
until the wizard above deletes it, instead of being moved onto a content
type that does not exist.
Impact
The content element cannot be selected any more, and existing elements of
it are deleted by the upgrade wizard.
No rendered output changes. These elements have rendered nothing since 2.1,
on every page they sit on, so removing them cannot change what a visitor
sees. What changes is the backend: the type is gone from the content
element type list, and any element the wizard has not deleted yet is shown
with an "INVALID VALUE" badge in the page module instead of looking like a
working plugin.
Affected Installations
All installations using the EXT:academic_persons_edit extension that
still have content elements of the profile switcher plugin — either as
CType or as a legacy list_type record.
Installations that never used the plugin are not affected. The upgrade
wizard reports nothing to do for them.
Migration
Run the upgrade wizard in the Install Tool, or on the command line:
To see which records are affected before running it:
SELECT uid, pid, header, CType FROM tt_content
WHERE deleted = 0AND CType = 'academicpersonsedit_profileswitcher';
-- TYPO3 v13 and below, for records that were never migrated:SELECT uid, pid, header, CType, list_type FROM tt_content
WHERE deleted = 0AND CType = 'list'AND list_type = 'academicpersonsedit_profileswitcher';
Copied!
There is no replacement to migrate to. The profile editing plugin lists all
profiles of the logged in user by itself, which is what the switcher was
there for.
Breaking: Site sets and static templates have been restructured
Description
The TypoScript of this extension was shipped twice: the static template read
Configuration/TypoScript/, and the site set
fgtclb/academic-persons-edit
shipped its own constants.typoscript
and setup.typoscript, each of them a single
@import
of
that folder. The page TSconfig existed only as
Configuration/TSconfig/page.tsconfig, reachable through the
auto-included Configuration/page.tsconfig and through nothing else — it
was not selectable on a page at all.
Both mechanisms now read one physical copy of every file, and both of them
deliver the extension per component instead of as one block:
Configuration/TypoScript/ProfileEditing/ holds the TypoScript of the
Profile editing content element and is what the static template
registers and what the set points its
typoscript
key at.
Configuration/TSconfig/ProfileEditing/page.tsconfig holds its page
TSconfig and is what the page field Page TSconfig offers and
what the set points its
pagets
key at.
Configuration/TypoScript/Full/ and
Configuration/TSconfig/Full/page.tsconfig are the aggregates for
installations that do not use site sets.
The content element is now hidden by default. The always-included
Configuration/page.tsconfig removes
academicpersonsedit_profileediting
from the selectable content
element types, and the page TSconfig of the component adds it back — so the
element is offered where it is wanted instead of on every page of every
installation. The TCA registration itself did not move, so the frontend renders
existing records exactly as before. Editing such a record in the backend is a
different matter — read the warning below before upgrading.
Impact
A
sys_template
record that selected the old static template keeps its
stored value, and that value now points at a folder holding no
constants.typoscript and no setup.typoscript. It is not an
error — the frontend simply loses the plugin configuration, and the plugin
renders with no template paths.
A site package that imported one of the shipped files by path fails to resolve
it.
@import
of a missing file is silent, so this also shows up as
missing configuration rather than as an error message.
The Profile editing content element is no longer offered in the
backend until the page TSconfig of the component is included, through the site
set or through the page field Page TSconfig.
Warning
Do not open an existing Profile editing record in the backend
form on a page that does not include that page TSconfig. An item removed
through
TCEFORM.tt_content.CType.removeItems
is excluded from
the [ invalid value ] fallback TYPO3 otherwise adds for a stored
value it does not know, and the stored value is dropped from the form data
as well. The field Type therefore comes up with nothing
selected, and saving the record writes whatever the browser preselected
into
CType
— the record silently becomes another content element.
The frontend keeps rendering it correctly until that happens.
Include the page TSconfig of the component on every page tree that holds
such records, and do it before editing them.
The set
fgtclb/academic-persons-edit
keeps its name and keeps delivering
everything, so a site configuration that depends on it needs no change.
Affected Installations
Installations that select the static template of this extension in a
sys_template
record, that import one of the shipped files from an own
site package, or that use the content element without including the page
TSconfig of this extension.
Migration
Replace the static template entry in the
sys_template
record:
Old entry
New entry
Academic Persons Edit Settings (academic_persons_edit),
stored as EXT:academic_persons_edit/Configuration/TypoScript
Academic Persons Edit: All components
(academic_persons_edit), stored as
EXT:academic_persons_edit/Configuration/TypoScript/Full — or
Academic Persons Edit: Profile editing
(academic_persons_edit), stored as
EXT:academic_persons_edit/Configuration/TypoScript/ProfileEditing
Add the page TSconfig entry, which did not exist before, in the page record of
the site root, tab Resources, field Page TSconfig:
Academic Persons Edit: All components (academic_persons_edit),
stored as
EXT:academic_persons_edit/Configuration/TSconfig/Full/page.tsconfig. Without
it the content element is not selectable any more, and existing records of it
lose their
CType
when they are saved from the backend form.
Sites that use the site set instead need no migration — but they must not use
both mechanisms at once, see the Configuration chapter.
A site configuration may name the new component set instead of the aggregate:
Set
Delivers
fgtclb/academic-persons-edit
Unchanged in name, now delivers through the component set below.
fgtclb/academic-persons-edit-profile-editing
The Profile editing content element only.
Feature: Show and hide contact records in the frontend editing
Description
Profile owners can now control which of their contact records are
displayed on the public profile directly from the frontend editing
plugin of EXT:academic_persons_edit.
For each physical address, email address and phone number a new
"show"/"hide" toggle is available in the contract editing view. Hiding
a record sets the standard TYPO3 hidden enable field on the record,
which removes it from the public profile display while keeping the data
intact.
The frontend editing list always shows hidden records (marked with a
"Hidden" badge) so that they can be made visible again at any time. The
other actions (view, edit, sort, delete) are only offered for visible
records — a hidden record has to be shown again before it can be
edited.
Records created and updated by the profile synchronization
(EXT:academic_persons create/update profile commands) can be hidden
as well: the synchronization keeps updating their data, but no longer
changes their visibility once a profile owner has decided to hide them.
Profile owners using the EXT:academic_persons_edit frontend editing
gain fine-grained control over the visibility of their:
physical addresses
email addresses
phone numbers
without having to delete records they only want to hide temporarily.
Affected Installations
All installations using the EXT:academic_persons_edit extension
starting with version 2.4.
Migration
No migration is required. Existing records keep their current
visibility (they are visible unless they were already hidden in the
backend). The new toggle can be used immediately.
Important: Adapted profile image partial
Description
Resources/Private/Partials/Profile/Show/Image.html read the image metadata from
a view variable image that is never assigned. The profile detail view of the
academicpersonsedit_profileediting plugin therefore never rendered the caption,
and it emitted an empty alt and title attribute even when the file carried
that metadata.
The profile carries a TYPO3CMSExtbaseDomainModelFileReference, and that
class exposes nothing but getOriginalResource(). Every other property path on it
resolves to null — which is why {profile.image.alternative} was as empty as
{image.description}. All values are now read through originalResource:
<!-- before --><imgalt="{profile.image.alternative}"title="{profile.image.title}"><f:ifcondition="{image.description}"><figcaptionclass="visually-hidden">{image.description}</figcaption></f:if><!-- after --><imgalt="{profile.image.originalResource.alternative}"title="{profile.image.originalResource.title}"><f:ifcondition="{profile.image.originalResource.description}"><figcaptionclass="visually-hidden">{profile.image.originalResource.description}</figcaption></f:if>
Copied!
The same partial rendered a copyright, which never worked either and was
removed instead of repaired. The partial itself is gone from version 3.0 on -
see Breaking: Replaced the profile editing view.
Impact
The profile detail view of the frontend editing plugin now renders
the alt and title attribute of the profile image from the file metadata
instead of always empty,
a figcaption when the image has a description.
Note that nothing in this extension writes image metadata — an image uploaded
through the plugin has none until it is maintained in the backend. Installations
that never maintained the metadata of their profile images therefore see no
change in the rendered output.
Affected Installations
Installations using the academicpersonsedit_profileediting plugin, and any
installation overriding Partials/Profile/Show/Image.html.
Migration
No configuration change is required.
Installations that override the partial keep their own copy and stay unaffected —
including its defect. Adopt the property paths above to render the metadata.
Important: Form data transformation only maps submitted fields
Description
The frontend editing of EXT:academic_persons_edit maps submitted form
data transfer objects (
\FGTCLB\AcademicPersonsEdit\Domain\Model\Dto\AbstractFormData
descendants) onto the domain models through the per-property factory
classes in
\FGTCLB\AcademicPersonsEdit\Domain\Factory\*
.
Until now these factories wrote every property on each request, so a
field that was not part of the submitted form was silently overwritten
with the empty default of the form data object, wiping already persisted
data. This is corrected: a property is now only applied when it was
actually sent within the current request.
To make this possible the following additions were made (all of them on
@internal
classes that are not part of the public API):
\FGTCLB\AcademicPersonsEdit\Domain\Model\Dto\AbstractFormData
carries the current request and the mapped argument name and exposes
wasPropertySentInRequest(string $propertyName): bool
to detect
which properties were part of the submission. The request and argument
name are provided by the new
\FGTCLB\AcademicPersonsEdit\Property\TypeConverter\AbstractFormDataConverter
,
which is registered for all
AbstractFormData
based arguments in
\FGTCLB\AcademicPersonsEdit\Controller\AbstractActionController::initializeAction()
.
All factory classes (Profile, ProfileInformation, Contract,
Address, Email, PhoneNumber) skip properties that were neither
sent within the request nor registered as override. The existing
readOnly / disabled validation configuration keeps precedence
and continues to protect persisted data.
For the case where a property is not part of the request but still has
to be written - for example when a PSR-14 event fills up data from
another source before the transformation runs -
AbstractFormData
gained a per-property override store via
setPropertyOverride(string $propertyName, mixed $value)
,
hasPropertyOverride(string $propertyName)
and
getPropertyOverride(string $propertyName)
. Registered overrides
are applied even when the property was not submitted.
Additionally
\FGTCLB\AcademicPersonsEdit\Domain\Factory\ContractFactory::setValidTo()
was fixed to evaluate the validation configuration of validTo instead
of validFrom.
Impact
The runtime behaviour of the frontend edit forms changes: submitting a
form no longer resets fields that are not contained in that form. Fields
are only written when they were part of the request or were explicitly
registered as override on the form data object. No public method
signature changed in an incompatible way.
Affected Installations
Only installations that extend or replace the internal form data factory
classes or
AbstractFormData
, or that relied on the previous
"always overwrite" behaviour of the transformation, need to take the
changed behaviour into account. All other installations benefit from the
fix without any action required.
Migration
No explicit migration is required. Custom code that populates a form data
object outside of the request (e.g. within a PSR-14 event) and expects
the value to be persisted must register it via
AbstractFormData::setPropertyOverride()
so the transformation
applies it despite the property not being part of the request.
Feature: Added options to move items to top or bottom in lists
Description
Profile related lists now provide additional actions to quickly move
entries directly to the first or last position without having to step
through them item by item. This applies to contracts and profile
information on a profile as well as email addresses, phone numbers
and physical addresses on a contract.
Instead of only being able to move an item one step up or down,
editors can now use dedicated "move to top" and "move to bottom"
actions to rearrange the order of items more efficiently. The sorting
values of affected records are normalized while reordering to keep a
clean, strictly increasing sorting sequence.
Impact
Editors working with the EXT:academic_persons_edit extension gain
more convenient tools for managing the order of:
profile contracts
profile information
contract email addresses
contract phone numbers
contract physical addresses
The new options reduce the number of clicks required to bring a record
to the top or bottom of a list and make it easier to maintain a stable
and predictable display order.
Affected Installations
All installations using the EXT:academic_persons_edit extension
starting with version 2.3.
Migration
No explicit migration is required. Existing records keep their current
order and sorting values. The new actions can be used immediately to
rearrange items as needed and will normalize sorting values on change.
Important: Added options to move items to top or bottom in lists
Description
Profile related lists now provide additional actions to quickly move
entries directly to the first or last position without having to step
through them item by item. This applies to contracts and profile
information on a profile as well as email addresses, phone numbers
and physical addresses on a contract.
Instead of only being able to move an item one step up or down,
editors can now use dedicated "move to top" and "move to bottom"
actions to rearrange the order of items more efficiently. The sorting
values of affected records are normalized while reordering to keep a
clean, strictly increasing sorting sequence.
Impact
Editors working with the EXT:academic_persons_edit extension gain
more convenient tools for managing the order of:
profile contracts
profile information
contract email addresses
contract phone numbers
contract physical addresses
The new options reduce the number of clicks required to bring a record
to the top or bottom of a list and make it easier to maintain a stable
and predictable display order.
Affected Installations
All installations using the EXT:academic_persons_edit extension
starting with version 2.3.
Migration
No explicit migration is required. Existing records keep their current
order and sorting values. The new actions can be used immediately to
rearrange items as needed and will normalize sorting values on change.
Breaking: Migrated extbase plugins from list_type to CType
Description
TYPO3 v13 deprecated the tt_content sub-type feature, only used for CType=list sub-typing also known
as list_type and mostly used based on old times for extbase based plugins. It has been possible since
the very beginning to register Extbase Plugins directly as CType instead of CType=list sub-type, which
has now done.
Technically this is a breaking change, and instances upgrading from 1.x version of the plugin needs to
update corresponding tt_content records in the database and eventually adopt addition, adjustments or
overrides requiring to use the correct CType.
Impact
The change relates to following plugins:
academicpersonsedit_profileediting
academicpersonsedit_profileswitcher
academicpersons_listanddetail
academicpersons_selectedcontracts
academicpersons_selectedprofiles
Affected Installations
All installations using the above listed plugins prior V2.0.
Migration
A TYPO3 UpgradeWizard academicPersonsEdit_pluginUpgradeWizard is provided to migrate
plugins from CType=list to dedicated CTypes matching the new registration.
Breaking: Removed nullable form dto argument from all controller newActions()
Description
The newAction() of the EXT:academic_persons_edit controllers only
displays the initial form for create a new entity submitting data
submission to the createAction() [POST] and never handles the form
again. Following that, there is no need to have form data as optional
argument for the newAction() and beside that this would allow to
prefill data used for the form using link manipulations as the action
is a GET action, which we do not want to work.
Not verified if having the form object as nullable for action in place
has been required in earlier extbase days, but today that is absolutely
not the case and could only be uses to prefill form data calling
that action with corresponding get arguments, which can be considered
dangerous and needs to be omitted in the first place.
In case the validation for the createAction() is invalid extbase
calls the errorAction() of the controller forwarding the request
internally to the newAction() along with the validationResult,
already omitting to send the form data argument and being null in
any-case. Using the <f:form.* /> fluid ViewHelpers to render the
form elements takes care of this and keep the entered values as
values even if the form dto object is initialized with empty values,
which means that we do not have to take care of that ourself.
Impact
The optional (nullable) form arguments are removed from following actions:
ContractController->newAction()
EmailAdressController->newAction()
PhoneNumberController->newAction()
PhysicalAddressController->newAction()
ProfileInformationController->newAction()
Affected Installations
All installations using the EXT:academic_persons_edit extension version
prior V2.1.
There may be use-cases in projects to provide kind of prefilled
actions, albeit this should be only a edge-case. In these cases,
the project should implement a custom controller and action to
create the entity with the prefilled data and display (redirect)
to the editAction(). If that case rises up in projects, we may
revisit this here and eventually come up with another solution,
considering security aspects more seriously in these cases.