Integration with other extensions¶
Anything that generates AI content can report it to nt_aimark. The labelling
then comes about with no extra editorial effort — a person only confirms it.
The coupling is a single event. No hard dependency in either direction: if
nt_aimark is not installed, the event goes nowhere and the reporting
extension never notices.
The event¶
use NetThinks\NtAimark\Event\AiContentGeneratedEvent;
$this->eventDispatcher->dispatch(new AiContentGeneratedEvent(
tableName: 'sys_file',
recordUid: $file->getUid(),
aiSystem: 'DALL·E 3',
aiVendor: 'OpenAI',
contentKind: AiContentGeneratedEvent::KIND_IMAGE,
fullyGenerated: true,
prompt: $prompt,
generatedAt: time(),
source: 'nt_ai',
));
| Field | Meaning |
|---|---|
tableName |
sys_file, sys_file_metadata, pages, tt_content, … |
recordUid |
uid in that table |
aiSystem |
Product name, e.g. "DALL·E 3" |
aiVendor |
Vendor, e.g. "OpenAI" |
contentKind |
image, audio, video, text or alt_text |
fullyGenerated |
true = fully generated, false = AI-assisted change |
prompt |
Optional, internal documentation — never rendered in the frontend |
generatedAt |
Optional creation time (drives the cutoff rule) |
source |
Optional, which extension is reporting — goes into the trail |
The event is marked @api and stays stable within a major version.
What happens with it — and what deliberately does not¶
Three decisions that are not obvious at first glance:
Media stays a suggestion¶
Even a first-hand report sets the status to "Suggestion", not to "AI
generated". The public statement "this image is AI generated" remains a human
decision, exactly as with automatic detection. The matching icon is
pre-filled from fullyGenerated, so confirming is one click.
A record a person has already settled is not overwritten.
Text is recorded as fact¶
For text the situation differs: the reporting extension wrote the text itself,
which is knowledge rather than a guess. tx_ntaimark_text_status is therefore
set directly.
That still produces no disclosure on its own — for that it additionally needs "matter of public interest", and only a person sets that. See Rules.
An alt text changes nothing about the image¶
KIND_ALT_TEXT is recorded but leaves the AI status of the image alone.
An alt text describes a picture and says nothing about how the picture came
about. Treating it otherwise would suddenly mark every image with an
AI-written alt text as "AI generated" — a false statement about the content,
and a frequent one at that.
Trail¶
Every report lands in tx_ntaimark_audit with the action reported and the
source from the source field (nt_ai, nt_lingua, otherwise import).
For nt_ai and nt_lingua¶
Sensible places to dispatch:
| Extension | Place | contentKind |
|---|---|---|
nt_ai |
Alt-text generation for a FAL image | alt_text |
nt_ai |
AI-generated text in a content element | text |
nt_lingua |
Translation or plain-language transformation | text, fullyGenerated: false |
For nt_lingua, fullyGenerated: false is the normal case: a translation is
an AI modification of an existing text, not a new creation.
Extension points¶
This repository is the free core package. It is complete on its own — no locked feature, no licence key, no upgrade hint anywhere in the interface. Additional features arrive as a separate Composer package and hook in through the points below, without patching this one.
Everything listed here is @api and stays stable within a major version.
Adjusting a labelling decision¶
use NetThinks\NtAimark\Service\LabelDecisionModifierInterface;
final class MyModifier implements LabelDecisionModifierInterface
{
public function modify(AiDeclaration $declaration, LabelDecision $decision): LabelDecision
{
return $decision;
}
}
Register with the tag nt_aimark.label_decision_modifier; with autoconfigure
on, implementing the interface is enough. Several modifiers run in registration
order, each seeing what the previous one produced.
The rules in DisclosureRuleService remain the single place the reasoning
lives — a modifier refines it, it does not replace it.
Events¶
| Event | When | Purpose |
|---|---|---|
AfterLabelDecisionEvent |
after every decision | Observing (counting, logging). To change the outcome, use the modifier. |
AfterStatusChangedEvent |
on every status change | Reacting: notify a service, invalidate a cache, feed a dashboard |
AfterStatusChangedEvent is dispatched from the audit trail and therefore
covers every route: the form, bulk editing, automatic detection, the CLI
and reports from sibling extensions. If it is in the trail, this event fired.
AfterLabelDecisionEvent fires once per resolved file — many times on a page
with many images. Keep listeners cheap.
Burning the icon into the image¶
IconCompositorInterface is registered and passes through unchanged by default
(NullIconCompositor). A second package overrides the service alias and takes
over.
Post-processing finished HTML¶
For grown projects whose templates cannot carry the ViewHelpers. Two building blocks are provided:
- A named place in the middleware chain:
netthinks/nt-aimark/label-injection. The core package passes through unchanged; overridingLabelInjectorInterfaceas a service alias takes over, with no middleware registration of your own. ProcessedFileDeclarationResolver: given an image path from the finished HTML, finds the matchingAiDeclaration— including scaled variants, throughsys_file_processedfile.
The method is called apply() rather than inject() on purpose: Symfony
treats methods whose name starts with "inject" as setter injection, and the
container then refuses to build the service.
Writing to the audit trail¶
AuditService is public: true, so a second package can add its own entries.
The evaluation view (audit log, export) is deliberately not part of the core
package — but writing happens here in full, so the data is complete.
Configuration keys¶
Keys belonging to an additional package live in their own namespace
(aimarkPro.*) and do not appear in the core package.
Why there is no licence check¶
TYPO3 extensions using core API are derivative works and therefore GPL-2.0-or-later. Selling is allowed; forbidding redistribution is not. A runtime lock would be legally contestable — and the wrong foundation for a product whose selling point is legal compliance. A test in this repository asserts that no licence or activation logic appears in the code.