# TYPO3 v13 Backend Module Modernization **Purpose:** Comprehensive guide for modernizing TYPO3 backend modules to v13 LTS standards **Source:** Real-world modernization of nr_temporal_cache backend module (45/100 → 95/100 compliance) **Target:** TYPO3 v13.4 LTS with PSR-12, modern JavaScript, and accessibility compliance --- ## Critical Compliance Issues ### 1. Extension Key Consistency **Problem:** Mixed extension keys throughout templates and JavaScript breaks translations and routing **Common Violations:** - Template translation keys using `EXT:wrong_name/` instead of `EXT:correct_name/` - JavaScript alert messages with hardcoded wrong extension names - Variable substitution using wrong extension prefix **Detection:** ```bash # Find all extension key references grep -rn "EXT:temporal_cache/" Resources/Private/Templates/ grep -rn "EXT:temporal_cache/" Resources/Public/JavaScript/ # Verify correct key in ext_emconf.php grep "\$EM_CONF\[" ext_emconf.php ``` **Example Violations:** ```html ``` ```javascript // WRONG: Hardcoded wrong extension name in alert alert('Error in Temporal Cache extension'); // CORRECT: Use TYPO3 Notification API with correct name Notification.error('Error', 'Failed in nr_temporal_cache'); ``` **Impact:** Broken translations, 404 errors on static assets, module registration failures **Severity:** 🔴 Critical - Breaks basic functionality **Fix Priority:** Immediate - Fix before any other modernization work --- ### 2. JavaScript Modernization (ES6 Modules) **Problem:** Inline JavaScript in templates is deprecated, not CSP-compliant, and hard to maintain **TYPO3 v13 Standard:** All JavaScript must be ES6 modules loaded via PageRenderer **Before (DEPRECATED):** ```html ``` **After (MODERN v13):** **Step 1: Create ES6 Module** (`Resources/Public/JavaScript/BackendModule.js`) ```javascript /** * Backend module JavaScript for nr_temporal_cache * TYPO3 v13 ES6 module */ import Modal from '@typo3/backend/modal.js'; import Notification from '@typo3/backend/notification.js'; class TemporalCacheModule { constructor() { this.initializeEventListeners(); } initializeEventListeners() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.init()); } else { this.init(); } } init() { this.initializeHarmonization(); this.initializeKeyboardNavigation(); } initializeHarmonization() { const selectAllCheckbox = document.getElementById('select-all'); const contentCheckboxes = document.querySelectorAll('.content-checkbox'); const harmonizeBtn = document.getElementById('harmonize-selected-btn'); if (!harmonizeBtn) return; if (selectAllCheckbox) { selectAllCheckbox.addEventListener('change', (e) => { contentCheckboxes.forEach(checkbox => { checkbox.checked = e.target.checked; }); this.updateHarmonizeButton(); }); } contentCheckboxes.forEach(checkbox => { checkbox.addEventListener('change', () => this.updateHarmonizeButton()); }); harmonizeBtn.addEventListener('click', () => this.performHarmonization()); } async performHarmonization() { const selectedUids = Array.from(document.querySelectorAll('.content-checkbox:checked')) .map(cb => parseInt(cb.dataset.uid)); if (selectedUids.length === 0) return; const harmonizeBtn = document.getElementById('harmonize-selected-btn'); const harmonizeUri = harmonizeBtn.dataset.actionUri; // Use TYPO3 Modal instead of confirm() Modal.confirm( 'Confirm Harmonization', `Harmonize ${selectedUids.length} content elements?`, Modal.SeverityEnum.warning, [ { text: 'Cancel', active: true, btnClass: 'btn-default', trigger: () => Modal.dismiss() }, { text: 'Harmonize', btnClass: 'btn-warning', trigger: () => { Modal.dismiss(); this.executeHarmonization(harmonizeUri, selectedUids); } } ] ); } async executeHarmonization(uri, selectedUids) { try { const response = await fetch(uri, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: selectedUids, dryRun: false }) }); const data = await response.json(); if (data.success) { // Use TYPO3 Notification API instead of alert() Notification.success('Harmonization Successful', data.message); setTimeout(() => window.location.reload(), 1500); } else { Notification.error('Harmonization Failed', data.message); } } catch (error) { Notification.error('Error', 'Failed to harmonize content: ' + error.message); } } initializeKeyboardNavigation() { document.addEventListener('keydown', (e) => { // Ctrl/Cmd + A: Select all if ((e.ctrlKey || e.metaKey) && e.key === 'a') { const selectAll = document.getElementById('select-all'); if (selectAll && document.activeElement.tagName !== 'INPUT') { e.preventDefault(); selectAll.checked = true; selectAll.dispatchEvent(new Event('change')); } } }); } } // Initialize and export export default new TemporalCacheModule(); ``` **Step 2: Load Module in Controller** (`Classes/Controller/Backend/TemporalCacheController.php`) ```php private function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void { $moduleTemplate->setTitle( $this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab') ); // Load JavaScript module $moduleTemplate->getPageRenderer()->loadJavaScriptModule( '@netresearch/nr-temporal-cache/backend-module.js' ); // Add DocHeader buttons $this->addDocHeaderButtons($moduleTemplate, $currentAction); // ... } ``` **Step 3: Remove Inline JavaScript from Templates** ```html ``` **Validation:** ```bash # Ensure NO inline JavaScript remains grep -rn "FooterAssets" Resources/Private/Templates/ grep -rn "

