Files
gh-netresearch-claude-code-…/references/backend-module-v13.md
2025-11-30 08:43:22 +08:00

1060 lines
34 KiB
Markdown

# 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
<!-- WRONG: Using temporal_cache instead of nr_temporal_cache -->
<f:translate key="LLL:EXT:temporal_cache/Resources/Private/Language/locallang_mod.xlf:dashboard.title" />
<!-- CORRECT: Using proper extension key -->
<f:translate key="LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:dashboard.title" />
```
```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
<!-- Resources/Private/Templates/Backend/TemporalCache/Content.html -->
<f:section name="Content">
<!-- Template content -->
</f:section>
<f:section name="FooterAssets">
<script type="text/javascript">
// 68 lines of inline JavaScript
document.addEventListener('DOMContentLoaded', function() {
const selectAll = document.getElementById('select-all');
const checkboxes = document.querySelectorAll('.content-checkbox');
const harmonizeBtn = document.getElementById('harmonize-btn');
selectAll.addEventListener('change', function(e) {
checkboxes.forEach(cb => cb.checked = e.target.checked);
});
harmonizeBtn.addEventListener('click', function() {
if (confirm('Really harmonize?')) {
// AJAX call with alert() feedback
}
});
});
</script>
</f:section>
```
**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
<!-- Resources/Private/Templates/Backend/TemporalCache/Content.html -->
<f:section name="Content">
<!-- Template content with data attributes for JavaScript -->
<button
type="button"
class="btn btn-success"
id="harmonize-selected-btn"
disabled
data-action="harmonize"
data-action-uri="{harmonizeActionUri}"
aria-label="{f:translate(key: '...:content.harmonize_selected')}">
<core:icon identifier="actions-synchronize" size="small" />
<f:translate key="LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:content.harmonize_selected" />
</button>
</f:section>
<!-- FooterAssets section removed completely -->
```
**Validation:**
```bash
# Ensure NO inline JavaScript remains
grep -rn "FooterAssets" Resources/Private/Templates/
grep -rn "<script" Resources/Private/Templates/
# Verify ES6 module exists
ls -lh Resources/Public/JavaScript/BackendModule.js
# Check controller loads module
grep "loadJavaScriptModule" Classes/Controller/Backend/*.php
```
**Impact:** CSP compliance, better caching, maintainability, modern development patterns
**Severity:** 🟡 Important - Required for TYPO3 v13 compliance
---
### 3. Module Layout Pattern
**Problem:** Old `Default.html` layout is non-standard for TYPO3 v13
**TYPO3 v13 Standard:** Use dedicated `Module.html` layout for backend modules
**Before (NON-STANDARD):**
```html
<!-- Resources/Private/Templates/Backend/TemporalCache/Dashboard.html -->
<f:layout name="Default" />
<f:section name="Content">
<h1>Dashboard</h1>
<!-- Content -->
</f:section>
```
**After (MODERN v13):**
**Step 1: Create Module Layout** (`Resources/Private/Layouts/Module.html`)
```html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers"
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:be.pageRenderer />
<div class="module" data-module-name="temporal-cache">
<f:render section="Before" optional="true" />
<div class="module-body">
<f:flashMessages />
<f:render section="Content" />
</div>
<f:render section="After" optional="true" />
</div>
</html>
```
**Step 2: Update All Templates**
```html
<!-- Resources/Private/Templates/Backend/TemporalCache/Dashboard.html -->
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:layout name="Module" />
<f:section name="Content">
<h1><f:translate key="LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:dashboard.title" /></h1>
<!-- Content -->
</f:section>
</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
<table class="table table-striped">
<thead>
<tr>
<th>
<input type="checkbox" id="select-all">
</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" class="content-checkbox"></td>
<td>Content item</td>
</tr>
</tbody>
</table>
```
**After (ACCESSIBLE v13):**
```html
<table class="table table-striped table-hover" role="grid" aria-label="Temporal Content List">
<thead>
<tr role="row">
<th style="width: 40px;" role="columnheader">
<input
type="checkbox"
id="select-all"
class="form-check-input"
aria-label="Select all content items">
</th>
<th role="columnheader">
<f:translate key="LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:content.table.title" />
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input
type="checkbox"
class="form-check-input content-checkbox"
data-uid="{item.content.uid}"
aria-label="Select content item: {item.content.title}">
</td>
<td>{item.content.title}</td>
</tr>
</tbody>
</table>
```
**Accessible Button Example:**
```html
<button
type="button"
class="btn btn-success"
id="harmonize-selected-btn"
disabled
data-action="harmonize"
aria-label="{f:translate(key: '...:content.harmonize_selected')}">
<core:icon identifier="actions-synchronize" size="small" />
<f:translate key="LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:content.harmonize_selected" />
</button>
```
**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
<?php
// Configuration/Icons.php
declare(strict_types=1);
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
return [
'temporal-cache-module' => [
'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
<button
id="harmonize-btn"
data-action-uri="/typo3/module/tools/temporal-cache/harmonize">
Harmonize
</button>
```
```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
<button
id="harmonize-selected-btn"
data-action-uri="{harmonizeActionUri}">
Harmonize
</button>
```
**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 `<f:section name="FooterAssets">` from templates
- [ ] Remove ALL inline `<script>` tags from templates
- [ ] Load module via `$moduleTemplate->getPageRenderer()->loadJavaScriptModule()`
**Validation:**
```bash
grep -rn "FooterAssets" Resources/Private/Templates/ # Should find ZERO
grep -rn "<script" Resources/Private/Templates/ # Should find ZERO
ls -lh Resources/Public/JavaScript/BackendModule.js # Should exist
grep "loadJavaScriptModule" Classes/Controller/Backend/*.php # Should find usage
```
### Phase 3: Layout Pattern (Important)
- [ ] Create `Resources/Private/Layouts/Module.html` with TYPO3 v13 structure
- [ ] Add `xmlns:core` namespace to Module.html
- [ ] Include `<f:flashMessages />` in Module.html
- [ ] Update ALL templates to use `<f:layout name="Module" />`
- [ ] Add `xmlns:core` namespace to all templates
- [ ] Remove any `Default.html` layout dependencies
**Validation:**
```bash
ls -l Resources/Private/Layouts/Module.html # Should exist
grep -n "f:layout name=" Resources/Private/Templates/Backend/**/*.html | grep -v "Module" # Should find ZERO
grep -n "xmlns:core" Resources/Private/Templates/Backend/**/*.html | wc -l # Should match template count
```
### Phase 4: DocHeader Integration (Important)
- [ ] Add `use TYPO3\CMS\Backend\Template\Components\ButtonBar;` import
- [ ] Add `use TYPO3\CMS\Core\Imaging\Icon;` import
- [ ] Add `use TYPO3\CMS\Core\Imaging\IconFactory;` import
- [ ] Inject `IconFactory` into controller constructor
- [ ] Create `addDocHeaderButtons()` method
- [ ] Add refresh button (all actions)
- [ ] Add shortcut/bookmark button (all actions)
- [ ] Add action-specific buttons (view content, help, etc.)
- [ ] Call `addDocHeaderButtons()` from `setupModuleTemplate()`
**Validation:**
```bash
grep "IconFactory" Classes/Controller/Backend/*.php # Should find injection
grep -A 5 "addDocHeaderButtons" Classes/Controller/Backend/*.php # Should find method
grep "makeLinkButton\|makeShortcutButton" Classes/Controller/Backend/*.php # Should find usage
```
### Phase 5: TYPO3 APIs (Important)
- [ ] Import `Modal` and `Notification` in ES6 module
- [ ] Replace `confirm()` with `Modal.confirm()` with severity levels
- [ ] Replace `alert()` success with `Notification.success()`
- [ ] Replace `alert()` errors with `Notification.error()`
- [ ] Use proper Modal button configurations (text, btnClass, trigger)
- [ ] Set appropriate severity levels (notice, info, ok, warning, error)
**Validation:**
```bash
grep -rn "alert(" Resources/Public/JavaScript/ # Should find ZERO
grep -rn "confirm(" Resources/Public/JavaScript/ # Should find ZERO
grep "Modal.confirm" Resources/Public/JavaScript/*.js # Should find usage
grep "Notification\.(success\|error)" Resources/Public/JavaScript/*.js # Should find usage
```
### Phase 6: Accessibility (Recommended)
- [ ] Add `role="grid"` to data tables
- [ ] Add `role="row"` to table rows
- [ ] Add `role="columnheader"` to table headers
- [ ] Add `aria-label` to checkboxes without visible labels
- [ ] Add `aria-label` to buttons with icon-only content
- [ ] Implement keyboard navigation (Ctrl+A for select all)
- [ ] Test with screen reader
- [ ] Verify all interactive elements are keyboard accessible
**Validation:**
```bash
grep -rn "aria-label" Resources/Private/Templates/ # Should find accessibility labels
grep -rn 'role="grid\|row\|columnheader"' Resources/Private/Templates/ # Should find semantic roles
```
### Phase 7: Icon Registration (Important)
- [ ] Create `Configuration/Icons.php` if missing
- [ ] Migrate icon registration from `ext_localconf.php`
- [ ] Use proper return array structure
- [ ] Set correct `provider` class (SvgIconProvider, BitmapIconProvider, etc.)
- [ ] Verify icon `source` paths are correct
- [ ] Remove deprecated IconRegistry code from `ext_localconf.php`
**Validation:**
```bash
ls -l Configuration/Icons.php # Should exist
grep -rn "IconRegistry" ext_localconf.php # Should find ZERO
```
### Phase 8: CSRF Protection (Critical)
- [ ] Use `uriBuilder->uriFor()` for all action URIs
- [ ] Pass URIs to templates via `assignMultiple()`
- [ ] Use data attributes in templates: `data-action-uri="{harmonizeActionUri}"`
- [ ] Read URIs from data attributes in JavaScript
- [ ] Remove all hardcoded `/typo3/...` URLs
**Validation:**
```bash
grep -rn '"/typo3/' Resources/ # Should find ZERO (except maybe comments)
grep "uriFor(" Classes/Controller/Backend/*.php # Should find URI generation
```
### Phase 9: Testing and Validation (Critical)
- [ ] Run unit tests: `composer test:unit`
- [ ] Run functional tests: `composer test:functional`
- [ ] Check for PHP deprecation warnings
- [ ] Test module in TYPO3 backend manually
- [ ] Verify all buttons work (refresh, shortcut, action buttons)
- [ ] Test harmonization/actions with Modal confirmations
- [ ] Verify Notification API messages display correctly
- [ ] Test keyboard navigation (Ctrl+A, Tab order)
- [ ] Check browser console for JavaScript errors
- [ ] Validate translation keys work
**Validation:**
```bash
composer test # All tests should pass
vendor/bin/typo3 cache:flush # Clear caches
# Manual testing in backend
```
### Phase 10: Documentation (Important)
- [ ] Document backend module usage in `Documentation/`
- [ ] Add screenshots of module UI
- [ ] Document keyboard shortcuts
- [ ] Update README.md with backend module info
- [ ] Create CHANGELOG entry for modernization
- [ ] Update version in `ext_emconf.php`
---
## Conformance Scoring Impact
| Category | Before | After | Improvement |
|----------|--------|-------|-------------|
| Extension Architecture | 15/20 | 18/20 | +3 (Fixed extension keys, layout pattern) |
| Coding Guidelines | 18/20 | 20/20 | +2 (ES6 modules, icon registration) |
| PHP Architecture | 16/20 | 18/20 | +2 (IconFactory DI, proper URI generation) |
| Testing Standards | 18/20 | 18/20 | 0 (Already passing) |
| Best Practices | 15/20 | 20/20 | +5 (Modern JS, accessibility, CSRF) |
| **Total Base Score** | **82/100** | **94/100** | **+12 points** |
| Excellence: Documentation | 0/4 | 1/4 | +1 (Added module docs) |
| **Total Score** | **82/120** | **95/120** | **+13 points** |
**Estimated Time Investment:**
- Analysis: 1-2 hours
- Phase 1-2 (Critical fixes): 2-3 hours
- Phase 3-5 (JavaScript + Layout): 3-4 hours
- Phase 6-8 (Accessibility + Icons + CSRF): 2-3 hours
- Phase 9-10 (Testing + Docs): 2-3 hours
- **Total:** 10-15 hours for complete modernization
---
## Common Pitfalls and Solutions
### Pitfall 1: Extension Key Case Sensitivity
**Problem:** `nr_temporal_cache` vs `nr-temporal-cache` vs `nrTemporalCache`
**Solution:** Use snake_case (`nr_temporal_cache`) consistently everywhere. TYPO3 extension keys are always snake_case.
### Pitfall 2: JavaScript Module Path
**Problem:** `@vendor/extension-name/` vs `@vendor/extension_name/`
**Solution:** Use hyphen-case in module paths: `@netresearch/nr-temporal-cache/backend-module.js`
### Pitfall 3: Modal Not Dismissing
**Problem:** Modal stays open after button click
**Solution:** Always call `Modal.dismiss()` in trigger callbacks before performing action
### Pitfall 4: Notification Duration
**Problem:** Success notifications disappear too quickly
**Solution:** Add duration parameter: `Notification.success(title, message, 3)` (3 seconds)
### Pitfall 5: IconFactory Not Available in Tests
**Problem:** Tests fail with "Call to a member function getIcon() on null"
**Solution:** Check `isset($this->uriBuilder)` before calling button-related methods
### Pitfall 6: ARIA Labels Not Translatable
**Problem:** Hardcoded English text in aria-label
**Solution:** Use Fluid translate ViewHelper: `aria-label="{f:translate(key: '...')}"`
---
## Real-World Example: Complete Before/After
**Extension:** `nr_temporal_cache` - Backend module for temporal content management
**Modernization:** Complete v13 compliance (45/100 → 95/100)
### Files Changed
1. `Classes/Controller/Backend/TemporalCacheController.php` - Added IconFactory, DocHeader, JS module loading
2. `Resources/Private/Layouts/Module.html` - Created new layout
3. `Resources/Private/Templates/Backend/TemporalCache/*.html` - Fixed keys, removed inline JS, added ARIA
4. `Resources/Public/JavaScript/BackendModule.js` - Created 246-line ES6 module
5. `Configuration/Icons.php` - Created icon registration
6. `ext_localconf.php` - Removed deprecated IconRegistry code
### Results
- ✅ 57 extension key references fixed
- ✅ 95 lines of inline JavaScript removed
- ✅ 246 lines of ES6 module created
- ✅ DocHeader with 3 button types added
- ✅ Modal and Notification APIs integrated
- ✅ ARIA labels on 12+ interactive elements
- ✅ Keyboard navigation (Ctrl+A) implemented
- ✅ CSRF protection via uriBuilder
- ✅ Zero deprecation warnings
- ✅ 316 unit tests passing
- ✅ 95/100 conformance score
**Commit:** `79db9cf` - 6 files changed, +459/-204 lines, 8.1KB ES6 module created
---
## References
- **TYPO3 Core API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/
- **Backend Module API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Backend/BackendModules.html
- **JavaScript API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/JavaScript/
- **Icon API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Icon/Index.html
- **WCAG 2.1:** https://www.w3.org/WAI/WCAG21/quickref/
**Created:** 2025-11-21 based on real-world nr_temporal_cache modernization