Developer
Architecture
- All classes are registered through
Configuration/Services.yamlwith autowiring and use constructor injection. - The page module panel and the file list button are PSR-14 event listeners.
- The bulk image metadata command is a Symfony console command,
registered with
schedulable: true. - The JSON-LD output is a PSR-15 frontend middleware.
- Backend actions are backend routes. The Content Quality module is an Extbase backend module.
- The rich-text editor button is a CKEditor 5 plugin loaded as an ES module.
The analysis pipeline, started by
Analysis:
PageContentExtractor::extractFromPage() page data from the database
RenderedHeadingExtractor::extract() headings from the rendered page (optional)
AccessibilityAnalyzer / SeoAnalyzer /
ReadabilityAnalyzer / DuplicateContentAnalyzer rule-based issues
SchemaAdvisor PageIntentDetector → SchemaMapper
→ JsonLdGenerator → SchemaValidator
AiAnalyzer, InternalLinkAnalyzer only if AI is enabled
ScoreCalculator scores
→ tx_t3contentquality_result + tx_t3contentquality_history
Using the analysis in your own code
Inject
\Woit\:
use Woit\T3ContentQuality\Service\AnalysisOrchestrator;
final class AnalyzeCommand extends Command
{
public function __construct(
private readonly AnalysisOrchestrator $analysisOrchestrator,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$result = $this->analysisOrchestrator->analyze(pageUid: 42, languageUid: 0);
$output->writeln('Overall score: ' . $result->overallScore);
return Command::SUCCESS;
}
}
Public methods of
Analysis:
analyze(int $page Uid, int $language Uid = 0): Analysis Result - Runs all checks and stores the result.
getStored Result (int $page Uid, int $language Uid = 0): ?array - Returns the stored result without analysing again.
analyzeSchema With Ai (int $page Uid, int $language Uid = 0): string - Runs AI schema detection and updates the stored result. Returns a
string starting with
ok:orerror:. checkLinks (int $page Uid, int $language Uid, Link Checker $link Checker): array - Checks the links of the page and stores the result.
getHistory (int $page Uid, int $language Uid = 0, int $limit = 20): array - Score history of a page.
getAll Results (): array - Stored results of all pages in the default language.
getUnanalyzed Page Count (): int - Number of visible standard pages without a result.
Note
The extension is in beta state. Class names and method signatures may change until version 1.0. There is no PHP interface for analyzers yet.
Domain models
AnalysisIssue
Immutable value object:
use Woit\T3ContentQuality\Domain\Model\AnalysisIssue;
$issue = new AnalysisIssue(
category: AnalysisIssue::CATEGORY_ACCESSIBILITY,
severity: AnalysisIssue::SEVERITY_ERROR,
message: 'Image has no ALT text.',
suggestion: 'Add a descriptive ALT text.',
);
Categories: CATEGORY_, CATEGORY_,
CATEGORY_, CATEGORY_.
Severities: SEVERITY_, SEVERITY_, SEVERITY_.
AnalysisResult
Collects issues and scores during an analysis:
$result->overallScore; // int, average of accessibility, SEO, readability
$result->accessibilityScore; // int 0–100
$result->seoScore; // int 0–100
$result->readabilityScore; // int 0–100
$result->schemaScore; // int 0–100
$result->providerUsed; // string
$result->modelUsed; // string
$result->metadata; // array, page facts
$result->getIssues(); // AnalysisIssue[]
$result->getIssuesByCategory('seo');
$result->getAiSuggestions(); // string[]
Page data
Page returns an array with the keys
page_, page_, seo_, meta_,
abstract, author, slug, doktype, crdate, tstamp,
schema_, headings (list of level and text),
images, links (list of text and href), text_ and
ctypes.
Analysis adds page_.
Other services
\Woit\T3Content Quality\ Security\ Permission Service can,Show Page () can,Edit Page () can,Edit Page Content () canandApply Fix Type () filterfor the current backend user.Showable Page Uids () \Woit\T3Content Quality\ Service\ Page Fix Applier - Stores AI proposals as pending rows and applies them with
applythrough the DataHandler.Rows () \Woit\T3Content Quality\ Domain\ Repository\ Approved Schema Repository - Reads approved JSON-LD. Used by the frontend middleware.
\Woit\T3Content Quality\ Service\ Ollama Endpoint - Resolves the Ollama URL from ollamaUrl.
Events used
\TYPO3\CMS\ Backend\ Controller\ Event\ Modify Page Layout Content Event Pageadds the quality panel to the page module.Module Content Listener \TYPO3\CMS\ Filelist\ Event\ Process File List Actions Event Filelistadds the metadata button to image rows in the file list.Actions Event Listener
Both listeners are registered in Configuration/Services.yaml with
the identifiers t3contentquality. and
t3contentquality.. To remove a listener, override its
service definition in your own Services..
Backend routes
All routes require a backend login and the TYPO3 route token. Build URLs
with
\TYPO3\. Each route checks the
page permissions listed in Page permissions; requests for
pages the user may not access are ignored.
| Route identifier | Method | Purpose |
|---|---|---|
t3contentquality_ | GET | Analyse page/language, redirect to the page module. |
t3contentquality_ | GET | Generate pending fixes; fix is title,
description, alt_ or all. |
t3contentquality_ | GET, POST | Apply pending fixes of a page, or the fixes listed in uids. |
t3contentquality_ | GET, POST | Discard pending fixes of a page, or the fixes listed in
uids. |
t3contentquality_ | POST | Generate pending fixes for page (max. 15) of type
fix. |
t3contentquality_ | GET | Check the links of a page. |
t3contentquality_ | GET | AI schema detection. |
t3contentquality_ | POST | Store jsonld as approved schema. |
t3contentquality_ | GET | Delete the approved schema. |
t3contentquality_ | GET | CSV export. |
t3contentquality_ | GET | Fill metadata of the image file, redirect to the
metadata form. |
AJAX route t3contentquality_ (POST, JSON body with
prompt, tone, language, format, max,
language) returns {"text": "…"} or {"error": "…"}.
Frontend middleware
\Woit\ is registered as
woit/. It re-encodes the stored
JSON-LD with JSON_, so < and > in values can not end the
<script> element. To disable it, for example
if you render JSON-LD yourself:
return [
'frontend' => [
'woit/t3-content-quality/json-ld-injector' => [
'disabled' => true,
],
],
];
Schema types
Supported schema.org types and their required and recommended properties
are defined in
\Woit\. Rule-based
detection is in
Page, the mapping of page data to
properties in
Schema. Both are regular services and can be
replaced in Services..
AI providers
All AI requests go through
\Woit\:
call(string $prompt, Ai Settings $settings): string - Text prompt, returns the model's answer.
callVision (string $prompt, string $image Base64, string $mime Type, Ai Settings $settings): string - Prompt plus one image.
\Woit\ reads the
extension configuration and returns an immutable
\Woit\ object (provider, key,
model, limits). The default model IDs are defined only there.
Requests are sent with TYPO3's
\TYPO3\
instead of the vendor SDKs, so the extension also works in classic mode
installations without Composer. For Anthropic the client
- sends
output_andconfig. effort fallbacks: "default"only to models that accept them (see anthropicEffort and anthropicFallback), - reads the answer by content block type, because thinking blocks can come first,
- turns
stop_into an exception with the refusal category.reason: refusal
To add a provider, add a match arm in
Ai and the
provider's settings in
Ai and
ext_conf_template.txt.
JavaScript modules
Configuration/ maps the import prefix
@woit/ to
EXT:.
CKEditor/ai- text- generator. js - CKEditor 5 plugin
Aiwith the toolbar itemText Generator ai.Text Generator backend-,loader. js panel-loader. js - Loading indicators for the module and the panel.
Image metadata services
The file list button (
Ai) and the console
command (
Generate) share the same services:
\Woit\T3Content Quality\ Service\ File Metadata Generator generatefills the empty fields of the default-language metadata record: EXIF/IPTC data,(File $file): array download_from the file name, then alternative text, title, description and caption vianame Ai. Fields without a database column are skipped. Existing values are never overwritten. Returns the written fields (field name => value); an empty array means nothing was written.Image Metadata Analyzer \Woit\T3Content Quality\ Service\ File Metadata Candidate Finder findreturns uids of images with an empty alternative text, ascending. Only JPEG, PNG, GIF and WebP files up to 5 MB in existing storages are returned, and files flagged as missing are excluded. UseFile Uids (int $after Uid, int $batch Size, int $storage Uid = 0, string $folder = ''): array $afteras a paging cursor.Uid
Example:
use TYPO3\CMS\Core\Resource\File;
use Woit\T3ContentQuality\Service\FileMetadataGenerator;
final class MyImageImport
{
public function __construct(
private readonly FileMetadataGenerator $fileMetadataGenerator,
) {}
public function afterImport(File $file): void
{
$writtenFields = $this->fileMetadataGenerator->generate($file);
}
}
The command
Generate pages through the
candidates with
File, skips files that are
missing on disk or in an offline storage (they do not count against
--) and returns Command:: if the AI is not usable or a
file throws an exception. Options are described in
Bulk image metadata (CLI and scheduler).
Tests
Unit tests are in Tests/ and use PHPUnit 11. They cover the
score calculation, schema validation and type detection, the JSON-LD
escaping in the middleware, the AI settings, the request and response
handling of
Ai, the parsing of AI answers, the image
metadata generation (
File) and the bulk command
(
Generate). No network access or database is
needed.
In a standalone checkout of the extension:
composer install
composer test:unit
In a TYPO3 project that has PHPUnit installed:
vendor/bin/phpunit -c vendor/woit/t3-content-quality/Build/phpunit/UnitTests.xml
If the extension is a path package in a project (for example
packages/ in DDEV) and the TYPO3 Composer plugins
are not allowed for it, install the test dependencies without plugins:
cd packages/t3_content_quality
composer install --no-plugins --no-scripts
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
Permission checks, the DataHandler integration and the database query of
File need a database and are not covered by
unit tests.