Dashboard

``` **After (MODERN v13):** **Step 1: Create Module Layout** (`Resources/Private/Layouts/Module.html`) ```html
``` **Step 2: Update All Templates** ```html

``` **Validation:** ```bash # Check all templates use Module layout grep -n "f:layout name=" Resources/Private/Templates/Backend/**/*.html # Verify Module.html exists ls -l Resources/Private/Layouts/Module.html # Ensure no Default.html dependencies ! grep -r "Default.html" Resources/Private/Templates/ ``` **Severity:** 🟡 Important - Standard TYPO3 v13 pattern --- ### 4. DocHeader Component Integration **Problem:** Backend modules should have standard DocHeader with refresh, shortcut, and action-specific buttons **TYPO3 v13 Standard:** Use ButtonBar, IconFactory for DocHeader components **Before (MISSING):** ```php // Classes/Controller/Backend/TemporalCacheController.php private function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void { $moduleTemplate->setTitle('Temporal Cache'); // No DocHeader buttons } ``` **After (MODERN v13):** **Step 1: Add Required Imports** ```php use TYPO3\CMS\Backend\Template\Components\ButtonBar; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; ``` **Step 2: Inject IconFactory** ```php public function __construct( private readonly ModuleTemplateFactory $moduleTemplateFactory, private readonly ExtensionConfiguration $extensionConfiguration, // ... other dependencies private readonly IconFactory $iconFactory // ADD THIS ) {} ``` **Step 3: Add DocHeader Buttons Method** ```php private function addDocHeaderButtons(ModuleTemplate $moduleTemplate, string $currentAction): void { if (!isset($this->uriBuilder)) { return; // Skip in tests } $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar(); // Refresh button (all actions) $refreshButton = $buttonBar->makeLinkButton() ->setHref($this->uriBuilder->reset()->uriFor($currentAction)) ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload')) ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)) ->setShowLabelText(false); $buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT, 1); // Shortcut/bookmark button (all actions) $shortcutButton = $buttonBar->makeShortcutButton() ->setRouteIdentifier('tools_TemporalCache') ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab')) ->setArguments(['action' => $currentAction]); $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2); // Action-specific buttons switch ($currentAction) { case 'dashboard': // Quick access to content list $contentButton = $buttonBar->makeLinkButton() ->setHref($this->uriBuilder->reset()->uriFor('content')) ->setTitle($this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:button.view_content')) ->setIcon($this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)) ->setShowLabelText(true); $buttonBar->addButton($contentButton, ButtonBar::BUTTON_POSITION_LEFT, 1); break; case 'wizard': // Help button for wizard $helpButton = $buttonBar->makeHelpButton() ->setFieldName('temporal_cache_wizard') ->setModuleName('_MOD_tools_TemporalCache'); $buttonBar->addButton($helpButton, ButtonBar::BUTTON_POSITION_RIGHT, 3); break; } } ``` **Step 4: Call from setupModuleTemplate** ```php private function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void { $moduleTemplate->setTitle( $this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab') ); $moduleTemplate->getPageRenderer()->loadJavaScriptModule( '@netresearch/nr-temporal-cache/backend-module.js' ); // Add DocHeader buttons $this->addDocHeaderButtons($moduleTemplate, $currentAction); // ... menu creation } ``` **Validation:** ```bash # Check IconFactory injection grep "IconFactory" Classes/Controller/Backend/*.php # Verify addDocHeaderButtons method exists grep -A 5 "addDocHeaderButtons" Classes/Controller/Backend/*.php # Check button types used grep "makeLinkButton\|makeShortcutButton\|makeHelpButton" Classes/Controller/Backend/*.php ``` **Common Button Types:** - `makeLinkButton()` - Navigate to URL - `makeShortcutButton()` - Bookmark module state - `makeHelpButton()` - Context-sensitive help - `makeInputButton()` - Form submission - `makeFullyRenderedButton()` - Custom HTML **Severity:** 🟡 Important - Standard TYPO3 UX pattern --- ### 5. TYPO3 Modal and Notification APIs **Problem:** Browser `alert()`, `confirm()`, `prompt()` are deprecated and not user-friendly **TYPO3 v13 Standard:** Use `@typo3/backend/modal.js` and `@typo3/backend/notification.js` **Before (DEPRECATED):** ```javascript // Inline JavaScript using browser APIs if (confirm('Really delete this item?')) { fetch('/delete', { method: 'POST' }) .then(() => alert('Deleted successfully')) .catch(() => alert('Error occurred')); } ``` **After (MODERN v13):** ```javascript import Modal from '@typo3/backend/modal.js'; import Notification from '@typo3/backend/notification.js'; // Confirmation Modal Modal.confirm( 'Delete Item', 'Really delete this item? This action cannot be undone.', Modal.SeverityEnum.warning, [ { text: 'Cancel', active: true, btnClass: 'btn-default', trigger: () => Modal.dismiss() }, { text: 'Delete', btnClass: 'btn-danger', trigger: () => { Modal.dismiss(); performDelete(); } } ] ); async function performDelete() { try { const response = await fetch('/delete', { method: 'POST' }); const data = await response.json(); if (data.success) { Notification.success('Success', 'Item deleted successfully'); } else { Notification.error('Error', data.message); } } catch (error) { Notification.error('Error', 'Failed to delete: ' + error.message); } } ``` **Modal Severity Levels:** - `Modal.SeverityEnum.notice` - Info/notice (blue) - `Modal.SeverityEnum.info` - Information (blue) - `Modal.SeverityEnum.ok` - Success (green) - `Modal.SeverityEnum.warning` - Warning (yellow) - `Modal.SeverityEnum.error` - Error (red) **Notification Types:** - `Notification.success(title, message, duration)` - Green success message - `Notification.error(title, message, duration)` - Red error message - `Notification.warning(title, message, duration)` - Yellow warning - `Notification.info(title, message, duration)` - Blue information - `Notification.notice(title, message, duration)` - Gray notice **Validation:** ```bash # Check for browser APIs (violations) grep -rn "alert(" Resources/Public/JavaScript/ grep -rn "confirm(" Resources/Public/JavaScript/ grep -rn "prompt(" Resources/Public/JavaScript/ # Verify TYPO3 APIs used grep "import.*Modal" Resources/Public/JavaScript/*.js grep "import.*Notification" Resources/Public/JavaScript/*.js ``` **Severity:** 🟡 Important - Modern UX and consistency --- ### 6. Accessibility (ARIA Labels and Roles) **Problem:** Backend modules must be accessible for screen readers and keyboard navigation **WCAG 2.1 AA Requirements:** - Semantic HTML roles - ARIA labels on interactive elements - Keyboard navigation support **Before (MISSING ACCESSIBILITY):** ```html
Title
Content item
``` **After (ACCESSIBLE v13):** ```html
{item.content.title}
``` **Accessible Button Example:** ```html ``` **Required ARIA Attributes:** - `role="grid"` - On data tables - `role="row"` - On table rows - `role="columnheader"` - On table headers - `aria-label="..."` - On interactive elements without visible text - `aria-labelledby="..."` - Reference to label element - `aria-describedby="..."` - Additional description **Keyboard Navigation:** ```javascript // Support Ctrl+A for select all document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'a') { const selectAll = document.getElementById('select-all'); if (selectAll && document.activeElement.tagName !== 'INPUT') { e.preventDefault(); selectAll.checked = true; selectAll.dispatchEvent(new Event('change')); } } }); ``` **Validation:** ```bash # Check for ARIA labels grep -rn "aria-label" Resources/Private/Templates/ # Check for semantic roles grep -rn 'role="grid\|row\|columnheader"' Resources/Private/Templates/ # Verify keyboard navigation support grep -rn "keydown\|keyup\|keypress" Resources/Public/JavaScript/ ``` **Severity:** 🟢 Recommended - WCAG 2.1 AA compliance --- ### 7. Icon Registration (Configuration/Icons.php) **Problem:** Icon registration in `ext_localconf.php` using `IconRegistry` is deprecated in TYPO3 v13 **TYPO3 v13 Standard:** Use `Configuration/Icons.php` return array **Before (DEPRECATED v13):** ```php // ext_localconf.php - DEPRECATED $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( \TYPO3\CMS\Core\Imaging\IconRegistry::class ); $iconRegistry->registerIcon( 'temporal-cache-module', \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, ['source' => 'EXT:nr_temporal_cache/Resources/Public/Icons/Extension.svg'] ); ``` **After (MODERN v13):** ```php [ 'provider' => SvgIconProvider::class, 'source' => 'EXT:nr_temporal_cache/Resources/Public/Icons/Extension.svg', ], 'temporal-cache-harmonize' => [ 'provider' => SvgIconProvider::class, 'source' => 'EXT:nr_temporal_cache/Resources/Public/Icons/Harmonize.svg', ], ]; ``` **Validation:** ```bash # Check for deprecated IconRegistry usage grep -rn "IconRegistry" ext_localconf.php ext_tables.php # Verify Configuration/Icons.php exists ls -l Configuration/Icons.php # Check icon registration format grep -A 3 "return \[" Configuration/Icons.php ``` **Severity:** 🟡 Important - Removes deprecation warnings --- ### 8. CSRF Protection (URI Generation) **Problem:** Hardcoded action URLs bypass TYPO3 CSRF protection **TYPO3 v13 Standard:** Use `uriBuilder` for all action URIs **Before (INSECURE):** ```html ``` ```javascript const uri = button.dataset.actionUri; fetch(uri, { method: 'POST', body: JSON.stringify(data) }); ``` **After (SECURE v13):** **Controller:** ```php public function contentAction(?ServerRequestInterface $request = null, ...): ResponseInterface { // ... $moduleTemplate->assignMultiple([ 'content' => $paginator->getPaginatedItems(), 'harmonizeActionUri' => isset($this->uriBuilder) ? $this->uriBuilder->reset()->uriFor('harmonize') : '', // ... ]); return $moduleTemplate->renderResponse('Backend/TemporalCache/Content'); } ``` **Template:** ```html ``` **JavaScript:** ```javascript const harmonizeBtn = document.getElementById('harmonize-selected-btn'); const harmonizeUri = harmonizeBtn.dataset.actionUri; // URI includes CSRF token automatically const response = await fetch(harmonizeUri, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: selectedUids }) }); ``` **Validation:** ```bash # Check for hardcoded URLs (violations) grep -rn '"/typo3/' Resources/Private/Templates/ grep -rn '"/typo3/' Resources/Public/JavaScript/ # Verify uriBuilder usage in controller grep "uriFor(" Classes/Controller/Backend/*.php # Check template receives URIs grep "Uri}" Resources/Private/Templates/Backend/**/*.html ``` **Severity:** 🔴 Critical - Security vulnerability --- ## Complete Modernization Checklist ### Phase 1: Extension Key Consistency (Critical) - [ ] Verify correct extension key in `ext_emconf.php` - [ ] Search and replace all `EXT:wrong_key/` → `EXT:correct_key/` in templates - [ ] Update JavaScript alert/console messages with correct extension name - [ ] Verify translation keys work in backend module - [ ] Check static asset paths (CSS, images, icons) **Validation:** ```bash grep -rn "EXT:temporal_cache/" Resources/ # Should find ZERO grep -rn "EXT:nr_temporal_cache/" Resources/ | wc -l # Should find ALL ``` ### Phase 2: JavaScript Modernization (Important) - [ ] Create `Resources/Public/JavaScript/BackendModule.js` as ES6 module - [ ] Import `@typo3/backend/modal.js` and `@typo3/backend/notification.js` - [ ] Implement class-based structure with proper initialization - [ ] Replace all `alert()` with `Notification` API - [ ] Replace all `confirm()` with `Modal.confirm()` - [ ] Add keyboard navigation support (Ctrl+A, etc.) - [ ] Remove ALL `` from templates - [ ] Remove ALL inline `